Decompiled source of BetterCheckpoint v1.3.1

BetterCheckpoint.dll

Decompiled 9 hours ago
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";
	}
}