using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Mirror;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using YAPYAP;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("SaveGuard")]
[assembly: AssemblyDescription("YAPYAP save protection and configurable failed-extraction recovery")]
[assembly: AssemblyCompany("SaveGuard")]
[assembly: AssemblyProduct("SaveGuard")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace SaveGuard
{
internal static class CompatibilityGuard
{
internal static bool Validate(string expectedHash, bool enforce, out string reason)
{
try
{
using FileStream inputStream = File.OpenRead(typeof(GameManager).Assembly.Location);
using SHA256 sHA = SHA256.Create();
string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", string.Empty).ToLowerInvariant();
if (string.Equals(text, expectedHash, StringComparison.OrdinalIgnoreCase))
{
reason = "Assembly hash verified.";
return true;
}
reason = "Expected " + expectedHash + ", found " + text + ".";
if (!enforce)
{
ManualLogSource log = Plugin.Log;
if (log != null)
{
log.LogWarning((object)("YAPYAP build differs from the verified build; continuing because EnforceBuildGuard is disabled. " + reason));
}
return true;
}
return false;
}
catch (Exception ex)
{
reason = "Unable to hash Assembly-CSharp.dll: " + ex.Message;
return !enforce;
}
}
}
internal static class FailureContext
{
internal static bool RestartScopeActive { get; private set; }
internal static bool SoftFailureOccurred { get; private set; }
internal static bool GameOverExecutionScope { get; private set; }
internal static void BeginRestart(bool reachedQuota)
{
RestartScopeActive = !reachedQuota && (Plugin.ProtectQuotaFailure?.Value ?? false);
if (reachedQuota)
{
SoftFailureOccurred = false;
}
}
internal static void MarkSoftFailure()
{
SoftFailureOccurred = true;
}
internal static void EndRestart()
{
RestartScopeActive = false;
SoftFailureOccurred = false;
}
internal static void BeginGameOverExecution()
{
GameOverExecutionScope = SoftFailureOccurred;
}
internal static void EndGameOverExecution()
{
GameOverExecutionScope = false;
SoftFailureOccurred = false;
}
internal static void Reset()
{
RestartScopeActive = false;
SoftFailureOccurred = false;
GameOverExecutionScope = false;
}
}
[BepInPlugin("com.saveguard.yapyap", "SaveGuard", "0.1.2")]
public sealed class Plugin : BaseUnityPlugin
{
public const string PluginGuid = "com.saveguard.yapyap";
public const string PluginName = "SaveGuard";
public const string PluginVersion = "0.1.2";
internal const string SupportedAssemblyHash = "7b6ef048e716ce4cf87bf5c6f190b3c11d39c50aa18a81467770f13ceed3c542";
internal static Plugin Instance;
internal static ManualLogSource Log;
internal static ConfigEntry<bool> ProtectQuotaFailure;
internal static ConfigEntry<int> RecoveryPercent;
internal static ConfigEntry<bool> CreateEmergencyBackup;
internal static ConfigEntry<int> MaxEmergencyBackups;
internal static ConfigEntry<bool> EnforceBuildGuard;
internal static ConfigEntry<bool> DebugLog;
private Harmony _harmony;
private void Awake()
{
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Expected O, but got Unknown
//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
//IL_00bf: Expected O, but got Unknown
//IL_0139: Unknown result type (might be due to invalid IL or missing references)
//IL_0143: Expected O, but got Unknown
Instance = this;
Log = ((BaseUnityPlugin)this).Logger;
ProtectQuotaFailure = ((BaseUnityPlugin)this).Config.Bind<bool>("Quota Failure", "ProtectSave", true, "Keep the current save and restart the same quota session from night one after failure.");
RecoveryPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Recovery", "RecoveryPercent", 100, new ConfigDescription("Chance for each eligible dropped item to return to the lobby.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[5] { 0, 25, 50, 75, 100 }), Array.Empty<object>()));
CreateEmergencyBackup = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "CreateEmergencyBackup", true, "Copy the current save before applying the quota-failure soft reset.");
MaxEmergencyBackups = ((BaseUnityPlugin)this).Config.Bind<int>("Safety", "MaxEmergencyBackups", 5, new ConfigDescription("Maximum SaveGuard backup files retained per profile.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
EnforceBuildGuard = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "EnforceBuildGuard", true, "Only install gameplay patches on the locally verified YAPYAP Assembly-CSharp build.");
DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLog", false, "Enable verbose SaveGuard logging.");
if (!CompatibilityGuard.Validate("7b6ef048e716ce4cf87bf5c6f190b3c11d39c50aa18a81467770f13ceed3c542", EnforceBuildGuard.Value, out var reason))
{
((BaseUnityPlugin)this).Logger.LogError((object)("Compatibility validation failed; SaveGuard gameplay patches were not installed. " + reason));
return;
}
_harmony = new Harmony("com.saveguard.yapyap");
_harmony.PatchAll(Assembly.GetExecutingAssembly());
((BaseUnityPlugin)this).Logger.LogInfo((object)"SaveGuard loaded: quota-failure protection and configurable item recovery are active.");
}
private void OnDestroy()
{
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
FailureContext.Reset();
Instance = null;
Log = null;
}
internal static void Debug(string message)
{
ConfigEntry<bool> debugLog = DebugLog;
if (debugLog != null && debugLog.Value)
{
ManualLogSource log = Log;
if (log != null)
{
log.LogInfo((object)message);
}
}
}
}
internal static class SaveBackupService
{
private static readonly MethodInfo SaveGameDataMethod = AccessTools.Method(typeof(GameManager), "SaveGameData", (Type[])null, (Type[])null);
internal static void TryCreateQuotaFailureBackup(GameManager gameManager)
{
ConfigEntry<bool> createEmergencyBackup = Plugin.CreateEmergencyBackup;
SaveManager val = default(SaveManager);
if (createEmergencyBackup == null || !createEmergencyBackup.Value || (Object)(object)gameManager == (Object)null || !Service.Get<SaveManager>(ref val))
{
return;
}
try
{
if (SaveGameDataMethod == null)
{
throw new MissingMethodException(typeof(GameManager).FullName, "SaveGameData");
}
SaveGameDataMethod.Invoke(gameManager, null);
int currentSlot = val.CurrentSlot;
string path = Path.Combine(Application.persistentDataPath, "saves");
string text = Path.Combine(path, $"save_slot_{currentSlot}.json");
if (!File.Exists(text))
{
Plugin.Debug($"No on-disk save found for slot {currentSlot}; emergency backup skipped.");
return;
}
string text2 = Path.Combine(path, "SaveGuardBackups");
Directory.CreateDirectory(text2);
string text3 = Path.Combine(text2, string.Format(arg1: DateTime.Now.ToString("yyyyMMdd-HHmmss-fff"), format: "save_slot_{0}_{1}.json", arg0: currentSlot));
File.Copy(text, text3, overwrite: false);
PruneBackups(text2, Plugin.MaxEmergencyBackups.Value);
ManualLogSource log = Plugin.Log;
if (log != null)
{
log.LogInfo((object)("Created quota-failure emergency backup: " + text3));
}
}
catch (Exception ex)
{
ManualLogSource log2 = Plugin.Log;
if (log2 != null)
{
log2.LogWarning((object)("Unable to create quota-failure emergency backup: " + ex.Message));
}
}
}
private static void PruneBackups(string directory, int maximum)
{
FileInfo[] array = (from file in new DirectoryInfo(directory).GetFiles("save_slot_*.json")
orderby file.LastWriteTimeUtc descending
select file).ToArray();
for (int num = maximum; num < array.Length; num++)
{
try
{
array[num].Delete();
}
catch (Exception ex)
{
Plugin.Debug("Unable to prune old backup " + array[num].FullName + ": " + ex.Message);
}
}
}
}
internal static class SaveGuardPolicy
{
internal static int ClampRecoveryPercent(int value)
{
if (value < 0)
{
return 0;
}
if (value > 100)
{
return 100;
}
return value;
}
internal static int NormalizeRecoveryPercent(int value)
{
int num = ClampRecoveryPercent(value);
if (num < 13)
{
return 0;
}
if (num < 38)
{
return 25;
}
if (num < 63)
{
return 50;
}
if (num < 88)
{
return 75;
}
return 100;
}
internal static float ToRecoveryChance(int percent)
{
return (float)NormalizeRecoveryPercent(percent) / 100f;
}
internal static bool ShouldUseSoftReset(bool enabled, bool scopeActive, bool reachedQuota)
{
if (enabled && scopeActive)
{
return !reachedQuota;
}
return false;
}
internal static bool ShouldSuppressGameOverDelete(bool enabled, bool softFailureOccurred, bool executionScope)
{
return enabled && softFailureOccurred && executionScope;
}
}
internal static class SettingsUiInjector
{
private const string SectionName = "SaveGuard_Section";
private const string TabName = "SaveGuard_Tab";
private static readonly FieldInfo SectionsField = AccessTools.Field(typeof(UISettings), "sections");
private static readonly int[] RecoveryOptions = new int[5] { 0, 25, 50, 75, 100 };
internal static void TryInject(UISettings settings)
{
//IL_018d: Unknown result type (might be due to invalid IL or missing references)
//IL_0192: Unknown result type (might be due to invalid IL or missing references)
//IL_0199: Unknown result type (might be due to invalid IL or missing references)
//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
//IL_01aa: Expected O, but got Unknown
if ((Object)(object)settings == (Object)null || !(SectionsField?.GetValue(settings) is SettingsSection[] array) || array.Length == 0 || array.Any((SettingsSection section) => (Object)(object)section?.SectionObj != (Object)null && ((Object)section.SectionObj).name == "SaveGuard_Section") || (!NetworkServer.active && NetworkClient.active))
{
return;
}
SettingsSection val = array[0];
if ((Object)(object)val?.SectionObj == (Object)null || (Object)(object)val.TabButton == (Object)null)
{
ManualLogSource log = Plugin.Log;
if (log != null)
{
log.LogWarning((object)"Unable to inject SaveGuard settings: no usable settings section template was found.");
}
return;
}
GameObject val2 = Object.Instantiate<GameObject>(val.SectionObj, val.SectionObj.transform.parent);
((Object)val2).name = "SaveGuard_Section";
ClearSection(val2);
Transform content = BuildScrollContent(val2);
GameObject val3 = Object.Instantiate<GameObject>(((Component)val.TabButton).gameObject, ((Component)val.TabButton).transform.parent);
((Object)val3).name = "SaveGuard_Tab";
Button component = val3.GetComponent<Button>();
UIFader componentInChildren = val3.GetComponentInChildren<UIFader>(true);
if ((Object)(object)component == (Object)null || (Object)(object)componentInChildren == (Object)null)
{
Object.Destroy((Object)(object)val2);
Object.Destroy((Object)(object)val3);
ManualLogSource log2 = Plugin.Log;
if (log2 != null)
{
log2.LogWarning((object)"Unable to inject SaveGuard settings: cloned tab was incomplete.");
}
return;
}
SetTabLabel(val3, Localized("存档守护", "SaveGuard"));
BuildControls(content);
val2.SetActive(false);
val3.SetActive(true);
SettingsSection[] array2 = (SettingsSection[])(object)new SettingsSection[array.Length + 1];
Array.Copy(array, array2, array.Length);
array2[array.Length] = new SettingsSection
{
SectionObj = val2,
TabButton = component,
Indictor = componentInChildren
};
SectionsField.SetValue(settings, array2);
ManualLogSource log3 = Plugin.Log;
if (log3 != null)
{
log3.LogInfo((object)"Injected the SaveGuard tab into the in-game Settings panel.");
}
}
private static void ClearSection(GameObject sectionObject)
{
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Expected O, but got Unknown
MonoBehaviour[] components = sectionObject.GetComponents<MonoBehaviour>();
foreach (MonoBehaviour val in components)
{
if ((Object)(object)val != (Object)null)
{
Object.DestroyImmediate((Object)(object)val);
}
}
List<GameObject> list = new List<GameObject>();
foreach (Transform item in sectionObject.transform)
{
Transform val2 = item;
list.Add(((Component)val2).gameObject);
}
foreach (GameObject item2 in list)
{
Object.DestroyImmediate((Object)(object)item2);
}
}
private static Transform BuildScrollContent(GameObject sectionObject)
{
//IL_0025: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Expected O, but got Unknown
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Expected O, but got Unknown
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
//IL_0101: Unknown result type (might be due to invalid IL or missing references)
//IL_0116: Unknown result type (might be due to invalid IL or missing references)
//IL_012b: Unknown result type (might be due to invalid IL or missing references)
//IL_0136: Unknown result type (might be due to invalid IL or missing references)
//IL_0140: Unknown result type (might be due to invalid IL or missing references)
//IL_017c: Unknown result type (might be due to invalid IL or missing references)
//IL_0186: Expected O, but got Unknown
GameObject val = new GameObject("Scroll View", new Type[2]
{
typeof(RectTransform),
typeof(ScrollRect)
});
val.transform.SetParent(sectionObject.transform, false);
SetFullRect(val.GetComponent<RectTransform>(), new Vector2(-40f, -40f));
GameObject val2 = new GameObject("Viewport", new Type[2]
{
typeof(RectTransform),
typeof(RectMask2D)
});
val2.transform.SetParent(val.transform, false);
RectTransform component = val2.GetComponent<RectTransform>();
SetFullRect(component, Vector2.zero);
GameObject val3 = new GameObject("Content", new Type[3]
{
typeof(RectTransform),
typeof(VerticalLayoutGroup),
typeof(ContentSizeFitter)
});
val3.transform.SetParent(val2.transform, false);
RectTransform component2 = val3.GetComponent<RectTransform>();
component2.anchorMin = new Vector2(0f, 1f);
component2.anchorMax = new Vector2(1f, 1f);
component2.pivot = new Vector2(0.5f, 1f);
component2.sizeDelta = Vector2.zero;
VerticalLayoutGroup component3 = val3.GetComponent<VerticalLayoutGroup>();
((LayoutGroup)component3).childAlignment = (TextAnchor)1;
((HorizontalOrVerticalLayoutGroup)component3).childControlHeight = true;
((HorizontalOrVerticalLayoutGroup)component3).childControlWidth = true;
((HorizontalOrVerticalLayoutGroup)component3).childForceExpandHeight = false;
((HorizontalOrVerticalLayoutGroup)component3).childForceExpandWidth = true;
((HorizontalOrVerticalLayoutGroup)component3).spacing = 12f;
((LayoutGroup)component3).padding = new RectOffset(30, 30, 25, 30);
val3.GetComponent<ContentSizeFitter>().verticalFit = (FitMode)2;
ScrollRect component4 = val.GetComponent<ScrollRect>();
component4.content = component2;
component4.viewport = component;
component4.horizontal = false;
component4.vertical = true;
component4.scrollSensitivity = 30f;
component4.movementType = (MovementType)2;
return (Transform)(object)component2;
}
private static void BuildControls(Transform content)
{
TMP_Text? obj = ((IEnumerable<TMP_Text>)Object.FindObjectsByType<TMP_Text>((FindObjectsInactive)1, (FindObjectsSortMode)0)).FirstOrDefault((Func<TMP_Text, bool>)((TMP_Text text) => (Object)(object)text != (Object)null && (Object)(object)text.font != (Object)null));
TMP_FontAsset font = ((obj != null) ? obj.font : null) ?? TMP_Settings.defaultFontAsset;
CreateHeader(content, font);
UISettingToggle val = ((IEnumerable<UISettingToggle>)Object.FindObjectsByType<UISettingToggle>((FindObjectsInactive)1, (FindObjectsSortMode)0)).FirstOrDefault((Func<UISettingToggle, bool>)((UISettingToggle control) => (Object)(object)control != (Object)null && !((Object)((Component)control).gameObject).name.StartsWith("SaveGuard", StringComparison.Ordinal)));
UISettingDropdown val2 = ((IEnumerable<UISettingDropdown>)Object.FindObjectsByType<UISettingDropdown>((FindObjectsInactive)1, (FindObjectsSortMode)0)).FirstOrDefault((Func<UISettingDropdown, bool>)((UISettingDropdown control) => (Object)(object)control != (Object)null && !((Object)((Component)control).gameObject).name.StartsWith("SaveGuard", StringComparison.Ordinal)));
if ((Object)(object)val != (Object)null)
{
CreateToggle(((Component)val).gameObject, content, "SaveGuard_ProtectQuotaFailure", Localized("任务失败存档", "Keep Save After Mission Failure"), Plugin.ProtectQuotaFailure.Value, delegate(bool value)
{
Plugin.ProtectQuotaFailure.Value = value;
}, font);
}
if ((Object)(object)val2 != (Object)null)
{
List<string> options = RecoveryOptions.Select((int value) => value + "%").ToList();
int initialValue = FindNearestRecoveryIndex(Plugin.RecoveryPercent.Value);
CreateDropdown(((Component)val2).gameObject, content, "SaveGuard_RecoveryPercent", Localized("物品回收率", "Item Recovery Rate"), options, initialValue, delegate(int index)
{
Plugin.RecoveryPercent.Value = RecoveryOptions[Mathf.Clamp(index, 0, RecoveryOptions.Length - 1)];
}, font);
}
}
private static void CreateHeader(Transform parent, TMP_FontAsset font)
{
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("SaveGuard_Title", new Type[3]
{
typeof(RectTransform),
typeof(TextMeshProUGUI),
typeof(LayoutElement)
});
val.transform.SetParent(parent, false);
TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
((TMP_Text)component).text = Localized("存档守护 (SaveGuard)", "SaveGuard");
((TMP_Text)component).fontSize = 30f;
((TMP_Text)component).alignment = (TextAlignmentOptions)514;
((Graphic)component).color = Color.white;
if ((Object)(object)font != (Object)null)
{
((TMP_Text)component).font = font;
}
val.GetComponent<LayoutElement>().preferredHeight = 55f;
}
private static void CreateToggle(GameObject template, Transform parent, string name, string label, bool initialValue, UnityAction<bool> callback, TMP_FontAsset font)
{
GameObject val = Object.Instantiate<GameObject>(template, parent);
((Object)val).name = name;
val.SetActive(true);
UISettingToggle component = val.GetComponent<UISettingToggle>();
if ((Object)(object)component == (Object)null)
{
return;
}
((UISettingElement<bool>)(object)component).SetSettingKey(string.Empty);
((UnityEventBase)((UISettingElement<bool>)(object)component).OnSettingChanged).RemoveAllListeners();
RemoveLocalisation(val);
TMP_Text valueLabel = default(TMP_Text);
ref TMP_Text reference = ref valueLabel;
object? obj = AccessTools.Field(typeof(UISettingElement<bool>), "valueLabel")?.GetValue(component);
reference = (TMP_Text)((obj is TMP_Text) ? obj : null);
TMP_Text val2 = ((IEnumerable<TMP_Text>)val.GetComponentsInChildren<TMP_Text>(true)).FirstOrDefault((Func<TMP_Text, bool>)((TMP_Text text) => (Object)(object)text != (Object)(object)valueLabel));
if ((Object)(object)val2 != (Object)null)
{
val2.text = label;
if ((Object)(object)font != (Object)null)
{
val2.font = font;
}
}
((UISettingElement<bool>)(object)component).SetValueNoNotify(initialValue);
((UISettingElement<bool>)(object)component).DisplayValue(initialValue);
((UISettingElement<bool>)(object)component).OnSettingChanged.AddListener(callback);
}
private static void CreateDropdown(GameObject template, Transform parent, string name, string label, List<string> options, int initialValue, UnityAction<int> callback, TMP_FontAsset font)
{
GameObject val = Object.Instantiate<GameObject>(template, parent);
((Object)val).name = name;
val.SetActive(true);
UISettingDropdown component = val.GetComponent<UISettingDropdown>();
if ((Object)(object)component == (Object)null)
{
return;
}
((UISettingElement<int>)(object)component).SetSettingKey(string.Empty);
((UnityEventBase)((UISettingElement<int>)(object)component).OnSettingChanged).RemoveAllListeners();
RemoveLocalisation(val);
TMP_Text valueLabel = default(TMP_Text);
ref TMP_Text reference = ref valueLabel;
object? obj = AccessTools.Field(typeof(UISettingElement<int>), "valueLabel")?.GetValue(component);
reference = (TMP_Text)((obj is TMP_Text) ? obj : null);
object? obj2 = AccessTools.Field(typeof(UISettingDropdown), "dropdown")?.GetValue(component);
TMP_Dropdown val2 = (TMP_Dropdown)((obj2 is TMP_Dropdown) ? obj2 : null);
TMP_Text captionText = ((val2 != null) ? val2.captionText : null);
TMP_Text val3 = ((IEnumerable<TMP_Text>)val.GetComponentsInChildren<TMP_Text>(true)).FirstOrDefault((Func<TMP_Text, bool>)((TMP_Text text) => (Object)(object)text != (Object)(object)valueLabel && (Object)(object)text != (Object)(object)captionText));
if ((Object)(object)val3 != (Object)null)
{
val3.text = label;
if ((Object)(object)font != (Object)null)
{
val3.font = font;
}
}
component.PopulateOptions(options);
((UISettingElement<int>)(object)component).SetValueNoNotify(initialValue);
((UISettingElement<int>)(object)component).DisplayValue(initialValue);
((UISettingElement<int>)(object)component).OnSettingChanged.AddListener(callback);
}
private static void RemoveLocalisation(GameObject gameObject)
{
LocalisedTMP[] componentsInChildren = gameObject.GetComponentsInChildren<LocalisedTMP>(true);
foreach (LocalisedTMP val in componentsInChildren)
{
if ((Object)(object)val != (Object)null)
{
Object.DestroyImmediate((Object)(object)val);
}
}
}
private static void SetTabLabel(GameObject tabObject, string label)
{
RemoveLocalisation(tabObject);
TMP_Text[] componentsInChildren = tabObject.GetComponentsInChildren<TMP_Text>(true);
for (int i = 0; i < componentsInChildren.Length; i++)
{
componentsInChildren[i].text = label;
}
}
private static int FindNearestRecoveryIndex(int value)
{
int num = SaveGuardPolicy.ClampRecoveryPercent(value);
int result = 0;
int num2 = int.MaxValue;
for (int i = 0; i < RecoveryOptions.Length; i++)
{
int num3 = Math.Abs(RecoveryOptions[i] - num);
if (num3 < num2)
{
num2 = num3;
result = i;
}
}
return result;
}
private static string Localized(string chinese, string english)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Invalid comparison between Unknown and I4
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Invalid comparison between Unknown and I4
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Invalid comparison between Unknown and I4
SystemLanguage systemLanguage = Application.systemLanguage;
if ((int)systemLanguage != 40 && (int)systemLanguage != 41 && (int)systemLanguage != 6)
{
return english;
}
return chinese;
}
private static void SetFullRect(RectTransform rect, Vector2 sizeDelta)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
rect.sizeDelta = sizeDelta;
}
}
}
namespace SaveGuard.Patches
{
[HarmonyPatch]
internal static class QuotaFailurePatch
{
[HarmonyPatch(typeof(GameManager), "OnStartServer")]
[HarmonyPrefix]
private static void OnStartServerPrefix()
{
FailureContext.Reset();
}
[HarmonyPatch(typeof(GameManager), "RestartGame")]
[HarmonyPrefix]
private static void RestartGamePrefix(GameManager __instance, bool reachedQuota)
{
FailureContext.BeginRestart(reachedQuota);
if (FailureContext.RestartScopeActive)
{
SaveBackupService.TryCreateQuotaFailureBackup(__instance);
}
}
[HarmonyPatch(typeof(GameManager), "RestartGame")]
[HarmonyPostfix]
private static void RestartGamePostfix()
{
FailureContext.EndRestart();
}
[HarmonyPatch(typeof(GameManager), "RestartGame")]
[HarmonyFinalizer]
private static Exception RestartGameFinalizer(Exception __exception)
{
FailureContext.EndRestart();
return __exception;
}
[HarmonyPatch(typeof(GameManager), "SvResetGameState")]
[HarmonyPrefix]
private static bool ResetGameStatePrefix(GameManager __instance, bool reachedQuota)
{
if (!SaveGuardPolicy.ShouldUseSoftReset(Plugin.ProtectQuotaFailure.Value, FailureContext.RestartScopeActive, reachedQuota))
{
return true;
}
if ((Object)(object)DungeonTasks.Instance != (Object)null && (Object)(object)DungeonManager.Instance != (Object)null)
{
DungeonTasks.Instance.CreateTasks(DungeonManager.Instance.Generator);
}
__instance.NetworkcurrentGameState = (GameState)0;
__instance.NetworkcurrentRound = 0;
__instance.NetworktotalScore = 0;
FailureContext.MarkSoftFailure();
ManualLogSource log = Plugin.Log;
if (log != null)
{
log.LogInfo((object)"Quota failure converted to a soft reset: save, gold, inventory, quota tier, hub and grimoire were preserved.");
}
return false;
}
[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
[HarmonyPrefix]
private static void ExecuteGameOverPrefix()
{
FailureContext.BeginGameOverExecution();
}
[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
[HarmonyPostfix]
private static void ExecuteGameOverPostfix()
{
FailureContext.EndGameOverExecution();
}
[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
[HarmonyFinalizer]
private static Exception ExecuteGameOverFinalizer(Exception __exception)
{
FailureContext.EndGameOverExecution();
return __exception;
}
[HarmonyPatch(typeof(GameManager), "SvExecuteGameOver")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> ExecuteGameOverTranspiler(IEnumerable<CodeInstruction> instructions)
{
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Expected O, but got Unknown
MethodInfo methodInfo = AccessTools.Method(typeof(SaveManager), "DeleteSlot", (Type[])null, (Type[])null);
MethodInfo methodInfo2 = AccessTools.Method(typeof(QuotaFailurePatch), "ScopedDeleteSlot", (Type[])null, (Type[])null);
if (methodInfo == null || methodInfo2 == null)
{
ManualLogSource log = Plugin.Log;
if (log != null)
{
log.LogWarning((object)"Unable to resolve DeleteSlot replacement; quota-failure deletion protection disabled.");
}
return instructions;
}
List<CodeInstruction> list = new List<CodeInstruction>();
int num = 0;
foreach (CodeInstruction instruction in instructions)
{
if (CodeInstructionExtensions.Calls(instruction, methodInfo))
{
list.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo2));
num++;
}
else
{
list.Add(instruction);
}
}
if (num != 1)
{
ManualLogSource log2 = Plugin.Log;
if (log2 != null)
{
log2.LogWarning((object)$"Expected one SaveManager.DeleteSlot call in SvExecuteGameOver, found {num}. Deletion protection disabled.");
}
return instructions;
}
return list;
}
private static void ScopedDeleteSlot(SaveManager saveManager, int slot)
{
if (SaveGuardPolicy.ShouldSuppressGameOverDelete(Plugin.ProtectQuotaFailure.Value, FailureContext.SoftFailureOccurred, FailureContext.GameOverExecutionScope) && slot == saveManager.CurrentSlot)
{
ManualLogSource log = Plugin.Log;
if (log != null)
{
log.LogInfo((object)$"Skipped quota-failure deletion of save slot {slot} while preserving the complete Game Over flow.");
}
}
else
{
saveManager.DeleteSlot(slot);
}
}
}
[HarmonyPatch(typeof(LostItemsTracker), "SvRecoverDroppedItemsToLobby")]
internal static class RecoveryPatch
{
private static readonly FieldInfo RecoveryChanceField = AccessTools.Field(typeof(LostItemsTracker), "droppedItemsRecoveryChance");
private static readonly FieldInfo MaxRecoveryPercentageField = AccessTools.Field(typeof(LostItemsTracker), "maxItemsRecoveryPercentage");
private static readonly FieldInfo PerPlayerCapField = AccessTools.Field(typeof(LostItemsTracker), "maxItemsToRecoveryPerNonExtractedPlayer");
[HarmonyPrefix]
private static void Prefix(LostItemsTracker __instance)
{
int num = SaveGuardPolicy.NormalizeRecoveryPercent(Plugin.RecoveryPercent.Value);
RecoveryChanceField.SetValue(__instance, SaveGuardPolicy.ToRecoveryChance(num));
if (num == 0)
{
MaxRecoveryPercentageField.SetValue(__instance, 0f);
PerPlayerCapField.SetValue(__instance, 0);
}
else
{
MaxRecoveryPercentageField.SetValue(__instance, 1f);
PerPlayerCapField.SetValue(__instance, 1000000);
}
Plugin.Debug($"Applying failed-extraction recovery setting: {num}%.");
}
}
[HarmonyPatch(typeof(UISettings), "Initialise")]
internal static class SettingsUiPatch
{
[HarmonyPrefix]
private static void Prefix(UISettings __instance)
{
try
{
SettingsUiInjector.TryInject(__instance);
}
catch (Exception ex)
{
ManualLogSource log = Plugin.Log;
if (log != null)
{
log.LogWarning((object)("Unable to inject the SaveGuard Settings tab: " + ex));
}
}
}
}
}