Decompiled source of SaveGameModChecker v1.0.0

SaveGameModChecker.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Persistence;
using Il2CppScheduleOne.UI.MainMenu;
using Il2CppSystem;
using Il2CppSystem.Reflection;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using SaveGameModCheckerNs;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(SaveGameModCheckerClass), "SaveGameModChecker", "1.0.0", "xVilho", null)]
[assembly: MelonColor(255, 200, 150, 255)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace SaveGameModCheckerNs;

internal static class SaveGameModCheckerConfig
{
	private static MelonPreferences_Category Category;

	public static MelonPreferences_Entry<bool> EnableMod;

	public static MelonPreferences_Entry<bool> WarnOnMissingMods;

	public static MelonPreferences_Entry<bool> WarnOnNewMods;

	public static MelonPreferences_Entry<bool> EnableDebugLogging;

	public static void Setup()
	{
		Category = MelonPreferences.CreateCategory("SaveGameModChecker", "SaveGame Mod Settings");
		EnableMod = Category.CreateEntry<bool>("EnableMod", true, "Master Switch", "If false, the mod will be completely disabled.", false, false, (ValueValidator)null, (string)null);
		WarnOnMissingMods = Category.CreateEntry<bool>("WarnOnMissingMods", true, "Warn on Missing Mods", "Show mismatch popup if mods from the save are missing.", false, false, (ValueValidator)null, (string)null);
		WarnOnNewMods = Category.CreateEntry<bool>("WarnOnNewMods", true, "Warn on New Mods", "Show mismatch popup if currently installed mods were not in the save.", false, false, (ValueValidator)null, (string)null);
		EnableDebugLogging = Category.CreateEntry<bool>("EnableDebugLogging", false, "Enable Debug Logging", "Whether to output debug logs to the MelonLoader console.", false, false, (ValueValidator)null, (string)null);
		Category.SaveToFile(true);
		if (!EnableMod.Value)
		{
			SaveGameModCheckerClass.Log("[Config] Master switch is OFF. Mod functionality is suspended.");
		}
		else
		{
			SaveGameModCheckerClass.Log("[Config] SaveGameModCheckerConfig initialized.");
		}
	}
}
[HarmonyPatch(typeof(LoadManager), "StartGame")]
public static class LoadManager_StartGame_Patch
{
	public static SaveInfo StoredSaveInfoToLoad;

	public static bool BypassNextLoad;

	public static bool Prefix(LoadManager __instance, SaveInfo info)
	{
		//IL_03d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_03da: Expected O, but got Unknown
		try
		{
			if (!SaveGameModCheckerConfig.EnableMod.Value)
			{
				return true;
			}
			if (info == null)
			{
				return true;
			}
			if (BypassNextLoad)
			{
				BypassNextLoad = false;
				return true;
			}
			int saveSlotNumber = info.SaveSlotNumber;
			string text = $"SAVEGAME_{saveSlotNumber}";
			string normalizedSaveFolderName = SaveGameModCheckerClass.GetNormalizedSaveFolderName(info.SavePath);
			if (!string.IsNullOrEmpty(normalizedSaveFolderName))
			{
				text = normalizedSaveFolderName;
			}
			string text2 = Path.Combine(Path.Combine(MelonEnvironment.UserDataDirectory, "SaveGameModChecker", text), "Mods.txt");
			List<string> list = MelonTypeBase<MelonMod>.RegisteredMelons.Select((MelonMod m) => ((MelonBase)m).Info.Name).ToList();
			bool flag = false;
			bool flag2 = false;
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			List<string> list4 = new List<string>();
			if (File.Exists(text2))
			{
				string[] array = (from s in File.ReadAllLines(text2)
					select s.Trim() into s
					where !string.IsNullOrWhiteSpace(s)
					select s).ToArray();
				HashSet<string> hashSet = new HashSet<string>(array);
				string[] array2 = array;
				foreach (string item in array2)
				{
					if (!list.Contains(item))
					{
						list2.Add(item);
						flag = true;
					}
					else
					{
						list4.Add(item);
					}
				}
				foreach (string item2 in list)
				{
					if (!hashSet.Contains(item2))
					{
						list3.Add(item2);
						flag2 = true;
					}
				}
			}
			if (!flag && !flag2 && File.Exists(text2))
			{
				SaveGameModCheckerClass.Log("Mods match perfectly for " + text + ". Read from " + text2);
			}
			bool flag3 = false;
			if (flag && SaveGameModCheckerConfig.WarnOnMissingMods.Value)
			{
				flag3 = true;
			}
			if (flag2 && SaveGameModCheckerConfig.WarnOnNewMods.Value)
			{
				flag3 = true;
			}
			if (flag3)
			{
				list2.Sort();
				list3.Sort();
				list4.Sort();
				string text3 = "[WARNING] " + text + " mod mismatch.";
				if (flag)
				{
					text3 = text3 + "\nMissing:\n- " + string.Join("\n- ", list2);
				}
				if (flag2)
				{
					text3 = text3 + "\nNew (Not in save):\n- " + string.Join("\n- ", list3);
				}
				SaveGameModCheckerClass.LogWarning(text3);
				string text4 = "";
				if (flag)
				{
					text4 += "<size=120%><b><color=red>MISSING FROM SAVE:</color></b></size>\n";
					foreach (string item3 in list2)
					{
						text4 = text4 + "- <color=red>" + item3 + "</color>\n";
					}
				}
				if (flag2)
				{
					if (text4 != "")
					{
						text4 += "\n";
					}
					text4 += "<size=120%><b><color=#FFA500>NEW (NOT IN SAVE):</color></b></size>\n";
					foreach (string item4 in list3)
					{
						text4 = text4 + "- <color=#FFA500>" + item4 + "</color>\n";
					}
				}
				if (list4.Count > 0)
				{
					if (text4 != "")
					{
						text4 += "\n";
					}
					text4 += "<size=120%><b><color=green>MATCHED MODS:</color></b></size>\n";
					foreach (string item5 in list4)
					{
						text4 = text4 + "- <color=green>" + item5 + "</color>\n";
					}
				}
				Data val = new Data("Mods Mismatch", text4.Trim(), true);
				StoredSaveInfoToLoad = info;
				MainMenuPopup val2 = Object.FindObjectOfType<MainMenuPopup>(true);
				if ((Object)(object)val2 != (Object)null)
				{
					val2.Open(val);
				}
				else
				{
					__instance.ExitToMenu((SaveInfo)null, val, false);
				}
				return false;
			}
		}
		catch (Exception value)
		{
			SaveGameModCheckerClass.LogError($"Error in LoadManager.StartGame prefix: {value}");
		}
		return true;
	}
}
[HarmonyPatch(typeof(SaveManager), "Save", new Type[] { })]
public static class SaveManager_Save_NoArg_Patch
{
	[HarmonyPostfix]
	public static void Postfix()
	{
		try
		{
			SaveGameModCheckerClass.SaveCurrentMods();
		}
		catch (Exception value)
		{
			SaveGameModCheckerClass.LogError($"Error in SaveManager.Save() postfix: {value}");
		}
	}
}
[HarmonyPatch(typeof(SaveManager), "Save", new Type[] { typeof(string) })]
public static class SaveManager_Save_String_Patch
{
	[HarmonyPostfix]
	public static void Postfix(string saveFolderPath)
	{
		try
		{
			SaveGameModCheckerClass.SaveCurrentMods(saveFolderPath);
		}
		catch (Exception value)
		{
			SaveGameModCheckerClass.LogError($"Error in SaveManager.Save(string) postfix: {value}");
		}
	}
}
[HarmonyPatch(typeof(MainMenuPopup), "Open", new Type[] { typeof(Data) })]
public static class MainMenuPopup_Open_Patch
{
	private static Button btnSafety;

	private static Button btnQuit;

	private static Button btnContinue;

	private static GameObject scrollContainer;

	public static void Postfix(MainMenuPopup __instance, Data data)
	{
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Expected O, but got Unknown
		//IL_010a: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_012f: 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_0137: Unknown result type (might be due to invalid IL or missing references)
		//IL_0143: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Expected O, but got Unknown
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0178: Unknown result type (might be due to invalid IL or missing references)
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_0215: Unknown result type (might be due to invalid IL or missing references)
		//IL_022b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0241: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0308: Unknown result type (might be due to invalid IL or missing references)
		//IL_031d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0332: Unknown result type (might be due to invalid IL or missing references)
		//IL_033c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0419: Unknown result type (might be due to invalid IL or missing references)
		//IL_042f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0442: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (data == null || data.Title != "Mods Mismatch")
			{
				return;
			}
			Image componentInChildren = ((Component)__instance).GetComponentInChildren<Image>();
			if ((Object)(object)componentInChildren != (Object)null)
			{
				((Graphic)componentInChildren).color = new Color(0.01f, 0.01f, 0.01f, 1f);
			}
			RectTransform component = ((Component)__instance).GetComponent<RectTransform>();
			float num = Mathf.Min((float)Screen.width * 0.7f, 950f);
			float num2 = Mathf.Min((float)Screen.height * 0.8f, 750f);
			component.sizeDelta = new Vector2(num, num2);
			Component val = FindTMPro(((Component)__instance).gameObject, "Description");
			if ((Object)(object)val != (Object)null && (Object)(object)scrollContainer == (Object)null)
			{
				GameObject gameObject = val.gameObject;
				scrollContainer = new GameObject("ModScrollContainer");
				scrollContainer.transform.SetParent(gameObject.transform.parent, false);
				ScrollRect val2 = scrollContainer.AddComponent<ScrollRect>();
				RectTransform component2 = scrollContainer.GetComponent<RectTransform>();
				component2.anchorMin = new Vector2(0.05f, 0.25f);
				component2.anchorMax = new Vector2(0.95f, 0.88f);
				Vector2 offsetMin = (component2.offsetMax = Vector2.zero);
				component2.offsetMin = offsetMin;
				GameObject val3 = new GameObject("Viewport");
				val3.transform.SetParent(scrollContainer.transform, false);
				RectTransform val4 = val3.AddComponent<RectTransform>();
				val4.anchorMin = Vector2.zero;
				val4.anchorMax = Vector2.one;
				val4.sizeDelta = Vector2.zero;
				((Graphic)val3.AddComponent<Image>()).color = new Color(0.05f, 0.05f, 0.05f, 0.9f);
				val3.AddComponent<Mask>().showMaskGraphic = true;
				gameObject.transform.SetParent(val3.transform, false);
				RectTransform component3 = gameObject.GetComponent<RectTransform>();
				component3.anchorMin = new Vector2(0f, 1f);
				component3.anchorMax = new Vector2(1f, 1f);
				component3.pivot = new Vector2(0.5f, 1f);
				component3.offsetMin = new Vector2(20f, 0f);
				component3.offsetMax = new Vector2(-20f, 0f);
				(gameObject.GetComponent<ContentSizeFitter>() ?? gameObject.AddComponent<ContentSizeFitter>()).verticalFit = (FitMode)2;
				val2.content = component3;
				val2.viewport = val4;
				val2.horizontal = false;
				val2.vertical = true;
				val2.scrollSensitivity = 35f;
				Il2CppReferenceArray<Object> val5 = Resources.FindObjectsOfTypeAll(Il2CppType.Of<Scrollbar>());
				if (val5 != null && ((Il2CppArrayBase<Object>)(object)val5).Length > 0)
				{
					Scrollbar component4 = Object.Instantiate<GameObject>(((Component)((Il2CppObjectBase)((Il2CppArrayBase<Object>)(object)val5)[0]).Cast<Scrollbar>()).gameObject, scrollContainer.transform).GetComponent<Scrollbar>();
					component4.direction = (Direction)2;
					RectTransform component5 = ((Component)component4).GetComponent<RectTransform>();
					component5.anchorMin = new Vector2(1f, 0f);
					component5.anchorMax = new Vector2(1f, 1f);
					component5.pivot = new Vector2(1f, 0.5f);
					component5.sizeDelta = new Vector2(25f, 0f);
					component5.anchoredPosition = Vector2.zero;
					val2.verticalScrollbar = component4;
				}
			}
			Button val6 = ((IEnumerable<Button>)((Component)__instance).GetComponentsInChildren<Button>(true)).FirstOrDefault((Func<Button, bool>)((Button b) => (Object)(object)((Component)b).GetComponent<ModCheckerButtonHandler>() == (Object)null && ((Component)b).gameObject.activeSelf));
			if ((Object)(object)val6 != (Object)null)
			{
				((Component)val6).gameObject.SetActive(false);
				if ((Object)(object)btnSafety == (Object)null)
				{
					btnSafety = CreateButton(val6, "Back to Safety", "Safety");
				}
				if ((Object)(object)btnQuit == (Object)null)
				{
					btnQuit = CreateButton(val6, "Quit Game", "Quit");
				}
				if ((Object)(object)btnContinue == (Object)null)
				{
					btnContinue = CreateButton(val6, "Continue Anyway", "Continue");
				}
				float num3 = 70f;
				float num4 = num * 0.3f;
				SetupButton(btnSafety, new Vector2(0f - num4, num3));
				SetupButton(btnQuit, new Vector2(0f, num3));
				SetupButton(btnContinue, new Vector2(num4, num3));
			}
		}
		catch (Exception value)
		{
			SaveGameModCheckerClass.LogError($"Error in MainMenuPopup.Open postfix: {value}");
		}
	}

	private static Button CreateButton(Button template, string label, string action)
	{
		Button component = Object.Instantiate<GameObject>(((Component)template).gameObject, ((Component)template).transform.parent).GetComponent<Button>();
		((Object)((Component)component).gameObject).name = action + "Btn";
		((Component)component).gameObject.SetActive(true);
		SetTMProText(FindTMPro(((Component)component).gameObject), label);
		((Component)component).gameObject.AddComponent<ModCheckerButtonHandler>().ActionType = action;
		return component;
	}

	private static void SetupButton(Button btn, Vector2 anchoredPos)
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: 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)
		RectTransform component = ((Component)btn).GetComponent<RectTransform>();
		Vector2 val = default(Vector2);
		((Vector2)(ref val))..ctor(0.5f, 0f);
		component.anchorMax = val;
		component.anchorMin = val;
		component.anchoredPosition = anchoredPos;
		component.sizeDelta = new Vector2(240f, 60f);
	}

	private static Component FindTMPro(GameObject go, string nameContains = "")
	{
		return ((IEnumerable<Component>)go.GetComponentsInChildren<Component>(true)).FirstOrDefault((Func<Component, bool>)((Component c) => ((MemberInfo)((Object)c).GetIl2CppType()).Name.Contains("TextMeshProUGUI") && (string.IsNullOrEmpty(nameContains) || ((Object)c.gameObject).name.Contains(nameContains))));
	}

	private static void SetTMProText(Component comp, string text)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Expected O, but got Unknown
		if (!((Object)(object)comp == (Object)null))
		{
			PropertyInfo property = ((Object)comp).GetIl2CppType().GetProperty("text");
			if (property != null)
			{
				property.SetValue((Object)(object)comp, new Object(IL2CPP.ManagedStringToIl2Cpp(text)));
			}
		}
	}
}
public class ModCheckerButtonHandler : MonoBehaviour
{
	public string ActionType;

	public ModCheckerButtonHandler(IntPtr ptr)
		: base(ptr)
	{
	}

	private void OnEnable()
	{
		((UnityEvent)((Component)this).GetComponent<Button>().onClick).AddListener(UnityAction.op_Implicit((Action)OnClick));
	}

	private void OnClick()
	{
		try
		{
			switch (ActionType)
			{
			case "Continue":
				if (LoadManager_StartGame_Patch.StoredSaveInfoToLoad != null)
				{
					LoadManager_StartGame_Patch.BypassNextLoad = true;
					Singleton<MainMenuPopup>.Instance.Screen.Close(false);
					Singleton<LoadManager>.Instance.StartGame(LoadManager_StartGame_Patch.StoredSaveInfoToLoad, false, true);
				}
				else
				{
					SaveGameModCheckerClass.LogError("StoredSaveInfoToLoad is null during 'Continue Anyway'. Cannot proceed.");
					Singleton<MainMenuPopup>.Instance.Screen.Close(false);
				}
				break;
			case "Quit":
				Application.Quit();
				break;
			case "Safety":
				Singleton<MainMenuPopup>.Instance.Screen.Close(false);
				break;
			}
		}
		catch (Exception value)
		{
			SaveGameModCheckerClass.LogError($"Error in ModCheckerButtonHandler.OnClick: {value}");
		}
	}
}
public static class BuildInfo
{
	public const string Name = "SaveGameModChecker";

	public const string Author = "xVilho";

	public const string Version = "1.0.0";
}
public class SaveGameModCheckerClass : MelonMod
{
	internal static Instance Logger;

	public static void Log(object msg)
	{
		if (SaveGameModCheckerConfig.EnableDebugLogging == null || SaveGameModCheckerConfig.EnableDebugLogging.Value)
		{
			Logger.Msg(msg?.ToString() ?? "null");
		}
	}

	public static void LogWarning(object msg)
	{
		if (SaveGameModCheckerConfig.EnableDebugLogging == null || SaveGameModCheckerConfig.EnableDebugLogging.Value)
		{
			Logger.Warning(msg?.ToString() ?? "null");
		}
	}

	public static void LogError(object msg)
	{
		Logger.Error(msg?.ToString() ?? "null");
	}

	public override void OnInitializeMelon()
	{
		try
		{
			Logger = ((MelonBase)this).LoggerInstance;
			ClassInjector.RegisterTypeInIl2Cpp<ModCheckerButtonHandler>();
			SaveGameModCheckerConfig.Setup();
			Log("[Init] Successfully initialized.");
		}
		catch (Exception value)
		{
			LogError($"Failed to initialize mod: {value}");
		}
	}

	public static string GetNormalizedSaveFolderName(string path)
	{
		if (string.IsNullOrEmpty(path))
		{
			return null;
		}
		try
		{
			if (!path.Contains("/") && !path.Contains("\\") && (path.StartsWith("SAVEGAME_", StringComparison.OrdinalIgnoreCase) || path.StartsWith("SaveGame_", StringComparison.OrdinalIgnoreCase)))
			{
				return path.ToUpperInvariant();
			}
			for (DirectoryInfo directoryInfo = new DirectoryInfo(path); directoryInfo != null; directoryInfo = directoryInfo.Parent)
			{
				if (directoryInfo.Name.StartsWith("SAVEGAME_", StringComparison.OrdinalIgnoreCase) || directoryInfo.Name.StartsWith("SaveGame_", StringComparison.OrdinalIgnoreCase))
				{
					return directoryInfo.Name.ToUpperInvariant();
				}
			}
		}
		catch
		{
		}
		return null;
	}

	public static void SaveCurrentMods(string overridePath = null)
	{
		try
		{
			if (!SaveGameModCheckerConfig.EnableMod.Value)
			{
				return;
			}
			SaveManager instance = Singleton<SaveManager>.Instance;
			string text = overridePath;
			if (string.IsNullOrEmpty(text) && (Object)(object)instance != (Object)null)
			{
				text = instance.PlayersSavePath;
			}
			if (string.IsNullOrEmpty(text) && (Object)(object)instance != (Object)null && !string.IsNullOrEmpty(instance.SaveName))
			{
				text = instance.SaveName;
			}
			if (string.IsNullOrEmpty(text))
			{
				LogWarning("Could not determine save path or name. Skipping mod list save.");
				return;
			}
			string normalizedSaveFolderName = GetNormalizedSaveFolderName(text);
			if (string.IsNullOrEmpty(normalizedSaveFolderName) && (Object)(object)instance != (Object)null && !string.IsNullOrEmpty(instance.SaveName))
			{
				normalizedSaveFolderName = GetNormalizedSaveFolderName(instance.SaveName);
			}
			if (string.IsNullOrEmpty(normalizedSaveFolderName))
			{
				LogWarning("[Save] Could not identify slot from source path or SaveName. Skipping mod list save.");
				return;
			}
			Log("[Save] Game triggered save for slot: " + normalizedSaveFolderName);
			string text2 = Path.Combine(MelonEnvironment.UserDataDirectory, "SaveGameModChecker", normalizedSaveFolderName);
			string path = Path.Combine(text2, "Mods.txt");
			Log("[Save] Target storage: Steam/steamapps/common/Schedule I/UserData/SaveGameModChecker/" + normalizedSaveFolderName + "/Mods.txt");
			if (!Directory.Exists(text2))
			{
				Directory.CreateDirectory(text2);
			}
			List<string> list = MelonTypeBase<MelonMod>.RegisteredMelons.Select((MelonMod m) => ((MelonBase)m).Info.Name).ToList();
			File.WriteAllLines(path, list);
			Log($"[Save] Successfully saved {list.Count} mods.");
		}
		catch (Exception value)
		{
			LogError($"Failed to save current mods: {value}");
		}
	}
}