Decompiled source of No Save Delete v1.2.4

Plugins/NoSaveDelete.dll

Decompiled a day ago
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("NoSaveDeleteMod")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("NoSaveDeleteMod")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("194122da-d5ca-4c8a-b922-4931bcfb19f6")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")]
public class AutoLoadMultiplayer
{
	private static async void Postfix()
	{
		if (!MyModConfig.AutoLoadInMultiplayer.Value)
		{
			Debug.Log((object)"[NoSaveDelete] Auto-load disabled due to AutoLoadInMultiplayer being enabled.");
		}
		else
		{
			if (!SemiFunc.IsMultiplayer() || !PhotonNetwork.IsMasterClient)
			{
				return;
			}
			FieldInfo deadSetField = AccessTools.Field(typeof(PlayerAvatar), "deadSet");
			List<PlayerAvatar> players = SemiFunc.PlayerGetList();
			bool allDead = true;
			foreach (PlayerAvatar player in players)
			{
				if (!(bool)deadSetField.GetValue(player))
				{
					Debug.Log((object)"[NoSaveDelete] Not all players are dead. Reload is not performed.");
					allDead = false;
					break;
				}
			}
			if (allDead)
			{
				FieldInfo saveFileField = AccessTools.Field(typeof(StatsManager), "saveFileCurrent");
				string saveFileName = (string)saveFileField.GetValue(StatsManager.instance);
				if (string.IsNullOrEmpty(saveFileName))
				{
					Debug.LogWarning((object)"[NoSaveDelete] No save file to load!");
				}
				else if ((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelShop)
				{
					Debug.Log((object)"[NoSaveDelete] All players died in the SHOP. Restarting save WITHOUT restoring backup...");
					await Task.Delay(650);
					SemiFunc.MenuActionSingleplayerGame(saveFileName);
				}
				else
				{
					Debug.Log((object)"[NoSaveDelete] All players died. Restoring backup...");
					RestoreBackup(saveFileName);
					await Task.Delay(650);
					SemiFunc.MenuActionSingleplayerGame(saveFileName);
				}
			}
		}
	}

	private static void RestoreBackup(string saveFileName)
	{
		string text = Path.Combine(Application.persistentDataPath, "saves", saveFileName);
		if (!Directory.Exists(text))
		{
			Debug.LogError((object)("[NoSaveDelete] Save folder not found: " + text));
			return;
		}
		string text2 = FindLatestBackup(text, saveFileName);
		if (!string.IsNullOrEmpty(text2))
		{
			try
			{
				string text3 = Path.Combine(text, saveFileName + ".es3");
				if (File.Exists(text3))
				{
					File.Delete(text3);
				}
				File.Move(text2, text3);
				Debug.Log((object)("[NoSaveDelete] Backup restored from: " + text2));
				return;
			}
			catch (IOException ex)
			{
				Debug.LogError((object)("[NoSaveDelete] Restore error: " + ex.Message));
				return;
			}
		}
		Debug.LogWarning((object)"[NoSaveDelete] No valid backup found!");
	}

	private static string FindLatestBackup(string directory, string saveFileName)
	{
		if (!Directory.Exists(directory))
		{
			return null;
		}
		string[] files = Directory.GetFiles(directory, saveFileName + "_BACKUP*.es3");
		if (files.Length == 0)
		{
			return null;
		}
		Regex regex = new Regex("_BACKUP(\\d+)", RegexOptions.IgnoreCase);
		return (from file in files
			select new
			{
				FilePath = file,
				BackupNumber = ExtractBackupNumber(file, regex)
			} into b
			where b.BackupNumber >= 0
			orderby b.BackupNumber descending
			select b).FirstOrDefault()?.FilePath;
	}

	private static int ExtractBackupNumber(string filePath, Regex regex)
	{
		string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
		Match match = regex.Match(fileNameWithoutExtension);
		if (match.Success && int.TryParse(match.Groups[1].Value, out var result))
		{
			return result;
		}
		return -1;
	}
}
[HarmonyPatch(typeof(GameDirector), "Update")]
public class CheckArenaAndRestoreBackup
{
	private static bool shouldRestoreBackup;

	private static bool hasLoggedArena;

	private static void Postfix(GameDirector __instance)
	{
		if (!MyModConfig.AutoLoadInMultiplayer.Value && SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient)
		{
			bool flag = (Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelArena;
			bool flag2 = (Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelLobbyMenu;
			if (flag && !hasLoggedArena)
			{
				hasLoggedArena = true;
				shouldRestoreBackup = true;
				Debug.Log((object)"[NoSaveDelete] The game is on the arena. Enabling backup restoration after returning to the lobby.");
			}
			if (shouldRestoreBackup && flag2)
			{
				shouldRestoreBackup = false;
				hasLoggedArena = false;
				DelayedRestoreBackup();
			}
		}
	}

	private static async Task DelayedRestoreBackup()
	{
		Debug.Log((object)"[NoSaveDelete] Waiting 3 seconds before restoring the backup...");
		await Task.Delay(3000);
		RestoreBackup();
	}

	private static void RestoreBackup()
	{
		FieldInfo fieldInfo = AccessTools.Field(typeof(StatsManager), "saveFileCurrent");
		string text = (string)fieldInfo.GetValue(StatsManager.instance);
		if (string.IsNullOrEmpty(text))
		{
			Debug.LogWarning((object)"[NoSaveDelete] No active save file!");
			return;
		}
		string text2 = Path.Combine(Application.persistentDataPath, "saves", text);
		if (!Directory.Exists(text2))
		{
			Debug.LogError((object)("[NoSaveDelete] Save folder not found: " + text2));
			return;
		}
		string text3 = FindLatestBackup(text2, text);
		if (!string.IsNullOrEmpty(text3))
		{
			try
			{
				string text4 = Path.Combine(text2, text + ".es3");
				if (File.Exists(text4))
				{
					File.Delete(text4);
				}
				File.Move(text3, text4);
				Debug.Log((object)("[NoSaveDelete] Backup successfully restored from: " + text3));
				MethodInfo method = typeof(StatsManager).GetMethod("LoadGame", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (method != null)
				{
					method.Invoke(StatsManager.instance, new object[1] { text });
					Debug.Log((object)("[NoSaveDelete] Called LoadGame() for " + text + ", data updated in memory."));
				}
				else
				{
					Debug.LogWarning((object)"[NoSaveDelete] Unable to find LoadGame method for reflection call!");
				}
				return;
			}
			catch (IOException ex)
			{
				Debug.LogError((object)("[NoSaveDelete] Restore error: " + ex.Message));
				return;
			}
		}
		Debug.LogWarning((object)"[NoSaveDelete] No backup found! Using the standard save.");
	}

	private static string FindLatestBackup(string directory, string saveFileName)
	{
		if (!Directory.Exists(directory))
		{
			Debug.LogError((object)"[NoSaveDelete] Save directory is empty or does not exist!");
			return null;
		}
		string[] files = Directory.GetFiles(directory, saveFileName + "_BACKUP*.es3");
		if (files.Length == 0)
		{
			Debug.LogWarning((object)"[NoSaveDelete] No backup files found.");
			return null;
		}
		Regex regex = new Regex("_BACKUP(\\d+)", RegexOptions.IgnoreCase);
		var anon = (from file in files
			select new
			{
				FilePath = file,
				BackupNumber = ExtractBackupNumber(file, regex)
			} into b
			where b.BackupNumber >= 0
			orderby b.BackupNumber descending
			select b).FirstOrDefault();
		if (anon == null)
		{
			Debug.LogWarning((object)"[NoSaveDelete] Unable to determine the latest backup.");
			return null;
		}
		Debug.Log((object)$"[NoSaveDelete] Selected backup: {anon.FilePath} (number {anon.BackupNumber})");
		return anon.FilePath;
	}

	private static int ExtractBackupNumber(string filePath, Regex regex)
	{
		string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
		Match match = regex.Match(fileNameWithoutExtension);
		if (match.Success && int.TryParse(match.Groups[1].Value, out var result))
		{
			return result;
		}
		return -1;
	}

	public static void ResetFlags()
	{
		shouldRestoreBackup = false;
		hasLoggedArena = false;
		Debug.Log((object)"[NoSaveDelete] Backup flags have been reset.");
	}
}
[HarmonyPatch(typeof(RunManager), "LeaveToMainMenu")]
public class ResetBackupFlagsOnMainMenuLeave
{
	private static void Prefix()
	{
		object value = AccessTools.Field(typeof(CheckArenaAndRestoreBackup), "shouldRestoreBackup").GetValue(null);
		bool flag = default(bool);
		int num;
		if (value is bool)
		{
			flag = (bool)value;
			num = 1;
		}
		else
		{
			num = 0;
		}
		if (((uint)num & (flag ? 1u : 0u)) != 0)
		{
			CheckArenaAndRestoreBackup.ResetFlags();
			Debug.Log((object)"[NoSaveDelete] The host has left the arena and returned to the main menu. Resetting flags...");
		}
		else
		{
			Debug.Log((object)"[NoSaveDelete] Returning to the main menu. Flags were not set — no reset needed.");
		}
	}
}
[HarmonyPatch(typeof(PlayerAvatar), "PlayerDeath")]
public class AutoLoadSingleplayer
{
	[HarmonyPatch(typeof(PlayerAvatar), "Revive")]
	public class CancelAutoLoadOnRevive
	{
		private static void Prefix()
		{
			if (_cancellationTokenSource != null)
			{
				_cancellationTokenSource.Cancel();
				Debug.Log((object)"[NoSaveDelete] Player revived. Auto-load canceled.");
			}
		}
	}

	private static CancellationTokenSource _cancellationTokenSource;

	private static async void Prefix()
	{
		if (MyModConfig.AllowGameDelete.Value)
		{
			Debug.Log((object)"[NoSaveDelete] Auto-load disabled due to AllowGameDelete being enabled.");
		}
		else
		{
			if (SemiFunc.IsMultiplayer())
			{
				return;
			}
			FieldInfo saveFileField = AccessTools.Field(typeof(StatsManager), "saveFileCurrent");
			string saveFileName = (string)saveFileField.GetValue(StatsManager.instance);
			if (!string.IsNullOrEmpty(saveFileName))
			{
				Debug.Log((object)("[NoSaveDelete] The player has died. Reloading the last save: " + saveFileName));
				_cancellationTokenSource?.Cancel();
				_cancellationTokenSource = new CancellationTokenSource();
				CancellationToken token = _cancellationTokenSource.Token;
				try
				{
					await Task.Delay(4000, token);
					if (!token.IsCancellationRequested)
					{
						SemiFunc.MenuActionSingleplayerGame(saveFileName);
					}
					return;
				}
				catch (TaskCanceledException)
				{
					Debug.Log((object)"[NoSaveDelete] Auto-load was canceled because the player revived.");
					return;
				}
			}
			Debug.LogWarning((object)"[NoSaveDelete] No save file to load!");
		}
	}
}
public static class MyModConfig
{
	public static ConfigEntry<bool> AllowPlayerDelete;

	public static ConfigEntry<bool> AllowGameDelete;

	public static ConfigEntry<bool> AutoLoadInMultiplayer;

	public static void Init(ConfigFile config)
	{
		AllowPlayerDelete = config.Bind<bool>("Settings", "AllowPlayerDelete", true, "Allow the player to delete saves? (true - yes, false - no)");
		AllowGameDelete = config.Bind<bool>("Settings", "AllowGameDelete", false, "Allow the game to delete saves? (true - yes, false - no)");
		AutoLoadInMultiplayer = config.Bind<bool>("Settings", "AutoLoadInMultiplayer", false, "Enable automatically load the last save in Multiplayer if ALL players die? (true - yes, false - no)");
	}
}
[HarmonyPatch(typeof(MenuPageSaves), "OnDeleteGame")]
public class MenuPageSavesPatch
{
	public static bool IsPlayerDeletingSave;

	private static bool Prefix()
	{
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		Debug.Log((object)$"Attempt to delete the save. AllowPlayerDelete = {MyModConfig.AllowPlayerDelete.Value}");
		if (!MyModConfig.AllowPlayerDelete.Value)
		{
			Debug.Log((object)"The player is not allowed to delete the save (config prohibits it).");
			MenuManager.instance.PagePopUp("Delete blocked.", Color.red, "You cannot delete the save. Please change the mod config.", "OK");
			return false;
		}
		Debug.Log((object)"The player is deleting the save.");
		IsPlayerDeletingSave = true;
		return true;
	}
}
[BepInPlugin("com.pxntxrez.nosavedelete", "NoSaveDeleteMod", "1.2.4")]
public class MyMod : BaseUnityPlugin
{
	private void Awake()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Expected O, but got Unknown
		MyModConfig.Init(((BaseUnityPlugin)this).Config);
		Harmony val = new Harmony("com.pxntxrez.nosavedelete");
		val.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Mod NoSaveDelete loaded.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Settings can be changed in BepInEx/config/com.pxntxrez.nosavedelete.cfg");
	}
}
[HarmonyPatch(typeof(StatsManager), "SaveFileDelete")]
public class SaveFileDeletePatch
{
	private static bool Prefix(string saveFileName)
	{
		if (MenuPageSavesPatch.IsPlayerDeletingSave)
		{
			Debug.Log((object)("Player delete save " + saveFileName + "."));
			MenuPageSavesPatch.IsPlayerDeletingSave = false;
			return true;
		}
		if (MyModConfig.AllowGameDelete.Value)
		{
			Debug.Log((object)("Game delete save " + saveFileName + " (allowed by config)."));
			return true;
		}
		Debug.Log((object)("The game tried to delete the save " + saveFileName + ", but the mod blocked it."));
		return false;
	}
}
[HarmonyPatch(typeof(StatsManager), "SaveGame")]
public class PreventSaveGameUltimate
{
	private static bool Prefix(ref string fileName)
	{
		if (MyModConfig.AllowGameDelete.Value)
		{
			return true;
		}
		if ((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelArena)
		{
			Debug.Log((object)"[NoSaveDelete] Blocking loss of items, charges, HP, and more has been activated! Because AllowGameDelete = false!");
			return false;
		}
		return true;
	}
}