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 ModIntegrity v1.0.1
IronLabs.ModIntegrity.dll
Decompiled 3 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.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using BepInEx; using BepInEx.Logging; using HarmonyLib; using IronLabs.SharedLib; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("IronLabs.ModIntegrity")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("IronLabs.ModIntegrity")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("9B7B2B9F-38C2-4E87-A7BD-E29310479B42")] [assembly: AssemblyFileVersion("1.0.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.1.40292")] namespace IronLabs.ModIntegrity { internal sealed class ConnectionState { internal string Nonce { get; } internal bool IsValidated { get; set; } internal ConnectionState(string nonce) { Nonce = nonce; } } internal static class ConnectionValidator { private static readonly Dictionary<ZRpc, ConnectionState> ServerStates = new Dictionary<ZRpc, ConnectionState>(); private static readonly Dictionary<ZRpc, string> ClientNonces = new Dictionary<ZRpc, string>(); internal static void Register(ZRpc rpc) { rpc.Register<ZPackage>("IronLabs.ModIntegrity.Challenge", (Action<ZRpc, ZPackage>)ReceiveChallenge); rpc.Register<ZPackage>("IronLabs.ModIntegrity.Response", (Action<ZRpc, ZPackage>)ReceiveResponse); } internal static void SendChallenge(ZRpc rpc) { string nonce = CreateNonce(); ServerStates[rpc] = new ConnectionState(nonce); rpc.Invoke("IronLabs.ModIntegrity.Challenge", new object[1] { IntegrityProtocol.CreateChallenge(nonce) }); ModIntegrityPlugin.Log.LogDebug("Sent DLL integrity challenge to " + Describe(rpc) + "."); } internal static void SendResponse(ZRpc rpc) { if (!ClientNonces.TryGetValue(rpc, out var value)) { ModIntegrityPlugin.Log.LogError("Server did not send a DLL integrity challenge."); return; } IReadOnlyList<DllRecord> readOnlyList = DllInventory.ReadClientInventory(); rpc.Invoke("IronLabs.ModIntegrity.Response", new object[1] { IntegrityProtocol.CreateResponse(value, readOnlyList) }); ModIntegrityPlugin.Log.LogDebug($"Sent {readOnlyList.Count} DLL hashes to the server."); } internal static bool IsValidated(ZRpc rpc) { ConnectionState value; return ServerStates.TryGetValue(rpc, out value) && value.IsValidated; } internal static void Remove(ZRpc rpc) { ServerStates.Remove(rpc); ClientNonces.Remove(rpc); } internal static void Clear() { ServerStates.Clear(); ClientNonces.Clear(); } private static void ReceiveChallenge(ZRpc rpc, ZPackage package) { try { ClientNonces[rpc] = IntegrityProtocol.ReadChallenge(package); ModIntegrityPlugin.Log.LogDebug("Received DLL integrity challenge from " + Describe(rpc) + "."); } catch (Exception ex) { ModIntegrityPlugin.Log.LogError("Invalid DLL integrity challenge: " + ex.Message); } } private static void ReceiveResponse(ZRpc rpc, ZPackage package) { try { ValidateResponse(rpc, package); } catch (Exception ex) { Reject(rpc, "Invalid integrity response: " + ex.Message); } } private static void ValidateResponse(ZRpc rpc, ZPackage package) { string nonce; IReadOnlyList<DllRecord> readOnlyList = IntegrityProtocol.ReadResponse(package, out nonce); if (!ServerStates.TryGetValue(rpc, out var value) || value.Nonce != nonce) { Reject(rpc, "The challenge nonce is missing, expired, or incorrect."); return; } ServerDllPolicy policy = DllInventory.ReadServerPolicy(); InventoryDifference inventoryDifference = InventoryComparer.Compare(policy, readOnlyList); if (!inventoryDifference.IsMatch) { LogDifference(rpc, inventoryDifference); RejectConnection(rpc); } else { value.IsValidated = true; ModIntegrityPlugin.Log.LogInfo($"DLL integrity validated for {Describe(rpc)}: {readOnlyList.Count} exact matches."); } } private static void LogDifference(ZRpc rpc, InventoryDifference difference) { ModIntegrityPlugin.Log.LogError("DLL integrity validation failed for " + Describe(rpc) + "."); LogItems("Missing", difference.Missing); LogItems("Unexpected", difference.Unexpected); LogItems("SHA-256 mismatch", difference.Different); } private static void LogItems(string category, IEnumerable<string> items) { foreach (string item in items) { ModIntegrityPlugin.Log.LogError("- " + category + ": " + item); } } private static void Reject(ZRpc rpc, string reason) { ModIntegrityPlugin.Log.LogError("DLL integrity validation failed for " + Describe(rpc) + ": " + reason); RejectConnection(rpc); } private static void RejectConnection(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static string CreateNonce() { byte[] array = new byte[32]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } return Convert.ToBase64String(array); } private static string Describe(ZRpc rpc) { object obj; if (rpc == null) { obj = null; } else { ISocket socket = rpc.GetSocket(); obj = ((socket != null) ? socket.GetEndPointString() : null); } if (obj == null) { obj = "unknown peer"; } return (string)obj; } } internal static class DllInventory { internal const string WhitelistDirectoryName = "ModIntegrity_Whitelist"; internal const string GreylistDirectoryName = "ModIntegrity_Greylist"; private static ServerDllPolicy _serverPolicy; internal static IReadOnlyList<DllRecord> ReadClientInventory() { return ReadDirectory(Paths.PluginPath); } internal static ServerDllPolicy ReadServerPolicy() { if (_serverPolicy != null) { return _serverPolicy; } if (!ZNet.instance.IsDedicated()) { IReadOnlyList<DllRecord> required = ReadDirectory(Paths.PluginPath); _serverPolicy = new ServerDllPolicy(required, new List<DllRecord>()); return _serverPolicy; } _serverPolicy = ReadDedicatedPolicy(); ModIntegrityPlugin.Log.LogInfo($"Loaded {_serverPolicy.Required.Count} required and " + $"{_serverPolicy.Optional.Count} optional DLL hashes."); return _serverPolicy; } internal static void ClearServerReference() { _serverPolicy = null; } private static ServerDllPolicy ReadDedicatedPolicy() { string text = Path.Combine(Paths.ConfigPath, "ModIntegrity_Whitelist"); if (!Directory.Exists(text)) { throw new DirectoryNotFoundException("Required DLL directory not found: " + text); } string text2 = Path.Combine(Paths.ConfigPath, "ModIntegrity_Greylist"); IReadOnlyList<DllRecord> readOnlyList2; if (!Directory.Exists(text2)) { IReadOnlyList<DllRecord> readOnlyList = new List<DllRecord>(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = ReadDirectory(text2); } IReadOnlyList<DllRecord> optional = readOnlyList2; return new ServerDllPolicy(ReadDirectory(text), optional); } private static IReadOnlyList<DllRecord> ReadDirectory(string root) { return (from path in Directory.GetFiles(root, "*.dll", SearchOption.AllDirectories) select CreateRecord(root, path)).OrderBy<DllRecord, string>((DllRecord record) => record.RelativePath, StringComparer.OrdinalIgnoreCase).ToList(); } private static DllRecord CreateRecord(string root, string path) { string text = path.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); text = text.Replace(Path.DirectorySeparatorChar, '/'); using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(path); return new DllRecord(text, ToHex(sHA.ComputeHash(inputStream))); } private static string ToHex(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", string.Empty); } } internal sealed class DllRecord { internal string RelativePath { get; } internal string Sha256 { get; } internal DllRecord(string relativePath, string sha256) { RelativePath = relativePath; Sha256 = sha256; } } internal static class IntegrityProtocol { internal const int Version = 1; internal const int MaximumDllCount = 1024; internal const string ChallengeRpc = "IronLabs.ModIntegrity.Challenge"; internal const string ResponseRpc = "IronLabs.ModIntegrity.Response"; internal static ZPackage CreateChallenge(string nonce) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(nonce); return val; } internal static ZPackage CreateResponse(string nonce, IReadOnlyList<DllRecord> inventory) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(nonce); val.Write(inventory.Count); foreach (DllRecord item in inventory) { val.Write(item.RelativePath); val.Write(item.Sha256); } return val; } internal static string ReadChallenge(ZPackage package) { AssertProtocolVersion(package.ReadInt()); return package.ReadString(); } internal static IReadOnlyList<DllRecord> ReadResponse(ZPackage package, out string nonce) { AssertProtocolVersion(package.ReadInt()); nonce = package.ReadString(); int num = package.ReadInt(); if (num < 0 || num > 1024) { throw new InvalidOperationException($"Invalid DLL count: {num}."); } List<DllRecord> list = new List<DllRecord>(num); for (int i = 0; i < num; i++) { list.Add(new DllRecord(package.ReadString(), package.ReadString())); } return list; } private static void AssertProtocolVersion(int version) { if (version != 1) { throw new InvalidOperationException($"Unsupported integrity protocol {version}; expected {1}."); } } } internal static class InventoryComparer { internal static InventoryDifference Compare(ServerDllPolicy policy, IReadOnlyList<DllRecord> actual) { Dictionary<string, DllRecord> dictionary = ToDictionary(policy.Required); Dictionary<string, DllRecord> optional = ToDictionary(policy.Optional); Dictionary<string, DllRecord> actual2 = ToDictionary(actual); EnsureDisjoint(dictionary, optional); InventoryDifference inventoryDifference = new InventoryDifference(); CompareRequired(dictionary, actual2, inventoryDifference); CompareOptional(optional, actual2, inventoryDifference); FindUnexpected(dictionary, optional, actual2, inventoryDifference); return inventoryDifference; } private static Dictionary<string, DllRecord> ToDictionary(IReadOnlyList<DllRecord> records) { return records.ToDictionary<DllRecord, string>((DllRecord record) => record.RelativePath, StringComparer.OrdinalIgnoreCase); } private static void CompareRequired(Dictionary<string, DllRecord> expected, Dictionary<string, DllRecord> actual, InventoryDifference difference) { foreach (KeyValuePair<string, DllRecord> item in expected.OrderBy((KeyValuePair<string, DllRecord> pair) => pair.Key)) { if (!actual.TryGetValue(item.Key, out var value)) { difference.Missing.Add(item.Key); } else if (!string.Equals(item.Value.Sha256, value.Sha256, StringComparison.OrdinalIgnoreCase)) { difference.Different.Add(item.Key); } } } private static void CompareOptional(Dictionary<string, DllRecord> optional, Dictionary<string, DllRecord> actual, InventoryDifference difference) { foreach (KeyValuePair<string, DllRecord> item in optional) { if (actual.TryGetValue(item.Key, out var value) && !string.Equals(item.Value.Sha256, value.Sha256, StringComparison.OrdinalIgnoreCase)) { difference.Different.Add(item.Key); } } } private static void FindUnexpected(Dictionary<string, DllRecord> required, Dictionary<string, DllRecord> optional, Dictionary<string, DllRecord> actual, InventoryDifference difference) { foreach (string item in from path in actual.Keys where !required.ContainsKey(path) && !optional.ContainsKey(path) orderby path select path) { difference.Unexpected.Add(item); } } private static void EnsureDisjoint(Dictionary<string, DllRecord> required, Dictionary<string, DllRecord> optional) { string text = required.Keys.FirstOrDefault(optional.ContainsKey); if (text != null) { throw new InvalidOperationException("DLL is present in both whitelist and greylist: " + text); } } } internal sealed class InventoryDifference { internal List<string> Missing { get; } = new List<string>(); internal List<string> Unexpected { get; } = new List<string>(); internal List<string> Different { get; } = new List<string>(); internal bool IsMatch => Missing.Count == 0 && Unexpected.Count == 0 && Different.Count == 0; } [BepInPlugin("IronLabs.ModIntegrity", "IronLabs.ModIntegrity", "1.0.1")] public sealed class ModIntegrityPlugin : IronLabsPlugin { internal const string PluginGuid = "IronLabs.ModIntegrity"; internal const string PluginName = "IronLabs.ModIntegrity"; internal const string PluginVersion = "1.0.1"; internal static ModLog Log { get; private set; } private void Awake() { Log = InitializePlugin("IronLabs.ModIntegrity"); Log.LogInfo("IronLabs.ModIntegrity 1.0.1 is enforcing exact DLL integrity."); } private void OnDestroy() { ConnectionValidator.Clear(); DllInventory.ClearServerReference(); ShutdownPlugin(); Log = null; } } [HarmonyPatch] internal static class NetworkPatches { [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static void RegisterIntegrityRpcs(ZNetPeer peer) { ConnectionValidator.Register(peer.m_rpc); } [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyPatch(typeof(ZNet), "RPC_ServerHandshake")] private static void SendServerChallenge(ZRpc rpc) { ConnectionValidator.SendChallenge(rpc); } [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")] private static void SendClientInventory(ZRpc rpc) { ConnectionValidator.SendResponse(rpc); } [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static bool RejectUnvalidatedPeer(ZNet __instance, ZRpc rpc) { if (!__instance.IsServer() || ConnectionValidator.IsValidated(rpc)) { return true; } ModIntegrityPlugin.Log.LogError("Rejected unvalidated peer " + rpc.GetSocket().GetEndPointString() + " before PeerInfo."); rpc.Invoke("Error", new object[1] { 3 }); return false; } [HarmonyPrefix] [HarmonyPatch(typeof(ZNet), "Disconnect")] private static void RemoveConnectionState(ZNetPeer peer) { if (peer != null) { ConnectionValidator.Remove(peer.m_rpc); } } } internal sealed class ServerDllPolicy { internal IReadOnlyList<DllRecord> Required { get; } internal IReadOnlyList<DllRecord> Optional { get; } internal ServerDllPolicy(IReadOnlyList<DllRecord> required, IReadOnlyList<DllRecord> optional) { Required = required; Optional = optional; } } } namespace IronLabs.SharedLib { public abstract class IronLabsPlugin : BaseUnityPlugin { private Harmony _harmony; private bool _patchesApplied; protected ModLog InitializePlugin(string pluginGuid) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown ModLog modLog = new ModLog(((BaseUnityPlugin)this).Logger); Version version = ((object)this).GetType().Assembly.GetName().Version; modLog.LogInfo($"AssemblyVersion: {version}."); _harmony = new Harmony(pluginGuid); PatchOwnNamespace(modLog); return modLog; } protected void PatchOwnNamespace(ModLog log) { if (_patchesApplied) { log.LogDebug("Harmony patches are already active; skipping registration."); return; } string text = ((object)this).GetType().Namespace; Type[] types = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type type in types) { if (type.Namespace == text) { _harmony.CreateClassProcessor(type).Patch(); } } _patchesApplied = true; log.LogDebug("Harmony patches were applied for the plugin namespace."); } protected void ShutdownPlugin() { if (_patchesApplied) { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _patchesApplied = false; } } } public sealed class ModLog { private readonly ManualLogSource _logger; public ModLog(ManualLogSource logger) { _logger = logger; } public void LogFatal(object message) { Write((LogLevel)1, message); } public void LogError(object message) { Write((LogLevel)2, message); } public void LogWarning(object message) { Write((LogLevel)4, message); } public void LogMessage(object message) { Write((LogLevel)8, message); } public void LogInfo(object message) { Write((LogLevel)16, message); } public void LogDebug(object message) { Write((LogLevel)32, message); } public void Log(LogLevel level, object message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Write(level, message); } private void Write(LogLevel level, object message) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) string arg = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); _logger.Log(level, (object)$"[{arg}] {message}"); } } }