using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Zichen-SaveKeeper-1.3.7")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+b61599c415beda119511ff1b786a29d5346f9ec2")] [assembly: AssemblyProduct("Zichen-SaveKeeper-1.3.7")] [assembly: AssemblyTitle("Zichen-SaveKeeper-1.3.7")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } public enum DisplayLanguage { 中文, English } [BepInPlugin("zichen.savekeeper", "SaveKeeper", "1.3.7")] public sealed class ZichenSaveKeeperPlugin : BaseUnityPlugin { private sealed class PendingStartupPopup { public string Title; public Color Color; public string Message; public int Priority; public int Sequence; } private readonly struct SaveProgressMarker { public readonly int RunLevel; public readonly int SaveLevel; public SaveProgressMarker(int runLevel, int saveLevel) { RunLevel = runLevel; SaveLevel = saveLevel; } public override string ToString() { int runLevel = RunLevel; string text = runLevel.ToString(); runLevel = SaveLevel; return "runLevel=" + text + ", saveLevel=" + runLevel; } } private readonly struct SaveProgressCandidate { public readonly string Path; public readonly bool IsMainSave; public readonly int BackupNumber; public readonly SaveProgressMarker Progress; public SaveProgressCandidate(string path, bool isMainSave, int backupNumber, SaveProgressMarker progress) { Path = path; IsMainSave = isMainSave; BackupNumber = backupNumber; Progress = progress; } } private sealed class SessionSaveContext { public bool PublicServerSavesMenuOpen; public string PendingNewSaveName; public string PublicRoomSaveName; public string PublishedHostCopyMetadataSignature; public readonly HostCopyAutoSaveState HostCopy = new HostCopyAutoSaveState(); public void ResetForJoinedRoom() { HostCopy.ResetForRoomChange(); } public void ResetForLeaveToMainMenu() { PublicServerSavesMenuOpen = false; PendingNewSaveName = null; PublicRoomSaveName = null; PublishedHostCopyMetadataSignature = null; HostCopy.ResetForRoomChange(); } } [HarmonyPatch(typeof(MenuPageSaves), "OnDeleteGame")] private static class MenuPageSavesOnDeleteGamePatch { private static bool Prefix() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (!IsEnabled()) { return true; } if (allowPlayerDelete != null && allowPlayerDelete.Value) { playerMenuDeleteInProgress = true; return true; } MenuManager.instance.PagePopUp("SaveKeeper", Color.red, UseChinese() ? "当前配置禁止手动删除存档。" : "Manual save deletion is disabled by configuration.", "OK", false); LogWarning("按配置已阻止手动删档。"); return false; } private static void Postfix() { playerMenuDeleteInProgress = false; } } [HarmonyPatch(typeof(MenuPageServerList), "ButtonCreateNew")] private static class MenuPageServerListButtonCreateNewPatch { private static bool Prefix() { if (!ShouldUsePublicServerSaveSelection()) { return true; } SemiFunc.MainMenuSetMultiplayer(); publicServerSavesMenuOpen = true; MenuManager.instance.PageCloseAll(); MenuManager.instance.PageOpen((MenuPageIndex)11, false); return false; } } [HarmonyPatch(typeof(MenuPageSaves), "Start")] private static class MenuPageSavesStartPatch { private static void Postfix(MenuPageSaves __instance) { if ((Object)(object)__instance != (Object)null) { __instance.maxSaveFiles = 9999; } if (publicServerSavesMenuOpen && (Object)(object)__instance != (Object)null) { SetGameModeHeader(__instance, "公开服务器存档 / Public Server Save"); } } } [HarmonyPatch(typeof(SemiFunc), "MenuActionSingleplayerGame")] private static class SemiFuncMenuActionSingleplayerGamePatch { private static void Prefix(string __0) { ResetArenaResumeState("准备进入单人存档"); if (!string.IsNullOrWhiteSpace(__0)) { pendingNewSaveName = null; } } } [HarmonyPatch(typeof(SemiFunc), "MenuActionHostGame")] private static class SemiFuncMenuActionHostGamePatch { private static void Prefix(string __0) { ResetArenaResumeState("准备进入私人房存档"); if (!string.IsNullOrWhiteSpace(__0)) { pendingNewSaveName = null; } } } [HarmonyPatch(typeof(SemiFunc), "MenuActionRandomMatchmaking")] private static class SemiFuncMenuActionRandomMatchmakingPatch { private static void Prefix(string __0) { ResetArenaResumeState("准备进入随机匹配存档"); if (!string.IsNullOrWhiteSpace(__0)) { pendingNewSaveName = null; return; } string text2 = (pendingNewSaveName = GenerateNewSaveFileName()); LogInfo("检测到随机匹配新游戏,等待确认主机身份后初始化存档: " + text2); } } [HarmonyPatch(typeof(MenuPageSaves), "OnNewGame")] private static class MenuPageSavesOnNewGamePatch { private static bool Prefix(MenuPageSaves __instance) { if (publicServerSavesMenuOpen) { if ((Object)(object)__instance != (Object)null && __instance.maxSaveFiles > 0 && GetSaveFileCount(__instance) >= __instance.maxSaveFiles) { return true; } LogInfo("公开服务器创建,等待游戏自动生成存档。"); OpenPublicServerCreatePage(null); return false; } return true; } private static void Postfix() { if (!publicServerSavesMenuOpen && IsHostAndEnabled()) { pendingNewSaveName = GetCurrentSaveFileName(); LogInfo("OnNewGame Postfix: " + (pendingNewSaveName ?? "null")); ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(EnsureInitialSaveCoroutine(pendingNewSaveName)); } } } } [HarmonyPatch(typeof(MenuPageSaves), "OnLoadGame")] private static class MenuPageSavesOnLoadGamePatch { private static bool Prefix(MenuPageSaves __instance) { string selectedSaveFileName = GetSelectedSaveFileName(__instance); if (!string.IsNullOrWhiteSpace(selectedSaveFileName)) { RecordSaveUsage(selectedSaveFileName); } if (!publicServerSavesMenuOpen) { return true; } OpenPublicServerCreatePage(selectedSaveFileName); return false; } } [HarmonyPatch(typeof(MenuPageSaves), "OnGoBack")] private static class MenuPageSavesOnGoBackPatch { private static bool Prefix() { if (!publicServerSavesMenuOpen) { return true; } publicServerSavesMenuOpen = false; MenuManager.instance.PageCloseAll(); MenuManager.instance.PageOpen((MenuPageIndex)14, false); return false; } } [HarmonyPatch(typeof(MenuPageServerListCreateNew), "ExitPage")] private static class MenuPageServerListCreateNewExitPagePatch { private static bool Prefix() { if (!publicServerSavesMenuOpen) { return true; } MenuManager.instance.PageCloseAll(); MenuManager.instance.PageOpen((MenuPageIndex)11, false); return false; } } [HarmonyPatch(typeof(MenuPageServerListCreateNew), "ButtonConfirm")] private static class MenuPageServerListCreateNewButtonConfirmPatch { private static void Prefix(MenuPageServerListCreateNew __instance) { if (!publicServerSavesMenuOpen || !HasPublicServerName(__instance)) { return; } ResetArenaResumeState("准备创建公开房间"); ApplyPublicRoomSaveQueue(StatsManager.instance); string saveFileNameFromCreatePage; if (!string.IsNullOrWhiteSpace(saveFileNameFromCreatePage = GetSaveFileNameFromCreatePage(__instance))) { LogInfo("公开房间加载已有存档: " + saveFileNameFromCreatePage); publicRoomSaveName = saveFileNameFromCreatePage; if (StatsManagerCurrentSaveField != null && (Object)(object)StatsManager.instance != (Object)null) { StatsManagerCurrentSaveField.SetValue(StatsManager.instance, saveFileNameFromCreatePage); } } else { publicRoomSaveName = null; pendingNewSaveName = null; LogInfo("公开房间创建新存档:等待原版创建并初始化 runStats。"); } LogInfo("公开房间创建,准备存档上下文: " + (saveFileNameFromCreatePage ?? "(new save)")); string publicServerRoomName = GetPublicServerRoomName(__instance); if (!string.IsNullOrWhiteSpace(publicServerRoomName) && !string.IsNullOrWhiteSpace(saveFileNameFromCreatePage)) { SetRoomNameForSave(saveFileNameFromCreatePage, publicServerRoomName); } } private static void Postfix(MenuPageServerListCreateNew __instance) { if (!publicServerSavesMenuOpen || !HasPublicServerName(__instance)) { return; } string currentSaveFileName = GetCurrentSaveFileName(); string publicServerRoomName = GetPublicServerRoomName(__instance); if (!string.IsNullOrWhiteSpace(currentSaveFileName)) { publicRoomSaveName = currentSaveFileName; if (!string.IsNullOrWhiteSpace(publicServerRoomName)) { SetRoomNameForSave(currentSaveFileName, publicServerRoomName); } if (string.IsNullOrWhiteSpace(GetSaveFileNameFromCreatePage(__instance))) { pendingNewSaveName = currentSaveFileName; LogInfo("公开房间新存档已由原版创建,延迟补首存: " + currentSaveFileName); ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(EnsureInitialSaveCoroutine(currentSaveFileName)); } } else { pendingNewSaveName = null; LogInfo("公开房间已加载现有存档: " + currentSaveFileName); } } else { LogWarning("公开房间创建后未取得有效存档名。"); } publicServerSavesMenuOpen = false; } } [HarmonyPatch(typeof(MenuManager), "Start")] private static class MenuManagerStartPatch { private static bool done; private static void Postfix() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (done) { return; } done = true; if (!IsEnabled()) { return; } if (!compatibilityPopupShown && compatibilityReport.HasIssues && (Object)(object)MenuManager.instance != (Object)null) { compatibilityPopupShown = true; string text = compatibilityReport.BuildPopupMessage(); if (!string.IsNullOrWhiteSpace(text)) { QueueStartupPopup(UseChinese() ? "SaveKeeper 兼容提示" : "SaveKeeper Compatibility", Color.yellow, text, 2); } } if (!compatibilityReport.HasIssues && IsHostAndEnabled() && showStartupPopup != null && showStartupPopup.Value && (Object)(object)MenuManager.instance != (Object)null) { QueueStartupPopup(UseChinese() ? "SaveKeeper" : "SaveKeeper", message: UseChinese() ? "SaveKeeper v1.3.7 已加载。\n存档保护功能已启用,只有主机需安装此模组。\n此提示仅弹出一次,后续启动不会再次显示。" : "SaveKeeper v1.3.7 loaded.\nHost-only mod. Save protection is active.\nThis popup only appears once.", color: Color.green, priority: 0); showStartupPopup.Value = false; } } } [HarmonyPatch(typeof(NetworkConnect), "OnJoinedRoom")] private static class NetworkConnectOnJoinedRoomPatch { private static void Postfix() { Room currentRoom = PhotonNetwork.CurrentRoom; TraceFlow("OnJoinedRoom:enter", "photonRoom=" + (((currentRoom != null) ? currentRoom.Name : null) ?? "null")); ResetArenaResumeState("已加入新房间"); sessionSaveContext.ResetForJoinedRoom(); TraceFlow("OnJoinedRoom:after-reset"); if (PhotonNetwork.IsMasterClient) { PublishHostCopyMetadataToRoom(); TraceFlow("OnJoinedRoom:published-host-metadata"); } if (!IsEnabled() || !compatibilityReport.RandomMatchmakingAvailable || !IsConnectRandomActive() || !string.IsNullOrWhiteSpace(publicRoomSaveName)) { return; } if (!PhotonNetwork.IsMasterClient) { if (!string.IsNullOrWhiteSpace(pendingNewSaveName)) { LogInfo("已加入他人的公开/匹配房间,清理本地待建存档: " + pendingNewSaveName); pendingNewSaveName = null; TraceFlow("OnJoinedRoom:cleared-pending-save-for-client"); } return; } if (!string.IsNullOrWhiteSpace(GetCurrentSaveFileName())) { pendingNewSaveName = null; return; } string text = pendingNewSaveName; if (string.IsNullOrWhiteSpace(text)) { text = (pendingNewSaveName = GenerateNewSaveFileName()); } if (StatsManagerCurrentSaveField == null) { LogWarning("随机匹配房主首存初始化不可用:缺少当前存档字段。"); return; } StatsManagerCurrentSaveField.SetValue(StatsManager.instance, text); LogInfo("随机匹配创建房间,初始化存档: " + text); TraceFlow("OnJoinedRoom:host-initial-save", "save=" + text); ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(EnsureInitialSaveCoroutine(text)); } PublishHostCopyMetadataToRoom(); } } [HarmonyPatch(typeof(NetworkConnect), "OnConnectedToMaster")] private static class NetworkConnectOnConnectedToMasterPatch { private static void Postfix() { TraceFlow("OnConnectedToMaster", "photonNick=" + (PhotonNetwork.NickName ?? "null")); } } [HarmonyPatch(typeof(PunManager), "SyncAllDictionaries")] private static class PunManagerSyncAllDictionariesPatch { private static void Prefix() { PublishHostCopyMetadataToRoom(); } } [HarmonyPatch(typeof(PunManager), "ReceiveSyncData")] private static class PunManagerReceiveSyncDataPatch { private static void Postfix(bool __1) { if (__1 && ShouldCopyHostSaveToLocal() && IsNonHostMultiplayerClient()) { SchedulePendingHostCopyAutoSavePlanBuild(); } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] private static class RunManagerChangeLevelPatch { private static void Prefix(RunManager __instance, ref bool __0, ref bool __1, ref ChangeLevelType __2) { if (!IsHostAndEnabled()) { return; } string[] obj = new string[8] { "[ChangeLevel] _completedLevel=", __0.ToString(), ", _levelFailed=", __1.ToString(), ", changeType=", ((object)(ChangeLevelType)(ref __2)).ToString(), ", current=", null }; object obj2; if (__instance == null) { obj2 = null; } else { Level levelCurrent = __instance.levelCurrent; obj2 = ((levelCurrent != null) ? ((Object)levelCurrent).name : null); } if (obj2 == null) { obj2 = "null"; } obj[7] = (string)obj2; LogInfo(string.Concat(obj)); string[] obj3 = new string[8] { "completed=", __0.ToString(), ", failed=", __1.ToString(), ", changeType=", ((object)(ChangeLevelType)(ref __2)).ToString(), ", current=", null }; object obj4; if (__instance == null) { obj4 = null; } else { Level levelCurrent2 = __instance.levelCurrent; obj4 = ((levelCurrent2 != null) ? ((Object)levelCurrent2).name : null); } if (obj4 == null) { obj4 = "null"; } obj3[7] = (string)obj4; TraceFlow("ChangeLevel:prefix", string.Concat(obj3)); if (__1 && (Object)(object)__instance?.levelCurrent != (Object)null && IsLevelArena(__instance.levelCurrent) && TryPrepareDirectArenaResume(__instance, ref __0, ref __1, ref __2)) { return; } originalArenaLevelsDuringForcedRace = null; if (deathArenaMode == null || deathArenaMode.Value == "官方随机" || (Object)(object)__instance == (Object)null || !__1 || (Object)(object)__instance.levelCurrent == (Object)null || (Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelLobby || IsLevelShop(__instance.levelCurrent) || IsLevelArena(__instance.levelCurrent)) { return; } string value = deathArenaMode.Value; Level val = ((value == "皇冠竞技场") ? FindCrownArenaLevel(__instance.levelArena) : FindArenaRaceLevel(__instance.levelArena)); if ((Object)(object)val == (Object)null) { if (!missingRaceArenaLogged) { missingRaceArenaLogged = true; LogWarning("Could not find requested death arena mode '" + value + "'. Keeping the game's original arena selection."); } } else { originalArenaLevelsDuringForcedRace = new List(__instance.levelArena); __instance.levelArena = new List { val }; LogInfo("强制死亡竞技场模式 '" + value + "' -> " + ((Object)val).name); } } private static void Postfix(RunManager __instance) { if ((Object)(object)__instance != (Object)null && originalArenaLevelsDuringForcedRace != null) { __instance.levelArena = originalArenaLevelsDuringForcedRace; originalArenaLevelsDuringForcedRace = null; } if (IsHostAndEnabled()) { string text = "null"; if ((Object)(object)__instance != (Object)null && RunManagerSaveLevelField != null) { object value = RunManagerSaveLevelField.GetValue(__instance); text = ((value != null) ? value.ToString() : "null"); } object obj; if (__instance == null) { obj = null; } else { Level levelCurrent = __instance.levelCurrent; obj = ((levelCurrent != null) ? ((Object)levelCurrent).name : null); } if (obj == null) { obj = "null"; } TraceFlow("ChangeLevel:postfix", "newCurrent=" + (string?)obj + ", saveLevel=" + text); } } } [HarmonyPatch(typeof(RunManager), "RestartScene")] private static class RunManagerRestartScenePatch { private static void Prefix(RunManager __instance) { TraceRestartScene("prefix", __instance); } private static void Postfix(RunManager __instance) { TraceRestartScene("postfix", __instance); } } [HarmonyPatch(typeof(NetworkConnect), "OnCreateRoomFailed")] private static class NetworkConnectOnCreateRoomFailedPatch { private static void Prefix(short __0, string __1) { TraceFlow("OnCreateRoomFailed", "returnCode=" + __0 + ", cause=" + (__1 ?? "null")); } } [HarmonyPatch(typeof(NetworkConnect), "OnJoinRoomFailed")] private static class NetworkConnectOnJoinRoomFailedPatch { private static void Prefix(short __0, string __1) { TraceFlow("OnJoinRoomFailed", "returnCode=" + __0 + ", cause=" + (__1 ?? "null")); } } [HarmonyPatch(typeof(NetworkConnect), "OnJoinRandomFailed")] private static class NetworkConnectOnJoinRandomFailedPatch { private static void Prefix(short __0, string __1) { TraceFlow("OnJoinRandomFailed", "returnCode=" + __0 + ", cause=" + (__1 ?? "null")); } } [HarmonyPatch(typeof(NetworkConnect), "OnDisconnected")] private static class NetworkConnectOnDisconnectedPatch { private static void Prefix(object __0) { TraceFlow("OnDisconnected", "cause=" + (__0?.ToString() ?? "null")); } } [HarmonyPatch(typeof(NetworkManager), "LeavePhotonRoom")] private static class NetworkManagerLeavePhotonRoomPatch { private static void Prefix() { TraceFlow("NetworkManager.LeavePhotonRoom"); } } [HarmonyPatch(typeof(NetworkManager), "TriggerLeavePhotonRoomForced")] private static class NetworkManagerTriggerLeavePhotonRoomForcedPatch { private static void Prefix() { TraceFlow("NetworkManager.TriggerLeavePhotonRoomForced"); } } [HarmonyPatch(typeof(StatsManager), "Awake")] private static class StatsManagerAwakePatch { private static void Postfix(StatsManager __instance) { if (IsHostAndEnabled()) { ApplyPublicRoomSaveQueue(__instance); } } } [HarmonyPatch(typeof(StatsManager), "SaveFileDelete")] private static class StatsManagerSaveFileDeletePatch { private static bool Prefix(string __0) { if (!IsHostAndEnabled()) { return true; } if (playerMenuDeleteInProgress) { playerMenuDeleteInProgress = false; RemoveRoomNameForSave(__0); LogWarning("放行手动删除存档: " + __0); return true; } if (blockGameDelete == null || !blockGameDelete.Value) { LogWarning("配置允许游戏删档: " + __0); return true; } if (!string.IsNullOrWhiteSpace(pendingNewSaveName) && __0 == pendingNewSaveName) { if (!MainSaveFileExists(__0)) { LogWarning("新游戏创建流程中的删档,放行: " + __0 + "(文件尚未写入)"); return true; } LogWarning("新存档已存在,拦截创建流程中的删档: " + __0); pendingNewSaveName = null; return false; } LogWarning("已阻止游戏自动删档: " + __0); return false; } } [HarmonyPatch(typeof(StatsManager), "SaveGame")] private static class StatsManagerSaveGamePatch { private static bool wasFirstSave; private static bool Prefix(string __0) { wasFirstSave = false; if (!IsHostAndEnabled()) { return true; } if (!ShouldBlockDeathOverwrite()) { return true; } if (!string.IsNullOrWhiteSpace(publicRoomSaveName) && __0 != publicRoomSaveName) { LogWarning("阻止公开房间生成第二个存档: " + __0 + ",沿用: " + publicRoomSaveName); if (MainSaveFileExists(publicRoomSaveName)) { StatsManager instance = StatsManager.instance; if (instance != null) { instance.SaveGame(publicRoomSaveName); } } return false; } AlignBackupIndexToExistingBackups(StatsManager.instance, __0); if (!MainSaveFileExists(__0)) { wasFirstSave = true; LogWarning("新存档首次保存,放行: " + __0); return true; } if (IsArenaNow()) { LogWarning("已阻止竞技场/死亡结算流程中的存档: " + __0); return false; } if (playerDeathSaveBlocked && !IsActualMultiplayerSession()) { LogWarning("已阻止单人死亡后的存档覆盖: " + __0); return false; } if (multiplayerDeathSaveBlocked && IsActualMultiplayerSession()) { LogWarning("已阻止多人全灭后的存档覆盖: " + __0); return false; } return true; } private static void Postfix(string __0, bool __runOriginal) { if (IsHostAndEnabled() && __runOriginal) { if (!string.IsNullOrWhiteSpace(pendingNewSaveName) && __0 == pendingNewSaveName) { LogInfo("新存档已成功写入,关闭创建保护窗口: " + __0); pendingNewSaveName = null; } if (wasFirstSave) { LogInfo("新存档首次写入完成: " + __0); } CleanupOldBackups(__0); PublishHostCopyMetadataToRoom(); if (!string.IsNullOrWhiteSpace(__0) && !__0.StartsWith("HOSTCOPY_", StringComparison.Ordinal)) { RecordSaveUsage(__0); } } } } [HarmonyPatch(typeof(StatsManager), "SaveFileSave")] private static class StatsManagerSaveFileSavePatch { private static void Prefix(StatsManager __instance) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (!IsHostAndEnabled() || !ShouldSavePublicRooms() || (Object)(object)__instance == (Object)null || (Object)(object)GameManager.instance == (Object)null || GameManagerLobbyTypeField == null) { return; } object value = GameManagerLobbyTypeField.GetValue(GameManager.instance); if (!(value is LobbyTypes)) { return; } LobbyTypes val = (LobbyTypes)value; if ((int)val == 0) { return; } if (__instance.savedLobbyTypes != null && !__instance.savedLobbyTypes.Contains(val)) { __instance.savedLobbyTypes.Add(val); } string text = publicRoomSaveName ?? GetCurrentSaveFileName(); if (!string.IsNullOrWhiteSpace(text)) { string currentSaveFileName = GetCurrentSaveFileName(); if (!string.IsNullOrWhiteSpace(currentSaveFileName) && currentSaveFileName != text) { StatsManagerCurrentSaveField?.SetValue(__instance, text); } LogInfo("准备为非私人房间保存进度: " + text); } } } [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeath")] private static class PlayerAvatarPlayerDeathPatch { private static void Prefix() { if (!IsHostAndEnabled() || !ShouldBlockDeathOverwrite() || IsShopNow()) { return; } playerDeathSaveBlocked = true; LogWarning("检测到单人死亡,已临时阻止存档覆盖。"); ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(ResetPlayerDeathBlockLater()); } if (restoreSaveAfterSingleplayerDeath == null || !restoreSaveAfterSingleplayerDeath.Value) { return; } string currentSaveFileName = GetCurrentSaveFileName(); if (!string.IsNullOrWhiteSpace(currentSaveFileName)) { ZichenSaveKeeperPlugin instance2 = Instance; if (instance2 != null) { ((MonoBehaviour)instance2).StartCoroutine(RestoreSingleplayerSaveAfterDeathLater(currentSaveFileName)); } } } } [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")] private static class PlayerAvatarPlayerDeathRpcPatch { private static void Postfix() { if (ShouldBlockDeathOverwrite() && CanTrackMultiplayerDeathState() && PhotonNetwork.IsMasterClient && AreAllPlayersDead()) { multiplayerDeathSaveBlocked = true; LogWarning("检测到所有玩家死亡,已临时阻止多人存档覆盖。"); ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(ResetMultiplayerDeathBlockLater()); } } } } [HarmonyPatch(typeof(PlayerAvatar), "Revive")] private static class PlayerAvatarRevivePatch { private static void Prefix() { playerDeathSaveBlocked = false; multiplayerDeathSaveBlocked = false; } } [HarmonyPatch(typeof(GameDirector), "Update")] private static class GameDirectorUpdatePatch { private static int frameSkipCounter; private static void Postfix() { frameSkipCounter++; if (frameSkipCounter < 60) { return; } frameSkipCounter = 0; if (!IsEnabled() || restoreBackupAfterArenaReturn == null || !restoreBackupAfterArenaReturn.Value || !PhotonNetwork.IsMasterClient) { return; } if (IsArenaNow()) { multiplayerArenaSeen = true; TraceFlow("GameDirectorUpdate:arena-seen"); } else if (multiplayerArenaSeen && !multiplayerArenaRestoreRunning && IsLobbyOrLobbyMenuNow()) { LogWarning("从多人竞技场返回,准备恢复存档并自动继续。"); TraceFlow("GameDirectorUpdate:trigger-restore"); ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(RestoreBackupAfterArenaReturnLater()); } } } } [HarmonyPatch(typeof(StatsManager), "LoadGame")] private static class StatsManagerLoadGamePatch { private static void Postfix() { pendingNewSaveName = null; if (!manualRestoreRunning && !singleplayerDeathReloadRunning) { playerDeathSaveBlocked = false; multiplayerDeathSaveBlocked = false; } } } [HarmonyPatch(typeof(RunManager), "LeaveToMainMenu")] private static class RunManagerLeaveToMainMenuPatch { private static void Prefix() { TraceFlow("LeaveToMainMenu:prefix"); playerDeathSaveBlocked = false; playerMenuDeleteInProgress = false; ResetArenaResumeState("返回主菜单"); manualRestoreRunning = false; singleplayerDeathReloadRunning = false; sessionSaveContext.ResetForLeaveToMainMenu(); TraceFlow("LeaveToMainMenu:after-reset"); } } private sealed class CompatibilityReport { private readonly List disabledFeatures = new List(); private readonly List details = new List(); public bool CurrentSaveAvailable = true; public bool BackupIndexAvailable = true; public bool HostCopyExportAvailable = true; public bool HostCopyPlayerNamesAvailable = true; public bool DeathStateAvailable = true; public bool PublicRoomSaveAvailable = true; public bool RandomMatchmakingAvailable = true; public bool HostCopyExportFilteringAvailable = true; public bool SaveProgressReadAvailable = true; public bool StartupPopupCoordinationAvailable = true; public bool SceneReloadContextAvailable = true; public bool PublicRoomUiAvailable = true; public bool InMemoryLoadAvailable = true; public bool SaveFileLimitAvailable = true; public bool NameChangerInteropAvailable = true; public bool PublicRoomDefaultNameAvailable = true; public bool HostCopyEncryptionPasswordAvailable = true; public bool DiagnosticTraceAvailable = true; public bool SaveOrderRecallAvailable = true; public bool SaveSelectorMenuAvailable = true; public bool LevelStartSnapshotAvailable = true; public bool HasIssues => disabledFeatures.Count > 0; public static CompatibilityReport CreateHealthy() { return new CompatibilityReport(); } public static CompatibilityReport Build() { CompatibilityReport compatibilityReport = new CompatibilityReport(); if (!RequireField(StatsManagerCurrentSaveField, "StatsManager.saveFileCurrent", compatibilityReport, "当前存档名读取、公开房绑定、首存保护、重载最近存档")) { compatibilityReport.CurrentSaveAvailable = false; compatibilityReport.PublicRoomSaveAvailable = false; compatibilityReport.RandomMatchmakingAvailable = false; } if (!RequireField(StatsManagerBackupIndexField, "StatsManager.backupIndex", compatibilityReport, "备份序号自校准")) { compatibilityReport.BackupIndexAvailable = false; } if (!RequireField(StatsManagerDictionaryOfDictionariesField, "StatsManager.dictionaryOfDictionaries", compatibilityReport, "非主机保存房主存档")) { compatibilityReport.HostCopyExportAvailable = false; } if (!RequireField(StatsManagerEncryptionPasswordField, "StatsManager.totallyNormalString", compatibilityReport, "房主快照加密密码读取")) { compatibilityReport.HostCopyEncryptionPasswordAvailable = false; } bool num = RequireField(StatsManagerDoNotSaveTheseDictionariesField, "StatsManager.doNotSaveTheseDictionaries", compatibilityReport, "房主快照导出过滤与默认值清理"); bool flag = RequireField(StatsManagerStripTheseDictionariesField, "StatsManager.stripTheseDictionaries", compatibilityReport, "房主快照导出过滤与默认值清理"); if (!num || !flag) { compatibilityReport.HostCopyExportFilteringAvailable = false; } if (!RequireField(PlayerAvatarDeadSetField, "PlayerAvatar.deadSet", compatibilityReport, "多人全灭死亡覆盖保护")) { compatibilityReport.DeathStateAvailable = false; } bool num2 = RequireField(PlayerAvatarNameField, "PlayerAvatar.playerName", compatibilityReport, "房主快照玩家名补全"); bool flag2 = RequireField(PlayerAvatarSteamIdField, "PlayerAvatar.steamID", compatibilityReport, "房主快照玩家名补全"); if (!num2 || !flag2) { compatibilityReport.HostCopyPlayerNamesAvailable = false; } if (!RequireField(GameManagerLobbyTypeField, "GameManager.lobbyType", compatibilityReport, "公开房和随机匹配正常保存")) { compatibilityReport.PublicRoomSaveAvailable = false; } if (!RequireField(GameManagerConnectRandomField, "GameManager.connectRandom", compatibilityReport, "随机匹配房主首存初始化")) { compatibilityReport.RandomMatchmakingAvailable = false; } bool num3 = RequireField(MenuManagerPagePopUpScheduledField, "MenuManager.pagePopUpScheduled", compatibilityReport, "启动弹窗避让"); bool flag3 = RequireField(MenuManagerCurrentMenuPageIndexField, "MenuManager.currentMenuPageIndex", compatibilityReport, "启动弹窗避让"); if (!num3 || !flag3) { compatibilityReport.StartupPopupCoordinationAvailable = false; } bool num4 = RequireField(RunManagerLoadLevelField, "RunManager.loadLevel", compatibilityReport, "决斗场后按当前进度继续"); bool flag4 = RequireField(RunManagerGameOverField, "RunManager.gameOver", compatibilityReport, "当前场景内重载最近存档"); if (!num4 || !flag4) { compatibilityReport.SceneReloadContextAvailable = false; } bool num5 = RequireField(DataDirectorNetworkServerNameField, "DataDirector.networkServerName", compatibilityReport, "调试追踪上下文"); bool flag5 = RequireField(DataDirectorNetworkJoinServerNameField, "DataDirector.networkJoinServerName", compatibilityReport, "调试追踪上下文"); bool flag6 = RequireField(RunManagerLobbyJoinField, "RunManager.lobbyJoin", compatibilityReport, "调试追踪上下文"); bool flag7 = RequireField(RunManagerRestartingField, "RunManager.restarting", compatibilityReport, "调试追踪上下文"); bool flag8 = RequireField(RunManagerRestartingDoneField, "RunManager.restartingDone", compatibilityReport, "调试追踪上下文"); bool flag9 = RequireField(RunManagerSaveLevelField, "RunManager.saveLevel", compatibilityReport, "调试追踪上下文"); if (!num5 || !flag5 || !flag6 || !flag7 || !flag8 || !flag9) { compatibilityReport.DiagnosticTraceAvailable = false; } bool num6 = RequireField(MenuManagerCurrentMenuPageField, "MenuManager.currentMenuPage", compatibilityReport, "公开房存档菜单联动"); bool flag10 = RequireField(MenuPageServerListCreateNewSaveFileNameField, "MenuPageServerListCreateNew.saveFileName", compatibilityReport, "公开房存档菜单联动"); bool flag11 = RequireField(MenuPageServerListCreateNewMenuPageParentField, "MenuPageServerListCreateNew.menuPageParent", compatibilityReport, "公开房存档菜单联动"); bool flag12 = RequireNestedField(typeof(MenuPageServerListCreateNew), "menuTextInput", "textCurrent", compatibilityReport, "公开房存档菜单联动"); bool flag13 = RequireField(MenuPageSavesSaveFilesField, "MenuPageSaves.saveFiles", compatibilityReport, "公开房存档菜单联动"); bool flag14 = RequireField(MenuPageSavesCurrentSaveFileNameField, "MenuPageSaves.currentSaveFileName", compatibilityReport, "公开房存档菜单联动"); bool flag15 = RequireNestedProperty(typeof(MenuPageSaves), "gameModeHeader", "text", compatibilityReport, "公开房存档菜单联动"); if (!num6 || !flag10 || !flag11 || !flag12 || !flag13 || !flag14 || !flag15) { compatibilityReport.PublicRoomUiAvailable = false; } if (!RequireField(MenuPageSavesMaxSaveFilesField, "MenuPageSaves.maxSaveFiles", compatibilityReport, "存档上限扩展")) { compatibilityReport.SaveFileLimitAvailable = false; } if (!RequireType(SteamClientType, "Steamworks.SteamClient", compatibilityReport, "公开房默认名称读取")) { compatibilityReport.PublicRoomDefaultNameAvailable = false; } else if (!RequireProperty(SteamClientNameProperty, SteamClientType, "Name", compatibilityReport, "公开房默认名称读取")) { compatibilityReport.PublicRoomDefaultNameAvailable = false; } RequireMethod(typeof(StatsManager), "SaveGame", new Type[1] { typeof(string) }, compatibilityReport, "存档写入保护"); RequireMethod(typeof(StatsManager), "SaveFileSave", Type.EmptyTypes, compatibilityReport, "公开房和随机匹配正常保存"); RequireMethod(typeof(StatsManager), "SaveFileDelete", new Type[1] { typeof(string) }, compatibilityReport, "阻止自动删档"); RequireMethod(typeof(MenuManager), "Start", Type.EmptyTypes, compatibilityReport, "启动提示和兼容提示"); RequireMethod(typeof(PunManager), "ReceiveSyncData", new Type[3] { typeof(Hashtable), typeof(bool), typeof(PhotonMessageInfo) }, compatibilityReport, "非主机保存房主存档"); RequireMethod(typeof(NetworkConnect), "OnJoinedRoom", Type.EmptyTypes, compatibilityReport, "公开房/随机匹配进房初始化"); RequireMethod(typeof(RunManager), "LeaveToMainMenu", Type.EmptyTypes, compatibilityReport, "会话状态清理"); bool flag16 = RequireMethod(typeof(StatsManager), "SaveFileGetRunLevel", new Type[2] { typeof(string), typeof(string) }, compatibilityReport, "重载最近存档与死亡回档进度比较"); bool flag17 = RequireMethod(typeof(StatsManager), "SaveFileGetRunStat", new Type[3] { typeof(string), typeof(string), typeof(string) }, compatibilityReport, "重载最近存档与死亡回档进度比较"); if (!flag16 || !flag17) { compatibilityReport.SaveProgressReadAvailable = false; } if (!RequireMethod(typeof(StatsManager), "LoadGame", new Type[2] { typeof(string), typeof(List) }, compatibilityReport, "当前场景内重载最近存档与死亡回档执行")) { compatibilityReport.InMemoryLoadAvailable = false; } if (!RequireMethod(typeof(StatsManager), "SaveFileGetAllAsync", Type.EmptyTypes, compatibilityReport, "最近使用存档置顶")) { compatibilityReport.SaveOrderRecallAvailable = false; } if (!CheckOptionalNameChangerInterop(compatibilityReport)) { compatibilityReport.NameChangerInteropAvailable = false; } bool flag18 = RequireMethod(typeof(StatsManager), "SaveFileGetAllAsync", Type.EmptyTypes, compatibilityReport, "F7 存档管理菜单"); bool flag19 = RequireMethod(typeof(StatsManager), "SaveFileCreate", new Type[2] { typeof(string), typeof(bool) }, compatibilityReport, "F7 存档管理菜单"); bool flag20 = RequireField(typeof(StatsManager).GetField("teamName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), "StatsManager.teamName", compatibilityReport, "F7 存档管理菜单"); if (!flag18 || !flag19 || !flag20) { compatibilityReport.SaveSelectorMenuAvailable = false; } if (!RequireMethod(typeof(SemiFunc), "OnLevelGenDone", Type.EmptyTypes, compatibilityReport, "关卡开局自动快照")) { compatibilityReport.LevelStartSnapshotAvailable = false; } return compatibilityReport; } public void AddPatchFailure(string featureName, string patchName, string reason) { AddDisabledFeature(featureName); details.Add("Patch failed: " + patchName + " -> " + reason); } public string BuildLogMessage() { if (!HasIssues) { return "Compatibility self-check passed."; } return "Compatibility self-check found possible game API changes. Affected features: " + string.Join(";", disabledFeatures.Distinct().ToArray()); } public string BuildPopupMessage() { if (!HasIssues) { return null; } bool flag = UseChinese(); string text = (flag ? "检测到游戏接口可能已变化,SaveKeeper 已进入兼容模式。\n以下功能可能不可用:" : "Some game APIs may have changed. SaveKeeper has entered compatibility mode.\nThe following features may be unavailable:"); foreach (string item in disabledFeatures.Distinct().Take(6)) { text = text + "\n- " + item; } return text + (flag ? "\n\n如需使用这些功能,请等待作者适配最新版本。" : "\n\nPlease wait for the mod to be updated for the latest game version."); } public IEnumerable GetDetailMessages() { return details.Distinct().ToArray(); } private static bool RequireField(FieldInfo field, string apiName, CompatibilityReport report, string affectedFeature) { if (field != null) { return true; } report.AddDisabledFeature(affectedFeature); report.details.Add("Missing field: " + apiName); return false; } private static bool RequireMethod(Type type, string methodName, Type[] parameterTypes, CompatibilityReport report, string affectedFeature) { if (type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes ?? Type.EmptyTypes, null) != null) { return true; } report.AddDisabledFeature(affectedFeature); string text = ((parameterTypes == null || parameterTypes.Length == 0) ? "()" : ("(" + string.Join(", ", parameterTypes.Select((Type parameterType) => parameterType.Name).ToArray()) + ")")); report.details.Add("Missing method: " + type.FullName + "." + methodName + text); return false; } private static bool RequireNestedField(Type ownerType, string ownerFieldName, string nestedFieldName, CompatibilityReport report, string affectedFeature) { FieldInfo fieldInfo = ownerType?.GetField(ownerFieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo == null) { report.AddDisabledFeature(affectedFeature); report.details.Add("Missing field: " + ownerType?.FullName + "." + ownerFieldName); return false; } if (fieldInfo.FieldType.GetField(nestedFieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) != null) { return true; } report.AddDisabledFeature(affectedFeature); report.details.Add("Missing field: " + fieldInfo.FieldType.FullName + "." + nestedFieldName); return false; } private static bool RequireNestedProperty(Type ownerType, string ownerFieldName, string nestedPropertyName, CompatibilityReport report, string affectedFeature) { FieldInfo fieldInfo = ownerType?.GetField(ownerFieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo == null) { report.AddDisabledFeature(affectedFeature); report.details.Add("Missing field: " + ownerType?.FullName + "." + ownerFieldName); return false; } if (fieldInfo.FieldType.GetProperty(nestedPropertyName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) != null) { return true; } report.AddDisabledFeature(affectedFeature); report.details.Add("Missing property: " + fieldInfo.FieldType.FullName + "." + nestedPropertyName); return false; } private static bool RequireType(Type type, string typeName, CompatibilityReport report, string affectedFeature) { if (type != null) { return true; } report.AddDisabledFeature(affectedFeature); report.details.Add("Missing type: " + typeName); return false; } private static bool RequireProperty(PropertyInfo property, Type ownerType, string propertyName, CompatibilityReport report, string affectedFeature) { if (property != null) { return true; } report.AddDisabledFeature(affectedFeature); report.details.Add("Missing property: " + ownerType?.FullName + "." + propertyName); return false; } private static bool CheckOptionalNameChangerInterop(CompatibilityReport report) { if (!Chainloader.PluginInfos.TryGetValue("NameChangeREPO", out var value)) { nameChangeRepoStartingNameField = null; nameChangeRepoStartingNameValueProperty = null; return true; } object obj = ((value != null) ? value.Instance : null); if (obj == null) { report.AddDisabledFeature("公开房默认名称联动"); report.details.Add("NameChangeREPO instance is null."); return false; } FieldInfo field = obj.GetType().GetField("StartingName", BindingFlags.Static | BindingFlags.Public); if (field == null) { report.AddDisabledFeature("公开房默认名称联动"); report.details.Add("Missing field: " + obj.GetType().FullName + ".StartingName"); return false; } PropertyInfo property = field.FieldType.GetProperty("Value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { nameChangeRepoStartingNameField = field; nameChangeRepoStartingNameValueProperty = property; return true; } report.AddDisabledFeature("公开房默认名称联动"); report.details.Add("Missing property: " + field.FieldType.FullName + ".Value"); return false; } private void AddDisabledFeature(string feature) { if (!disabledFeatures.Contains(feature)) { disabledFeatures.Add(feature); } } } private sealed class HostCopyMetadata { public bool PublishedBySaveKeeper; public string TeamName; public bool TeamNameChanged; public float TimePlayed; public string DateAndTime; public string SourceSaveName; public string SourceRoomName; public string SourceRoomId; public string SourceHostName; public string SourceHostUserId; } private sealed class HostCopyExportPlan { public string SaveFileName; public string ExportSignature; public Dictionary> Snapshot; public Dictionary PlayerNamesSnapshot; public HostCopyMetadata Metadata; public string TeamName; public bool TeamNameChanged; public float TimePlayed; public string DateAndTime; public int SaveVersion; } private sealed class HostCopyAutoSaveState { public bool ExportRunning; public string SaveName; public string SessionToken; public string LastExportSignature; public string PendingAutoSaveName; public string PendingAutoSaveSignature; public HostCopyExportPlan PendingAutoSavePlan; public bool PendingPlanBuildRequested; public float PendingPlanBuildReadyAt; public void ClearPendingAutoSave() { PendingAutoSaveName = null; PendingAutoSaveSignature = null; PendingAutoSavePlan = null; } public void ClearPendingPlanBuild() { PendingPlanBuildRequested = false; PendingPlanBuildReadyAt = 0f; } public void ResetForRoomChange() { ExportRunning = false; SaveName = null; SessionToken = null; LastExportSignature = null; ClearPendingAutoSave(); ClearPendingPlanBuild(); } } [HarmonyPatch(typeof(StatsManager), "SaveFileGetAllAsync")] private static class StatsManagerSaveFileGetAllAsyncPatch { private static void Postfix(ref Task> __result) { if (CanRecallLastUsedSave()) { Task> task = __result; if (task != null) { __result = WrapAndReorderAsync(task); } } } private static async Task> WrapAndReorderAsync(Task> inner) { List list = await inner; try { ReorderSaveFoldersByMru(list); } catch (Exception ex) { LogWarning("MRU 重排 SaveFileGetAllAsync 结果失败: " + ex.Message); } return list; } } public sealed class SaveSelectorBehaviour : MonoBehaviour { private sealed class SaveFileEntry { public string FileName; public string TeamName; public string DateTime; public int Level; public int Currency; public int TotalHaul; public float TimePlayed; public List PlayerNames; public List Backups; public bool IsValid; } private enum ViewMode { List, ConfirmLoad, ConfirmDelete, NewSave, Rename } [CompilerGenerated] private sealed class d__202 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float seconds; public SaveSelectorBehaviour <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__202(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown int num = <>1__state; SaveSelectorBehaviour saveSelectorBehaviour = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(seconds); <>1__state = 1; return true; case 1: <>1__state = -1; saveSelectorBehaviour.isOperationRunning = false; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const float WindowWidth = 860f; private const float WindowHeight = 720f; private bool windowOpen; private bool windowCentered; private Rect windowRect = new Rect(0f, 0f, 860f, 720f); private Vector2 scrollPosition = Vector2.zero; private bool savesLoaded; private bool isLoadingSaves; private bool isOperationRunning; private string statusMessage = string.Empty; private Color statusColor = Color.white; private float statusUntilUnscaledTime; private readonly List saveFiles = new List(); private string currentSaveFileName = string.Empty; private ViewMode mode; private string selectedSaveToLoad = string.Empty; private string selectedSaveToDelete = string.Empty; private string newSaveTeamName = string.Empty; private string selectedSaveToRename = string.Empty; private string renameNewTeamName = string.Empty; private bool stylesInit; private GUIStyle sWindow; private GUIStyle sTitle; private GUIStyle sLabel; private GUIStyle sLabelDim; private GUIStyle sStatus; private GUIStyle sCurrentBox; private GUIStyle sSection; private GUIStyle sBox; private GUIStyle sSectionLabel; private GUIStyle sCorruptLabel; private GUIStyle sWarn; private GUIStyle sHostLabel; private GUIStyle sTextField; private GUIStyle sScroll; private GUIStyle sBtnGreen; private GUIStyle sBtnRed; private GUIStyle sBtnBlue; private GUIStyle sBtnNormal; private GUIStyle sBtnDisabled; private GUIStyle sBtnSmallNormal; private GUIStyle sBtnSmallBlue; private GUIStyle sBtnSmallRed; private GUIStyle sBtnSmallDisabled; private Texture2D texDark; private Texture2D texMid; private Texture2D texLight; private Texture2D texSection; private Texture2D texCurrent; private Texture2D texGreen; private Texture2D texRed; private Texture2D texBlue; private Texture2D texGray; private static bool ZH => UseChinese(); private static string T_WindowTitleList { get { if (!ZH) { return "✦ SaveKeeper"; } return "✦ 存档管家 / SaveKeeper"; } } private static string T_WindowTitleLoad { get { if (!ZH) { return "✦ SaveKeeper / Confirm Load"; } return "✦ 存档管家 / 确认读档"; } } private static string T_WindowTitleDelete { get { if (!ZH) { return "✦ SaveKeeper / Confirm Delete"; } return "✦ 存档管家 / 确认删除"; } } private static string T_WindowTitleNew { get { if (!ZH) { return "✦ SaveKeeper / New Save"; } return "✦ 存档管家 / 新建存档"; } } private static string T_WindowTitleRename { get { if (!ZH) { return "✦ SaveKeeper / Rename Save"; } return "✦ 存档管家 / 重命名存档"; } } private static string T_CurrentSavePrefix { get { if (!ZH) { return "Current save: "; } return "当前存档:"; } } private static string T_NoneInBrackets { get { if (!ZH) { return "(none)"; } return "(暂无)"; } } private static string T_HostMode { get { if (!ZH) { return "● Host"; } return "● 主机模式"; } } private static string T_ViewerMode { get { if (!ZH) { return "○ Read-only (not host)"; } return "○ 仅查看(非主机)"; } } private static string T_NonHostHint { get { if (!ZH) { return "You are in someone else's room. Browse only; load / new / rename / delete are disabled."; } return "当前在他人房间,仅可浏览。读档 / 新建 / 重命名 / 删除按钮已禁用。"; } } private static string T_SectionSavesTitle { get { if (!ZH) { return "Saves"; } return "存档列表"; } } private static string T_Loading { get { if (!ZH) { return "Loading..."; } return "加载中..."; } } private static string T_LoadingFull { get { if (!ZH) { return "Loading saves..."; } return "正在加载存档列表..."; } } private static string T_NoSavesFound { get { if (!ZH) { return "No saves found."; } return "没有找到任何存档。"; } } private static string T_BtnNewSave { get { if (!ZH) { return "New Save"; } return "新建存档"; } } private static string T_BtnRefresh { get { if (!ZH) { return "Refresh"; } return "刷新列表"; } } private static string T_BtnClose { get { if (!ZH) { return "Close [F7 / ESC]"; } return "关闭 [F7 / ESC]"; } } private static string T_BtnLoad { get { if (!ZH) { return "Load"; } return "读档"; } } private static string T_BtnRename { get { if (!ZH) { return "Rename"; } return "重命名"; } } private static string T_BtnDelete { get { if (!ZH) { return "Delete"; } return "删除"; } } private static string T_BtnCurrent { get { if (!ZH) { return "Current"; } return "当前"; } } private static string T_BtnConfirmLoad { get { if (!ZH) { return "Load"; } return "确认读档"; } } private static string T_BtnConfirmDelete { get { if (!ZH) { return "Delete"; } return "确认删除"; } } private static string T_BtnConfirmNew { get { if (!ZH) { return "Create"; } return "创建"; } } private static string T_BtnConfirmRename { get { if (!ZH) { return "Rename"; } return "重命名"; } } private static string T_BtnCancel { get { if (!ZH) { return "Cancel"; } return "取消"; } } private static string T_DateLabel { get { if (!ZH) { return "Date: "; } return "日期:"; } } private static string T_DateUnknown { get { if (!ZH) { return "Unknown"; } return "未知"; } } private static string T_LevelLabel { get { if (!ZH) { return "Level: "; } return "关卡:"; } } private static string T_CurrencyLabel { get { if (!ZH) { return "Cash: "; } return "现金:"; } } private static string T_TimeLabel { get { if (!ZH) { return "Time: "; } return "时长:"; } } private static string T_TotalLabel { get { if (!ZH) { return "Total haul: $"; } return "总收益:$"; } } private static string T_BackupsLabel { get { if (!ZH) { return "Backups: "; } return "备份:"; } } private static string T_BackupsCountSuffix { get { if (!ZH) { return ""; } return " 份"; } } private static string T_BackupsNone { get { if (!ZH) { return "Backups: —"; } return "备份:—"; } } private static string T_PlayersLabel { get { if (!ZH) { return "Players: "; } return "玩家:"; } } private static string T_FileNameLabel { get { if (!ZH) { return "File: "; } return "文件名:"; } } private static string T_CountFmt { get { if (!ZH) { return "{0} total"; } return "共 {0} 份"; } } private static string T_CorruptedFmt { get { if (!ZH) { return "⚠ Corrupted save ({0})"; } return "⚠ 存档已损坏({0})"; } } private static string T_ConfirmLoadHeader { get { if (!ZH) { return "Load this save?"; } return "确定要读取这份存档吗?"; } } private static string T_LoadProgressWarn { get { if (!ZH) { return "⚠ Unsaved progress may be lost."; } return "⚠ 当前未保存的进度可能丢失。"; } } private static string T_MultiplayerHint { get { if (!ZH) { return "Multiplayer: will reload inside the current room without dropping teammates."; } return "多人房:将在当前房间内读档,不会踢掉队友。"; } } private static string T_ConfirmDeleteHeader { get { if (!ZH) { return "Delete this save?"; } return "确定要删除这份存档吗?"; } } private static string T_DeleteIrreversible { get { if (!ZH) { return "⚠ This cannot be undone!"; } return "⚠ 此操作不可撤销!"; } } private static string T_NewSaveHeader { get { if (!ZH) { return "New Save"; } return "新建存档"; } } private static string T_NewSavePrompt { get { if (!ZH) { return "Team name (empty = default R.E.P.O.):"; } return "队伍名称(留空则使用默认 R.E.P.O.):"; } } private static string T_RenameHeader { get { if (!ZH) { return "Rename Save"; } return "重命名存档"; } } private static string T_RenameWho { get { if (!ZH) { return "Renaming: "; } return "正在重命名:"; } } private static string T_RenameNewName { get { if (!ZH) { return "New team name:"; } return "新队伍名称:"; } } private static string T_StatusLoadFailFmt { get { if (!ZH) { return "Failed to load saves: {0}"; } return "加载存档列表失败:{0}"; } } private static string T_StatusStatsNotReady { get { if (!ZH) { return "Cannot read saves (StatsManager not ready)."; } return "无法读取存档列表(StatsManager 未就绪)。"; } } private static string T_StatusGetAllFail { get { if (!ZH) { return "Cannot get saves list."; } return "无法获取存档列表。"; } } private static string T_StatusNonHostNoLoad { get { if (!ZH) { return "Only the host can load saves."; } return "非主机不可读档。"; } } private static string T_StatusNonHostNoDelete { get { if (!ZH) { return "Only the host can delete saves."; } return "非主机不可删除存档。"; } } private static string T_StatusNonHostNoNew { get { if (!ZH) { return "Only the host can create saves."; } return "非主机不可新建存档。"; } } private static string T_StatusNonHostNoRename { get { if (!ZH) { return "Only the host can rename saves."; } return "非主机不可重命名存档。"; } } private static string T_StatusStatsManagerMissing { get { if (!ZH) { return "StatsManager not ready."; } return "StatsManager 未就绪。"; } } private static string T_StatusDeletedFmt { get { if (!ZH) { return "Deleted: {0}"; } return "已删除存档:{0}"; } } private static string T_StatusDeleteFailFmt { get { if (!ZH) { return "Delete failed: {0}"; } return "删除存档失败:{0}"; } } private static string T_StatusCreatedFmt { get { if (!ZH) { return "Created new save: {0} (will be written to disk once the game starts)"; } return "已创建新存档:{0}(开始游戏后写入磁盘)"; } } private static string T_StatusCreateFailFmt { get { if (!ZH) { return "Create failed: {0}"; } return "新建存档失败:{0}"; } } private static string T_StatusRenameInvalid { get { if (!ZH) { return "Invalid rename parameters."; } return "重命名参数无效。"; } } private static string T_StatusRenamedFmt { get { if (!ZH) { return "Renamed to: {0}"; } return "已重命名为:{0}"; } } private static string T_StatusRenameFailFmt { get { if (!ZH) { return "Rename failed: {0}"; } return "重命名失败:{0}"; } } private void Update() { if (!ShouldEnableSaveSelectorMenu()) { if (windowOpen) { SetWindowOpen(open: false); } } else if (Input.GetKeyDown((KeyCode)288)) { SetWindowOpen(!windowOpen); } else { if (!windowOpen) { return; } KeepCursorFree(); if (Input.GetKeyDown((KeyCode)27)) { if (mode != 0) { mode = ViewMode.List; } else { SetWindowOpen(open: false); } } } } private void LateUpdate() { if (windowOpen && ShouldEnableSaveSelectorMenu()) { KeepCursorFree(); } } private void OnDestroy() { if (windowOpen) { ReleaseCursor(); } } private static void TakeCursor() { try { if ((Object)(object)CursorManager.instance != (Object)null) { CursorManager.instance.Unlock(99999f); } } catch { } Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } private static void ReleaseCursor() { try { if (CursorManagerUnlockTimerField != null && (Object)(object)CursorManager.instance != (Object)null) { CursorManagerUnlockTimerField.SetValue(CursorManager.instance, 0f); } } catch { } try { if (SemiFunc.IsMainMenu() || SemiFunc.RunIsLobbyMenu()) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; return; } } catch { } Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; } private static void KeepCursorFree() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)Cursor.lockState != 0) { Cursor.lockState = (CursorLockMode)0; } if (!Cursor.visible) { Cursor.visible = true; } try { if (CursorManagerUnlockTimerField != null && (Object)(object)CursorManager.instance != (Object)null) { CursorManagerUnlockTimerField.SetValue(CursorManager.instance, 99999f); } } catch { } } private void SetWindowOpen(bool open) { if (windowOpen == open) { return; } windowOpen = open; if (open) { windowCentered = false; mode = ViewMode.List; currentSaveFileName = GetCurrentSaveFileName() ?? string.Empty; if (!savesLoaded && !isLoadingSaves) { LoadSaveFilesAsync(); } TakeCursor(); LogInfo("F7 打开存档管理菜单(鼠标已解锁)。"); } else { mode = ViewMode.List; ReleaseCursor(); LogInfo("F7 关闭存档管理菜单。"); } } private void OnGUI() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) if (windowOpen && ShouldEnableSaveSelectorMenu()) { EnsureStyles(); if (Event.current != null && (int)Event.current.type == 7) { KeepCursorFree(); } if (!windowCentered) { ((Rect)(ref windowRect)).x = ((float)Screen.width - ((Rect)(ref windowRect)).width) * 0.5f; ((Rect)(ref windowRect)).y = ((float)Screen.height - ((Rect)(ref windowRect)).height) * 0.5f; windowCentered = true; } string text = mode switch { ViewMode.ConfirmLoad => T_WindowTitleLoad, ViewMode.ConfirmDelete => T_WindowTitleDelete, ViewMode.NewSave => T_WindowTitleNew, ViewMode.Rename => T_WindowTitleRename, _ => T_WindowTitleList, }; windowRect = GUI.Window(33051, windowRect, new WindowFunction(DrawWindowContents), text, sWindow); } } private void DrawWindowContents(int id) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) switch (mode) { case ViewMode.ConfirmLoad: DrawConfirmLoad(); break; case ViewMode.ConfirmDelete: DrawConfirmDelete(); break; case ViewMode.NewSave: DrawNewSaveForm(); break; case ViewMode.Rename: DrawRenameForm(); break; default: DrawListView(); break; } GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref windowRect)).width, 28f)); } private void DrawListView() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Expected O, but got Unknown //IL_02b3: Unknown result type (might be due to invalid IL or missing references) currentSaveFileName = GetCurrentSaveFileName() ?? string.Empty; bool flag = !IsActualMultiplayerSession() || PhotonNetwork.IsMasterClient; GUILayout.BeginArea(new Rect(14f, 32f, ((Rect)(ref windowRect)).width - 28f, ((Rect)(ref windowRect)).height - 40f)); GUILayout.BeginVertical(sCurrentBox, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(T_CurrentSavePrefix, sTitle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); if (!string.IsNullOrEmpty(currentSaveFileName)) { GUILayout.Label(currentSaveFileName, sHostLabel, Array.Empty()); } else { GUILayout.Label(T_NoneInBrackets, sLabelDim, Array.Empty()); } GUILayout.FlexibleSpace(); GUILayout.Label(flag ? T_HostMode : T_ViewerMode, flag ? sHostLabel : sWarn, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) }); GUILayout.EndHorizontal(); GUILayout.EndVertical(); if (!flag) { GUILayout.BeginVertical(sSection, Array.Empty()); GUILayout.Label(T_NonHostHint, sWarn, Array.Empty()); GUILayout.EndVertical(); } GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(T_SectionSavesTitle, sSectionLabel, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(isLoadingSaves ? T_Loading : string.Format(T_CountFmt, saveFiles.Count), sLabelDim, Array.Empty()); GUILayout.EndHorizontal(); float num = ((Rect)(ref windowRect)).height - 240f; scrollPosition = GUILayout.BeginScrollView(scrollPosition, sScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num) }); if (isLoadingSaves || !savesLoaded) { GUILayout.Label(T_LoadingFull, sLabelDim, Array.Empty()); } else if (saveFiles.Count == 0) { GUILayout.Label(T_NoSavesFound, sLabelDim, Array.Empty()); } else { foreach (SaveFileEntry saveFile in saveFiles) { DrawSaveFileEntry(saveFile, flag); GUILayout.Space(4f); } } GUILayout.EndScrollView(); if (!string.IsNullOrEmpty(statusMessage) && Time.unscaledTime <= statusUntilUnscaledTime) { GUILayout.Space(2f); GUIStyle val = new GUIStyle(sStatus); val.normal.textColor = statusColor; GUILayout.Label(statusMessage, val, Array.Empty()); } GUILayout.Space(6f); GUILayout.BeginHorizontal(Array.Empty()); GUI.enabled = flag && !isOperationRunning; if (GUILayout.Button(T_BtnNewSave, flag ? sBtnGreen : sBtnDisabled, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(140f), GUILayout.Height(34f) })) { newSaveTeamName = string.Empty; mode = ViewMode.NewSave; } GUI.enabled = !isLoadingSaves; if (GUILayout.Button(T_BtnRefresh, sBtnNormal, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(120f), GUILayout.Height(34f) })) { LoadSaveFilesAsync(); } GUI.enabled = true; GUILayout.FlexibleSpace(); if (GUILayout.Button(T_BtnClose, sBtnNormal, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(180f), GUILayout.Height(34f) })) { SetWindowOpen(open: false); } GUILayout.EndHorizontal(); GUILayout.EndArea(); } private void DrawSaveFileEntry(SaveFileEntry entry, bool isHost) { //IL_003f: 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_004c: Unknown result type (might be due to invalid IL or missing references) bool num = entry.FileName == currentSaveFileName; GUILayout.BeginVertical(num ? sCurrentBox : sBox, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUIStyle val = (GUIStyle)(num ? ((object)sHostLabel) : ((object)new GUIStyle(sLabel) { fontSize = 14, fontStyle = (FontStyle)1 })); GUILayout.Label((num ? "● " : "○ ") + (entry.TeamName ?? entry.FileName), val, Array.Empty()); GUILayout.FlexibleSpace(); bool flag = isHost && !isOperationRunning && entry.IsValid; if (num) { GUI.enabled = false; GUILayout.Button(T_BtnCurrent, sBtnSmallDisabled, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(64f), GUILayout.Height(26f) }); GUI.enabled = true; } else if (!entry.IsValid) { GUI.enabled = false; GUILayout.Button("—", sBtnSmallDisabled, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(64f), GUILayout.Height(26f) }); GUI.enabled = true; } else { GUI.enabled = flag; if (GUILayout.Button(T_BtnLoad, flag ? sBtnSmallBlue : sBtnSmallDisabled, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(64f), GUILayout.Height(26f) })) { selectedSaveToLoad = entry.FileName; mode = ViewMode.ConfirmLoad; } GUI.enabled = true; } if (entry.IsValid) { GUI.enabled = isHost && !isOperationRunning; if (GUILayout.Button(T_BtnRename, flag ? sBtnSmallNormal : sBtnSmallDisabled, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(80f), GUILayout.Height(26f) })) { selectedSaveToRename = entry.FileName; renameNewTeamName = entry.TeamName ?? string.Empty; mode = ViewMode.Rename; } GUI.enabled = true; } else { GUILayout.Space(80f); } GUI.enabled = isHost && !isOperationRunning; if (GUILayout.Button(T_BtnDelete, (isHost && !isOperationRunning) ? sBtnSmallRed : sBtnSmallDisabled, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(64f), GUILayout.Height(26f) })) { selectedSaveToDelete = entry.FileName; mode = ViewMode.ConfirmDelete; } GUI.enabled = true; GUILayout.EndHorizontal(); if (entry.IsValid) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(T_DateLabel + (entry.DateTime ?? T_DateUnknown), sLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); GUILayout.Label(T_LevelLabel + (entry.Level + 1), sLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); GUILayout.Label(T_CurrencyLabel + entry.Currency + "k", sLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); GUILayout.Label(T_TimeLabel + FormatTime(entry.TimePlayed), sLabel, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(T_TotalLabel + entry.TotalHaul + "k", sLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); if (entry.Backups != null && entry.Backups.Count > 0) { GUILayout.Label(T_BackupsLabel + entry.Backups.Count + T_BackupsCountSuffix, sLabel, Array.Empty()); } else { GUILayout.Label(T_BackupsNone, sLabelDim, Array.Empty()); } GUILayout.EndHorizontal(); if (entry.PlayerNames != null && entry.PlayerNames.Count > 0) { string separator = (ZH ? "、" : ", "); GUILayout.Label(T_PlayersLabel + string.Join(separator, entry.PlayerNames.ToArray()), sLabel, Array.Empty()); } GUILayout.Label(T_FileNameLabel + entry.FileName, sLabelDim, Array.Empty()); } else { GUILayout.Label(string.Format(T_CorruptedFmt, entry.FileName), sCorruptLabel, Array.Empty()); } GUILayout.EndVertical(); } private void DrawConfirmLoad() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown GUILayout.BeginArea(new Rect(14f, 36f, ((Rect)(ref windowRect)).width - 28f, ((Rect)(ref windowRect)).height - 44f)); GUILayout.Space(40f); GUIStyle val = new GUIStyle(sLabel) { alignment = (TextAnchor)4, fontSize = 15 }; GUILayout.Label(T_ConfirmLoadHeader, val, Array.Empty()); GUILayout.Space(8f); GUIStyle val2 = new GUIStyle(sTitle) { alignment = (TextAnchor)4, fontSize = 17 }; GUILayout.Label(selectedSaveToLoad, val2, Array.Empty()); GUILayout.Space(20f); GUIStyle val3 = new GUIStyle(sWarn) { alignment = (TextAnchor)4 }; GUILayout.Label(T_LoadProgressWarn, val3, Array.Empty()); if (IsActualMultiplayerSession()) { GUIStyle val4 = new GUIStyle(sLabelDim) { alignment = (TextAnchor)4 }; GUILayout.Space(4f); GUILayout.Label(T_MultiplayerHint, val4, Array.Empty()); } GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(T_BtnConfirmLoad, sBtnGreen, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(160f), GUILayout.Height(40f) })) { ExecuteLoadSelectedSave(); } GUILayout.Space(16f); if (GUILayout.Button(T_BtnCancel, sBtnNormal, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(140f), GUILayout.Height(40f) })) { mode = ViewMode.List; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(20f); GUILayout.EndArea(); } private void DrawConfirmDelete() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown GUILayout.BeginArea(new Rect(14f, 36f, ((Rect)(ref windowRect)).width - 28f, ((Rect)(ref windowRect)).height - 44f)); GUILayout.Space(40f); GUIStyle val = new GUIStyle(sLabel) { alignment = (TextAnchor)4, fontSize = 15 }; GUILayout.Label(T_ConfirmDeleteHeader, val, Array.Empty()); GUILayout.Space(8f); GUIStyle val2 = new GUIStyle(sTitle) { alignment = (TextAnchor)4, fontSize = 17 }; GUILayout.Label(selectedSaveToDelete, val2, Array.Empty()); GUILayout.Space(20f); GUIStyle val3 = new GUIStyle(sCorruptLabel) { alignment = (TextAnchor)4, fontSize = 15 }; GUILayout.Label(T_DeleteIrreversible, val3, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(T_BtnConfirmDelete, sBtnRed, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(160f), GUILayout.Height(40f) })) { ExecuteDeleteSelectedSave(); } GUILayout.Space(16f); if (GUILayout.Button(T_BtnCancel, sBtnNormal, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(140f), GUILayout.Height(40f) })) { mode = ViewMode.List; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(20f); GUILayout.EndArea(); } private void DrawNewSaveForm() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown GUILayout.BeginArea(new Rect(14f, 36f, ((Rect)(ref windowRect)).width - 28f, ((Rect)(ref windowRect)).height - 44f)); GUILayout.Space(40f); GUIStyle val = new GUIStyle(sLabel) { alignment = (TextAnchor)4, fontSize = 15 }; GUILayout.Label(T_NewSaveHeader, val, Array.Empty()); GUILayout.Space(20f); GUILayout.Label(T_NewSavePrompt, sLabel, Array.Empty()); GUILayout.Space(4f); GUI.SetNextControlName("ZichenSaveSelector_NewSaveField"); newSaveTeamName = GUILayout.TextField(newSaveTeamName ?? string.Empty, 30, sTextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) }); GUI.FocusControl("ZichenSaveSelector_NewSaveField"); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(T_BtnConfirmNew, sBtnGreen, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(160f), GUILayout.Height(40f) })) { ExecuteCreateNewSave(); } GUILayout.Space(16f); if (GUILayout.Button(T_BtnCancel, sBtnNormal, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(140f), GUILayout.Height(40f) })) { mode = ViewMode.List; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(20f); GUILayout.EndArea(); } private void DrawRenameForm() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown GUILayout.BeginArea(new Rect(14f, 36f, ((Rect)(ref windowRect)).width - 28f, ((Rect)(ref windowRect)).height - 44f)); GUILayout.Space(40f); GUIStyle val = new GUIStyle(sLabel) { alignment = (TextAnchor)4, fontSize = 15 }; GUILayout.Label(T_RenameHeader, val, Array.Empty()); GUILayout.Space(20f); GUILayout.Label(T_RenameWho, sLabel, Array.Empty()); GUILayout.Label(selectedSaveToRename, sTitle, Array.Empty()); GUILayout.Space(12f); GUILayout.Label(T_RenameNewName, sLabel, Array.Empty()); GUILayout.Space(4f); GUI.SetNextControlName("ZichenSaveSelector_RenameField"); renameNewTeamName = GUILayout.TextField(renameNewTeamName ?? string.Empty, 30, sTextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) }); GUI.FocusControl("ZichenSaveSelector_RenameField"); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(T_BtnConfirmRename, sBtnBlue, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(160f), GUILayout.Height(40f) })) { ExecuteRenameSelectedSave(); } GUILayout.Space(16f); if (GUILayout.Button(T_BtnCancel, sBtnNormal, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(140f), GUILayout.Height(40f) })) { mode = ViewMode.List; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(20f); GUILayout.EndArea(); } private async Task LoadSaveFilesAsync() { if (isLoadingSaves) { return; } isLoadingSaves = true; savesLoaded = false; saveFiles.Clear(); try { if ((Object)(object)StatsManager.instance == (Object)null || StatsManagerSaveFileGetAllAsyncMethod == null) { SetStatus(T_StatusStatsNotReady, new Color(1f, 0.55f, 0.55f)); return; } Task> task = (Task>)StatsManagerSaveFileGetAllAsyncMethod.Invoke(StatsManager.instance, null); if (task == null) { SetStatus(T_StatusGetAllFail, new Color(1f, 0.55f, 0.55f)); return; } List obj = (await task) ?? new List(); StatsManager instance = StatsManager.instance; foreach (SaveFolder item in obj) { SaveFileEntry saveFileEntry = new SaveFileEntry { FileName = item.name, Backups = item.backups }; string s = instance.SaveFileGetRunLevel(item.name, (string)null); string s2 = instance.SaveFileGetRunCurrency(item.name, (string)null); string s3 = instance.SaveFileGetTotalHaul(item.name, (string)null); if (int.TryParse(s, out var result) && int.TryParse(s2, out var result2) && int.TryParse(s3, out var result3)) { saveFileEntry.IsValid = true; saveFileEntry.TeamName = instance.SaveFileGetTeamName(item.name, (string)null) ?? item.name; saveFileEntry.DateTime = instance.SaveFileGetDateAndTime(item.name, (string)null) ?? "未知"; saveFileEntry.Level = result; saveFileEntry.Currency = result2; saveFileEntry.TotalHaul = result3; saveFileEntry.TimePlayed = instance.SaveFileGetTimePlayed(item.name, (string)null); try { saveFileEntry.PlayerNames = instance.SaveFileGetPlayerNames(item.name, (string)null); } catch { saveFileEntry.PlayerNames = null; } } else { saveFileEntry.IsValid = false; saveFileEntry.TeamName = item.name; } saveFiles.Add(saveFileEntry); } savesLoaded = true; LogInfo($"F7 菜单已加载 {saveFiles.Count} 份存档。"); } catch (Exception ex) { SetStatus(string.Format(T_StatusLoadFailFmt, ex.Message), new Color(1f, 0.55f, 0.55f)); LogWarning("F7 菜单加载存档失败: " + ex.Message); } finally { isLoadingSaves = false; } } private void ExecuteLoadSelectedSave() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) string text = selectedSaveToLoad; mode = ViewMode.List; if (string.IsNullOrEmpty(text)) { return; } if (!IsHostAndEnabled()) { SetStatus(T_StatusNonHostNoLoad, new Color(1f, 0.85f, 0.35f)); return; } LogInfo("F7 菜单触发读档:" + text); try { if (StatsManagerCurrentSaveField != null && (Object)(object)StatsManager.instance != (Object)null) { StatsManagerCurrentSaveField.SetValue(StatsManager.instance, text); } } catch (Exception ex) { LogWarning("F7 切换 saveFileCurrent 失败: " + ex.Message); } try { RecordSaveUsage(text); } catch { } isOperationRunning = true; SetWindowOpen(open: false); ManualRestoreLatestProgress(); ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(ClearOperationFlagAfterDelay(2f)); } } [IteratorStateMachine(typeof(d__202))] private IEnumerator ClearOperationFlagAfterDelay(float seconds) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__202(0) { <>4__this = this, seconds = seconds }; } private void ExecuteDeleteSelectedSave() { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) string text = selectedSaveToDelete; mode = ViewMode.List; if (string.IsNullOrEmpty(text)) { return; } if (!IsHostAndEnabled()) { SetStatus(T_StatusNonHostNoDelete, new Color(1f, 0.85f, 0.35f)); return; } LogInfo("F7 菜单触发删除存档:" + text); isOperationRunning = true; playerMenuDeleteInProgress = true; try { StatsManager instance = StatsManager.instance; if (instance != null) { instance.SaveFileDelete(text); } SetStatus(string.Format(T_StatusDeletedFmt, text), new Color(0.5f, 1f, 0.5f)); } catch (Exception ex) { SetStatus(string.Format(T_StatusDeleteFailFmt, ex.Message), new Color(1f, 0.55f, 0.55f)); LogWarning("F7 删除失败: " + ex.Message); } finally { playerMenuDeleteInProgress = false; isOperationRunning = false; } LoadSaveFilesAsync(); } private void ExecuteCreateNewSave() { //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(newSaveTeamName) ? "R.E.P.O." : newSaveTeamName.Trim()); mode = ViewMode.List; if (!IsHostAndEnabled()) { SetStatus(T_StatusNonHostNoNew, new Color(1f, 0.85f, 0.35f)); return; } if ((Object)(object)StatsManager.instance == (Object)null || StatsManagerSaveFileCreateMethod == null) { SetStatus(T_StatusStatsManagerMissing, new Color(1f, 0.55f, 0.55f)); return; } LogInfo("F7 菜单创建新存档,队伍名:" + text); isOperationRunning = true; try { if (StatsManagerTeamNameField != null) { StatsManagerTeamNameField.SetValue(StatsManager.instance, text); } StatsManagerSaveFileCreateMethod.Invoke(StatsManager.instance, new object[2] { string.Empty, false }); string text2 = GetCurrentSaveFileName(); if (!string.IsNullOrEmpty(text2)) { RecordSaveUsage(text2); } SetStatus(string.Format(T_StatusCreatedFmt, text), new Color(0.5f, 1f, 0.5f)); } catch (Exception ex) { SetStatus(string.Format(T_StatusCreateFailFmt, ex.Message), new Color(1f, 0.55f, 0.55f)); LogWarning("F7 新建失败: " + ex.Message); } finally { isOperationRunning = false; } LoadSaveFilesAsync(); } private void ExecuteRenameSelectedSave() { //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) string text = selectedSaveToRename; string text2 = (renameNewTeamName ?? string.Empty).Trim(); mode = ViewMode.List; if (string.IsNullOrEmpty(text) || string.IsNullOrWhiteSpace(text2)) { SetStatus(T_StatusRenameInvalid, new Color(1f, 0.85f, 0.35f)); return; } if (!IsHostAndEnabled()) { SetStatus(T_StatusNonHostNoRename, new Color(1f, 0.85f, 0.35f)); return; } LogInfo("F7 菜单重命名存档 " + text + " → " + text2); isOperationRunning = true; try { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { SetStatus(T_StatusStatsManagerMissing, new Color(1f, 0.55f, 0.55f)); return; } string value = StatsManagerCurrentSaveField?.GetValue(instance) as string; if (StatsManagerLoadGameMethod != null) { StatsManagerLoadGameMethod.Invoke(instance, new object[2] { text, null }); } if (StatsManagerTeamNameField != null) { StatsManagerTeamNameField.SetValue(instance, text2); } typeof(StatsManager).GetField("teamNameChanged", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(instance, true); instance.SaveGame(text); if (!string.IsNullOrEmpty(value)) { StatsManagerCurrentSaveField?.SetValue(instance, value); } SetStatus(string.Format(T_StatusRenamedFmt, text2), new Color(0.5f, 1f, 0.5f)); } catch (Exception ex) { SetStatus(string.Format(T_StatusRenameFailFmt, ex.Message), new Color(1f, 0.55f, 0.55f)); LogWarning("F7 重命名失败: " + ex.Message); } finally { isOperationRunning = false; } LoadSaveFilesAsync(); } private void SetStatus(string message, Color color) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) statusMessage = message; statusColor = color; statusUntilUnscaledTime = Time.unscaledTime + 5f; } private static string FormatTime(float seconds) { int num = (int)(seconds / 3600f); int num2 = (int)(seconds % 3600f / 60f); if (num <= 0) { return $"{num2}m"; } return $"{num}h {num2}m"; } private void EnsureStyles() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0191: 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_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Expected O, but got Unknown //IL_01ae: Expected O, but got Unknown //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Expected O, but got Unknown //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Expected O, but got Unknown //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Expected O, but got Unknown //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Expected O, but got Unknown //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Expected O, but got Unknown //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_032f: Expected O, but got Unknown //IL_0334: Expected O, but got Unknown //IL_033f: Unknown result type (might be due to invalid IL or missing references) //IL_0344: Unknown result type (might be due to invalid IL or missing references) //IL_0355: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Expected O, but got Unknown //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Expected O, but got Unknown //IL_038a: Expected O, but got Unknown //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Expected O, but got Unknown //IL_03d1: Expected O, but got Unknown //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_03e9: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_0405: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Expected O, but got Unknown //IL_041b: Unknown result type (might be due to invalid IL or missing references) //IL_0425: Expected O, but got Unknown //IL_043f: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0466: Expected O, but got Unknown //IL_0480: Unknown result type (might be due to invalid IL or missing references) //IL_04a1: Unknown result type (might be due to invalid IL or missing references) //IL_04a6: Unknown result type (might be due to invalid IL or missing references) //IL_04ae: Unknown result type (might be due to invalid IL or missing references) //IL_04bf: Unknown result type (might be due to invalid IL or missing references) //IL_04c5: Unknown result type (might be due to invalid IL or missing references) //IL_04cf: Unknown result type (might be due to invalid IL or missing references) //IL_04e0: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_04f0: Unknown result type (might be due to invalid IL or missing references) //IL_04f5: Unknown result type (might be due to invalid IL or missing references) //IL_04ff: Expected O, but got Unknown //IL_0504: Expected O, but got Unknown //IL_050f: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Expected O, but got Unknown //IL_052f: Unknown result type (might be due to invalid IL or missing references) //IL_0557: Unknown result type (might be due to invalid IL or missing references) //IL_057f: Unknown result type (might be due to invalid IL or missing references) //IL_05a7: Unknown result type (might be due to invalid IL or missing references) //IL_05d4: Unknown result type (might be due to invalid IL or missing references) //IL_0600: Unknown result type (might be due to invalid IL or missing references) //IL_0624: Unknown result type (might be due to invalid IL or missing references) //IL_0648: Unknown result type (might be due to invalid IL or missing references) //IL_0668: Unknown result type (might be due to invalid IL or missing references) //IL_0690: Unknown result type (might be due to invalid IL or missing references) //IL_06b8: Unknown result type (might be due to invalid IL or missing references) //IL_06e5: Unknown result type (might be due to invalid IL or missing references) //IL_0711: Unknown result type (might be due to invalid IL or missing references) //IL_0735: Unknown result type (might be due to invalid IL or missing references) //IL_0759: Unknown result type (might be due to invalid IL or missing references) if (stylesInit) { return; } try { texDark = MakeTex(new Color(0.12f, 0.12f, 0.14f, 0.97f)); texMid = MakeTex(new Color(0.18f, 0.18f, 0.22f, 0.95f)); texLight = MakeTex(new Color(0.25f, 0.25f, 0.3f, 0.9f)); texSection = MakeTex(new Color(0.15f, 0.15f, 0.2f, 0.95f)); texCurrent = MakeTex(new Color(0.1f, 0.3f, 0.18f, 0.95f)); texGreen = MakeTex(new Color(0.2f, 0.55f, 0.2f)); texRed = MakeTex(new Color(0.6f, 0.2f, 0.2f)); texBlue = MakeTex(new Color(0.3f, 0.5f, 0.8f)); texGray = MakeTex(new Color(0.3f, 0.3f, 0.34f, 0.85f)); GUIStyle val = new GUIStyle(GUI.skin.window); val.normal.background = texDark; val.normal.textColor = Color.white; val.onNormal.background = texDark; val.onNormal.textColor = Color.white; val.fontSize = 16; val.padding = new RectOffset(0, 0, 28, 0); sWindow = val; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 15, fontStyle = (FontStyle)1 }; val2.normal.textColor = new Color(0.9f, 0.85f, 0.6f); sTitle = val2; GUIStyle val3 = new GUIStyle(GUI.skin.label) { fontSize = 13, wordWrap = true }; val3.normal.textColor = new Color(0.88f, 0.88f, 0.88f); sLabel = val3; GUIStyle val4 = new GUIStyle(sLabel); val4.normal.textColor = new Color(0.62f, 0.62f, 0.66f); sLabelDim = val4; GUIStyle val5 = new GUIStyle(sLabel) { fontStyle = (FontStyle)1 }; val5.normal.textColor = new Color(0.6f, 1f, 0.7f); sHostLabel = val5; GUIStyle val6 = new GUIStyle(GUI.skin.label) { fontSize = 13, fontStyle = (FontStyle)1 }; val6.normal.textColor = new Color(0.7f, 0.9f, 0.7f); sStatus = val6; GUIStyle val7 = new GUIStyle(GUI.skin.box); val7.normal.background = texCurrent; val7.normal.textColor = Color.white; val7.padding = new RectOffset(10, 10, 8, 8); val7.margin = new RectOffset(0, 0, 0, 4); sCurrentBox = val7; GUIStyle val8 = new GUIStyle(GUI.skin.box); val8.normal.background = texSection; val8.normal.textColor = Color.white; val8.padding = new RectOffset(10, 10, 8, 8); val8.margin = new RectOffset(0, 0, 2, 4); sSection = val8; GUIStyle val9 = new GUIStyle(GUI.skin.box); val9.normal.background = texMid; val9.normal.textColor = Color.white; val9.padding = new RectOffset(10, 10, 8, 8); sBox = val9; GUIStyle val10 = new GUIStyle(GUI.skin.label) { fontSize = 14, fontStyle = (FontStyle)1 }; val10.normal.textColor = new Color(0.95f, 0.8f, 0.4f); sSectionLabel = val10; sCorruptLabel = new GUIStyle(sLabel); sCorruptLabel.normal.textColor = new Color(1f, 0.45f, 0.45f); sCorruptLabel.fontStyle = (FontStyle)1; sWarn = new GUIStyle(sLabel); sWarn.normal.textColor = new Color(1f, 0.85f, 0.35f); sWarn.fontStyle = (FontStyle)1; GUIStyle val11 = new GUIStyle(GUI.skin.textField) { fontSize = 14 }; val11.normal.background = texMid; val11.normal.textColor = Color.white; val11.focused.background = texLight; val11.focused.textColor = Color.white; val11.padding = new RectOffset(8, 8, 6, 6); sTextField = val11; sScroll = new GUIStyle(GUI.skin.scrollView); sBtnGreen = MakeBtn(texGreen, new Color(0.28f, 0.68f, 0.28f), 14, bold: true); sBtnRed = MakeBtn(texRed, new Color(0.72f, 0.28f, 0.28f), 14, bold: true); sBtnBlue = MakeBtn(texBlue, new Color(0.4f, 0.6f, 0.9f), 14, bold: true); sBtnNormal = MakeBtn(texLight, new Color(0.34f, 0.34f, 0.4f), 14, bold: false); sBtnDisabled = MakeBtn(texGray, new Color(0.3f, 0.3f, 0.34f, 0.85f), 14, bold: false); sBtnDisabled.normal.textColor = new Color(0.55f, 0.55f, 0.55f); sBtnDisabled.hover.textColor = new Color(0.55f, 0.55f, 0.55f); sBtnDisabled.active.textColor = new Color(0.55f, 0.55f, 0.55f); sBtnSmallBlue = MakeBtn(texBlue, new Color(0.4f, 0.6f, 0.9f), 12, bold: false); sBtnSmallRed = MakeBtn(texRed, new Color(0.72f, 0.28f, 0.28f), 12, bold: false); sBtnSmallNormal = MakeBtn(texLight, new Color(0.34f, 0.34f, 0.4f), 12, bold: false); sBtnSmallDisabled = MakeBtn(texGray, new Color(0.3f, 0.3f, 0.34f, 0.85f), 12, bold: false); sBtnSmallDisabled.normal.textColor = new Color(0.55f, 0.55f, 0.55f); sBtnSmallDisabled.hover.textColor = new Color(0.55f, 0.55f, 0.55f); sBtnSmallDisabled.active.textColor = new Color(0.55f, 0.55f, 0.55f); stylesInit = true; } catch { } } private static GUIStyle MakeBtn(Texture2D bg, Color hover, int fontSize, bool bold) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_00ae: Expected O, but got Unknown Texture2D background = MakeTex(hover); GUIStyle val = new GUIStyle(GUI.skin.button) { fontSize = fontSize, fontStyle = (FontStyle)(bold ? 1 : 0) }; val.normal.background = bg; val.normal.textColor = Color.white; val.hover.background = background; val.hover.textColor = Color.white; val.active.background = background; val.active.textColor = new Color(1f, 0.95f, 0.6f); val.padding = new RectOffset(10, 10, 6, 6); val.margin = new RectOffset(2, 2, 2, 2); return val; } private static Texture2D MakeTex(Color color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); val.SetPixels((Color[])(object)new Color[4] { color, color, color, color }); val.Apply(); ((Object)val).hideFlags = (HideFlags)61; return val; } } [HarmonyPatch(typeof(SemiFunc), "OnLevelGenDone")] private static class SemiFuncOnLevelGenDonePatch { private static void Postfix() { if (IsHostAndEnabled() && ShouldUseLevelStartSnapshot()) { CaptureLevelStartSnapshot("OnLevelGenDone"); } } } internal sealed class CfgI18N { public string SectionCN; public string SectionEN; public string KeyCN; public string KeyEN; public string DescCN; public string DescEN; public ConfigurationManagerAttributes Attrs; public string ValueEN; public string ValueCN; public ConfigEntry ReadOnlyEntry; } internal sealed class LiveLabel { public WeakReference Tmp; public CfgI18N I18N; public bool IsSection; } internal sealed class LiveValue { public WeakReference InputField; public CfgI18N I18N; } [CompilerGenerated] private sealed class d__237 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__237(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; LogInfo("决斗场后跳过邀请界面:开始执行。"); try { StartRunWithoutInviteFromCurrentProgress(); } catch (Exception ex) { LogInfo("自动开始游戏失败: " + ex.Message); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__218 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__218(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = Time.unscaledTime + 15f; break; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if (pendingStartupPopups.Count > 0 && Time.unscaledTime < 5__2) { MenuManager instance = MenuManager.instance; if ((Object)(object)instance == (Object)null || IsPopupSlotBusy(instance)) { <>2__current = null; <>1__state = 1; return true; } PendingStartupPopup pendingStartupPopup = (from popup in pendingStartupPopups orderby popup.Priority descending, popup.Sequence select popup).First(); pendingStartupPopups.Remove(pendingStartupPopup); instance.PagePopUp(pendingStartupPopup.Title, pendingStartupPopup.Color, pendingStartupPopup.Message, "OK", false); <>2__current = null; <>1__state = 2; return true; } pendingStartupPopups.Clear(); startupPopupDrainRunning = false; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__232 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string saveFileName; private float 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__232(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(3f); <>1__state = 1; return true; case 1: <>1__state = -1; if (string.IsNullOrWhiteSpace(saveFileName)) { return false; } if (IsActualMultiplayerSession() && !PhotonNetwork.IsMasterClient) { LogInfo("跳过初始存档写入:当前是非主机客户端。"); if (pendingNewSaveName == saveFileName) { pendingNewSaveName = null; } return false; } 5__2 = 0f; break; case 2: <>1__state = -1; 5__2 += 0.25f; break; case 3: <>1__state = -1; 5__2 += 0.25f; break; } if (5__2 < 6f) { if ((Object)(object)StatsManager.instance == (Object)null) { <>2__current = (object)new WaitForSeconds(0.25f); <>1__state = 2; return true; } if (IsActualMultiplayerSession()) { if (!PhotonNetwork.IsConnected || !PhotonNetwork.InRoom) { LogInfo("跳过初始存档写入:联机会话已结束。"); if (pendingNewSaveName == saveFileName) { pendingNewSaveName = null; } return false; } if (!PhotonNetwork.IsMasterClient) { <>2__current = (object)new WaitForSeconds(0.25f); <>1__state = 3; return true; } } string currentSaveFileName = GetCurrentSaveFileName(); if (!string.IsNullOrWhiteSpace(currentSaveFileName) && !string.Equals(currentSaveFileName, saveFileName, StringComparison.Ordinal)) { LogInfo("跳过初始存档写入:当前存档已切换到 " + currentSaveFileName); if (pendingNewSaveName == saveFileName) { pendingNewSaveName = null; } return false; } } if (MainSaveFileExists(saveFileName)) { return false; } try { LogInfo("游戏未自动保存新存档,强制写入: " + saveFileName); StatsManager instance = StatsManager.instance; if (instance != null) { instance.SaveGame(saveFileName); } } catch (Exception ex) { LogWarning("强制写入新存档失败: " + ex.Message); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__210 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string saveFileName; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__210(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: { <>1__state = -1; manualRestoreRunning = true; LogInfo("Manual latest progress reload started: " + saveFileName); TraceFlow("ManualRestore:start", "save=" + saveFileName); bool flag = false; if (IsActualMultiplayerSession()) { if (!PhotonNetwork.IsMasterClient) { LogWarning("Manual restore failed because only the host can reload a multiplayer save."); ShowPopup(UseChinese() ? "只有主机可以重载最近存档。" : "Only the host can reload the most recent save.", Color.yellow); manualRestoreRunning = false; return false; } multiplayerDeathSaveBlocked = true; } else { playerDeathSaveBlocked = true; } RestoreBestSaveAfterArenaReturn(saveFileName); <>2__current = (object)new WaitForSeconds(0.65f); <>1__state = 1; return true; } case 1: { <>1__state = -1; bool flag; try { flag = TriggerManualRestoreReload(saveFileName); TraceFlow("ManualRestore:triggered-reload", "save=" + saveFileName + ", inPlaceReload=" + flag); } catch (Exception ex) { LogError("Manual restore failed to trigger game reload: " + ex.Message); TryLoadGameInMemory(saveFileName); ShowPopup(UseChinese() ? "已恢复存档数据,但触发重载失败。" : "Save data was restored, but reload failed to trigger.", Color.yellow); playerDeathSaveBlocked = false; multiplayerDeathSaveBlocked = false; manualRestoreRunning = false; return false; } if (!flag) { <>2__current = (object)new WaitForSeconds(1f); <>1__state = 2; return true; } break; } case 2: <>1__state = -1; TryLoadGameInMemory(saveFileName); break; } playerDeathSaveBlocked = false; multiplayerDeathSaveBlocked = false; manualRestoreRunning = false; LogInfo("Manual latest progress reload completed: " + saveFileName); ShowPopup(UseChinese() ? "已重载最近存档。" : "Reloaded the most recent save.", Color.green); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__234 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__234(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(12f); <>1__state = 1; return true; case 1: <>1__state = -1; multiplayerDeathSaveBlocked = false; LogInfo("多人死亡存档阻止已过期。"); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__233 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__233(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(12f); <>1__state = 1; return true; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if (singleplayerDeathReloadRunning || manualRestoreRunning) { LogInfo("单人恢复流程仍在进行,继续保持死亡存档阻止。"); <>2__current = (object)new WaitForSeconds(0.25f); <>1__state = 2; return true; } playerDeathSaveBlocked = false; LogInfo("单人死亡存档阻止已过期。"); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__236 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__236(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; multiplayerArenaRestoreRunning = true; <>2__current = (object)new WaitForSeconds(3f); <>1__state = 1; return true; case 1: { <>1__state = -1; string currentSaveFileName = GetCurrentSaveFileName(); if (string.IsNullOrWhiteSpace(currentSaveFileName)) { LogWarning("Could not restore backup after arena return because current save is empty."); } else { RestoreBestSaveAfterArenaReturn(currentSaveFileName); TryLoadGameInMemory(currentSaveFileName); LogInfo("Checked save recovery after returning from multiplayer arena: " + currentSaveFileName); } multiplayerArenaSeen = false; multiplayerArenaRestoreRunning = false; multiplayerDeathSaveBlocked = false; if (ShouldAutoResumeAfterArena()) { if ((Object)(object)Instance == (Object)null) { LogInfo("Instance 为空,无法启动自动继续协程。"); } else { LogInfo("决斗场跳过条件通过,启动自动继续。"); ((MonoBehaviour)Instance).StartCoroutine(AutoStartAfterArenaCoroutine()); } } else { LogInfo("决斗场跳过条件未通过,进入邀请界面。"); } return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__235 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string saveFileName; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__235(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(3f); <>1__state = 1; return true; case 1: { <>1__state = -1; if (!IsEnabled() || IsActualMultiplayerSession() || string.IsNullOrWhiteSpace(saveFileName)) { return false; } if (singleplayerDeathReloadRunning) { LogInfo("Singleplayer death reload ignored because another reload is already running."); return false; } singleplayerDeathReloadRunning = true; playerDeathSaveBlocked = true; ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)("[Restore] 单人死亡 → 进入恢复链:" + saveFileName)); } RestoreBestSaveAfterArenaReturn(saveFileName); try { if (CanReloadSingleplayerRecentSaveInPlace()) { LogInfo("Reloading current scene after singleplayer death using F9 flow: " + saveFileName); TryLoadGameInMemory(saveFileName); RestartCurrentSceneFromLoadedSave("单人死亡后重开当前场景"); } else { LogInfo("Reloading latest save after singleplayer death: " + saveFileName); SemiFunc.MenuActionSingleplayerGame(saveFileName, (List)null); } } catch (Exception ex) { LogError("Failed to reload save after singleplayer death: " + ex.Message); TryLoadGameInMemory(saveFileName); ShowPopup(UseChinese() ? "已恢复存档数据,但自动重新进入存档失败。" : "Save data was restored, but auto re-entering the save failed.", Color.yellow); playerDeathSaveBlocked = false; singleplayerDeathReloadRunning = false; return false; } if (!CanReloadSingleplayerRecentSaveInPlace()) { <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 2; return true; } break; } case 2: <>1__state = -1; TryLoadGameInMemory(saveFileName); break; } playerDeathSaveBlocked = false; singleplayerDeathReloadRunning = false; LogInfo("Checked singleplayer save recovery after death: " + saveFileName); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string PluginGuid = "zichen.savekeeper"; public const string PluginName = "SaveKeeper"; public const string PluginVersion = "1.3.7"; private const string GlobalSection = "全局设置"; private const string InfoSection = "模组信息"; private const string ProtectionSection = "A.基础保护"; private const string RoomSection = "B.多人联机"; private const string RecoverySection = "C.死亡竞技场"; private const string UiSection = "D.提示与调试"; private const int DeathSaveBlockSeconds = 12; private const string DeathArenaModeRace = "赛车比赛"; private const string DeathArenaModeCrown = "皇冠竞技场"; private const string DeathArenaModeOfficial = "官方随机"; private const int MaxBackupCount = 10; private const int MaxHostCopySessionSaveCount = 30; private const string DefaultTeamName = "R.E.P.O."; private const int HostCopyTimePlayedSignatureBucketSeconds = 30; private const string SaveFilePrefix = "REPO_SAVE_"; private const string HostCopySavePrefix = "HOSTCOPY_"; private const string RoomPropertyServerName = "server_name"; private const string HostCopyRoomPropertyMetadataVersion = "sk_copy_meta_v"; private const string HostCopyRoomPropertyTeamName = "sk_copy_team_name"; private const string HostCopyRoomPropertyTeamNameChanged = "sk_copy_team_name_changed"; private const string HostCopyRoomPropertyTimePlayed = "sk_copy_time_played"; private const string HostCopyRoomPropertyDateAndTime = "sk_copy_date"; private const string HostCopyRoomPropertySourceSaveName = "sk_copy_source_save"; private const string HostCopyRoomPropertySourceRoomName = "sk_copy_source_room_name"; private const string HostCopyRoomPropertySourceRoomId = "sk_copy_source_room_id"; private const string HostCopyRoomPropertySourceHostName = "sk_copy_source_host_name"; private const string HostCopyRoomPropertySourceHostUserId = "sk_copy_source_host_user_id"; private const int StartupPopupPriorityNormal = 0; private const int StartupPopupPriorityConflict = 1; private const int StartupPopupPriorityCompatibility = 2; private static readonly FieldInfo StatsManagerCurrentSaveField = typeof(StatsManager).GetField("saveFileCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo StatsManagerBackupIndexField = typeof(StatsManager).GetField("backupIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo StatsManagerDictionaryOfDictionariesField = typeof(StatsManager).GetField("dictionaryOfDictionaries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo StatsManagerDoNotSaveTheseDictionariesField = typeof(StatsManager).GetField("doNotSaveTheseDictionaries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo StatsManagerStripTheseDictionariesField = typeof(StatsManager).GetField("stripTheseDictionaries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo StatsManagerEncryptionPasswordField = typeof(StatsManager).GetField("totallyNormalString", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarDeadSetField = typeof(PlayerAvatar).GetField("deadSet", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarNameField = typeof(PlayerAvatar).GetField("playerName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarSteamIdField = typeof(PlayerAvatar).GetField("steamID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo GameManagerLobbyTypeField = typeof(GameManager).GetField("lobbyType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo GameManagerConnectRandomField = typeof(GameManager).GetField("connectRandom", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo DataDirectorNetworkServerNameField = typeof(DataDirector).GetField("networkServerName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo DataDirectorNetworkJoinServerNameField = typeof(DataDirector).GetField("networkJoinServerName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerLobbyJoinField = typeof(RunManager).GetField("lobbyJoin", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerLoadLevelField = typeof(RunManager).GetField("loadLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerGameOverField = typeof(RunManager).GetField("gameOver", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerPreviousRunLevelField = typeof(RunManager).GetField("previousRunLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerRestartingField = typeof(RunManager).GetField("restarting", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerRestartingDoneField = typeof(RunManager).GetField("restartingDone", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RunManagerSaveLevelField = typeof(RunManager).GetField("saveLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuManagerPagePopUpScheduledField = typeof(MenuManager).GetField("pagePopUpScheduled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuManagerCurrentMenuPageIndexField = typeof(MenuManager).GetField("currentMenuPageIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuManagerCurrentMenuPageField = typeof(MenuManager).GetField("currentMenuPage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuPageServerListCreateNewMenuTextInputField = typeof(MenuPageServerListCreateNew).GetField("menuTextInput", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuPageServerListCreateNewSaveFileNameField = typeof(MenuPageServerListCreateNew).GetField("saveFileName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuPageServerListCreateNewMenuPageParentField = typeof(MenuPageServerListCreateNew).GetField("menuPageParent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuTextInputTextCurrentField = MenuPageServerListCreateNewMenuTextInputField?.FieldType.GetField("textCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuPageSavesSaveFilesField = typeof(MenuPageSaves).GetField("saveFiles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuPageSavesCurrentSaveFileNameField = typeof(MenuPageSaves).GetField("currentSaveFileName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuPageSavesMaxSaveFilesField = typeof(MenuPageSaves).GetField("maxSaveFiles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo MenuPageSavesGameModeHeaderField = typeof(MenuPageSaves).GetField("gameModeHeader", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly PropertyInfo MenuPageSavesGameModeHeaderTextProperty = MenuPageSavesGameModeHeaderField?.FieldType.GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo StatsManagerSaveFileGetRunLevelMethod = typeof(StatsManager).GetMethod("SaveFileGetRunLevel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(string) }, null); private static readonly MethodInfo StatsManagerSaveFileGetRunStatMethod = typeof(StatsManager).GetMethod("SaveFileGetRunStat", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly MethodInfo StatsManagerLoadGameMethod = typeof(StatsManager).GetMethod("LoadGame", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(List) }, null); private static readonly Type SteamClientType = AccessTools.TypeByName("Steamworks.SteamClient"); private static readonly PropertyInfo SteamClientNameProperty = SteamClientType?.GetProperty("Name", BindingFlags.Static | BindingFlags.Public); private static FieldInfo nameChangeRepoStartingNameField; private static PropertyInfo nameChangeRepoStartingNameValueProperty; private static readonly Regex BackupNumberRegex = new Regex("_BACKUP(\\d+)", RegexOptions.IgnoreCase); private static readonly Dictionary PatchFeatureNamesByType = new Dictionary(StringComparer.Ordinal) { { "MenuPageSavesOnDeleteGamePatch", "手动删除存档保护" }, { "MenuPageServerListButtonCreateNewPatch", "公开服务器读取本地存档" }, { "MenuPageSavesStartPatch", "公开服务器读取本地存档" }, { "SemiFuncMenuActionSingleplayerGamePatch", "新存档初始化" }, { "SemiFuncMenuActionHostGamePatch", "新存档初始化" }, { "SemiFuncMenuActionRandomMatchmakingPatch", "随机匹配房主首存初始化" }, { "MenuPageSavesOnNewGamePatch", "新存档初始化" }, { "MenuPageSavesOnLoadGamePatch", "公开服务器读取本地存档" }, { "MenuPageSavesOnGoBackPatch", "公开服务器读取本地存档" }, { "MenuPageServerListCreateNewExitPagePatch", "公开服务器读取本地存档" }, { "MenuPageServerListCreateNewButtonConfirmPatch", "公开服务器读取本地存档" }, { "MenuManagerStartPatch", "启动提示和兼容提示" }, { "NetworkConnectOnJoinedRoomPatch", "公开房和随机匹配进房初始化" }, { "PunManagerSyncAllDictionariesPatch", "房主快照元数据同步" }, { "PunManagerReceiveSyncDataPatch", "非主机保存房主存档" }, { "RunManagerChangeLevelPatch", "死亡后进入比赛类型" }, { "StatsManagerAwakePatch", "公开房和随机匹配正常保存" }, { "StatsManagerSaveFileDeletePatch", "阻止自动删档" }, { "StatsManagerSaveGamePatch", "存档写入保护" }, { "StatsManagerSaveFileSavePatch", "公开房和随机匹配正常保存" }, { "PlayerAvatarPlayerDeathPatch", "单人死亡覆盖保护" }, { "PlayerAvatarPlayerDeathRpcPatch", "多人全灭死亡覆盖保护" }, { "PlayerAvatarRevivePatch", "死亡覆盖保护状态清理" }, { "GameDirectorUpdatePatch", "死亡比赛后校验进度" }, { "StatsManagerLoadGamePatch", "存档重载后的状态清理" }, { "RunManagerLeaveToMainMenuPatch", "会话状态清理" }, { "SemiFuncOnLevelGenDonePatch", "关卡开局自动快照" } }; private static ConfigEntry globalFeatureEnabled; private static ConfigEntry allowPlayerDelete; private static ConfigEntry blockGameDelete; private static ConfigEntry blockDeathOverwrite; private static ConfigEntry savePublicRooms; private static ConfigEntry publicServerSaveSelection; private static ConfigEntry copyHostSaveToLocal; private static ConfigEntry restoreBackupAfterArenaReturn; private static ConfigEntry restoreSaveAfterSingleplayerDeath; private static ConfigEntry manualRestoreShortcut; private static ConfigEntry showConflictWarning; private static ConfigEntry verboseLogging; private static ConfigEntry deathArenaMode; private static ConfigEntry skipLobbyAfterArena; private static ConfigEntry showStartupPopup; private static ConfigEntry _cfgLanguage; private Harmony harmony; private ConfigEntry moduleNameInfo; private ConfigEntry moduleVersionInfo; private static bool playerDeathSaveBlocked; private static bool multiplayerDeathSaveBlocked; private static bool playerMenuDeleteInProgress; private static bool multiplayerArenaSeen; private static bool multiplayerArenaRestoreRunning; private static List originalArenaLevelsDuringForcedRace; private static bool missingRaceArenaLogged; private static bool noSaveDeleteConflictDetected; private static string noSaveDeleteConflictSource; private static bool conflictPopupShown; private static bool manualRestoreRunning; private static bool singleplayerDeathReloadRunning; private static readonly SessionSaveContext sessionSaveContext = new SessionSaveContext(); private static CompatibilityReport compatibilityReport = CompatibilityReport.CreateHealthy(); private static bool compatibilityPopupShown; private static bool startupPopupDrainRunning; private static int startupPopupSequence; private static readonly List pendingStartupPopups = new List(); private float hostCopyAutoSavePollTimer; private static string lastRestartSceneTraceState; private static readonly string[] _chineseModKeywords = new string[6] { "chinese", "简体", "繁體", "繁体", "汉化", "中文" }; private static readonly string[] _chineseEnvironmentPluginGuids = new string[1] { "gravydevsupreme.xunity.autotranslator" }; private const string RecentSavesFileName = "zichen.savekeeper.recentsaves.txt"; private const string LegacyLastSaveFileName = "zichen.savekeeper.lastsave.txt"; private const int MaxRecentSaves = 50; private const KeyCode SaveSelectorToggleKey = 288; private const int SaveSelectorWindowId = 33051; private const float SaveSelectorPad = 14f; private static readonly FieldInfo CursorManagerUnlockTimerField = AccessTools.Field(typeof(CursorManager), "unlockTimer"); private static readonly FieldInfo StatsManagerTeamNameField = typeof(StatsManager).GetField("teamName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo StatsManagerSaveFileGetAllAsyncMethod = typeof(StatsManager).GetMethod("SaveFileGetAllAsync", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo StatsManagerSaveFileCreateMethod = typeof(StatsManager).GetMethod("SaveFileCreate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(string), typeof(bool) }, null); internal const string LevelStartSnapshotSuffix = "_LEVELSTART"; internal static readonly Dictionary _cfgByEnSection = new Dictionary(StringComparer.Ordinal); internal static readonly Dictionary _cfgByEnKey = new Dictionary(StringComparer.Ordinal); private static readonly List _cfgI18Ns = new List(20); private static readonly List _liveLabels = new List(64); private static readonly List _liveValues = new List(8); private static readonly Dictionary _labelTmpFieldCache = new Dictionary(); private static FieldInfo _inputStringSystemField; private static MethodInfo _inputStringSystemSetValue; private static bool publicServerSavesMenuOpen { get { return sessionSaveContext.PublicServerSavesMenuOpen; } set { sessionSaveContext.PublicServerSavesMenuOpen = value; } } private static bool clientHostCopyExportRunning { get { return sessionSaveContext.HostCopy.ExportRunning; } set { sessionSaveContext.HostCopy.ExportRunning = value; } } private static string pendingNewSaveName { get { return sessionSaveContext.PendingNewSaveName; } set { sessionSaveContext.PendingNewSaveName = value; } } private static string publicRoomSaveName { get { return sessionSaveContext.PublicRoomSaveName; } set { sessionSaveContext.PublicRoomSaveName = value; } } private static string clientHostCopySaveName { get { return sessionSaveContext.HostCopy.SaveName; } set { sessionSaveContext.HostCopy.SaveName = value; } } private static string clientHostCopySessionToken { get { return sessionSaveContext.HostCopy.SessionToken; } set { sessionSaveContext.HostCopy.SessionToken = value; } } private static string clientHostCopyLastExportSignature { get { return sessionSaveContext.HostCopy.LastExportSignature; } set { sessionSaveContext.HostCopy.LastExportSignature = value; } } private static string clientHostCopyPendingAutoSaveName { get { return sessionSaveContext.HostCopy.PendingAutoSaveName; } set { sessionSaveContext.HostCopy.PendingAutoSaveName = value; } } private static string clientHostCopyPendingAutoSaveSignature { get { return sessionSaveContext.HostCopy.PendingAutoSaveSignature; } set { sessionSaveContext.HostCopy.PendingAutoSaveSignature = value; } } private static HostCopyExportPlan clientHostCopyPendingAutoSavePlan { get { return sessionSaveContext.HostCopy.PendingAutoSavePlan; } set { sessionSaveContext.HostCopy.PendingAutoSavePlan = value; } } private static bool clientHostCopyPendingPlanBuildRequested { get { return sessionSaveContext.HostCopy.PendingPlanBuildRequested; } set { sessionSaveContext.HostCopy.PendingPlanBuildRequested = value; } } private static float clientHostCopyPendingPlanBuildReadyAt { get { return sessionSaveContext.HostCopy.PendingPlanBuildReadyAt; } set { sessionSaveContext.HostCopy.PendingPlanBuildReadyAt = value; } } private static string publishedHostCopyMetadataSignature { get { return sessionSaveContext.PublishedHostCopyMetadataSignature; } set { sessionSaveContext.PublishedHostCopyMetadataSignature = value; } } public static ZichenSaveKeeperPlugin Instance { get; private set; } public static bool UseChinese() { if (_cfgLanguage != null) { return _cfgLanguage.Value == DisplayLanguage.中文; } return true; } private void Awake() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown DetachFromManager(); Instance = this; ResetConfigIfVersionChanged(); BindConfig(); compatibilityReport = CompatibilityReport.Build(); harmony = new Harmony("zichen.savekeeper.patch"); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { assembly.GetTypes(); } catch { } } PatchHarmonySafely(); TryHookREPOConfig(harmony); if (_cfgLanguage != null) { _cfgLanguage.SettingChanged += delegate { RefreshAllI18N(); }; } if (compatibilityReport.HasIssues) { ((BaseUnityPlugin)this).Logger.LogWarning((object)compatibilityReport.BuildLogMessage()); foreach (string detailMessage in compatibilityReport.GetDetailMessages()) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Compatibility detail: " + detailMessage)); } } AttachSaveSelectorBehaviour(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"zichen-savekeeper v1.3.7 loaded."); } private void DetachFromManager() { ((Component)this).gameObject.transform.parent = null; ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; if (Instance == this) { Instance = null; } } private void BindConfig() { BindModuleInfoConfig(); BindGlobalConfig(); BindSaveProtectionConfig(); BindRoomConfig(); BindRecoveryConfig(); BindUiAndDiagnosticsConfig(); DetectNoSaveDeleteConflict(); } private static DisplayLanguage DetectDefaultLanguage() { try { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { string text = pluginInfo.Key ?? string.Empty; PluginInfo value = pluginInfo.Value; object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Name : null); } if (obj == null) { obj = string.Empty; } string text2 = (string)obj; string[] chineseModKeywords = _chineseModKeywords; foreach (string value2 in chineseModKeywords) { if (text.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0) { return DisplayLanguage.中文; } } chineseModKeywords = _chineseEnvironmentPluginGuids; foreach (string b in chineseModKeywords) { if (string.Equals(text, b, StringComparison.OrdinalIgnoreCase)) { return DisplayLanguage.中文; } } } } catch { } return DisplayLanguage.English; } private void BindModuleInfoConfig() { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown DisplayLanguage displayLanguage = DetectDefaultLanguage(); ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes { Order = 1100 }; CfgI18N cfgI18N = NewI18N("Mod Info", "模组信息", "Language/语言", "Language/语言", "Switch display language. Live updates BepInEx ConfigManager; reopen REPOConfig Mods menu to refresh.", "公开文档和配置说明语言切换。BepInEx 配置管理器立即生效;REPOConfig 关闭再打开 Mods 菜单刷新。", configurationManagerAttributes); _cfgLanguage = ((BaseUnityPlugin)this).Config.Bind("Mod Info", "Language/语言", displayLanguage, new ConfigDescription(cfgI18N.DescEN, (AcceptableValueBase)null, new object[1] { configurationManagerAttributes })); moduleNameInfo = BindReadOnlyI18N("Mod Info", "模组信息", "Mod Name", "模组名称", "SaveKeeper", "存档管家", 1000); moduleVersionInfo = BindReadOnlyI18N("Mod Info", "模组信息", "Mod Version", "模组版本号", "1.3.7", "1.3.7", 990); } private void BindGlobalConfig() { globalFeatureEnabled = BindI18N("Global Settings", "全局设置", "Mod Enable", "模组启用", "Master toggle. When off, every SaveKeeper feature and notification is disabled.", "总开关。关闭后,SaveKeeper 的所有功能与提示都不会生效。", defaultValue: true, null, 975); } private void BindSaveProtectionConfig() { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) blockGameDelete = BindI18N("A.Basic Protection", "A.基础保护", "Block Auto Delete", "阻止游戏自动删档", "Blocks game-flow triggered auto deletion. Recommended on.", "开启后,游戏流程触发的自动删档会被阻止。建议保持开启。", defaultValue: true, null, 880); blockDeathOverwrite = BindI18N("A.Basic Protection", "A.基础保护", "Block Death Overwrite", "阻止死亡覆盖存档", "Prevents the game from writing the post-death state into the current save when a player dies, all players die, or arena settles.", "开启后,玩家死亡、全员死亡或进入竞技场结算时,会阻止游戏把失败后的状态写进当前存档。", defaultValue: true, null, 870); restoreSaveAfterSingleplayerDeath = BindI18N("A.Basic Protection", "A.基础保护", "Restore After Singleplayer Death", "单人死亡后恢复存档", "Keeps the higher-progress version among main / backup saves and re-enters the current save, avoiding restart from level 1.", "开启后,单人死亡后会保留主存档/备份中进度更新的一份,并重新进入当前存档,避免从第一关开始。", defaultValue: true, null, 868); allowPlayerDelete = BindI18N("A.Basic Protection", "A.基础保护", "Allow Manual Save Delete", "允许手动删除存档", "When on, manual deletion in the saves menu is allowed. When off, manual delete is also blocked.", "开启后,玩家在存档菜单里主动删除存档会被放行。关闭后,玩家手动删除也会被阻止。", defaultValue: true, null, 865); manualRestoreShortcut = BindI18N("A.Basic Protection", "A.基础保护", "Reload Recent Save Shortcut", "重载最近存档快捷键", "Compares main save and backups and reloads the better one based on current room type. Use this to recover from a roll-back.", "按下后,会比较当前主存档和备份进度,并按当前房间类型重载进度更高的一份。适合在出现回退后手动救回。", new KeyboardShortcut((KeyCode)290, Array.Empty()), null, 864); } private void BindRoomConfig() { savePublicRooms = BindI18N("B.Multiplayer", "B.多人联机", "Auto Save", "自动保存存档", "Public and matchmaking rooms persist normal progression. Death and arena dangerous saves are still blocked by protection.", "开启后,公开房间和随机匹配房间会像私人房间一样保存正常推进进度。死亡和竞技场危险保存仍会被保护逻辑拦截。", defaultValue: true, null, 890); publicServerSaveSelection = BindI18N("B.Multiplayer", "B.多人联机", "Public Server Uses Local Saves", "公开可使用本地存档", "Adds local save reading/selection on top of vanilla public-server creation. Disabled returns to vanilla behavior; vanilla cannot read local saves on public servers.", "开启后,在原版公开服务器创建流程上,额外增加“读取并选择自己本地存档开局”的功能:会先打开本地存档列表,选择新存档或已有存档后再创建公开房间。关闭后恢复游戏原版行为;原版不支持公开服务器读取自己的本地存档开局。", defaultValue: true, null, 864); copyHostSaveToLocal = BindI18N("B.Multiplayer", "B.多人联机", "Non-Host Save Host Snapshot", "非主机保存房主存档", "Off by default. When on, non-host clients auto-save the host's synced progress as a local snapshot once a level synced for the first time, and update on subsequent level transitions.", "默认关闭。开启后,非主机客户端加入别人的房间并收到主机完整进度同步时,会在第一次真正进入关卡且同步完成后自动保存一次;后续每次切换到下一关并收到新的主机同步时,也会继续自动更新本地快照。不需要把房主进度另存到本地时,保持关闭即可。", defaultValue: false, null, 850); } private void BindRecoveryConfig() { deathArenaMode = BindI18N("C.Death Arena", "C.死亡竞技场", "Death Arena Mode", "死亡后进入比赛类型", "Choose which arena to enter after wipe. Racing: force racing; Crown: force crown arena; Random: vanilla random.", "选择全灭后进入哪种比赛。赛车比赛:强制进入赛车;皇冠竞技场:强制进入普通皇冠竞技场;官方随机:完全使用游戏原版随机逻辑。", "赛车比赛", (AcceptableValueBase)(object)new AcceptableValueList(new string[3] { "赛车比赛", "皇冠竞技场", "官方随机" }), 860); skipLobbyAfterArena = BindI18N("C.Death Arena", "C.死亡竞技场", "Skip Lobby After Arena", "决斗场后跳过邀请界面", "When on, after the death arena the run continues directly based on current save without entering LobbyMenu's invite wait screen.", "开启后,死亡比赛结束后会按当前存档进度直接继续,不进入 LobbyMenu 的好友邀请等待界面。", defaultValue: true, null, 855); restoreBackupAfterArenaReturn = BindI18N("C.Death Arena", "C.死亡竞技场", "Check Progress After Death Arena", "死亡比赛后校验进度", "When multiplayer wipe enters death arena and returns to room, compares main save and latest backup, keeps the higher progress to avoid level rollback.", "开启后,多人全灭进入死亡比赛再回到房间时,会比较主存档和最新备份,保留进度更高的一份,避免回退关卡。", defaultValue: true, null, 830); } private void BindUiAndDiagnosticsConfig() { showConflictWarning = BindI18N("D.Notifications & Debug", "D.提示与调试", "Mod Compatibility Warning", "提示可能影响的模组", "When on, if No Save Delete is loaded too, shows a notification at the start screen that it may affect SaveKeeper.", "开启后,如果检测到 No Save Delete 同时加载,会在游戏开始界面提示它可能影响 SaveKeeper 的部分功能。", defaultValue: true, null, 800); showStartupPopup = BindI18N("D.Notifications & Debug", "D.提示与调试", "Startup Notification", "启动提示", "When on, shows a one-time popup announcing SaveKeeper has loaded. Auto-disables after one show; can be re-enabled manually.", "开启后,启动游戏时弹窗提示 SaveKeeper 已加载。弹过一次后自动关闭,可手动重新开启。", defaultValue: true, null, 795); verboseLogging = BindI18N("D.Notifications & Debug", "D.提示与调试", "Verbose Logging", "详细日志", "Outputs detailed save/restore/backup-cleanup logs. Keep off for normal play.", "开启后输出保存、恢复、备份清理等详细日志。发布和日常游玩建议关闭。", defaultValue: false, null, 790); } private void ResetConfigIfVersionChanged() { try { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; string text = ReadConfigPluginVersion(configFilePath); if (!string.IsNullOrWhiteSpace(text) && !(text == "1.3.7")) { ResetConfigFileToDefaults(configFilePath); ((BaseUnityPlugin)this).Logger.LogWarning((object)"SaveKeeper version changed. Old config was reset to defaults."); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to reset SaveKeeper config by version: " + ex.Message)); } } private static string ReadConfigPluginVersion(string configPath) { if (!File.Exists(configPath)) { return null; } string input = File.ReadAllText(configPath); Match match = Regex.Match(input, "(?m)^Mod Version\\s*=\\s*(.+?)\\s*$"); if (!match.Success) { match = Regex.Match(input, "(?m)^模组版本号\\s*=\\s*(.+?)\\s*$"); } if (!match.Success) { return null; } return match.Groups[1].Value.Trim(); } private void ResetConfigFileToDefaults(string configPath) { ((BaseUnityPlugin)this).Config.Clear(); if (File.Exists(configPath)) { File.Delete(configPath); } ((BaseUnityPlugin)this).Config.Reload(); } private void Update() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) ShowConflictWarningOnce(); if (HasPendingHostCopyUpdateWork()) { hostCopyAutoSavePollTimer -= Time.unscaledDeltaTime; if (hostCopyAutoSavePollTimer <= 0f) { hostCopyAutoSavePollTimer = 0.2f; TryProcessPendingHostCopyUpdateWork(); } } else { hostCopyAutoSavePollTimer = 0f; } if (IsHostAndEnabled() && manualRestoreShortcut != null) { KeyboardShortcut value = manualRestoreShortcut.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !((Object)(object)StatsManager.instance == (Object)null)) { ManualRestoreLatestProgress(); } } } private void DrawInfo(ConfigEntryBase entry) { GUILayout.Label(entry.BoxedValue?.ToString() ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); } private static ConfigDescription ConfigDescriptionWithOrder(string description, int order) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = order } }); } private static ConfigDescription ConfigDescriptionWithOrder(string description, AcceptableValueBase acceptableValues, int order) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown return new ConfigDescription(description, acceptableValues, new object[1] { new ConfigurationManagerAttributes { Order = order } }); } private static bool IsEnabled() { if (globalFeatureEnabled != null) { return globalFeatureEnabled.Value; } return false; } private static bool IsHostAndEnabled() { if (!IsEnabled()) { return false; } if (PhotonNetwork.IsConnected && !PhotonNetwork.IsMasterClient) { return false; } return true; } private static bool ShouldBlockDeathOverwrite() { if (IsEnabled() && blockDeathOverwrite != null) { return blockDeathOverwrite.Value; } return false; } private static bool CanUseBackupIndexAlignment() { return compatibilityReport.BackupIndexAvailable; } private static bool CanTrackMultiplayerDeathState() { return compatibilityReport.DeathStateAvailable; } private static bool ShouldUsePublicServerSaveSelection() { if (IsEnabled() && compatibilityReport.CurrentSaveAvailable && compatibilityReport.PublicRoomSaveAvailable && compatibilityReport.PublicRoomUiAvailable && publicServerSaveSelection != null) { return publicServerSaveSelection.Value; } return false; } private static bool ShouldSavePublicRooms() { if (IsEnabled() && compatibilityReport.PublicRoomSaveAvailable && savePublicRooms != null) { return savePublicRooms.Value; } return false; } private static bool CanReadSaveProgressMarkers() { return compatibilityReport.SaveProgressReadAvailable; } private static bool CanCoordinateStartupPopup() { return compatibilityReport.StartupPopupCoordinationAvailable; } private static bool CanUseSceneReloadContext() { return compatibilityReport.SceneReloadContextAvailable; } private static bool CanUsePublicRoomUiIntegration() { return compatibilityReport.PublicRoomUiAvailable; } private static bool CanLoadGameInMemory() { return compatibilityReport.InMemoryLoadAvailable; } private static bool CanExpandSaveFileLimit() { return compatibilityReport.SaveFileLimitAvailable; } private static bool CanReadDefaultPublicRoomName() { return compatibilityReport.PublicRoomDefaultNameAvailable; } private static bool CanUseHostCopyEncryptionField() { return compatibilityReport.HostCopyEncryptionPasswordAvailable; } private static bool CanUseDiagnosticTraceReflection() { return compatibilityReport.DiagnosticTraceAvailable; } private static bool IsConnectRandomActive() { try { bool flag = default(bool); int num; if ((Object)(object)GameManager.instance != (Object)null && GameManagerConnectRandomField != null) { object value = GameManagerConnectRandomField.GetValue(GameManager.instance); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static string GetDataDirectorString(FieldInfo field) { if (!CanUseDiagnosticTraceReflection() || field == null || (Object)(object)DataDirector.instance == (Object)null) { return "null"; } try { return (field.GetValue(DataDirector.instance) as string) ?? "null"; } catch { return "error"; } } private static string DescribeNetworkIntent() { return "serverName=" + GetDataDirectorString(DataDirectorNetworkServerNameField) + ", joinServerName=" + GetDataDirectorString(DataDirectorNetworkJoinServerNameField); } private static bool IsActualMultiplayerSession() { if (SemiFunc.IsMultiplayer()) { return PhotonNetwork.IsConnected; } return false; } private static void LogInfo(string message) { if (verboseLogging != null && verboseLogging.Value) { ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)message); } } } private static void LogWarning(string message) { ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)message); } } private static void LogError(string message) { ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogError((object)message); } } private static string DescribeSessionState() { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) string text = GetCurrentSaveFileName() ?? "null"; string text2 = publicRoomSaveName ?? "null"; string text3 = pendingNewSaveName ?? "null"; Room currentRoom = PhotonNetwork.CurrentRoom; string text4 = ((currentRoom != null) ? currentRoom.Name : null) ?? "null"; RunManager instance = RunManager.instance; object obj; if (instance == null) { obj = null; } else { Level levelCurrent = instance.levelCurrent; obj = ((levelCurrent != null) ? ((Object)levelCurrent).name : null); } if (obj == null) { obj = "null"; } string text5 = (string)obj; string text6 = "unknown"; try { if ((Object)(object)GameManager.instance != (Object)null && GameManagerLobbyTypeField != null) { object value = GameManagerLobbyTypeField.GetValue(GameManager.instance); if (value is LobbyTypes) { LobbyTypes val = (LobbyTypes)value; text6 = ((object)(LobbyTypes)(ref val)).ToString(); } } } catch { } int runManagerLoadLevel = GetRunManagerLoadLevel(RunManager.instance); bool flag = IsConnectRandomActive(); bool flag2 = SemiFunc.IsMultiplayer(); bool isConnected = PhotonNetwork.IsConnected; bool isMasterClient = PhotonNetwork.IsMasterClient; bool flag3 = false; bool flag4 = false; bool flag5 = false; string text7 = DescribeNetworkIntent(); if (CanUseDiagnosticTraceReflection() && (Object)(object)RunManager.instance != (Object)null) { bool flag6 = default(bool); int num; if (RunManagerLobbyJoinField != null) { object value = RunManagerLobbyJoinField.GetValue(RunManager.instance); if (value is bool) { flag6 = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } flag3 = (byte)((uint)num & (flag6 ? 1u : 0u)) != 0; bool flag7 = default(bool); int num2; if (RunManagerRestartingField != null) { object value = RunManagerRestartingField.GetValue(RunManager.instance); if (value is bool) { flag7 = (bool)value; num2 = 1; } else { num2 = 0; } } else { num2 = 0; } flag4 = (byte)((uint)num2 & (flag7 ? 1u : 0u)) != 0; bool flag8 = default(bool); int num3; if (RunManagerRestartingDoneField != null) { object value = RunManagerRestartingDoneField.GetValue(RunManager.instance); if (value is bool) { flag8 = (bool)value; num3 = 1; } else { num3 = 0; } } else { num3 = 0; } flag5 = (byte)((uint)num3 & (flag8 ? 1u : 0u)) != 0; } return "currentSave=" + text + ", publicRoomSave=" + text2 + ", pendingNewSave=" + text3 + ", room=" + text4 + ", level=" + text5 + ", loadLevel=" + runManagerLoadLevel + ", lobbyType=" + text6 + ", connectRandom=" + flag + ", multiplayer=" + flag2 + ", connected=" + isConnected + ", isMaster=" + isMasterClient + ", arenaSeen=" + multiplayerArenaSeen + ", arenaRestoreRunning=" + multiplayerArenaRestoreRunning + ", deathBlock=" + multiplayerDeathSaveBlocked + ", lobbyJoin=" + flag3 + ", restarting=" + flag4 + ", restartingDone=" + flag5 + ", " + text7; } private static void TraceFlow(string tag, string extra = null) { if (verboseLogging != null && verboseLogging.Value) { string text = "[Trace] " + tag + " | " + DescribeSessionState(); if (!string.IsNullOrWhiteSpace(extra)) { text = text + " | " + extra; } ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)text); } } } private static void TraceRestartScene(string phase, RunManager runManager) { if (!IsHostAndEnabled()) { return; } if (!CanUseDiagnosticTraceReflection()) { string tag = "RestartScene:" + phase; object obj; if (runManager == null) { obj = null; } else { Level levelCurrent = runManager.levelCurrent; obj = ((levelCurrent != null) ? ((Object)levelCurrent).name : null); } if (obj == null) { obj = "null"; } TraceFlow(tag, "current=" + (string?)obj); return; } string[] array = new string[7]; object obj2; if (runManager == null) { obj2 = null; } else { Level levelCurrent2 = runManager.levelCurrent; obj2 = ((levelCurrent2 != null) ? ((Object)levelCurrent2).name : null); } if (obj2 == null) { obj2 = "null"; } array[0] = (string)obj2; array[1] = "|"; array[2] = RunManagerRestartingField?.GetValue(runManager)?.ToString() ?? "null"; array[3] = "|"; array[4] = RunManagerRestartingDoneField?.GetValue(runManager)?.ToString() ?? "null"; array[5] = "|"; array[6] = RunManagerLobbyJoinField?.GetValue(runManager)?.ToString() ?? "null"; string text = string.Concat(array); string b = phase + "|" + text; if (!string.Equals(lastRestartSceneTraceState, b, StringComparison.Ordinal)) { lastRestartSceneTraceState = b; string tag2 = "RestartScene:" + phase; object obj3; if (runManager == null) { obj3 = null; } else { Level levelCurrent3 = runManager.levelCurrent; obj3 = ((levelCurrent3 != null) ? ((Object)levelCurrent3).name : null); } if (obj3 == null) { obj3 = "null"; } TraceFlow(tag2, "current=" + (string?)obj3); } } private static void ResetArenaResumeState(string reason = null) { TraceFlow("ResetArenaResumeState:before", reason); multiplayerArenaSeen = false; multiplayerArenaRestoreRunning = false; multiplayerDeathSaveBlocked = false; if (!string.IsNullOrWhiteSpace(reason)) { LogInfo("已重置死亡竞技场自动继续状态: " + reason); } TraceFlow("ResetArenaResumeState:after", reason); } private static bool IsMultiplayerInitialSaveReady(string saveFileName) { if (!IsActualMultiplayerSession() || !PhotonNetwork.IsMasterClient) { return false; } if (!string.Equals(GetCurrentSaveFileName(), saveFileName, StringComparison.Ordinal)) { return false; } if ((Object)(object)StatsManager.instance == (Object)null) { return false; } return true; } private static void ManualRestoreLatestProgress() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (manualRestoreRunning) { LogInfo("Manual restore ignored because another restore is already running."); return; } string currentSaveFileName = GetCurrentSaveFileName(); if (string.IsNullOrWhiteSpace(currentSaveFileName)) { LogWarning("Manual restore failed because current save is empty."); ShowPopup(UseChinese() ? "当前没有可恢复的存档。" : "No save available to restore.", Color.yellow); return; } ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(ManualRestoreLatestProgressCoroutine(currentSaveFileName)); } } [IteratorStateMachine(typeof(d__210))] private static IEnumerator ManualRestoreLatestProgressCoroutine(string saveFileName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__210(0) { saveFileName = saveFileName }; } private static bool TriggerManualRestoreReload(string saveFileName) { //IL_00a1: 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_00df: Invalid comparison between Unknown and I4 //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Invalid comparison between Unknown and I4 //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) TraceFlow("ManualRestore:reload-entry", "save=" + saveFileName); if (!IsActualMultiplayerSession()) { if (CanReloadSingleplayerRecentSaveInPlace()) { TraceFlow("ManualRestore:reload-singleplayer-current-scene", "save=" + saveFileName); TryLoadGameInMemory(saveFileName); RestartCurrentSceneFromLoadedSave("单人重开当前场景"); return true; } SemiFunc.MainMenuSetSingleplayer(); TraceFlow("ManualRestore:reload-singleplayer", "save=" + saveFileName); SemiFunc.MenuActionSingleplayerGame(saveFileName, (List)null); return false; } if (CanReloadMultiplayerRecentSaveInPlace()) { TraceFlow("ManualRestore:reload-multiplayer-in-room", "save=" + saveFileName); TryLoadGameInMemory(saveFileName); RestartCurrentSceneFromLoadedSave("手动重载最近存档"); return true; } SemiFunc.MainMenuSetMultiplayer(); LobbyTypes val = (LobbyTypes)0; if ((Object)(object)GameManager.instance != (Object)null && GameManagerLobbyTypeField != null && GameManagerLobbyTypeField.GetValue(GameManager.instance) is LobbyTypes val2) { val = val2; } if ((int)val == 1) { TraceFlow("ManualRestore:reload-public-fallback", "save=" + saveFileName); SemiFunc.MenuActionHostGame(saveFileName, (List)null); return false; } if ((int)val == 2) { TraceFlow("ManualRestore:reload-matchmaking", "save=" + saveFileName); SemiFunc.MenuActionRandomMatchmaking(saveFileName, (List)null); return false; } TraceFlow("ManualRestore:reload-private", "save=" + saveFileName); SemiFunc.MenuActionHostGame(saveFileName, (List)null); return false; } private static bool CanReloadSingleplayerRecentSaveInPlace() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 if ((Object)(object)RunManager.instance == (Object)null || (Object)(object)GameDirector.instance == (Object)null) { return false; } if (SemiFunc.MenuLevel() || SemiFunc.RunIsLobby() || SemiFunc.RunIsLobbyMenu()) { return false; } return (int)GameDirector.instance.currentState == 2; } private static bool CanReloadMultiplayerRecentSaveInPlace() { if ((Object)(object)RunManager.instance != (Object)null && PhotonNetwork.IsMasterClient && PhotonNetwork.InRoom) { return (Object)(object)GameDirector.instance != (Object)null; } return false; } private static void RestartCurrentSceneFromLoadedSave(string reason = null) { if ((Object)(object)RunManager.instance == (Object)null) { LogInfo("RunManager.instance 为空,无法重载当前场景。"); return; } if (!CanUseSceneReloadContext()) { LogWarning("当前缺少场景重载所需的兼容上下文,跳过当前场景内重载。"); return; } string text = (string.IsNullOrWhiteSpace(reason) ? "按当前存档重载当前场景" : reason); if (TryRandomizeLoadedRunLevelBeforeSceneReload(text)) { return; } string[] obj = new string[5] { text, ":level=", null, null, null }; Level levelCurrent = RunManager.instance.levelCurrent; obj[2] = ((levelCurrent != null) ? ((Object)levelCurrent).name : null) ?? "null"; obj[3] = ", loadLevel="; obj[4] = GetRunManagerLoadLevel(RunManager.instance).ToString(); LogInfo(string.Concat(obj)); RunManager.instance.RestartScene(); if (SemiFunc.RunIsTutorial()) { return; } bool flag = default(bool); int num; if (RunManagerGameOverField != null) { object value = RunManagerGameOverField.GetValue(RunManager.instance); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } SemiFunc.OnSceneSwitch((byte)((uint)num & (flag ? 1u : 0u)) != 0, false); } private static void ShowPopup(string message, Color color) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)MenuManager.instance != (Object)null) { MenuManager.instance.PagePopUp("SaveKeeper", color, message, "OK", false); } } catch { } } private static void QueueStartupPopup(string title, Color color, string message, int priority) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(message)) { return; } MenuManager instance = MenuManager.instance; if ((Object)(object)instance != (Object)null && pendingStartupPopups.Count == 0 && (!CanCoordinateStartupPopup() || !IsPopupSlotBusy(instance))) { instance.PagePopUpScheduled(title, color, message, "OK", false); return; } pendingStartupPopups.Add(new PendingStartupPopup { Title = title, Color = color, Message = message, Priority = priority, Sequence = startupPopupSequence++ }); if (!startupPopupDrainRunning && (Object)(object)Instance != (Object)null) { startupPopupDrainRunning = true; ((MonoBehaviour)Instance).StartCoroutine(DrainStartupPopupQueueCoroutine()); } } private static bool IsPopupSlotBusy(MenuManager menuManager) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Invalid comparison between Unknown and I4 if (!CanCoordinateStartupPopup()) { return false; } if ((Object)(object)menuManager == (Object)null) { return true; } try { bool flag = default(bool); int num; if (MenuManagerPagePopUpScheduledField != null) { object value = MenuManagerPagePopUpScheduledField.GetValue(menuManager); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } if (MenuManagerCurrentMenuPageIndexField != null) { object value = MenuManagerCurrentMenuPageIndexField.GetValue(menuManager); if (value is MenuPageIndex) { MenuPageIndex val = (MenuPageIndex)value; return (int)val == 9 || (int)val == 7; } } } catch { } return false; } [IteratorStateMachine(typeof(d__218))] private static IEnumerator DrainStartupPopupQueueCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__218(0); } private static void AlignBackupIndexToExistingBackups(StatsManager statsManager, string saveFileName) { try { if (CanUseBackupIndexAlignment() && !((Object)(object)statsManager == (Object)null) && !(StatsManagerBackupIndexField == null) && !string.IsNullOrWhiteSpace(saveFileName)) { int latestBackupNumber = GetLatestBackupNumber(GetBackupFiles(GetSaveDirectory(saveFileName), saveFileName)); int num2 = ((StatsManagerBackupIndexField.GetValue(statsManager) is int num) ? num : 0); if (num2 != latestBackupNumber) { StatsManagerBackupIndexField.SetValue(statsManager, latestBackupNumber); LogInfo("对齐备份序号: " + saveFileName + " " + num2 + " -> " + latestBackupNumber); } } } catch (Exception ex) { LogWarning("对齐备份序号失败: " + ex.Message); } } private static string GetSaveRootDirectory() { return Path.Combine(Application.persistentDataPath, "saves"); } private static string GetSaveDirectory(string saveFileName) { return Path.Combine(GetSaveRootDirectory(), saveFileName); } private static string GetMainSavePath(string saveFileName) { return Path.Combine(GetSaveDirectory(saveFileName), saveFileName + ".es3"); } private static List GetBackupFiles(string saveDirectory, string saveFileName) { if (string.IsNullOrWhiteSpace(saveFileName) || !Directory.Exists(saveDirectory)) { return new List(); } return (from path in Directory.GetFiles(saveDirectory, saveFileName + "_BACKUP*.es3") select new BackupFileInfo { Path = path, Number = ExtractBackupNumber(path, BackupNumberRegex) } into item where item.Number >= 0 orderby item.Number descending select item).ToList(); } private static int GetLatestBackupNumber(List backups) { if (backups == null || backups.Count <= 0) { return 0; } return backups[0].Number; } private static string GenerateNewSaveFileName() { string text = DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss_fff", CultureInfo.InvariantCulture); string text2 = "REPO_SAVE_" + text; string text3 = text2; int num = 1; while (Directory.Exists(GetSaveDirectory(text3)) || File.Exists(GetMainSavePath(text3))) { text3 = text2 + "_" + num.ToString(CultureInfo.InvariantCulture); num++; } return text3; } private static int GetLatestBackupNumber(string saveDirectory, string saveFileName) { return GetLatestBackupNumber(GetBackupFiles(saveDirectory, saveFileName)); } private static void DetectNoSaveDeleteConflict() { try { noSaveDeleteConflictDetected = false; noSaveDeleteConflictSource = null; foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { string text = pluginInfo.Key ?? string.Empty; PluginInfo value = pluginInfo.Value; object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Name : null); } if (obj == null) { obj = string.Empty; } string text2 = (string)obj; if (LooksLikeNoSaveDelete(text) || LooksLikeNoSaveDelete(text2)) { noSaveDeleteConflictDetected = true; noSaveDeleteConflictSource = GetPluginLocation(pluginInfo.Value); LogWarning("Detected No Save Delete conflict from loaded plugin: " + text + " / " + text2 + GetSourceLogSuffix(noSaveDeleteConflictSource)); break; } } } catch (Exception ex) { LogWarning("Failed to scan No Save Delete conflict: " + ex.Message); } } private static string GetPluginLocation(PluginInfo pluginInfo) { try { return (pluginInfo != null) ? pluginInfo.Location : null; } catch { return null; } } private static string GetSourceLogSuffix(string source) { if (!string.IsNullOrWhiteSpace(source)) { return " at " + source; } return string.Empty; } private static bool LooksLikeNoSaveDelete(string value) { if (string.IsNullOrWhiteSpace(value)) { return false; } string text = value.Replace("_", string.Empty).Replace("-", string.Empty).Replace(" ", string.Empty); if (text.IndexOf("NoSaveDelete", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("PxntxrezStudioNoSaveDelete", StringComparison.OrdinalIgnoreCase) < 0) { return text.IndexOf("com.pxntxrez.nosavedelete".Replace(".", string.Empty), StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static void ShowConflictWarningOnce() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (!conflictPopupShown && noSaveDeleteConflictDetected && showConflictWarning != null && showConflictWarning.Value && !((Object)(object)MenuManager.instance == (Object)null)) { conflictPopupShown = true; QueueStartupPopup(UseChinese() ? "SaveKeeper" : "SaveKeeper", message: UseChinese() ? "这是 SaveKeeper 弹出的提示。\n检测到本地同时加载了一个可能影响 SaveKeeper 存档保护流程的模组:No Save Delete。\n它可能会导致 SaveKeeper 的部分功能失效。为了让 SaveKeeper 正常工作,建议在本地模组管理器中禁用或移除 No Save Delete。" : "This message is shown by SaveKeeper.\nSaveKeeper detected another local mod that may affect its save-protection flow: No Save Delete.\nIt may cause some SaveKeeper features to stop working correctly. To keep SaveKeeper working as intended, please disable or remove No Save Delete in your local mod manager.", color: Color.red, priority: 1); } } [IteratorStateMachine(typeof(d__232))] private static IEnumerator EnsureInitialSaveCoroutine(string saveFileName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__232(0) { saveFileName = saveFileName }; } [IteratorStateMachine(typeof(d__233))] private static IEnumerator ResetPlayerDeathBlockLater() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__233(0); } [IteratorStateMachine(typeof(d__234))] private static IEnumerator ResetMultiplayerDeathBlockLater() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__234(0); } [IteratorStateMachine(typeof(d__235))] private static IEnumerator RestoreSingleplayerSaveAfterDeathLater(string saveFileName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__235(0) { saveFileName = saveFileName }; } [IteratorStateMachine(typeof(d__236))] private static IEnumerator RestoreBackupAfterArenaReturnLater() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__236(0); } [IteratorStateMachine(typeof(d__237))] private static IEnumerator AutoStartAfterArenaCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__237(0); } private static bool ShouldAutoResumeAfterArena() { if (skipLobbyAfterArena != null && skipLobbyAfterArena.Value && IsActualMultiplayerSession()) { return PhotonNetwork.IsMasterClient; } return false; } private static int GetRunManagerLoadLevel(RunManager runManager) { if ((Object)(object)runManager == (Object)null || RunManagerLoadLevelField == null) { return 0; } try { return Convert.ToInt32(RunManagerLoadLevelField.GetValue(runManager), CultureInfo.InvariantCulture); } catch { return 0; } } private static int BuildRunReloadRandomSeed(RunManager runManager) { int tickCount = Environment.TickCount; tickCount = (tickCount * 397) ^ (int)(DateTime.UtcNow.Ticks & 0x7FFFFFFF); tickCount = (tickCount * 397) ^ PhotonNetwork.ServerTimestamp; int num = tickCount * 397; Room currentRoom = PhotonNetwork.CurrentRoom; tickCount = num ^ ((currentRoom == null) ? null : currentRoom.Name?.GetHashCode()).GetValueOrDefault(); tickCount = (tickCount * 397) ^ (GetCurrentSaveFileName()?.GetHashCode() ?? 0); tickCount = (tickCount * 397) ^ (runManager?.levelsCompleted ?? 0); int num2 = tickCount * 397; int? obj; if (runManager == null) { obj = null; } else { Level levelCurrent = runManager.levelCurrent; obj = ((levelCurrent == null) ? null : ((Object)levelCurrent).name?.GetHashCode()); } int? num3 = obj; tickCount = num2 ^ num3.GetValueOrDefault(); if (tickCount != 0) { return tickCount; } return 1; } private static void ApplyRunReloadRandomSeed(RunManager runManager, string reason) { int num = BuildRunReloadRandomSeed(runManager); Random.InitState(num); if ((Object)(object)GameDirector.instance != (Object)null) { GameDirector.instance.RandomSeed = true; GameDirector.instance.Seed = num; } LogInfo(reason + ":已刷新运行关随机种子 seed=" + num + "。"); } private static bool TryRandomizeLoadedRunLevelBeforeSceneReload(string reason = null) { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || GetRunManagerLoadLevel(instance) != 0) { return false; } string text = (string.IsNullOrWhiteSpace(reason) ? "读档后随机运行关" : reason); if (RunManagerPreviousRunLevelField != null) { RunManagerPreviousRunLevelField.SetValue(instance, instance.levelCurrent); } ApplyRunReloadRandomSeed(instance, text); LogInfo(text + ":按运行关随机链重新选择地图。"); instance.ChangeLevel(true, false, (ChangeLevelType)1); return true; } private static ChangeLevelType GetArenaResumeChangeLevelType(RunManager runManager) { if (!CanUseSceneReloadContext()) { return (ChangeLevelType)1; } return (ChangeLevelType)(GetRunManagerLoadLevel(runManager) switch { 1 => 5, 2 => 0, _ => 1, }); } private static bool TryPrepareDirectArenaResume(RunManager runManager, ref bool completedLevel, ref bool levelFailed, ref ChangeLevelType changeLevelType) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected I4, but got Unknown if (!ShouldAutoResumeAfterArena() || !CanUseSceneReloadContext() || restoreBackupAfterArenaReturn == null || !restoreBackupAfterArenaReturn.Value || (Object)(object)runManager == (Object)null || (Object)(object)runManager.levelLobbyMenu == (Object)null) { return false; } string currentSaveFileName = GetCurrentSaveFileName(); if (string.IsNullOrWhiteSpace(currentSaveFileName)) { LogWarning("决斗场后无法直接继续:当前存档名为空。"); return false; } LogWarning("死亡比赛结束,直接恢复存档并继续当前进度: " + currentSaveFileName); RestoreBestSaveAfterArenaReturn(currentSaveFileName); TryLoadGameInMemory(currentSaveFileName); runManager.levelCurrent = runManager.levelLobbyMenu; completedLevel = true; levelFailed = false; changeLevelType = (ChangeLevelType)(int)GetArenaResumeChangeLevelType(runManager); multiplayerArenaSeen = false; multiplayerArenaRestoreRunning = false; multiplayerDeathSaveBlocked = false; LogInfo("决斗场后直接继续:loadLevel=" + GetRunManagerLoadLevel(runManager) + ", changeType=" + ((object)(ChangeLevelType)(ref changeLevelType)).ToString()); return true; } private static void StartRunWithoutInviteFromCurrentProgress(string reason = null) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)RunManager.instance == (Object)null) { LogInfo("RunManager.instance 为空,无法自动开始。"); return; } if (PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.IsOpen = false; PhotonNetwork.CurrentRoom.IsVisible = false; } try { SteamManager instance = SteamManager.instance; if (instance != null) { instance.LockLobby(); } } catch (Exception ex) { LogWarning("自动继续前锁定房间失败: " + ex.Message); } ChangeLevelType arenaResumeChangeLevelType = GetArenaResumeChangeLevelType(RunManager.instance); string text = (string.IsNullOrWhiteSpace(reason) ? "按当前存档进度继续" : reason); LogInfo(text + ":loadLevel=" + GetRunManagerLoadLevel(RunManager.instance) + ", changeType=" + ((object)(ChangeLevelType)(ref arenaResumeChangeLevelType)).ToString()); RunManager.instance.ChangeLevel(true, false, arenaResumeChangeLevelType); } private static string GetCurrentSaveFileName() { if ((Object)(object)StatsManager.instance == (Object)null || StatsManagerCurrentSaveField == null) { return null; } return StatsManagerCurrentSaveField.GetValue(StatsManager.instance) as string; } private static bool AreAllPlayersDead() { if (!CanTrackMultiplayerDeathState() || PlayerAvatarDeadSetField == null) { return false; } List list = SemiFunc.PlayerGetList(); if (list == null || list.Count == 0) { return false; } foreach (PlayerAvatar item in list) { if (!((Object)(object)item == (Object)null)) { object value = PlayerAvatarDeadSetField.GetValue(item); if (!(value is bool) || !(bool)value) { return false; } } } return true; } private static bool IsArenaNow() { try { return SemiFunc.RunIsArena(); } catch { return false; } } private static bool IsShopNow() { try { return SemiFunc.RunIsShop(); } catch { return false; } } private static bool IsLevelArena(Level level) { try { return SemiFunc.IsLevelArena(level); } catch { return false; } } private static bool IsLevelShop(Level level) { try { return SemiFunc.IsLevelShop(level); } catch { return false; } } private static Level FindArenaRaceLevel(List arenaLevels) { if (arenaLevels == null || arenaLevels.Count == 0) { return null; } foreach (Level arenaLevel in arenaLevels) { if (IsRaceLevelName(arenaLevel)) { return arenaLevel; } } if (arenaLevels.Count > 1) { return arenaLevels[1]; } return null; } private static Level FindCrownArenaLevel(List arenaLevels) { if (arenaLevels == null || arenaLevels.Count == 0) { return null; } foreach (Level arenaLevel in arenaLevels) { string text = (((Object)arenaLevel).name ?? string.Empty).ToLower(); if (text.Contains("crown") || text.Contains("arena")) { return arenaLevel; } } foreach (Level arenaLevel2 in arenaLevels) { string text2 = (((Object)arenaLevel2).name ?? string.Empty).ToLower(); if (!text2.Contains("race") && !text2.Contains("duel")) { return arenaLevel2; } } return arenaLevels[0]; } private static bool IsRaceLevelName(Level level) { if ((Object)(object)level == (Object)null) { return false; } string text = ((Object)level).name ?? string.Empty; string text2 = level.NarrativeName ?? string.Empty; if (text.IndexOf("race", StringComparison.OrdinalIgnoreCase) < 0 && text2.IndexOf("race", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("racing", StringComparison.OrdinalIgnoreCase) < 0) { return text2.IndexOf("racing", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static bool IsLobbyOrLobbyMenuNow() { try { return SemiFunc.RunIsLobby() || SemiFunc.RunIsLobbyMenu(); } catch { return false; } } private static void SetSavedLobbyType(StatsManager statsManager, LobbyTypes lobbyType, bool enabled) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) try { if (statsManager?.savedLobbyTypes == null) { return; } if (enabled) { if (!statsManager.savedLobbyTypes.Contains(lobbyType)) { statsManager.savedLobbyTypes.Add(lobbyType); } } else if ((int)lobbyType != 0 && statsManager.savedLobbyTypes.Contains(lobbyType)) { statsManager.savedLobbyTypes.Remove(lobbyType); } } catch (Exception ex) { LogWarning("Failed to update saved lobby type queue: " + ex.Message); } } private static void ApplyPublicRoomSaveQueue(StatsManager statsManager) { bool enabled = ShouldSavePublicRooms(); SetSavedLobbyType(statsManager, (LobbyTypes)1, enabled); SetSavedLobbyType(statsManager, (LobbyTypes)2, enabled); } private static MenuPageServerListCreateNew OpenPublicServerCreatePage(string saveFileName) { if (!CanUsePublicRoomUiIntegration()) { LogWarning("公开房存档菜单联动所需 UI 字段缺失,跳过打开选档创建页。"); return null; } if ((Object)(object)MenuManager.instance == (Object)null) { return null; } object? obj = MenuManagerCurrentMenuPageField?.GetValue(MenuManager.instance); MenuPage value = (MenuPage)((obj is MenuPage) ? obj : null); MenuPageServerListCreateNew component = ((Component)MenuManager.instance.PageOpenOnTop((MenuPageIndex)15)).GetComponent(); MenuPageServerListCreateNewMenuPageParentField?.SetValue(component, value); MenuPageServerListCreateNewSaveFileNameField?.SetValue(component, saveFileName); object obj2 = MenuPageServerListCreateNewMenuTextInputField?.GetValue(component); if (obj2 != null && string.IsNullOrWhiteSpace(MenuTextInputTextCurrentField?.GetValue(obj2) as string)) { string roomNameForSave = GetRoomNameForSave(saveFileName); MenuTextInputTextCurrentField?.SetValue(obj2, roomNameForSave); LogInfo("公开房间默认名称: " + roomNameForSave); } return component; } private static string GetDefaultPublicServerName() { if (!CanReadDefaultPublicRoomName()) { return "Public Lobby"; } try { string text = SteamClientNameProperty?.GetValue(null, null) as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } } catch { } return "Public Lobby"; } private static string ReadNameChangerName() { if (!compatibilityReport.NameChangerInteropAvailable) { return null; } try { if (!Chainloader.PluginInfos.TryGetValue("NameChangeREPO", out var _)) { return null; } object obj = nameChangeRepoStartingNameField?.GetValue(null); if (obj == null) { return null; } return nameChangeRepoStartingNameValueProperty?.GetValue(obj) as string; } catch { return null; } } private static string GetDefaultNameForPublicRoom() { string text = ReadNameChangerName(); if (!string.IsNullOrWhiteSpace(text)) { return text; } return GetDefaultPublicServerName(); } private static string GetRoomNamesFilePath() { return Path.Combine(Application.persistentDataPath, "saves", "zichen.savekeeper.rooms.txt"); } private static Dictionary LoadRoomNamesMap() { Dictionary dictionary = new Dictionary(); try { string roomNamesFilePath = GetRoomNamesFilePath(); if (!File.Exists(roomNamesFilePath)) { return dictionary; } string[] array = File.ReadAllLines(roomNamesFilePath); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0) { string text2 = text.Substring(0, num).Trim(); string value = text.Substring(num + 1).Trim(); if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(value)) { dictionary[text2] = value; } } } } catch { } return dictionary; } private static void SaveRoomNamesMap(Dictionary map) { try { string roomNamesFilePath = GetRoomNamesFilePath(); string directoryName = Path.GetDirectoryName(roomNamesFilePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } List list = new List(); foreach (KeyValuePair item in map) { if (!string.IsNullOrWhiteSpace(item.Key) && !string.IsNullOrWhiteSpace(item.Value)) { list.Add(item.Key + "=" + item.Value); } } File.WriteAllLines(roomNamesFilePath, list.ToArray()); } catch { } } private static string GetRoomNameForSave(string saveFileName) { if (string.IsNullOrWhiteSpace(saveFileName)) { return GetDefaultNameForPublicRoom(); } if (LoadRoomNamesMap().TryGetValue(saveFileName, out var value) && !string.IsNullOrWhiteSpace(value)) { return value; } return GetDefaultNameForPublicRoom(); } private static void SetRoomNameForSave(string saveFileName, string roomName) { if (!string.IsNullOrWhiteSpace(saveFileName) && !string.IsNullOrWhiteSpace(roomName)) { string defaultNameForPublicRoom = GetDefaultNameForPublicRoom(); if (!(roomName == defaultNameForPublicRoom)) { Dictionary dictionary = LoadRoomNamesMap(); dictionary[saveFileName] = roomName; SaveRoomNamesMap(dictionary); LogWarning("已保存公开房间名: " + saveFileName + " -> " + roomName); } } } private static void RemoveRoomNameForSave(string saveFileName) { if (!string.IsNullOrWhiteSpace(saveFileName)) { Dictionary dictionary = LoadRoomNamesMap(); if (dictionary.Remove(saveFileName)) { SaveRoomNamesMap(dictionary); LogWarning("已清理公开房间名: " + saveFileName); } } } private static string GetPublicServerRoomName(MenuPageServerListCreateNew createPage) { if (!CanUsePublicRoomUiIntegration()) { return null; } try { object obj = MenuPageServerListCreateNewMenuTextInputField?.GetValue(createPage); if (obj == null) { return null; } return MenuTextInputTextCurrentField?.GetValue(obj) as string; } catch { return null; } } private static string GetSaveFileNameFromCreatePage(MenuPageServerListCreateNew createPage) { if (!CanUsePublicRoomUiIntegration()) { return null; } try { return MenuPageServerListCreateNewSaveFileNameField?.GetValue(createPage) as string; } catch { return null; } } private static int GetSaveFileCount(MenuPageSaves menuPageSaves) { if (!CanUsePublicRoomUiIntegration()) { return 0; } try { return (MenuPageSavesSaveFilesField?.GetValue(menuPageSaves) as IList)?.Count ?? 0; } catch { return 0; } } private static string GetSelectedSaveFileName(MenuPageSaves menuPageSaves) { if (!CanUsePublicRoomUiIntegration()) { return null; } try { return MenuPageSavesCurrentSaveFileNameField?.GetValue(menuPageSaves) as string; } catch { return null; } } private static void SetGameModeHeader(MenuPageSaves menuPageSaves, string text) { if (!CanUsePublicRoomUiIntegration()) { return; } try { object obj = MenuPageSavesGameModeHeaderField?.GetValue(menuPageSaves); MenuPageSavesGameModeHeaderTextProperty?.SetValue(obj, text, null); } catch (Exception ex) { LogWarning("Failed to update public save menu header: " + ex.Message); } } private static bool HasPublicServerName(MenuPageServerListCreateNew createPage) { if (!CanUsePublicRoomUiIntegration()) { return false; } try { object obj = MenuPageServerListCreateNewMenuTextInputField?.GetValue(createPage); if (obj == null) { return false; } return !string.IsNullOrWhiteSpace(MenuTextInputTextCurrentField?.GetValue(obj) as string); } catch { return true; } } private static void TryLoadGameInMemory(string saveFileName) { if (!CanLoadGameInMemory()) { LogWarning("缺少内存重载存档所需接口,跳过内存重载。"); return; } try { if (StatsManagerLoadGameMethod == null || (Object)(object)StatsManager.instance == (Object)null) { LogWarning("StatsManager.LoadGame was not found."); return; } ClearStripDictionariesBeforeLoad(); StatsManagerLoadGameMethod.Invoke(StatsManager.instance, new object[2] { saveFileName, null }); } catch (Exception ex) { LogError("Failed to reload save in memory: " + ex.Message); } } private static void ClearStripDictionariesBeforeLoad() { try { StatsManager instance = StatsManager.instance; if ((Object)(object)instance == (Object)null) { return; } IDictionary dictionary = StatsManagerDictionaryOfDictionariesField?.GetValue(instance) as IDictionary; IDictionary dictionary2 = StatsManagerStripTheseDictionariesField?.GetValue(instance) as IDictionary; if (dictionary == null || dictionary2 == null) { return; } foreach (object key2 in dictionary2.Keys) { if (key2 is string key && dictionary.Contains(key) && dictionary[key] is IDictionary dictionary3) { dictionary3.Clear(); } } } catch (Exception ex) { LogWarning("ClearStripDictionariesBeforeLoad 失败: " + ex.Message); } } private static void RestoreBestSaveAfterArenaReturn(string saveFileName) { ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)("[Restore] 进入恢复链:" + saveFileName)); } if (TryRestoreFromLevelStartSnapshot(saveFileName)) { return; } try { string text = Path.Combine(Application.persistentDataPath, "saves", saveFileName); string text2 = Path.Combine(text, saveFileName + ".es3"); if (!Directory.Exists(text)) { LogWarning("Save directory was not found: " + text); return; } SaveProgressCandidate? saveProgressCandidate = FindBestSaveCandidate(text, saveFileName, text2); if (!saveProgressCandidate.HasValue) { LogWarning("No backup file found after arena return for save: " + saveFileName); return; } if (!File.Exists(text2)) { File.Copy(saveProgressCandidate.Value.Path, text2, overwrite: true); LogInfo("Main save was missing. Restored best candidate: " + saveProgressCandidate.Value.Path + " (" + saveProgressCandidate.Value.Progress.ToString() + ")."); return; } if (!CanReadSaveProgressMarkers()) { LogWarning("缺少存档进度比较所需接口,保留当前主档;仅在主档缺失时恢复备份。"); return; } SaveProgressMarker current = ReadSaveProgressMarker(saveFileName, saveFileName); if (!saveProgressCandidate.Value.IsMainSave && IsProgressNewer(saveProgressCandidate.Value.Progress, current)) { File.Copy(saveProgressCandidate.Value.Path, text2, overwrite: true); LogInfo("Backup has newer progress. Restored best candidate " + saveProgressCandidate.Value.Progress.ToString() + " over main " + current.ToString() + "."); } else { LogInfo("Keeping main save after arena return. Main " + current.ToString() + ", best candidate " + saveProgressCandidate.Value.Progress.ToString() + "."); } } catch (Exception ex) { LogError("Failed to choose best save after arena return: " + ex.Message); } } private static bool MainSaveFileExists(string saveFileName) { try { if (string.IsNullOrWhiteSpace(saveFileName)) { return false; } return File.Exists(GetMainSavePath(saveFileName)); } catch { return false; } } private static SaveProgressMarker ReadSaveProgressMarker(string folderName, string fileName) { int runLevel = -1; int saveLevel = -1; if (!CanReadSaveProgressMarkers()) { return new SaveProgressMarker(runLevel, saveLevel); } try { if (int.TryParse(ReadSaveRunLevel(folderName, fileName), out var result)) { runLevel = result; } if (int.TryParse(ReadSaveRunStat(folderName, fileName, "save level"), out var result2)) { saveLevel = result2; } } catch (Exception ex) { LogWarning("Failed to read save progress from " + fileName + ": " + ex.Message); } return new SaveProgressMarker(runLevel, saveLevel); } private static string ReadSaveRunLevel(string folderName, string fileName) { if ((Object)(object)StatsManager.instance == (Object)null || StatsManagerSaveFileGetRunLevelMethod == null) { return null; } try { return StatsManagerSaveFileGetRunLevelMethod.Invoke(StatsManager.instance, new object[2] { folderName, fileName }) as string; } catch (Exception ex) { LogWarning("Failed to read save run level from " + fileName + ": " + ex.Message); return null; } } private static string ReadSaveRunStat(string folderName, string fileName, string statKey) { if (!CanReadSaveProgressMarkers() || (Object)(object)StatsManager.instance == (Object)null || StatsManagerSaveFileGetRunStatMethod == null) { return null; } try { return StatsManagerSaveFileGetRunStatMethod.Invoke(StatsManager.instance, new object[3] { folderName, fileName, statKey }) as string; } catch (Exception ex) { LogWarning("Failed to read save stat '" + statKey + "' from " + fileName + ": " + ex.Message); return null; } } private static bool IsProgressNewer(SaveProgressMarker candidate, SaveProgressMarker current) { if (candidate.RunLevel != current.RunLevel) { return candidate.RunLevel > current.RunLevel; } return candidate.SaveLevel > current.SaveLevel; } private static string FindLatestBackup(string saveDirectory, string saveFileName) { List backupFiles = GetBackupFiles(saveDirectory, saveFileName); if (backupFiles.Count <= 0) { return null; } return backupFiles[0].Path; } private static SaveProgressCandidate? FindBestSaveCandidate(string saveDirectory, string saveFileName, string mainSavePath) { SaveProgressCandidate? result = null; if (File.Exists(mainSavePath)) { result = new SaveProgressCandidate(mainSavePath, isMainSave: true, int.MaxValue, ReadSaveProgressMarker(saveFileName, saveFileName)); } foreach (BackupFileInfo backupFile in GetBackupFiles(saveDirectory, saveFileName)) { SaveProgressCandidate saveProgressCandidate = new SaveProgressCandidate(backupFile.Path, isMainSave: false, backupFile.Number, ReadSaveProgressMarker(saveFileName, Path.GetFileNameWithoutExtension(backupFile.Path))); if (!result.HasValue || CompareSaveProgressCandidate(saveProgressCandidate, result.Value) > 0) { result = saveProgressCandidate; } } return result; } private static int CompareSaveProgressCandidate(SaveProgressCandidate left, SaveProgressCandidate right) { int runLevel; if (left.Progress.RunLevel != right.Progress.RunLevel) { runLevel = left.Progress.RunLevel; return runLevel.CompareTo(right.Progress.RunLevel); } if (left.Progress.SaveLevel != right.Progress.SaveLevel) { runLevel = left.Progress.SaveLevel; return runLevel.CompareTo(right.Progress.SaveLevel); } if (left.IsMainSave != right.IsMainSave) { if (!left.IsMainSave) { return -1; } return 1; } runLevel = left.BackupNumber; return runLevel.CompareTo(right.BackupNumber); } private static void CleanupOldBackups(string saveFileName) { try { if (string.IsNullOrWhiteSpace(saveFileName)) { return; } string saveDirectory = GetSaveDirectory(saveFileName); if (!Directory.Exists(saveDirectory)) { return; } List backupFiles = GetBackupFiles(saveDirectory, saveFileName); if (backupFiles.Count <= 10) { return; } foreach (BackupFileInfo item in backupFiles.Skip(10)) { File.Delete(item.Path); LogInfo("Deleted old backup: " + item.Path); } } catch (Exception ex) { LogError("Failed to clean old backups: " + ex.Message); } } private static int ExtractBackupNumber(string filePath, Regex regex) { Match match = regex.Match(Path.GetFileNameWithoutExtension(filePath)); if (match.Success && int.TryParse(match.Groups[1].Value, out var result)) { return result; } return -1; } private void PatchHarmonySafely() { foreach (Type loadableType in GetLoadableTypes(typeof(ZichenSaveKeeperPlugin).Assembly)) { if (!(loadableType == null) && loadableType.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Any()) { try { harmony.CreateClassProcessor(loadableType).Patch(); } catch (Exception ex) { string patchFeatureName = GetPatchFeatureName(loadableType); compatibilityReport.AddPatchFailure(patchFeatureName, loadableType.Name, ex.Message); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped Harmony patch " + loadableType.FullName + ": " + ex.Message)); } } } } private static string GetPatchFeatureName(Type patchType) { if (!(patchType != null) || !PatchFeatureNamesByType.TryGetValue(patchType.Name, out var value)) { return patchType?.Name ?? "未知功能"; } return value; } private static IEnumerable GetLoadableTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type type) => type != null); } } private static bool CanSupplementHostCopyPlayerNames() { return compatibilityReport.HostCopyPlayerNamesAvailable; } private static bool ShouldCopyHostSaveToLocal() { if (IsEnabled() && compatibilityReport.HostCopyExportAvailable && copyHostSaveToLocal != null) { return copyHostSaveToLocal.Value; } return false; } private static bool IsHostCopyAutoSaveReadyContext() { try { return SemiFunc.RunIsLevel() && SemiFunc.LevelGenDone(); } catch { return false; } } private static bool IsNonHostMultiplayerClient() { if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsConnected) { return !PhotonNetwork.IsMasterClient; } return false; } private static bool HasPendingHostCopyUpdateWork() { if (!clientHostCopyPendingPlanBuildRequested) { return !string.IsNullOrWhiteSpace(clientHostCopyPendingAutoSaveSignature); } return true; } private static void TryProcessPendingHostCopyUpdateWork() { if (!ShouldCopyHostSaveToLocal() || !IsNonHostMultiplayerClient()) { clientHostCopyPendingPlanBuildRequested = false; clientHostCopyPendingPlanBuildReadyAt = 0f; ClearPendingHostCopyAutoSave(); } else { TryQueuePendingHostCopyAutoSavePlanBuild(); TryProcessPendingHostCopyAutoSave(); } } private static void TryProcessPendingHostCopyAutoSave() { if (!clientHostCopyExportRunning && !string.IsNullOrWhiteSpace(clientHostCopyPendingAutoSaveName) && !string.IsNullOrWhiteSpace(clientHostCopyPendingAutoSaveSignature) && IsHostCopyAutoSaveReadyContext()) { string text = clientHostCopyPendingAutoSaveName; string text2 = clientHostCopyPendingAutoSaveSignature; HostCopyExportPlan hostCopyExportPlan = clientHostCopyPendingAutoSavePlan; if (!string.IsNullOrWhiteSpace(text) && ((hostCopyExportPlan != null && string.Equals(hostCopyExportPlan.ExportSignature, text2, StringComparison.Ordinal)) ? ExportHostSnapshotToLocalSave(hostCopyExportPlan) : ExportHostSnapshotToLocalSave(text))) { clientHostCopyPendingAutoSaveName = null; clientHostCopyPendingAutoSaveSignature = null; clientHostCopyPendingAutoSavePlan = null; LogInfo("关卡同步完成后已自动保存房主存档到本地: " + text + " / " + text2); } } } private static void ClearPendingHostCopyAutoSave() { sessionSaveContext.HostCopy.ClearPendingAutoSave(); } private static void SchedulePendingHostCopyAutoSavePlanBuild() { if (!ShouldCopyHostSaveToLocal() || !IsNonHostMultiplayerClient()) { clientHostCopyPendingPlanBuildRequested = false; clientHostCopyPendingPlanBuildReadyAt = 0f; ClearPendingHostCopyAutoSave(); } else { clientHostCopyPendingPlanBuildRequested = true; clientHostCopyPendingPlanBuildReadyAt = Time.unscaledTime + 0.5f; } } private static void TryQueuePendingHostCopyAutoSavePlanBuild() { if (!clientHostCopyPendingPlanBuildRequested || Time.unscaledTime < clientHostCopyPendingPlanBuildReadyAt || clientHostCopyExportRunning || (Object)(object)StatsManager.instance == (Object)null) { return; } clientHostCopyExportRunning = true; try { QueuePendingHostCopyAutoSave(); } finally { clientHostCopyExportRunning = false; clientHostCopyPendingPlanBuildRequested = false; clientHostCopyPendingPlanBuildReadyAt = 0f; } } private static void QueuePendingHostCopyAutoSave() { StatsManager instance = StatsManager.instance; if (!((Object)(object)instance == (Object)null)) { string text = GetClientHostCopySaveName(); if (!string.IsNullOrWhiteSpace(text) && TryBuildHostCopyExportPlan(instance, text, out var exportPlan) && !string.Equals(exportPlan.ExportSignature, clientHostCopyLastExportSignature, StringComparison.Ordinal) && !string.Equals(exportPlan.ExportSignature, clientHostCopyPendingAutoSaveSignature, StringComparison.Ordinal)) { clientHostCopyPendingAutoSaveName = exportPlan.SaveFileName; clientHostCopyPendingAutoSaveSignature = exportPlan.ExportSignature; clientHostCopyPendingAutoSavePlan = exportPlan; } } } private static bool ExportHostSnapshotToLocalSave(string saveFileName) { if (!TryBuildHostCopyExportPlan(StatsManager.instance, saveFileName, out var exportPlan)) { return false; } return ExportHostSnapshotToLocalSave(exportPlan); } private static bool ExportHostSnapshotToLocalSave(HostCopyExportPlan exportPlan) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown try { if (exportPlan == null || string.IsNullOrWhiteSpace(exportPlan.SaveFileName)) { return false; } if (string.Equals(exportPlan.ExportSignature, clientHostCopyLastExportSignature, StringComparison.Ordinal)) { LogInfo("跳过复制房主存档:当前同步快照没有变化。"); return false; } StatsManager instance = StatsManager.instance; int saveVersion = exportPlan.SaveVersion; string text = (((Object)(object)instance != (Object)null && CanUseHostCopyEncryptionField()) ? (StatsManagerEncryptionPasswordField?.GetValue(instance) as string) : null); if (string.IsNullOrWhiteSpace(text)) { text = "Why would you want to cheat?... :o It's no fun. :') :'D"; } string text2 = Path.Combine(Application.persistentDataPath, "saves", exportPlan.SaveFileName); Directory.CreateDirectory(text2); string text3 = Path.Combine(text2, exportPlan.SaveFileName + ".es3"); RotateExportSnapshotBackupIfNeeded(text2, exportPlan.SaveFileName, text3); ES3Settings val = new ES3Settings(new Enum[1] { (Enum)(object)(Location)4 }) { encryptionType = (EncryptionType)1, encryptionPassword = text, path = text3 }; ES3.Save("teamName", exportPlan.TeamName, val); ES3.Save("teamNameChanged", exportPlan.TeamNameChanged, val); ES3.Save("dateAndTime", exportPlan.DateAndTime, val); ES3.Save("timePlayed", exportPlan.TimePlayed, val); ES3.Save>("playerNames", exportPlan.PlayerNamesSnapshot, val); ES3.Save>>("dictionaryOfDictionaries", exportPlan.Snapshot, val); ES3.Save("saveVersion", saveVersion, val); SaveHostCopyMetadataFields(val, exportPlan.Metadata); ES3.StoreCachedFile(val); clientHostCopyLastExportSignature = exportPlan.ExportSignature; CleanupOldBackups(exportPlan.SaveFileName); CleanupOldHostCopySessionSaves(exportPlan.SaveFileName); string text4 = (string.IsNullOrWhiteSpace(exportPlan.Metadata.SourceRoomName) ? (exportPlan.Metadata.SourceHostName ?? "unknown host") : (exportPlan.Metadata.SourceRoomName + " / " + (exportPlan.Metadata.SourceHostName ?? "unknown host"))); LogWarning("已复制当前房主同步进度到本地存档: " + exportPlan.SaveFileName + " <- " + text4); return true; } catch (Exception ex) { LogError("复制房主存档到本地失败: " + ex.Message); return false; } } private static bool TryBuildHostCopyExportPlan(StatsManager stats, string saveFileName, out HostCopyExportPlan exportPlan) { exportPlan = null; if ((Object)(object)stats == (Object)null || string.IsNullOrWhiteSpace(saveFileName)) { return false; } Dictionary> dictionary = BuildClientSnapshotDictionary(stats); if (dictionary.Count == 0) { LogInfo("跳过复制房主存档:当前同步快照为空。"); return false; } Dictionary playerNamesSnapshot = BuildPlayerNamesSnapshot(stats); HostCopyMetadata metadata = BuildHostCopyMetadata(stats); string teamName = BuildClientCopyTeamName(stats, metadata); bool teamNameChanged = BuildClientCopyTeamNameChanged(stats, metadata, teamName); float timePlayed = BuildClientCopyTimePlayed(stats, metadata); string dateAndTime = BuildClientCopyDateAndTime(stats, metadata); string exportSignature = BuildHostSnapshotSignature(saveFileName, dictionary, playerNamesSnapshot, metadata, teamName, teamNameChanged, timePlayed, dateAndTime, stats.saveVersion); exportPlan = new HostCopyExportPlan { SaveFileName = saveFileName, ExportSignature = exportSignature, Snapshot = dictionary, PlayerNamesSnapshot = playerNamesSnapshot, Metadata = metadata, TeamName = teamName, TeamNameChanged = teamNameChanged, TimePlayed = timePlayed, DateAndTime = dateAndTime, SaveVersion = stats.saveVersion }; return true; } private static Dictionary> BuildClientSnapshotDictionary(StatsManager stats) { if (!compatibilityReport.HostCopyExportFilteringAvailable) { LogWarning("房主快照导出过滤链不可用,将跳过原版过滤与默认值清理。"); } List list = (StatsManagerDoNotSaveTheseDictionariesField?.GetValue(stats) as List) ?? new List(); Dictionary dictionary = (StatsManagerStripTheseDictionariesField?.GetValue(stats) as Dictionary) ?? new Dictionary(); Dictionary> dictionary2 = new Dictionary>(); if (!(StatsManagerDictionaryOfDictionariesField?.GetValue(stats) is IEnumerable>> enumerable)) { return dictionary2; } foreach (KeyValuePair> item in enumerable) { if (list.Contains(item.Key)) { continue; } Dictionary dictionary3 = new Dictionary(); if (dictionary.TryGetValue(item.Key, out var value)) { foreach (KeyValuePair item2 in item.Value) { if (item2.Value != value) { dictionary3[item2.Key] = item2.Value; } } } else { foreach (KeyValuePair item3 in item.Value) { dictionary3[item3.Key] = item3.Value; } } dictionary2[item.Key] = dictionary3; } return dictionary2; } private static Dictionary BuildPlayerNamesSnapshot(StatsManager stats) { Dictionary dictionary = ((stats.playerNames != null) ? new Dictionary(stats.playerNames) : new Dictionary()); if (!CanSupplementHostCopyPlayerNames()) { return dictionary; } List list = SemiFunc.PlayerGetList(); if (list == null) { return dictionary; } foreach (PlayerAvatar item in list) { if (!((Object)(object)item == (Object)null)) { string text = PlayerAvatarSteamIdField?.GetValue(item) as string; string value = PlayerAvatarNameField?.GetValue(item) as string; if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(value)) { dictionary[text] = value; } } } return dictionary; } private static void SaveHostCopyMetadataFields(ES3Settings settings, HostCopyMetadata metadata) { ES3.Save("saveKeeperHostCopySourceTeamName", metadata.TeamName ?? string.Empty, settings); ES3.Save("saveKeeperHostCopySourceRoomName", metadata.SourceRoomName ?? string.Empty, settings); ES3.Save("saveKeeperHostCopySourceRoomId", metadata.SourceRoomId ?? string.Empty, settings); ES3.Save("saveKeeperHostCopySourceHostName", metadata.SourceHostName ?? string.Empty, settings); ES3.Save("saveKeeperHostCopySourceHostUserId", metadata.SourceHostUserId ?? string.Empty, settings); ES3.Save("saveKeeperHostCopySourceSaveName", metadata.SourceSaveName ?? string.Empty, settings); ES3.Save("saveKeeperHostCopyExportedAtUtc", DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture), settings); ES3.Save("saveKeeperHostCopyUsedPublishedMetadata", metadata.PublishedBySaveKeeper, settings); ES3.Save("saveKeeperHostCopyMetadataVersion", 1, settings); } private static HostCopyMetadata BuildHostCopyMetadata(StatsManager stats) { HostCopyMetadata hostCopyMetadata = TryReadPublishedHostCopyMetadata() ?? new HostCopyMetadata(); string text = stats?.teamName; if ((string.IsNullOrWhiteSpace(hostCopyMetadata.TeamName) || string.Equals(hostCopyMetadata.TeamName, "R.E.P.O.", StringComparison.Ordinal)) && !string.IsNullOrWhiteSpace(text) && !string.Equals(text, "R.E.P.O.", StringComparison.Ordinal)) { hostCopyMetadata.TeamName = text; } if (!hostCopyMetadata.TeamNameChanged) { hostCopyMetadata.TeamNameChanged = (stats != null && stats.teamNameChanged) || (!string.IsNullOrWhiteSpace(hostCopyMetadata.TeamName) && !string.Equals(hostCopyMetadata.TeamName, "R.E.P.O.", StringComparison.Ordinal)); } if (hostCopyMetadata.TimePlayed <= 0f) { hostCopyMetadata.TimePlayed = Mathf.Max(0f, stats?.timePlayed ?? 0f); } if (string.IsNullOrWhiteSpace(hostCopyMetadata.DateAndTime)) { hostCopyMetadata.DateAndTime = ((!string.IsNullOrWhiteSpace(stats?.dateAndTime)) ? stats.dateAndTime : DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } if (string.IsNullOrWhiteSpace(hostCopyMetadata.SourceSaveName)) { hostCopyMetadata.SourceSaveName = GetCurrentSaveFileName() ?? pendingNewSaveName; } if (string.IsNullOrWhiteSpace(hostCopyMetadata.SourceRoomName)) { hostCopyMetadata.SourceRoomName = GetCurrentRoomDisplayName(); } if (string.IsNullOrWhiteSpace(hostCopyMetadata.SourceRoomId)) { Room currentRoom = PhotonNetwork.CurrentRoom; hostCopyMetadata.SourceRoomId = ((currentRoom != null) ? currentRoom.Name : null); } if (string.IsNullOrWhiteSpace(hostCopyMetadata.SourceHostName)) { Player masterClient = PhotonNetwork.MasterClient; hostCopyMetadata.SourceHostName = ((masterClient != null) ? masterClient.NickName : null); } if (string.IsNullOrWhiteSpace(hostCopyMetadata.SourceHostUserId)) { Player masterClient2 = PhotonNetwork.MasterClient; hostCopyMetadata.SourceHostUserId = ((masterClient2 != null) ? masterClient2.UserId : null); } return hostCopyMetadata; } private static HostCopyMetadata TryReadPublishedHostCopyMetadata() { if (!HasCurrentRoomProperty("sk_copy_meta_v") && !HasCurrentRoomProperty("sk_copy_team_name") && !HasCurrentRoomProperty("sk_copy_source_save")) { return null; } string value; bool value2; float value3; string value4; string value5; string value6; string value7; string value8; string value9; return new HostCopyMetadata { PublishedBySaveKeeper = true, TeamName = (TryGetCurrentRoomPropertyString("sk_copy_team_name", out value) ? value : null), TeamNameChanged = (TryGetCurrentRoomPropertyBool("sk_copy_team_name_changed", out value2) && value2), TimePlayed = (TryGetCurrentRoomPropertyFloat("sk_copy_time_played", out value3) ? Mathf.Max(0f, value3) : 0f), DateAndTime = (TryGetCurrentRoomPropertyString("sk_copy_date", out value4) ? value4 : null), SourceSaveName = (TryGetCurrentRoomPropertyString("sk_copy_source_save", out value5) ? value5 : null), SourceRoomName = (TryGetCurrentRoomPropertyString("sk_copy_source_room_name", out value6) ? value6 : null), SourceRoomId = (TryGetCurrentRoomPropertyString("sk_copy_source_room_id", out value7) ? value7 : null), SourceHostName = (TryGetCurrentRoomPropertyString("sk_copy_source_host_name", out value8) ? value8 : null), SourceHostUserId = (TryGetCurrentRoomPropertyString("sk_copy_source_host_user_id", out value9) ? value9 : null) }; } private static void PublishHostCopyMetadataToRoom() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Expected O, but got Unknown //IL_014b: Expected O, but got Unknown if (IsHostAndEnabled() && SemiFunc.IsMultiplayer() && PhotonNetwork.IsConnected && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null && !((Object)(object)StatsManager.instance == (Object)null)) { HostCopyMetadata hostCopyMetadata = BuildLocalHostCopyMetadata(StatsManager.instance); string a = BuildPublishedHostCopyMetadataSignature(hostCopyMetadata); if (!string.Equals(a, publishedHostCopyMetadataSignature, StringComparison.Ordinal)) { Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"sk_copy_meta_v", (object)1); ((Dictionary)val).Add((object)"sk_copy_team_name", (object)(hostCopyMetadata.TeamName ?? string.Empty)); ((Dictionary)val).Add((object)"sk_copy_team_name_changed", (object)hostCopyMetadata.TeamNameChanged); ((Dictionary)val).Add((object)"sk_copy_time_played", (object)hostCopyMetadata.TimePlayed); ((Dictionary)val).Add((object)"sk_copy_date", (object)(hostCopyMetadata.DateAndTime ?? string.Empty)); ((Dictionary)val).Add((object)"sk_copy_source_save", (object)(hostCopyMetadata.SourceSaveName ?? string.Empty)); ((Dictionary)val).Add((object)"sk_copy_source_room_name", (object)(hostCopyMetadata.SourceRoomName ?? string.Empty)); ((Dictionary)val).Add((object)"sk_copy_source_room_id", (object)(hostCopyMetadata.SourceRoomId ?? string.Empty)); ((Dictionary)val).Add((object)"sk_copy_source_host_name", (object)(hostCopyMetadata.SourceHostName ?? string.Empty)); ((Dictionary)val).Add((object)"sk_copy_source_host_user_id", (object)(hostCopyMetadata.SourceHostUserId ?? string.Empty)); Hashtable val2 = val; PhotonNetwork.CurrentRoom.SetCustomProperties(val2, (Hashtable)null, (WebFlags)null); publishedHostCopyMetadataSignature = a; LogInfo("已同步房主存档元数据到房间属性: " + (hostCopyMetadata.SourceSaveName ?? "(empty)")); } } } private static HostCopyMetadata BuildLocalHostCopyMetadata(StatsManager stats) { string currentRoomDisplayName = GetCurrentRoomDisplayName(); string text = stats?.teamName; HostCopyMetadata obj = new HostCopyMetadata { PublishedBySaveKeeper = true, TeamName = ((!string.IsNullOrWhiteSpace(text)) ? text : "R.E.P.O."), TeamNameChanged = ((stats != null && stats.teamNameChanged) || (!string.IsNullOrWhiteSpace(text) && !string.Equals(text, "R.E.P.O.", StringComparison.Ordinal))), TimePlayed = Mathf.Max(0f, stats?.timePlayed ?? 0f), DateAndTime = ((!string.IsNullOrWhiteSpace(stats?.dateAndTime)) ? stats.dateAndTime : DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)), SourceSaveName = (GetCurrentSaveFileName() ?? pendingNewSaveName), SourceRoomName = currentRoomDisplayName }; Room currentRoom = PhotonNetwork.CurrentRoom; obj.SourceRoomId = ((currentRoom != null) ? currentRoom.Name : null); Player localPlayer = PhotonNetwork.LocalPlayer; object obj2 = ((localPlayer != null) ? localPlayer.NickName : null); if (obj2 == null) { Player masterClient = PhotonNetwork.MasterClient; obj2 = ((masterClient != null) ? masterClient.NickName : null); } obj.SourceHostName = (string)obj2; Player localPlayer2 = PhotonNetwork.LocalPlayer; object obj3 = ((localPlayer2 != null) ? localPlayer2.UserId : null); if (obj3 == null) { Player masterClient2 = PhotonNetwork.MasterClient; obj3 = ((masterClient2 != null) ? masterClient2.UserId : null); } obj.SourceHostUserId = (string)obj3; return obj; } private static string BuildPublishedHostCopyMetadataSignature(HostCopyMetadata metadata) { uint hash = 2166136261u; AddHashString(ref hash, metadata.TeamName); AddHashInt(ref hash, metadata.TeamNameChanged ? 1 : 0); AddHashInt(ref hash, GetTimePlayedSignatureBucket(metadata.TimePlayed)); AddHashString(ref hash, metadata.DateAndTime); AddHashString(ref hash, metadata.SourceSaveName); AddHashString(ref hash, metadata.SourceRoomName); AddHashString(ref hash, metadata.SourceRoomId); AddHashString(ref hash, metadata.SourceHostName); AddHashString(ref hash, metadata.SourceHostUserId); return hash.ToString("X8", CultureInfo.InvariantCulture); } private static string BuildClientCopyTeamName(StatsManager stats, HostCopyMetadata metadata) { string text = metadata.SourceHostName; if (string.IsNullOrWhiteSpace(text)) { Player masterClient = PhotonNetwork.MasterClient; text = ((masterClient != null) ? masterClient.NickName : null); } if (!string.IsNullOrWhiteSpace(text)) { return text + "的存档"; } return "房主的存档"; } private static bool BuildClientCopyTeamNameChanged(StatsManager stats, HostCopyMetadata metadata, string teamName) { return !string.IsNullOrWhiteSpace(teamName); } private static float BuildClientCopyTimePlayed(StatsManager stats, HostCopyMetadata metadata) { if (metadata.TimePlayed > 0f) { return Mathf.Max(0f, metadata.TimePlayed); } return Mathf.Max(0f, stats?.timePlayed ?? 0f); } private static string BuildClientCopyDateAndTime(StatsManager stats, HostCopyMetadata metadata) { if (!string.IsNullOrWhiteSpace(metadata.DateAndTime)) { return metadata.DateAndTime; } if (!string.IsNullOrWhiteSpace(stats?.dateAndTime)) { return stats.dateAndTime; } return DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); } private static string GetClientHostCopySaveName() { if (!string.IsNullOrWhiteSpace(clientHostCopySaveName)) { return clientHostCopySaveName; } if (string.IsNullOrWhiteSpace(clientHostCopySessionToken)) { clientHostCopySessionToken = DateTime.Now.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture); } string currentRoomDisplayName = GetCurrentRoomDisplayName(); Player masterClient = PhotonNetwork.MasterClient; string text = SanitizeSaveNameSegment((masterClient != null) ? masterClient.NickName : null, 18); string text2 = SanitizeSaveNameSegment(currentRoomDisplayName, 18); if (string.IsNullOrWhiteSpace(text)) { text = "Host"; } if (string.IsNullOrWhiteSpace(text2)) { text2 = "Room"; } Player masterClient2 = PhotonNetwork.MasterClient; string hostName = ((masterClient2 != null) ? masterClient2.NickName : null); Room currentRoom = PhotonNetwork.CurrentRoom; string roomName = ((currentRoom != null) ? currentRoom.Name : null); Player masterClient3 = PhotonNetwork.MasterClient; string text3 = BuildStableCopySuffix(hostName, roomName, (masterClient3 != null) ? masterClient3.UserId : null, clientHostCopySessionToken); clientHostCopySaveName = "HOSTCOPY_" + text + "_" + text2 + "_" + text3; return clientHostCopySaveName; } private static string SanitizeSaveNameSegment(string value, int maxLength) { if (string.IsNullOrWhiteSpace(value) || maxLength <= 0) { return string.Empty; } string input = new string(value.Select((char ch) => ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') && (ch < '0' || ch > '9') && ch != '-' && ch != '_') ? '_' : ch).ToArray()); input = Regex.Replace(input, "_{2,}", "_").Trim(new char[1] { '_' }); if (input.Length > maxLength) { input = input.Substring(0, maxLength).Trim(new char[1] { '_' }); } return input; } private static string BuildStableCopySuffix(string hostName, string roomName, string userId, string sessionToken) { return ComputeStableHash((hostName ?? string.Empty) + "|" + (roomName ?? string.Empty) + "|" + (userId ?? string.Empty) + "|" + (sessionToken ?? string.Empty)).ToString("X8", CultureInfo.InvariantCulture); } private static string BuildHostSnapshotSignature(string saveFileName, Dictionary> snapshot, Dictionary playerNamesSnapshot, HostCopyMetadata metadata, string teamName, bool teamNameChanged, float timePlayed, string dateAndTime, int saveVersion) { uint hash = 2166136261u; AddHashString(ref hash, saveFileName); AddHashString(ref hash, teamName); AddHashInt(ref hash, teamNameChanged ? 1 : 0); AddHashInt(ref hash, GetTimePlayedSignatureBucket(timePlayed)); AddHashString(ref hash, dateAndTime); AddHashString(ref hash, metadata.SourceSaveName); AddHashString(ref hash, metadata.SourceRoomName); AddHashString(ref hash, metadata.SourceRoomId); AddHashString(ref hash, metadata.SourceHostName); AddHashString(ref hash, metadata.SourceHostUserId); AddHashInt(ref hash, saveVersion); foreach (KeyValuePair item in playerNamesSnapshot.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { AddHashString(ref hash, item.Key); AddHashString(ref hash, item.Value); } foreach (KeyValuePair> item2 in snapshot.OrderBy>, string>((KeyValuePair> pair) => pair.Key, StringComparer.Ordinal)) { AddHashString(ref hash, item2.Key); foreach (KeyValuePair item3 in item2.Value.OrderBy, string>((KeyValuePair valuePair) => valuePair.Key, StringComparer.Ordinal)) { AddHashString(ref hash, item3.Key); AddHashInt(ref hash, item3.Value); } } return hash.ToString("X8", CultureInfo.InvariantCulture); } private static int GetTimePlayedSignatureBucket(float timePlayed) { return Mathf.FloorToInt(Mathf.Max(0f, timePlayed) / 30f); } private static string GetCurrentRoomDisplayName() { if (TryGetCurrentRoomPropertyString("server_name", out var value) && !string.IsNullOrWhiteSpace(value)) { return value; } Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { return null; } return currentRoom.Name; } private static bool HasCurrentRoomProperty(string key) { object value; return TryGetCurrentRoomPropertyValue(key, out value); } private static bool TryGetCurrentRoomPropertyString(string key, out string value) { value = null; if (!TryGetCurrentRoomPropertyValue(key, out var value2)) { return false; } value = value2 as string; return value != null; } private static bool TryGetCurrentRoomPropertyBool(string key, out bool value) { value = false; if (!TryGetCurrentRoomPropertyValue(key, out var value2)) { return false; } object obj = value2; if (!(obj is bool flag)) { if (!(obj is byte b)) { if (!(obj is int num)) { if (obj is string value3 && bool.TryParse(value3, out var result)) { value = result; return true; } return false; } int num2 = num; value = num2 != 0; return true; } byte b2 = b; value = b2 != 0; return true; } bool flag2 = flag; value = flag2; return true; } private static bool TryGetCurrentRoomPropertyFloat(string key, out float value) { value = 0f; if (!TryGetCurrentRoomPropertyValue(key, out var value2)) { return false; } object obj = value2; if (!(obj is float num)) { if (!(obj is double num2)) { if (!(obj is int num3)) { if (!(obj is long num4)) { if (obj is string s && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { value = result; return true; } return false; } long num5 = num4; value = num5; return true; } int num6 = num3; value = num6; return true; } double num7 = num2; value = (float)num7; return true; } float num8 = num; value = num8; return true; } private static bool TryGetCurrentRoomPropertyValue(string key, out object value) { value = null; Room currentRoom = PhotonNetwork.CurrentRoom; Hashtable val = ((currentRoom != null) ? ((RoomInfo)currentRoom).CustomProperties : null); if (val == null || !((Dictionary)(object)val).ContainsKey((object)key)) { return false; } value = val[(object)key]; return true; } private static uint ComputeStableHash(string value) { uint hash = 2166136261u; AddHashString(ref hash, value); return hash; } private static void AddHashString(ref uint hash, string value) { string text = value ?? string.Empty; for (int i = 0; i < text.Length; i++) { hash ^= text[i]; hash *= 16777619u; } hash ^= 255u; hash *= 16777619u; } private static void AddHashInt(ref uint hash, int value) { hash ^= (byte)value; hash *= 16777619u; hash ^= (byte)(value >> 8); hash *= 16777619u; hash ^= (byte)(value >> 16); hash *= 16777619u; hash ^= (byte)(value >> 24); hash *= 16777619u; } private static void RotateExportSnapshotBackupIfNeeded(string saveDirectory, string saveFileName, string savePath) { if (File.Exists(savePath)) { string destFileName = Path.Combine(saveDirectory, saveFileName + "_BACKUP" + (GetLatestBackupNumber(GetBackupFiles(saveDirectory, saveFileName)) + 1) + ".es3"); File.Move(savePath, destFileName); } } private static void CleanupOldHostCopySessionSaves(string currentSaveFileName) { try { if (string.IsNullOrWhiteSpace(currentSaveFileName) || !currentSaveFileName.StartsWith("HOSTCOPY_", StringComparison.OrdinalIgnoreCase)) { return; } string saveRootDirectory = GetSaveRootDirectory(); if (!Directory.Exists(saveRootDirectory)) { return; } List list = (from info in Directory.GetDirectories(saveRootDirectory, "HOSTCOPY_*").Select(BuildHostCopySaveDirectoryInfo) where info != null orderby info.LastWriteTimeUtc descending select info).ToList(); if (list.Count <= 30) { return; } foreach (HostCopySaveDirectoryInfo item in list.Skip(30)) { if (!string.Equals(item.SaveFileName, currentSaveFileName, StringComparison.OrdinalIgnoreCase)) { Directory.Delete(item.DirectoryPath, recursive: true); LogInfo("Deleted old HOSTCOPY session save: " + item.DirectoryPath); } } } catch (Exception ex) { LogError("Failed to clean old HOSTCOPY session saves: " + ex.Message); } } private static HostCopySaveDirectoryInfo BuildHostCopySaveDirectoryInfo(string directoryPath) { if (string.IsNullOrWhiteSpace(directoryPath)) { return null; } string fileName = Path.GetFileName(directoryPath); string path = Path.Combine(directoryPath, fileName + ".es3"); DateTime lastWriteTimeUtc = (File.Exists(path) ? File.GetLastWriteTimeUtc(path) : Directory.GetLastWriteTimeUtc(directoryPath)); return new HostCopySaveDirectoryInfo { SaveFileName = fileName, DirectoryPath = directoryPath, LastWriteTimeUtc = lastWriteTimeUtc }; } private static bool CanRecallLastUsedSave() { return compatibilityReport.SaveOrderRecallAvailable; } private static void RecordSaveUsage(string saveFileName) { if (string.IsNullOrWhiteSpace(saveFileName) || !CanRecallLastUsedSave()) { return; } try { List list = ReadRecentSaves(); if (list.Count <= 0 || !string.Equals(list[0], saveFileName, StringComparison.Ordinal)) { list.RemoveAll((string name) => string.Equals(name, saveFileName, StringComparison.Ordinal)); list.Insert(0, saveFileName); if (list.Count > 50) { list.RemoveRange(50, list.Count - 50); } WriteRecentSaves(list); LogInfo("已记录最近使用存档(MRU 顶部):" + saveFileName + ";当前 MRU 顺序:" + string.Join(" → ", list.ToArray())); } } catch (Exception ex) { LogWarning("记录最近使用存档失败: " + ex.Message); } } private static List ReadRecentSaves() { List list = new List(); try { string recentSavesFilePath = GetRecentSavesFilePath(); if (File.Exists(recentSavesFilePath)) { string[] array = File.ReadAllLines(recentSavesFilePath); for (int i = 0; i < array.Length; i++) { string text = (array[i] ?? string.Empty).Trim(); if (!string.IsNullOrEmpty(text)) { list.Add(text); } } return list; } string legacyLastUsedSaveFilePath = GetLegacyLastUsedSaveFilePath(); if (File.Exists(legacyLastUsedSaveFilePath)) { string text2 = File.ReadAllText(legacyLastUsedSaveFilePath).Trim(); if (!string.IsNullOrEmpty(text2)) { list.Add(text2); LogInfo("迁移旧版 lastsave.txt 到 recentsaves.txt:" + text2); WriteRecentSaves(list); } try { File.Delete(legacyLastUsedSaveFilePath); } catch { } } } catch (Exception ex) { LogWarning("读取最近使用存档列表失败: " + ex.Message); } return list; } private static void WriteRecentSaves(List mru) { string recentSavesFilePath = GetRecentSavesFilePath(); string directoryName = Path.GetDirectoryName(recentSavesFilePath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(recentSavesFilePath, string.Join(Environment.NewLine, mru) + Environment.NewLine); } private static string GetRecentSavesFilePath() { return Path.Combine(Paths.ConfigPath, "zichen.savekeeper.recentsaves.txt"); } private static string GetLegacyLastUsedSaveFilePath() { return Path.Combine(Paths.ConfigPath, "zichen.savekeeper.lastsave.txt"); } private static void ReorderSaveFoldersByMru(List folders) { if (folders == null || folders.Count <= 1) { return; } List list = ReadRecentSaves(); if (list.Count == 0) { return; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (SaveFolder folder in folders) { if (folder != null && !string.IsNullOrEmpty(folder.name) && !dictionary.ContainsKey(folder.name)) { dictionary[folder.name] = folder; } } List list2 = new List(folders); List list3 = new List(folders.Count); HashSet hashSet = new HashSet(); foreach (string item in list) { if (dictionary.TryGetValue(item, out var value) && hashSet.Add(value)) { list3.Add(value); } } foreach (SaveFolder item2 in list2) { if (hashSet.Add(item2)) { list3.Add(item2); } } bool flag = false; for (int i = 0; i < folders.Count; i++) { if (folders[i] != list3[i]) { flag = true; break; } } if (!flag) { return; } folders.Clear(); folders.AddRange(list3); List list4 = new List(folders.Count); foreach (SaveFolder folder2 in folders) { list4.Add(folder2?.name ?? "(null)"); } LogInfo("已按 MRU 重排 SaveFileGetAllAsync 返回的 list,最终顺序:" + string.Join(" → ", list4.ToArray())); } internal static bool ShouldEnableSaveSelectorMenu() { if (IsEnabled()) { return compatibilityReport.SaveSelectorMenuAvailable; } return false; } private void AttachSaveSelectorBehaviour() { try { if ((Object)(object)((Component)this).gameObject.GetComponent() == (Object)null) { ((Component)this).gameObject.AddComponent(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"F7 存档管理菜单已附加到 SaveKeeper 插件 GameObject。"); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("附加 F7 存档管理菜单失败: " + ex.Message)); } } internal static bool ShouldUseLevelStartSnapshot() { if (IsEnabled()) { return compatibilityReport.LevelStartSnapshotAvailable; } return false; } private static void CaptureLevelStartSnapshot(string reason) { try { string currentSaveFileName = GetCurrentSaveFileName(); if (string.IsNullOrWhiteSpace(currentSaveFileName)) { ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)("[LevelStart] 跳过:当前没有有效存档名(reason=" + reason + ")。")); } return; } string text = Path.Combine(Path.Combine(Application.persistentDataPath, "saves", currentSaveFileName), currentSaveFileName + ".es3"); string levelStartSnapshotPath = GetLevelStartSnapshotPath(currentSaveFileName); if (!File.Exists(text)) { ZichenSaveKeeperPlugin instance2 = Instance; if (instance2 != null) { ((BaseUnityPlugin)instance2).Logger.LogInfo((object)("[LevelStart] 主档尚未生成,跳过(reason=" + reason + ", path=" + text + ")。")); } return; } File.Copy(text, levelStartSnapshotPath, overwrite: true); ZichenSaveKeeperPlugin instance3 = Instance; if (instance3 != null) { ManualLogSource logger = ((BaseUnityPlugin)instance3).Logger; string[] obj = new string[7] { "[LevelStart] 已写入开局快照:", Path.GetFileName(levelStartSnapshotPath), "(reason=", reason, ", level=", null, null }; RunManager instance4 = RunManager.instance; object obj2; if (instance4 == null) { obj2 = null; } else { Level levelCurrent = instance4.levelCurrent; obj2 = ((levelCurrent != null) ? ((Object)levelCurrent).name : null); } if (obj2 == null) { obj2 = "null"; } obj[5] = (string)obj2; obj[6] = ")"; logger.LogInfo((object)string.Concat(obj)); } } catch (Exception ex) { LogWarning("[LevelStart] 写入开局快照失败:" + ex.Message); } } internal static string GetLevelStartSnapshotPath(string saveFileName) { return Path.Combine(Path.Combine(Application.persistentDataPath, "saves", saveFileName), saveFileName + "_LEVELSTART.es3"); } internal static bool TryRestoreFromLevelStartSnapshot(string saveFileName) { if (!ShouldUseLevelStartSnapshot()) { return false; } if (string.IsNullOrWhiteSpace(saveFileName)) { return false; } try { string levelStartSnapshotPath = GetLevelStartSnapshotPath(saveFileName); if (!File.Exists(levelStartSnapshotPath)) { ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)("[LevelStart] 没有开局快照,回退到默认恢复链:" + saveFileName)); } return false; } string destFileName = Path.Combine(Path.Combine(Application.persistentDataPath, "saves", saveFileName), saveFileName + ".es3"); File.Copy(levelStartSnapshotPath, destFileName, overwrite: true); ZichenSaveKeeperPlugin instance2 = Instance; if (instance2 != null) { ((BaseUnityPlugin)instance2).Logger.LogInfo((object)("[LevelStart] 已用开局快照覆盖主档:" + saveFileName)); } return true; } catch (Exception ex) { LogWarning("[LevelStart] 用开局快照恢复失败:" + ex.Message); return false; } } private ConfigEntry BindI18N(string sectEN, string sectCN, string keyEN, string keyCN, string descEN, string descCN, T defaultValue, AcceptableValueBase acceptableValues = null, int order = 0, bool readOnly = false, Action customDrawer = null) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes { Order = order, ReadOnly = (readOnly ? new bool?(true) : null), CustomDrawer = customDrawer }; NewI18N(sectEN, sectCN, keyEN, keyCN, descEN, descCN, configurationManagerAttributes); return ((BaseUnityPlugin)this).Config.Bind(sectEN, keyEN, defaultValue, new ConfigDescription(descEN, acceptableValues, new object[1] { configurationManagerAttributes })); } private ConfigEntry BindReadOnlyI18N(string sectEN, string sectCN, string keyEN, string keyCN, string valueEN, string valueCN, int order) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes { Order = order, ReadOnly = true, HideDefaultButton = true, CustomDrawer = DrawInfo }; CfgI18N cfgI18N = NewI18N(sectEN, sectCN, keyEN, keyCN, "", "", configurationManagerAttributes); cfgI18N.ValueEN = valueEN; cfgI18N.ValueCN = valueCN; cfgI18N.ReadOnlyEntry = ((BaseUnityPlugin)this).Config.Bind(sectEN, keyEN, UseChinese() ? valueCN : valueEN, new ConfigDescription("", (AcceptableValueBase)null, new object[1] { configurationManagerAttributes })); string text = (UseChinese() ? valueCN : valueEN); if (cfgI18N.ReadOnlyEntry.Value != text) { cfgI18N.ReadOnlyEntry.Value = text; } return cfgI18N.ReadOnlyEntry; } private static CfgI18N NewI18N(string sectEN, string sectCN, string keyEN, string keyCN, string descEN, string descCN, ConfigurationManagerAttributes attrs) { CfgI18N cfgI18N = new CfgI18N { SectionEN = sectEN, SectionCN = sectCN, KeyEN = keyEN, KeyCN = keyCN, DescEN = descEN, DescCN = descCN, Attrs = attrs }; ApplyI18N(cfgI18N); _cfgI18Ns.Add(cfgI18N); _cfgByEnSection[sectEN] = cfgI18N; _cfgByEnKey[keyEN] = cfgI18N; return cfgI18N; } private static void ApplyI18N(CfgI18N i18n) { bool flag = UseChinese(); i18n.Attrs.Category = (flag ? i18n.SectionCN : i18n.SectionEN); i18n.Attrs.DispName = (flag ? i18n.KeyCN : i18n.KeyEN); i18n.Attrs.Description = (flag ? i18n.DescCN : i18n.DescEN); if (i18n.ReadOnlyEntry != null) { string text = (flag ? i18n.ValueCN : i18n.ValueEN); if (i18n.ReadOnlyEntry.Value != text) { i18n.ReadOnlyEntry.Value = text; } } } private static void RefreshAllI18N() { for (int i = 0; i < _cfgI18Ns.Count; i++) { ApplyI18N(_cfgI18Ns[i]); } RefreshLiveLabels(); } internal static void TryHookREPOConfig(Harmony h) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("MenuLib.MenuAPI"); if (type == null) { LogInfo("MenuLib 未安装,跳过 REPOConfig 双语劫持。"); return; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(REPOConfigCreateHook), "Postfix", (Type[])null, (Type[])null)); HashSet hashSet = new HashSet(StringComparer.Ordinal) { "CreateREPOLabel", "CreateREPOToggle", "CreateREPOSlider", "CreateREPOInputField", "CreateREPOButton" }; int num = 0; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type)) { if (!hashSet.Contains(declaredMethod.Name)) { continue; } ParameterInfo[] parameters = declaredMethod.GetParameters(); if (parameters.Length != 0 && !(parameters[0].ParameterType != typeof(string))) { try { h.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch { } } } if (num > 0) { ZichenSaveKeeperPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)$"REPOConfig 双语显示已挂载({num} 个 UI 创建方法)。"); } } else { LogInfo("REPOConfig 没找到可劫持的 UI 方法。"); } } catch (Exception ex) { LogInfo("REPOConfig 劫持失败:" + ex.Message); } } internal static void RegisterLiveLabelIfMatched(string label, object uiElement) { if (string.IsNullOrEmpty(label) || uiElement == null) { return; } CfgI18N value; bool flag = _cfgByEnSection.TryGetValue(label, out value); if (!flag && !_cfgByEnKey.TryGetValue(label, out value)) { return; } TextMeshProUGUI labelTmp = GetLabelTmp(uiElement); if ((Object)(object)labelTmp == (Object)null) { return; } bool flag2 = UseChinese(); string text = ((!flag) ? (flag2 ? value.KeyCN : value.KeyEN) : (flag2 ? value.SectionCN : value.SectionEN)); if (!string.IsNullOrEmpty(text)) { try { ((TMP_Text)labelTmp).text = text; } catch { } } _liveLabels.Add(new LiveLabel { Tmp = new WeakReference(labelTmp), I18N = value, IsSection = flag }); if (!flag && value.ReadOnlyEntry != null) { Object val = (Object)((uiElement is Object) ? uiElement : null); if (val != null && AccessTools.Field(uiElement.GetType(), "inputStringSystem") != null) { _liveValues.Add(new LiveValue { InputField = new WeakReference(val), I18N = value }); TrySetInputFieldValue(val, UseChinese() ? value.ValueCN : value.ValueEN); } } if (_liveLabels.Count > 256) { PruneLiveLabels(); } } private static void PruneLiveLabels() { for (int num = _liveLabels.Count - 1; num >= 0; num--) { if (!_liveLabels[num].Tmp.TryGetTarget(out var target) || (Object)(object)target == (Object)null || (Object)(object)((Component)target).gameObject == (Object)null) { _liveLabels.RemoveAt(num); } } } private static TextMeshProUGUI GetLabelTmp(object uiElement) { try { Type type = uiElement.GetType(); if (!_labelTmpFieldCache.TryGetValue(type, out var value)) { value = AccessTools.Field(type, "labelTMP"); _labelTmpFieldCache[type] = value; } object? obj = value?.GetValue(uiElement); return (TextMeshProUGUI)((obj is TextMeshProUGUI) ? obj : null); } catch { return null; } } internal static void RefreshLiveLabels() { bool flag = UseChinese(); for (int num = _liveLabels.Count - 1; num >= 0; num--) { LiveLabel liveLabel = _liveLabels[num]; if (!liveLabel.Tmp.TryGetTarget(out var target) || (Object)(object)target == (Object)null || (Object)(object)((Component)target).gameObject == (Object)null) { _liveLabels.RemoveAt(num); } else { string text = ((!liveLabel.IsSection) ? (flag ? liveLabel.I18N.KeyCN : liveLabel.I18N.KeyEN) : (flag ? liveLabel.I18N.SectionCN : liveLabel.I18N.SectionEN)); try { ((TMP_Text)target).text = text; } catch { _liveLabels.RemoveAt(num); } } } for (int num2 = _liveValues.Count - 1; num2 >= 0; num2--) { LiveValue liveValue = _liveValues[num2]; if (!liveValue.InputField.TryGetTarget(out var target2) || target2 == (Object)null) { _liveValues.RemoveAt(num2); } else { string newVal = (flag ? liveValue.I18N.ValueCN : liveValue.I18N.ValueEN); if (!TrySetInputFieldValue(target2, newVal)) { _liveValues.RemoveAt(num2); } } } } private static bool TrySetInputFieldValue(Object ifObj, string newVal) { try { Type type = ((object)ifObj).GetType(); if (_inputStringSystemField == null || _inputStringSystemField.DeclaringType != type) { _inputStringSystemField = AccessTools.Field(type, "inputStringSystem"); } if (_inputStringSystemField == null) { return false; } object value = _inputStringSystemField.GetValue(ifObj); if (value == null) { return false; } if (_inputStringSystemSetValue == null || _inputStringSystemSetValue.DeclaringType != value.GetType()) { _inputStringSystemSetValue = AccessTools.Method(value.GetType(), "SetValue", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); } if (_inputStringSystemSetValue == null) { return false; } _inputStringSystemSetValue.Invoke(value, new object[2] { newVal ?? string.Empty, false }); return true; } catch { return false; } } } internal sealed class ConfigurationManagerAttributes { public bool? ShowRangeAsPercent; public Action CustomDrawer; public bool? Browsable; public string Category; public object DefaultValue; public bool? HideDefaultButton; public bool? HideSettingName; public string Description; public string DispName; public int? Order; public bool? ReadOnly; public bool? IsAdvanced; } internal sealed class BackupFileInfo { public string Path; public int Number; } internal sealed class HostCopySaveDirectoryInfo { public string SaveFileName; public string DirectoryPath; public DateTime LastWriteTimeUtc; } internal static class REPOConfigCreateHook { public static void Postfix(object[] __args, object __result) { try { if (__args != null && __args.Length != 0 && __result != null && __args[0] is string label) { ZichenSaveKeeperPlugin.RegisterLiveLabelIfMatched(label, __result); } } catch { } } }