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 ModGuard v0.1.0
plugins/ModGuard.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using ModGuard.Core; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("ModGuard")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+5a52f87754dc33d5234578c089a875026412dbb8")] [assembly: AssemblyProduct("ModGuard")] [assembly: AssemblyTitle("ModGuard")] [assembly: AssemblyVersion("0.1.0.0")] [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 ModGuard { internal static class ClientReporter { private static ZRpc _reportedTo; internal static void RegisterNotice(ZNetPeer serverPeer) { serverPeer.m_rpc.Register<string>("SansaraModGuard_Notice", (Action<ZRpc, string>)OnNotice); } internal static void ReportOnce() { if ((Object)(object)ZNet.instance == (Object)null) { return; } ZRpc serverRPC = ZNet.instance.GetServerRPC(); if (serverRPC != null && serverRPC != _reportedTo) { _reportedTo = serverRPC; List<PluginEntry> list = (from info in Chainloader.PluginInfos.Values select new PluginEntry(info.Metadata.GUID, info.Metadata.Name, info.Metadata.Version.ToString()) into p orderby p.Guid select p).ToList(); serverRPC.Invoke("SansaraModGuard_Report", new object[1] { ReportPackage.Write(list) }); Plugin.Log.LogInfo((object)$"reported {list.Count} plugins to the server"); } } private static void OnNotice(ZRpc rpc, string text) { Plugin.Log.LogWarning((object)text); if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, true, true); } } } internal static class ListFiles { private static readonly HashSet<string> MissingLogged = new HashSet<string>(); internal static string Directory => Path.Combine(Paths.ConfigPath, "sansara.ModGuard"); internal static HashSet<string> Load(string fileName) { string text = Path.Combine(Directory, fileName); if (!File.Exists(text)) { if (MissingLogged.Add(fileName)) { Plugin.Log.LogWarning((object)("list file not found, treated as empty: " + text)); } return GuidList.Parse(""); } MissingLogged.Remove(fileName); try { return GuidList.Parse(File.ReadAllText(text)); } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { Plugin.Log.LogWarning((object)("list file unreadable: " + text + ": " + ex.Message)); return null; } } } internal static class ModGuardConfig { internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<GuardAction> BlockedAction; internal static ConfigEntry<GuardAction> UnknownAction; internal static ConfigEntry<string> WarnMessage; internal static ConfigEntry<float> ReportCooldownSeconds; internal static void Bind(ConfigFile config) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown Enabled = config.Bind<bool>("Server", "Enabled", true, "Check plugin reports from clients that have Mod Guard. Clients without it are never affected."); BlockedAction = config.Bind<GuardAction>("Server", "BlockedAction", GuardAction.Log, "Reaction to plugins listed in sansara.ModGuard/blocked.txt: Log, Warn or Kick."); UnknownAction = config.Bind<GuardAction>("Server", "UnknownAction", GuardAction.Log, "Reaction to plugins missing from sansara.ModGuard/allowed.txt: Log, Warn or Kick."); WarnMessage = config.Bind<string>("Server", "WarnMessage", "На сервере Sansara эти моды не разрешены, удалите их:", "Header of the message shown to the player for Warn and Kick."); ReportCooldownSeconds = config.Bind<float>("Server", "ReportCooldownSeconds", 10f, new ConfigDescription("Reports from one connection more often than this are ignored.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3600f), Array.Empty<object>())); } } [BepInPlugin("sansara.ModGuard", "ModGuard", "0.1.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "sansara.ModGuard"; public const string Version = "0.1.0"; internal static ManualLogSource Log; internal static Plugin Instance; private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModGuardConfig.Bind(((BaseUnityPlugin)this).Config); new Harmony("sansara.ModGuard").PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)"Mod Guard 0.1.0 loaded"); } } internal static class ReportPackage { internal static ZPackage Write(IReadOnlyList<PluginEntry> plugins) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(plugins.Count); foreach (PluginEntry plugin in plugins) { val.Write(plugin.Guid); val.Write(plugin.Name); val.Write(plugin.Version); } return val; } internal static bool TryRead(ZPackage pkg, out List<PluginEntry> plugins) { plugins = new List<PluginEntry>(); try { int num = pkg.ReadInt(); if (!ReportLimits.IsValidCount(num)) { return false; } for (int i = 0; i < num; i++) { PluginEntry pluginEntry = new PluginEntry(pkg.ReadString(), pkg.ReadString(), pkg.ReadString()); if (!ReportLimits.IsValidEntry(pluginEntry)) { return false; } plugins.Add(pluginEntry); } return true; } catch (Exception) { return false; } } } internal static class ServerGuard { internal const string ReportRpc = "SansaraModGuard_Report"; internal const string NoticeRpc = "SansaraModGuard_Notice"; private static readonly MethodInfo InternalKick = AccessTools.Method(typeof(ZNet), "InternalKick", new Type[1] { typeof(ZNetPeer) }, (Type[])null); internal const float KickDelaySeconds = 5f; private static bool _kickMissingLogged; private static ReportThrottle<ZRpc> _throttle; internal static void Register(ZNetPeer peer) { peer.m_rpc.Register<ZPackage>("SansaraModGuard_Report", (Action<ZRpc, ZPackage>)OnReport); } private static void OnReport(ZRpc rpc, ZPackage pkg) { if (!ModGuardConfig.Enabled.Value || (Object)(object)ZNet.instance == (Object)null) { return; } List<ZNetPeer> peers = ZNet.instance.GetPeers(); ZNetPeer val = peers.Find((ZNetPeer p) => p.m_rpc == rpc); if (val == null || val.m_uid == 0L) { return; } if (_throttle == null) { _throttle = new ReportThrottle<ZRpc>(ModGuardConfig.ReportCooldownSeconds.Value); } _throttle.Prune((ZRpc r) => peers.Exists((ZNetPeer p) => p.m_rpc == r)); if (!_throttle.TryAccept(rpc, Time.realtimeSinceStartup)) { return; } string playerName = val.m_playerName; string hostName = val.m_socket.GetHostName(); if (!ReportPackage.TryRead(pkg, out var plugins)) { Plugin.Log.LogWarning((object)ReportFormatter.InvalidLine(playerName, hostName)); return; } GuardAction value = ModGuardConfig.BlockedAction.Value; GuardAction value2 = ModGuardConfig.UnknownAction.Value; HashSet<string> hashSet = ListFiles.Load("allowed.txt"); HashSet<string> hashSet2 = ListFiles.Load("blocked.txt"); if (hashSet == null || hashSet2 == null) { Plugin.Log.LogWarning((object)("lists unreadable, report skipped id=" + hostName)); return; } Classification result = Classifier.Classify(plugins, hashSet, hashSet2); GuardAction guardAction = ActionPolicy.Resolve(result, value, value2); Plugin.Log.LogInfo((object)ReportFormatter.LogLine(playerName, hostName, result, guardAction)); if (guardAction != GuardAction.Log) { string text = ReportFormatter.NoticeText(ModGuardConfig.WarnMessage.Value, ActionPolicy.Flagged(result, value, value2)); rpc.Invoke("SansaraModGuard_Notice", new object[1] { text }); if (guardAction == GuardAction.Kick) { Plugin.Log.LogInfo((object)$"kick scheduled in {5f:0}s id={hostName}"); ((MonoBehaviour)Plugin.Instance).StartCoroutine(KickLater(val, hostName)); } } } private static IEnumerator KickLater(ZNetPeer peer, string id) { yield return (object)new WaitForSeconds(5f); if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.GetPeers().Contains(peer)) { Plugin.Log.LogInfo((object)("kick skipped, peer left id=" + id)); yield break; } Plugin.Log.LogInfo((object)("kicking id=" + id)); if (InternalKick != null) { InternalKick.Invoke(ZNet.instance, new object[1] { peer }); yield break; } if (!_kickMissingLogged) { _kickMissingLogged = true; Plugin.Log.LogError((object)"ZNet.InternalKick not found (game update?), falling back to Disconnect"); } ZNet.instance.Disconnect(peer); } } } namespace ModGuard.Core { public static class ActionPolicy { public static GuardAction Resolve(Classification result, GuardAction blockedAction, GuardAction unknownAction) { blockedAction = Sanitise(blockedAction); unknownAction = Sanitise(unknownAction); GuardAction guardAction = GuardAction.Log; if (result.Blocked.Count > 0 && blockedAction > guardAction) { guardAction = blockedAction; } if (result.Unknown.Count > 0 && unknownAction > guardAction) { guardAction = unknownAction; } return guardAction; } public static List<PluginEntry> Flagged(Classification result, GuardAction blockedAction, GuardAction unknownAction) { blockedAction = Sanitise(blockedAction); unknownAction = Sanitise(unknownAction); List<PluginEntry> list = new List<PluginEntry>(); if (blockedAction > GuardAction.Log) { list.AddRange(result.Blocked); } if (unknownAction > GuardAction.Log) { list.AddRange(result.Unknown); } return list; } private static GuardAction Sanitise(GuardAction action) { if (!Enum.IsDefined(typeof(GuardAction), action)) { return GuardAction.Log; } return action; } } public sealed class Classification { public int Total { get; } public IReadOnlyList<PluginEntry> Blocked { get; } public IReadOnlyList<PluginEntry> Unknown { get; } public Classification(int total, IReadOnlyList<PluginEntry> blocked, IReadOnlyList<PluginEntry> unknown) { Total = total; Blocked = blocked; Unknown = unknown; } } public static class Classifier { public static Classification Classify(IReadOnlyList<PluginEntry> plugins, ISet<string> allowed, ISet<string> blocked) { List<PluginEntry> list = new List<PluginEntry>(); List<PluginEntry> list2 = new List<PluginEntry>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (PluginEntry plugin in plugins) { if (hashSet.Add(plugin.Guid)) { if (blocked.Contains(plugin.Guid)) { list.Add(plugin); } else if (!allowed.Contains(plugin.Guid)) { list2.Add(plugin); } } } return new Classification(hashSet.Count, list, list2); } } public enum GuardAction { Log, Warn, Kick } public static class GuidList { public static HashSet<string> Parse(string text) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(text)) { return hashSet; } string[] array = text.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i]; int num = text2.IndexOf('#'); if (num >= 0) { text2 = text2.Substring(0, num); } text2 = text2.Trim(); if (text2.Length > 0) { hashSet.Add(text2); } } return hashSet; } } public sealed class PluginEntry { public string Guid { get; } public string Name { get; } public string Version { get; } public PluginEntry(string guid, string name, string version) { Guid = guid ?? ""; Name = name ?? ""; Version = version ?? ""; } } public static class ReportFormatter { public const int MaxNoticeEntries = 10; public static string LogLine(string player, string id, Classification result, GuardAction action) { return $"report player=\"{Clean(player)}\" id={Clean(id)} plugins={result.Total} " + $"blocked={GuidsOrDash(result.Blocked)} unknown={GuidsOrDash(result.Unknown)} action={action}"; } public static string InvalidLine(string player, string id) { return "invalid report player=\"" + Clean(player) + "\" id=" + Clean(id); } public static string NoticeText(string header, IReadOnlyList<PluginEntry> flagged) { StringBuilder stringBuilder = new StringBuilder(header); int num = Math.Min(flagged.Count, 10); for (int i = 0; i < num; i++) { stringBuilder.Append('\n').Append(Clean(flagged[i].Name)).Append(" (") .Append(Clean(flagged[i].Guid)) .Append(')'); } if (flagged.Count > num) { stringBuilder.Append("\nи ещё ").Append(flagged.Count - num); } return stringBuilder.ToString(); } private static string GuidsOrDash(IReadOnlyList<PluginEntry> plugins) { if (plugins.Count == 0) { return "-"; } string[] array = new string[plugins.Count]; for (int i = 0; i < plugins.Count; i++) { array[i] = Token(plugins[i].Guid); } return string.Join(",", array); } private static string Clean(string value) { StringBuilder stringBuilder = new StringBuilder(value?.Length ?? 0); string text = value ?? ""; foreach (char c in text) { stringBuilder.Append(IsUnsafe(c) ? '?' : ((c == '"') ? '\'' : c)); } return stringBuilder.ToString(); } private static bool IsUnsafe(char c) { if (char.IsControl(c)) { return true; } UnicodeCategory unicodeCategory = char.GetUnicodeCategory(c); if (unicodeCategory != UnicodeCategory.LineSeparator && unicodeCategory != UnicodeCategory.ParagraphSeparator) { return unicodeCategory == UnicodeCategory.Format; } return true; } private static string Token(string value) { StringBuilder stringBuilder = new StringBuilder(value?.Length ?? 0); string text = value ?? ""; foreach (char c in text) { stringBuilder.Append((c < '\u0080' && (char.IsLetterOrDigit(c) || c == '.' || c == '_' || c == '-')) ? c : '_'); } return stringBuilder.ToString(); } } public static class ReportLimits { public const int MaxPlugins = 256; public const int MaxStringLength = 128; public static bool IsValidCount(int count) { if (count >= 0) { return count <= 256; } return false; } public static bool IsValidEntry(PluginEntry entry) { if (entry != null && entry.Guid.Length > 0 && entry.Guid.Length <= 128 && entry.Name.Length <= 128) { return entry.Version.Length <= 128; } return false; } } public sealed class ReportThrottle<TKey> { private readonly double _cooldown; private readonly Dictionary<TKey, double> _lastAccepted = new Dictionary<TKey, double>(); public ReportThrottle(double cooldownSeconds) { _cooldown = cooldownSeconds; } public bool TryAccept(TKey key, double now) { if (_lastAccepted.TryGetValue(key, out var value) && now - value < _cooldown) { return false; } _lastAccepted[key] = now; return true; } public void Prune(Func<TKey, bool> isAlive) { foreach (TKey item in _lastAccepted.Keys.Where((TKey k) => !isAlive(k)).ToList()) { _lastAccepted.Remove(item); } } } } namespace ModGuard.Patches { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class OnNewConnectionPatch { private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { ServerGuard.Register(peer); } else { ClientReporter.RegisterNotice(peer); } } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class PlayerOnSpawnedPatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { ClientReporter.ReportOnce(); } } } }