Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ModlistGuard v0.1.0
plugins/ModlistGuard.dll
Decompiled 4 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ModlistGuard")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("ModlistGuard")] [assembly: AssemblyTitle("ModlistGuard")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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; } } } namespace ModlistGuard { [BepInPlugin("com.noodle.modlistguard", "ModlistGuard", "0.1.0")] internal class ModlistGuardPlugin : BaseUnityPlugin { public const string PluginGuid = "com.noodle.modlistguard"; public const string PluginName = "ModlistGuard"; public const string PluginVersion = "0.1.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> IgnoreVersionChanges; internal static ConfigEntry<string> IgnoredMods; private void Awake() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; IgnoreVersionChanges = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IgnoreVersionChanges", false, "Only warn when a mod was added or removed, not when one was updated."); IgnoredMods = ((BaseUnityPlugin)this).Config.Bind<string>("General", "IgnoredMods", "", "Comma-separated plugin GUIDs that never count as a mismatch — client-only mods like minimap or UI tweaks that cannot affect a save."); new Harmony("com.noodle.modlistguard").PatchAll(typeof(WorldStartPatch)); } } internal readonly struct ModEntry { public readonly string Guid; public readonly string Name; public readonly string Version; public ModEntry(string guid, string name, string version) { Guid = guid; Name = name; Version = version; } } internal static class ModlistStore { private static string Dir => Path.Combine(Utils.GetSaveDataPath((FileSource)2), "modlist_guard"); private static string PathFor(World world) { return Path.Combine(Dir, world.m_uid + ".txt"); } public static List<ModEntry> Current() { return (from p in Chainloader.PluginInfos.Values where p.Metadata.GUID != "com.noodle.modlistguard" select new ModEntry(p.Metadata.GUID, p.Metadata.Name, p.Metadata.Version.ToString())).OrderBy<ModEntry, string>((ModEntry m) => m.Guid, StringComparer.Ordinal).ToList(); } public static List<ModEntry> Load(World world) { string text = PathFor(world); if (!File.Exists(text)) { return null; } try { return (from line in File.ReadAllLines(text) select line.Split(new char[1] { '\t' }) into f where f.Length == 3 select new ModEntry(f[0], f[2], f[1])).ToList(); } catch (Exception ex) { ModlistGuardPlugin.Log.LogWarning((object)("Could not read " + text + ": " + ex.Message)); return null; } } public static void Save(World world, List<ModEntry> mods) { try { Directory.CreateDirectory(Dir); File.WriteAllLines(PathFor(world), mods.Select((ModEntry m) => m.Guid + "\t" + m.Version + "\t" + m.Name)); } catch (Exception ex) { ModlistGuardPlugin.Log.LogWarning((object)("Could not record mods for " + world.m_name + ": " + ex.Message)); } } } internal class ModlistDiff { public readonly List<ModEntry> Missing = new List<ModEntry>(); public readonly List<ModEntry> Added = new List<ModEntry>(); public readonly List<(ModEntry From, ModEntry To)> Updated = new List<(ModEntry, ModEntry)>(); private const int MaxNames = 10; public bool IsEmpty { get { if (Missing.Count == 0 && Added.Count == 0) { return Updated.Count == 0; } return false; } } public ModlistDiff(List<ModEntry> saved, List<ModEntry> current) { HashSet<string> ignored = new HashSet<string>(from s in ModlistGuardPlugin.IgnoredMods.Value.Split(new char[1] { ',' }) select s.Trim() into s where s.Length > 0 select s); Dictionary<string, ModEntry> before = saved.Where((ModEntry m) => !ignored.Contains(m.Guid)).ToDictionary((ModEntry m) => m.Guid); Dictionary<string, ModEntry> after = current.Where((ModEntry m) => !ignored.Contains(m.Guid)).ToDictionary((ModEntry m) => m.Guid); Missing.AddRange(before.Values.Where((ModEntry m) => !after.ContainsKey(m.Guid))); Added.AddRange(after.Values.Where((ModEntry m) => !before.ContainsKey(m.Guid))); if (ModlistGuardPlugin.IgnoreVersionChanges.Value) { return; } foreach (ModEntry value2 in after.Values) { if (before.TryGetValue(value2.Guid, out var value) && value.Version != value2.Version) { Updated.Add((value, value2)); } } } public string Describe() { List<string> list = new List<string>(); if (Missing.Count > 0) { list.Add("<color=#ff7070>Missing:</color> " + Join(Missing.Select((ModEntry m) => m.Name))); } if (Added.Count > 0) { list.Add("<color=#80e080>New:</color> " + Join(Added.Select((ModEntry m) => m.Name))); } if (Updated.Count > 0) { list.Add("<color=#f0d060>Updated:</color> " + Join(Updated.Select(((ModEntry From, ModEntry To) u) => u.To.Name + " " + u.From.Version + "→" + u.To.Version))); } return string.Join("\n", list); } private static string Join(IEnumerable<string> names) { List<string> list = names.ToList(); string text = string.Join(", ", list.Take(10)); if (list.Count <= 10) { return text; } return $"{text} and {list.Count - 10} more"; } } [HarmonyPatch(typeof(FejdStartup), "OnWorldStart")] internal static class WorldStartPatch { [CompilerGenerated] private static class <>O { public static PopupButtonCallback <0>__Pop; } private static long? s_acknowledged; private static bool Prefix(FejdStartup __instance, out bool __state) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown __state = __instance.m_startingWorld; World world = __instance.m_world; if (world == null || __instance.m_startingWorld || (int)world.m_dataError != 0 || s_acknowledged == world.m_uid) { return true; } List<ModEntry> list = ModlistStore.Current(); List<ModEntry> list2 = ModlistStore.Load(world); string text; if (list2 == null) { if (list.Count > 0) { return true; } text = "No mods are loaded, and ModlistGuard has no record of this world.\n\nIf it was played with mods, go back and launch through r2modman.\n\nLoad anyway?"; } else { ModlistDiff modlistDiff = new ModlistDiff(list2, list); if (modlistDiff.IsEmpty) { return true; } text = "This world was last played with different mods.\n\n" + modlistDiff.Describe(); if (modlistDiff.Missing.Count > 0) { text += "\n\nLoading without them permanently deletes what they added."; } text += "\n\nLoad anyway?"; } long uid = world.m_uid; string text2 = text; PopupButtonCallback val = delegate { UnifiedPopup.Pop(); s_acknowledged = uid; __instance.OnWorldStart(); }; object obj = <>O.<0>__Pop; if (obj == null) { PopupButtonCallback val2 = UnifiedPopup.Pop; <>O.<0>__Pop = val2; obj = (object)val2; } UnifiedPopup.Push((PopupBase)new YesNoPopup("Mods changed", text2, val, (PopupButtonCallback)obj, false, false)); return false; } private static void Postfix(FejdStartup __instance, bool __state) { if (!__state && __instance.m_startingWorld && __instance.m_world != null) { ModlistStore.Save(__instance.m_world, ModlistStore.Current()); s_acknowledged = null; } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }