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;
}
}