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 ProtectQuotaFailure; internal static ConfigEntry RecoveryPercent; internal static ConfigEntry CreateEmergencyBackup; internal static ConfigEntry MaxEmergencyBackups; internal static ConfigEntry EnforceBuildGuard; internal static ConfigEntry 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("Quota Failure", "ProtectSave", true, "Keep the current save and restart the same quota session from night one after failure."); RecoveryPercent = ((BaseUnityPlugin)this).Config.Bind("Recovery", "RecoveryPercent", 100, new ConfigDescription("Chance for each eligible dropped item to return to the lobby.", (AcceptableValueBase)(object)new AcceptableValueList(new int[5] { 0, 25, 50, 75, 100 }), Array.Empty())); CreateEmergencyBackup = ((BaseUnityPlugin)this).Config.Bind("Safety", "CreateEmergencyBackup", true, "Copy the current save before applying the quota-failure soft reset."); MaxEmergencyBackups = ((BaseUnityPlugin)this).Config.Bind("Safety", "MaxEmergencyBackups", 5, new ConfigDescription("Maximum SaveGuard backup files retained per profile.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), Array.Empty())); EnforceBuildGuard = ((BaseUnityPlugin)this).Config.Bind("Compatibility", "EnforceBuildGuard", true, "Only install gameplay patches on the locally verified YAPYAP Assembly-CSharp build."); DebugLog = ((BaseUnityPlugin)this).Config.Bind("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 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 createEmergencyBackup = Plugin.CreateEmergencyBackup; SaveManager val = default(SaveManager); if (createEmergencyBackup == null || !createEmergencyBackup.Value || (Object)(object)gameManager == (Object)null || !Service.Get(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(val.SectionObj, val.SectionObj.transform.parent); ((Object)val2).name = "SaveGuard_Section"; ClearSection(val2); Transform content = BuildScrollContent(val2); GameObject val3 = Object.Instantiate(((Component)val.TabButton).gameObject, ((Component)val.TabButton).transform.parent); ((Object)val3).name = "SaveGuard_Tab"; Button component = val3.GetComponent