using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BetterCheckpoint;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using ReluProtocol;
using ReluProtocol.Enum;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(CheckpointMod), "BetterCheckpoint", "1.3.1", "lyj_0322", null)]
[assembly: MelonGame("ReLUGames", "MIMESIS")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace BetterCheckpoint;
internal static class BuildInfo
{
internal const string Name = "BetterCheckpoint";
internal const string Version = "1.3.1";
internal const string Author = "lyj_0322";
}
public class CheckpointMod : MelonMod
{
internal const string PrefCategory = "BetterCheckpoint";
internal static Instance Log;
internal static MelonPreferences_Entry<bool> Enabled;
internal static MelonPreferences_Entry<bool> FullRestore;
internal static MelonPreferences_Entry<int> StagePenalty;
internal static MelonPreferences_Entry<bool> KeepCycle;
internal static MelonPreferences_Entry<bool> VerboseLog;
private Harmony _harmony;
public override void OnInitializeMelon()
{
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Expected O, but got Unknown
Log = ((MelonBase)this).LoggerInstance;
BuildPrefs();
CheckpointRuntime.EnsureReady();
try
{
_harmony = new Harmony("mimesis.checkpoint.v1");
_harmony.PatchAll(typeof(CheckpointMod).Assembly);
Log.Msg("检查点 v1.3.1 已加载,补丁已应用");
}
catch (Exception ex)
{
Log.Error("补丁安装失败(模组不会生效):" + ex);
}
}
private static void BuildPrefs()
{
MelonPreferences_Category obj = MelonPreferences.CreateCategory("BetterCheckpoint", "检查点");
Enabled = obj.CreateEntry<bool>("Enabled", true, "总开关", "关闭后本模组完全不介入,团灭仍然回到第一关", false, false, (ValueValidator)null, (string)null);
FullRestore = obj.CreateEntry<bool>("FullRestore", true, "完整回档", "开(默认)= 每次发车那一刻自动存一份检查点(车已修好、东西已买完、道具在身上),团灭后整套还原;关 = 只把关卡号写回去,道具和钱按原版结算", false, false, (ValueValidator)null, (string)null);
StagePenalty = obj.CreateEntry<int>("StagePenalty", 0, "团灭后退回关卡数", "0 = 留在当前关卡,回到最近一次发车的那个检查点;1 = 退一关;2 以上退更多(退关时天数归零);99 = 原版行为(回第一关)", false, false, (ValueValidator)null, (string)null);
KeepCycle = obj.CreateEntry<bool>("KeepCycle", false, "保留循环数", "只在关闭「完整回档」时生效。关 = 下一趟从本关第 1 天的地牢重新开始;开 = 下一趟仍是团灭那天的难度档", false, false, (ValueValidator)null, (string)null);
VerboseLog = obj.CreateEntry<bool>("VerboseLog", false, "详细日志", "在控制台打印团灭前后的关卡数变化与回档明细,排查时用", false, false, (ValueValidator)null, (string)null);
}
public override void OnDeinitializeMelon()
{
try
{
if (_harmony != null)
{
_harmony.UnpatchSelf();
}
}
catch (Exception)
{
}
}
}
[HarmonyPatch(typeof(VRoomManager), "TerminateSession")]
internal static class Patch_TerminateSession
{
private static void Prefix(VRoomManager __instance, out SessionSnapshot __state)
{
__state = CheckpointRuntime.Capture(__instance);
}
private static void Postfix(VRoomManager __instance, SessionSnapshot __state)
{
CheckpointRuntime.Restore(__instance, __state);
}
}
[HarmonyPatch(typeof(MaintenanceRoom), "OnRequestStartSession")]
internal static class Patch_OnRequestStartSession
{
private static void Prefix(MaintenanceRoom __instance)
{
CheckpointRuntime.CaptureDeparture(__instance);
}
}
[HarmonyPatch(typeof(MaintenanceRoom), "OnCompleteSession")]
internal static class Patch_OnCompleteSession
{
private static void Postfix(MaintenanceRoom __instance, GameSessionInfo info, bool promoted)
{
CheckpointRuntime.RestoreFull(__instance, info, promoted);
}
}
internal struct SessionSnapshot
{
internal int Stage;
internal int Cycle;
internal bool Valid;
}
internal static class CheckpointRuntime
{
private const BindingFlags AllFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly FieldInfo FSessionInfo = FindField(typeof(VRoomManager), "_gameSessionInfo");
private static readonly PropertyInfo PStage = typeof(GameSessionInfo).GetProperty("StageCount", BindingFlags.Instance | BindingFlags.Public);
private static readonly PropertyInfo PCycle = typeof(GameSessionInfo).GetProperty("CycleCount", BindingFlags.Instance | BindingFlags.Public);
private static readonly MethodInfo SetStage = ((PStage != null) ? PStage.GetSetMethod(nonPublic: true) : null);
private static readonly MethodInfo SetCycle = ((PCycle != null) ? PCycle.GetSetMethod(nonPublic: true) : null);
private static readonly PropertyInfo PWorld = typeof(Hub).GetProperty("vworld", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
private static bool _warned;
private static bool _loggedOnce;
private static bool _loggedCapture;
private static int _lastCaptureTick;
private const string CheckpointSuffix = "C";
internal static void EnsureReady()
{
if (_warned)
{
return;
}
_warned = true;
if (typeof(VRoomManager).GetMethod("TerminateSession", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) == null)
{
Instance log = CheckpointMod.Log;
if (log != null)
{
log.Error("[检查点] 找不到 VRoomManager.TerminateSession,模组不会生效。");
}
}
if (typeof(MaintenanceRoom).GetMethod("OnRequestStartSession", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) == null)
{
Instance log2 = CheckpointMod.Log;
if (log2 != null)
{
log2.Error("[检查点] 找不到 MaintenanceRoom.OnRequestStartSession,发车前存档不会生效。");
}
}
if (typeof(MaintenanceRoom).GetMethod("SaveGameData", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) == null)
{
Instance log3 = CheckpointMod.Log;
if (log3 != null)
{
log3.Error("[检查点] 找不到 MaintenanceRoom.SaveGameData,发车前存档不会生效。");
}
}
if (typeof(MaintenanceRoom).GetMethod("OnCompleteSession", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) == null)
{
Instance log4 = CheckpointMod.Log;
if (log4 != null)
{
log4.Error("[检查点] 找不到 MaintenanceRoom.OnCompleteSession,完整回档不会生效。");
}
}
if (typeof(MaintenanceRoom).GetMethod("ApplyLoadedGameData", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) == null)
{
Instance log5 = CheckpointMod.Log;
if (log5 != null)
{
log5.Error("[检查点] 找不到 MaintenanceRoom.ApplyLoadedGameData,完整回档不会生效。");
}
}
if (FSessionInfo == null || SetStage == null)
{
Instance log6 = CheckpointMod.Log;
if (log6 != null)
{
log6.Error("[检查点] 反射失败,模组不会生效。缺失:" + ((FSessionInfo == null) ? "_gameSessionInfo " : "") + ((SetStage == null) ? "StageCount setter " : ""));
}
}
}
private static FieldInfo FindField(Type type, string name)
{
Type type2 = type;
while (type2 != null)
{
FieldInfo field = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field;
}
type2 = type2.BaseType;
}
return null;
}
private static GameSessionInfo GetInfo(VRoomManager manager)
{
if (manager == null || FSessionInfo == null)
{
return null;
}
try
{
object? value = FSessionInfo.GetValue(manager);
return (GameSessionInfo)((value is GameSessionInfo) ? value : null);
}
catch (Exception)
{
return null;
}
}
private static int Read(PropertyInfo property, object target, int fallback)
{
if (property == null || target == null)
{
return fallback;
}
try
{
object value = property.GetValue(target, null);
return (value is int) ? ((int)value) : fallback;
}
catch (Exception)
{
return fallback;
}
}
internal static SessionSnapshot Capture(VRoomManager manager)
{
SessionSnapshot result = default(SessionSnapshot);
GameSessionInfo info = GetInfo(manager);
if (info == null)
{
return result;
}
result.Stage = Read(PStage, info, 1);
result.Cycle = Read(PCycle, info, 0);
result.Valid = true;
return result;
}
internal static void Restore(VRoomManager manager, SessionSnapshot snapshot)
{
if (!snapshot.Valid || CheckpointMod.Enabled == null || !CheckpointMod.Enabled.Value || SetStage == null)
{
return;
}
int num = ((CheckpointMod.StagePenalty != null) ? Mathf.Clamp(CheckpointMod.StagePenalty.Value, 0, 99) : 0);
if (num >= 99)
{
return;
}
GameSessionInfo info = GetInfo(manager);
if (info == null)
{
return;
}
int stage = snapshot.Stage;
int cycle = snapshot.Cycle;
int num2 = Mathf.Max(1, stage - num);
int num3 = Read(PStage, info, 1);
bool flag = CheckpointMod.KeepCycle != null && CheckpointMod.KeepCycle.Value;
try
{
SetStage.Invoke(info, new object[1] { num2 });
if (SetCycle != null)
{
SetCycle.Invoke(info, new object[1] { flag ? cycle : 0 });
}
}
catch (Exception ex)
{
Instance log = CheckpointMod.Log;
if (log != null)
{
log.Warning("[检查点] 写回关卡失败:" + ex.Message);
}
return;
}
if ((CheckpointMod.FullRestore == null || !CheckpointMod.FullRestore.Value) && (!_loggedOnce || (CheckpointMod.VerboseLog != null && CheckpointMod.VerboseLog.Value)))
{
_loggedOnce = true;
Instance log2 = CheckpointMod.Log;
if (log2 != null)
{
log2.Msg("[检查点] 团灭:关卡 " + num3 + " → " + num2 + "(只回关卡号,基准 " + stage + ",退回 " + num + " 关,天 " + (flag ? cycle : 0) + ")");
}
}
}
internal unsafe static void CaptureDeparture(MaintenanceRoom room)
{
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
if (room == null || CheckpointMod.Enabled == null || !CheckpointMod.Enabled.Value || CheckpointMod.FullRestore == null || !CheckpointMod.FullRestore.Value)
{
return;
}
int tickCount = Environment.TickCount;
if (tickCount - _lastCaptureTick < 1000)
{
return;
}
_lastCaptureTick = tickCount;
try
{
VWorld world = GetWorld();
PlatformMgr instance = MonoSingleton<PlatformMgr>.Instance;
if (world == null || (Object)(object)instance == (Object)null)
{
return;
}
int saveSlotID = world.SaveSlotID;
if (!MMSaveGameData.CheckSaveSlotID(saveSlotID, false))
{
return;
}
string saveFileName = MMSaveGameData.GetSaveFileName(saveSlotID, true);
string text = CheckpointFileName(saveSlotID);
string saveFileFullPath = instance.GetSaveFileFullPath(saveFileName);
string saveFileFullPath2 = instance.GetSaveFileFullPath(text);
byte[] original = (File.Exists(saveFileFullPath) ? File.ReadAllBytes(saveFileFullPath) : null);
MsgErrorCode val = room.SaveGameData(saveSlotID, (List<string>)null, true);
if (!"Success".Equals(((object)(*(MsgErrorCode*)(&val))/*cast due to .constrained prefix*/).ToString()))
{
Warn("发车前存档失败:" + ((object)(*(MsgErrorCode*)(&val))/*cast due to .constrained prefix*/).ToString());
PutBack(saveFileFullPath, original);
return;
}
File.WriteAllBytes(saveFileFullPath2, File.ReadAllBytes(saveFileFullPath));
PutBack(saveFileFullPath, original);
MMSaveGameData val2 = instance.Load<MMSaveGameData>(text);
if (val2 == null)
{
Warn("发车前存档写入后读取失败,检查点不可用");
}
else if (!_loggedCapture || (CheckpointMod.VerboseLog != null && CheckpointMod.VerboseLog.Value))
{
_loggedCapture = true;
Instance log = CheckpointMod.Log;
if (log != null)
{
log.Msg("[检查点] 发车前已存档:关卡 " + val2.StageCount + ",钱 " + val2.Currency + ",道具 " + (val2.LootingObjectInfos.Count + val2.PlayerItemElements.Count) + " 件");
}
}
}
catch (Exception ex)
{
Warn("发车前存档失败:" + ex);
}
}
private static void PutBack(string path, byte[] original)
{
try
{
if (original != null)
{
File.WriteAllBytes(path, original);
}
else if (File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception ex)
{
Warn("还原原版自动存档失败:" + ex.Message);
}
}
internal static void RestoreFull(MaintenanceRoom room, GameSessionInfo info, bool promoted)
{
if (promoted || room == null || info == null || CheckpointMod.Enabled == null || !CheckpointMod.Enabled.Value || CheckpointMod.FullRestore == null || !CheckpointMod.FullRestore.Value)
{
return;
}
int num = ((CheckpointMod.StagePenalty != null) ? Mathf.Clamp(CheckpointMod.StagePenalty.Value, 0, 99) : 0);
if (num >= 99)
{
return;
}
string source;
MMSaveGameData val = LoadCheckpoint(out source);
if (val == null)
{
Warn("读不到检查点存档,本次只恢复关卡号(退回快照方式)");
return;
}
if (val.LootingObjectInfos == null || val.PlayerItemElements == null)
{
Warn("检查点存档缺少道具列表,本次只恢复关卡号");
return;
}
int stageCount = val.StageCount;
val.StageCount = Mathf.Max(1, stageCount - num);
if (num > 0)
{
val.CycleCount = 0;
}
val.TramRepaired = true;
int num2 = val.LootingObjectInfos.Count + val.PlayerItemElements.Count;
try
{
info.ApplyLoadedGameData(val);
room.ApplyLoadedGameData(val, info);
}
catch (Exception ex)
{
Warn("完整回档失败,本次只恢复关卡号:" + ex);
return;
}
Instance log = CheckpointMod.Log;
if (log != null)
{
log.Msg("[检查点] 完整回档(" + source + "):关卡 " + stageCount + " → " + val.StageCount + "(钱 " + val.Currency + ",道具 " + num2 + " 件,天 " + val.CycleCount + ")");
}
}
private static void Warn(string message)
{
if (CheckpointMod.VerboseLog != null && CheckpointMod.VerboseLog.Value)
{
Instance log = CheckpointMod.Log;
if (log != null)
{
log.Warning("[检查点] " + message);
}
}
}
private static MMSaveGameData LoadCheckpoint(out string source)
{
source = null;
try
{
VWorld world = GetWorld();
PlatformMgr instance = MonoSingleton<PlatformMgr>.Instance;
if (world == null || (Object)(object)instance == (Object)null)
{
return null;
}
int saveSlotID = world.SaveSlotID;
if (saveSlotID < 0)
{
return null;
}
string text = CheckpointFileName(saveSlotID);
if (instance.IsSaveFileExist(text))
{
MMSaveGameData val = instance.Load<MMSaveGameData>(text);
if (val != null)
{
source = "发车前存档";
return val;
}
}
string saveFileName = MMSaveGameData.GetSaveFileName(saveSlotID, true);
if (instance.IsSaveFileExist(saveFileName))
{
MMSaveGameData val2 = instance.Load<MMSaveGameData>(saveFileName);
if (val2 != null)
{
source = "维修室存档";
return val2;
}
}
return null;
}
catch (Exception ex)
{
Instance log = CheckpointMod.Log;
if (log != null)
{
log.Warning("[检查点] 读取检查点失败:" + ex.Message);
}
return null;
}
}
private static VWorld GetWorld()
{
Hub s = Hub.s;
if ((Object)(object)s == (Object)null || PWorld == null)
{
return null;
}
try
{
object? value = PWorld.GetValue(s, null);
return (VWorld)((value is VWorld) ? value : null);
}
catch (Exception)
{
return null;
}
}
private static string CheckpointFileName(int slot)
{
return "MMGameData" + slot.ToString("D2") + "C.sav";
}
}