using 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.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using Nessie.ATLYSS.EasySettings; using Nessie.ATLYSS.EasySettings.UIElements; using UnityEngine; using UnityEngine.Events; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("com.AutoModeration")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.2.1.0")] [assembly: AssemblyInformationalVersion("1.2.1+c3630505b96bcc9cc98b15bf2f83026e7285f33d")] [assembly: AssemblyProduct("Auto Moderation")] [assembly: AssemblyTitle("AutoModeration")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 AutoModeration { public class WarningRecord { public DateTime Timestamp { get; set; } public string PlayerName { get; set; } public string SteamID { get; set; } public string TriggeringMessage { get; set; } public int WarnCount { get; set; } public int MaxWarnings { get; set; } public override string ToString() { return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}] Player: {PlayerName} (ID: {SteamID}) | Warning {WarnCount}/{MaxWarnings} | Trigger: \"{TriggeringMessage}\""; } } public class BlockRule { public string Pattern { get; set; } public MatchType Type { get; set; } public bool IsMatch(string message) { return Type switch { MatchType.Contains => message.IndexOf(Pattern, StringComparison.OrdinalIgnoreCase) >= 0, MatchType.StartsWith => message.StartsWith(Pattern, StringComparison.OrdinalIgnoreCase), MatchType.EndsWith => message.EndsWith(Pattern, StringComparison.OrdinalIgnoreCase), MatchType.Exact => Regex.IsMatch(message, "\\b" + Regex.Escape(Pattern) + "\\b", RegexOptions.IgnoreCase), _ => false, }; } } public enum MatchType { Contains, StartsWith, EndsWith, Exact } [BepInPlugin("s0apysfederati0n", "AutoModeration", "1.2.1")] public class Main : BaseUnityPlugin { internal static ManualLogSource Log; internal static string WarningLogPath; internal static List HostBlockRules = new List(); internal static HashSet HostBlockedHashes = new HashSet(); internal static List HostRegexPatterns = new List(); internal static List ClientCensorRules = new List(); internal static HashSet ClientCensorHashes = new HashSet(); internal static List ParsedAllowedPhrases = new List(); internal static Dictionary PlayerWarningLevels = new Dictionary(); internal static List MonitoredChannels = new List(); internal static HashSet TrustedSteamIDsList = new HashSet(); internal static ConfigEntry AutoModEnabled; internal static ConfigEntry DisableInSinglePlayer; internal static ConfigEntry MonitoredChatChannels; internal static ConfigEntry DebugMode; internal static ConfigEntry TrustedUsers; internal static ConfigEntry AllowedPhrases; internal static ConfigEntry HostBlockedWords; internal static ConfigEntry HostRegexPatternsConfig; internal static ConfigEntry EnableHostActions; internal static ConfigEntry HostAction; internal static ConfigEntry PunishmentAnnouncement; internal static ConfigEntry WarningSystemEnabled; internal static ConfigEntry WarningsUntilAction; internal static ConfigEntry ResetWarningsOnDisconnect; internal static ConfigEntry ClientCensoredWords; internal static ConfigEntry ClientCensorEnabled; internal static ConfigEntry CensorReplacementChar; private void Awake() { //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Expected O, but got Unknown //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Expected O, but got Unknown //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Expected O, but got Unknown //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); WarningLogPath = Path.Combine(directoryName, "AutoMod_WarningLog.txt"); AutoModEnabled = ((BaseUnityPlugin)this).Config.Bind("1. General", "Enabled", true, "Master Toggle for the mod."); DisableInSinglePlayer = ((BaseUnityPlugin)this).Config.Bind("1. General", "Disable in Single-Player", true, "Disable functionality in singleplayer."); MonitoredChatChannels = ((BaseUnityPlugin)this).Config.Bind("1. General", "Monitored Channels", "GLOBAL", "Comma-separated list of chat channels to monitor."); DebugMode = ((BaseUnityPlugin)this).Config.Bind("1. General", "Debug Mode", false, "Log extra info to console."); AllowedPhrases = ((BaseUnityPlugin)this).Config.Bind("2. Exceptions", "Allowed Phrases", "", "Phrases that are always allowed."); TrustedUsers = ((BaseUnityPlugin)this).Config.Bind("2. Exceptions", "Trusted Steam IDs", "", "Steam IDs that bypass ALL filters."); HostBlockedWords = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Blocked Words", "*arandomexample*, blockthisword*, *someothertest", "Words that trigger Kick/Ban (Host only)."); HostRegexPatternsConfig = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Regex Patterns", "", "Regex patterns that trigger Host Actions."); EnableHostActions = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Enable Punishments", true, "If enabled, host will Kick/Ban."); HostAction = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Punishment Type", "Kick", new ConfigDescription("Action to take.", (AcceptableValueBase)(object)new AcceptableValueList(new string[2] { "Kick", "Ban" }), Array.Empty())); PunishmentAnnouncement = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Announcement Mode", "Local", new ConfigDescription("Who sees the kick/ban message?", (AcceptableValueBase)(object)new AcceptableValueList(new string[3] { "Global", "Local", "None" }), Array.Empty())); WarningSystemEnabled = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Warning System", true, "Enable progressive warnings."); WarningsUntilAction = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Warnings Limit", 3, "Infractions before punishment."); ResetWarningsOnDisconnect = ((BaseUnityPlugin)this).Config.Bind("3. Host Filters", "Reset on Disconnect", true, "Clear warnings when player leaves."); ClientCensorEnabled = ((BaseUnityPlugin)this).Config.Bind("4. Client Filters", "Enable Censor", true, "Enable local text censoring."); ClientCensoredWords = ((BaseUnityPlugin)this).Config.Bind("4. Client Filters", "Censored Words", "*censorexample*", "Words that will be censored locally."); CensorReplacementChar = ((BaseUnityPlugin)this).Config.Bind("4. Client Filters", "Censor Character", "*", "The character used to hide words."); RefreshAllLists(); Settings.OnInitialized.AddListener(new UnityAction(AddSettings)); Settings.OnApplySettings.AddListener(new UnityAction(RefreshAllLists)); Harmony.CreateAndPatchAll(typeof(HarmonyPatches), (string)null); Log.LogInfo((object)"[AutoModeration v1.2.1] loaded."); } private void AddSettings() { SettingsTab orAddCustomTab = Settings.GetOrAddCustomTab("AutoModeration"); orAddCustomTab.AddHeader("General"); orAddCustomTab.AddToggle(AutoModEnabled); orAddCustomTab.AddToggle(DisableInSinglePlayer); orAddCustomTab.AddToggle(DebugMode); orAddCustomTab.AddTextField(MonitoredChatChannels, "Text..."); orAddCustomTab.AddHeader("Exceptions"); orAddCustomTab.AddTextField(TrustedUsers, "Steam ID 1, Steam ID 2..."); orAddCustomTab.AddTextField(AllowedPhrases, "phrase1, phrase2..."); orAddCustomTab.AddHeader("Host Configuration (Server-Side)"); orAddCustomTab.AddToggle(EnableHostActions); orAddCustomTab.AddTextField(HostBlockedWords, "word1, *wildcard*, word2..."); string[] actionOptions = new string[2] { "Kick", "Ban" }; int num = Array.IndexOf(actionOptions, HostAction.Value); if (num == -1) { num = 0; } AtlyssDropdown val2 = orAddCustomTab.AddDropdown("Punishment Type", actionOptions, num); val2.OnValueChanged.AddListener((UnityAction)delegate(int val) { HostAction.Value = actionOptions[val]; }); string[] announceOptions = new string[3] { "Global", "Local", "None" }; int num2 = Array.IndexOf(announceOptions, PunishmentAnnouncement.Value); if (num2 == -1) { num2 = 1; } AtlyssDropdown val3 = orAddCustomTab.AddDropdown("Announcement Mode", announceOptions, num2); val3.OnValueChanged.AddListener((UnityAction)delegate(int val) { PunishmentAnnouncement.Value = announceOptions[val]; }); orAddCustomTab.AddToggle(WarningSystemEnabled); orAddCustomTab.AddAdvancedSlider(WarningsUntilAction); orAddCustomTab.AddToggle(ResetWarningsOnDisconnect); orAddCustomTab.AddTextField(HostRegexPatternsConfig, "Regex patterns..."); orAddCustomTab.AddHeader("Client Configuration (Local)"); orAddCustomTab.AddToggle(ClientCensorEnabled); orAddCustomTab.AddTextField(ClientCensoredWords, "word1, word2..."); orAddCustomTab.AddTextField(CensorReplacementChar, "Text..."); } private void RefreshAllLists() { UpdateMonitoredChannelsList(); UpdateAllowedPhrasesList(); UpdateTrustedList(); UpdateRuleLists(HostBlockedWords.Value, HostBlockRules, HostBlockedHashes); UpdateRuleLists(ClientCensoredWords.Value, ClientCensorRules, ClientCensorHashes); UpdateRegexList(); if (DebugMode.Value) { Log.LogInfo((object)"AutoMod configuration reloaded."); } } internal static string ComputeSha256Hash(string rawData) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(rawData)); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < array.Length; i++) { stringBuilder.Append(array[i].ToString("x2")); } return stringBuilder.ToString(); } private void UpdateMonitoredChannelsList() { if (string.IsNullOrWhiteSpace(MonitoredChatChannels.Value)) { MonitoredChannels.Clear(); return; } MonitoredChannels = (from c in MonitoredChatChannels.Value.Split(',') select c.Trim().ToUpperInvariant() into c where !string.IsNullOrEmpty(c) select c).ToList(); } private void UpdateTrustedList() { TrustedSteamIDsList.Clear(); if (string.IsNullOrWhiteSpace(TrustedUsers.Value)) { return; } string[] array = TrustedUsers.Value.Split(','); foreach (string text in array) { string text2 = text.Trim(); if (!string.IsNullOrEmpty(text2)) { TrustedSteamIDsList.Add(text2); } } } private void UpdateAllowedPhrasesList() { if (string.IsNullOrWhiteSpace(AllowedPhrases.Value)) { ParsedAllowedPhrases.Clear(); return; } ParsedAllowedPhrases = (from p in AllowedPhrases.Value.Split(',') select p.Trim() into p where !string.IsNullOrEmpty(p) select p).ToList(); } private void UpdateRuleLists(string configValue, List rulesList, HashSet hashesList) { rulesList.Clear(); hashesList.Clear(); if (string.IsNullOrWhiteSpace(configValue)) { return; } string[] array = configValue.Split(','); string[] array2 = array; foreach (string text in array2) { string text2 = text.Trim(); if (string.IsNullOrEmpty(text2)) { continue; } if (text2.Contains("*")) { bool flag = text2.StartsWith("*"); bool flag2 = text2.EndsWith("*"); string pattern = text2.Trim('*'); if (flag && flag2) { rulesList.Add(new BlockRule { Pattern = pattern, Type = MatchType.Contains }); } else if (flag) { rulesList.Add(new BlockRule { Pattern = pattern, Type = MatchType.EndsWith }); } else if (flag2) { rulesList.Add(new BlockRule { Pattern = pattern, Type = MatchType.StartsWith }); } } else { string item = ComputeSha256Hash(text2.ToLowerInvariant()); hashesList.Add(item); } } } private void UpdateRegexList() { HostRegexPatterns.Clear(); if (string.IsNullOrWhiteSpace(HostRegexPatternsConfig.Value)) { return; } string[] array = HostRegexPatternsConfig.Value.Split(','); foreach (string text in array) { try { string text2 = text.Trim(); if (!string.IsNullOrEmpty(text2)) { HostRegexPatterns.Add(new Regex(text2, RegexOptions.IgnoreCase | RegexOptions.Compiled)); } } catch (Exception ex) { Log.LogError((object)("[AUTOMOD] Invalid Regex '" + text + "': " + ex.Message)); } } } } [HarmonyPatch] internal static class HarmonyPatches { [HarmonyPrefix] [HarmonyPatch(typeof(ChatBehaviour), "UserCode_Rpc_RecieveChatMessage__String__Boolean__ChatChannel")] internal static bool InterceptChatMessage_Prefix(ChatBehaviour __instance, ref string message, ChatChannel _chatChannel) { //IL_019e: Unknown result type (might be due to invalid IL or missing references) if (Main.DisableInSinglePlayer.Value && AtlyssNetworkManager._current._soloMode) { return true; } if (!Main.AutoModEnabled.Value || !Main.MonitoredChannels.Contains(((object)(ChatChannel)(ref _chatChannel)).ToString().ToUpperInvariant())) { return true; } try { string text = Regex.Replace(message, "|", string.Empty); object? obj = typeof(ChatBehaviour).GetField("_player", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(__instance); Player val = (Player)((obj is Player) ? obj : null); if (val != null) { if (val._steamID != null && Main.TrustedSteamIDsList.Contains(val._steamID)) { return true; } if (val._isHostPlayer) { return true; } string text2 = val._nickname ?? "Unknown Player"; string text3 = text; foreach (string parsedAllowedPhrase in Main.ParsedAllowedPhrases) { text3 = Regex.Replace(text3, Regex.Escape(parsedAllowedPhrase), string.Empty, RegexOptions.IgnoreCase); } if ((Object)(object)Player._mainPlayer != (Object)null && Player._mainPlayer._isHostPlayer && CheckList(text3, Main.HostBlockRules, Main.HostBlockedHashes, Main.HostRegexPatterns, out string reason)) { string text4 = $"[AUTOMOD HOST] Infraction by [{text2}] in [{_chatChannel}]. Reason: {reason}. Msg: \"{text}\""; Main.Log.LogWarning((object)text4); ProcessHostInfraction(val, text2, text); return false; } if (Main.ClientCensorEnabled.Value && CheckList(text3, Main.ClientCensorRules, Main.ClientCensorHashes, null, out string reason2)) { if (Main.DebugMode.Value) { Main.Log.LogInfo((object)("[AUTOMOD LOCAL] Censoring message from " + text2 + ". Reason: " + reason2)); } message = CensorMessage(message, text3); return true; } } } catch (Exception arg) { Main.Log.LogError((object)$"[AUTOMOD] Error: {arg}"); } return true; } private static bool CheckList(string message, List rules, HashSet hashes, List regexes, out string reason) { reason = string.Empty; if (string.IsNullOrWhiteSpace(message)) { return false; } foreach (BlockRule rule in rules) { if (rule.IsMatch(message)) { reason = "rule '" + rule.Pattern + "'"; return true; } } if (hashes.Count > 0) { char[] separator = new char[11] { ' ', '.', ',', '!', '?', ';', ':', '-', '_', '\n', '\r' }; string[] array = message.Split(separator, StringSplitOptions.RemoveEmptyEntries); string[] array2 = array; foreach (string text in array2) { if (!string.IsNullOrWhiteSpace(text)) { string item = Main.ComputeSha256Hash(text.ToLowerInvariant()); if (hashes.Contains(item)) { reason = "hashed word"; return true; } } } } if (regexes != null) { foreach (Regex regex in regexes) { if (regex.IsMatch(message)) { reason = $"regex '{regex}'"; return true; } } } return false; } private static string CensorMessage(string originalMessage, string messageToCheck) { string text = Main.CensorReplacementChar.Value.Substring(0, 1); if (string.IsNullOrEmpty(text)) { text = "*"; } string text2 = originalMessage; foreach (BlockRule clientCensorRule in Main.ClientCensorRules) { if (clientCensorRule.IsMatch(text2)) { text2 = Regex.Replace(text2, Regex.Escape(clientCensorRule.Pattern), new string(text[0], clientCensorRule.Pattern.Length), RegexOptions.IgnoreCase); } } char[] array = new char[11] { ' ', '.', ',', '!', '?', ';', ':', '-', '_', '\n', '\r' }; string[] array2 = Regex.Split(text2, "(\\s+|[.,!?;:\\-_])"); StringBuilder stringBuilder = new StringBuilder(); string[] array3 = array2; foreach (string text3 in array3) { if (string.IsNullOrWhiteSpace(text3) || text3.Length < 2) { stringBuilder.Append(text3); continue; } string item = Main.ComputeSha256Hash(text3.ToLowerInvariant()); if (Main.ClientCensorHashes.Contains(item)) { stringBuilder.Append(new string(text[0], text3.Length)); } else { stringBuilder.Append(text3); } } return stringBuilder.ToString(); } [HarmonyPostfix] [HarmonyPatch(typeof(HostConsole), "Destroy_PeerListEntry")] internal static void OnPlayerDisconnect_Postfix(HostConsole __instance, int _connID) { if (Main.ResetWarningsOnDisconnect.Value) { HC_PeerListEntry val = ((IEnumerable)__instance._peerListEntries).FirstOrDefault((Func)((HC_PeerListEntry e) => ((ListDataEntry)e)._dataID == _connID)); if ((Object)(object)val?._peerPlayer != (Object)null && !string.IsNullOrEmpty(val._peerPlayer._steamID) && Main.PlayerWarningLevels.ContainsKey(val._peerPlayer._steamID)) { Main.PlayerWarningLevels.Remove(val._peerPlayer._steamID); } } } private static void ProcessHostInfraction(Player targetPlayer, string targetPlayerName, string triggeringMessage) { Player mainPlayer = Player._mainPlayer; if (mainPlayer == null || !mainPlayer._isHostPlayer) { return; } string steamID = targetPlayer._steamID; if (!string.IsNullOrEmpty(steamID)) { if (!Main.PlayerWarningLevels.ContainsKey(steamID)) { Main.PlayerWarningLevels[steamID] = 0; } Main.PlayerWarningLevels[steamID]++; int num = Main.PlayerWarningLevels[steamID]; int value = Main.WarningsUntilAction.Value; WarningRecord warningRecord = new WarningRecord { Timestamp = DateTime.Now, PlayerName = targetPlayerName, SteamID = steamID, TriggeringMessage = triggeringMessage, WarnCount = num, MaxWarnings = value }; SaveWarningToFile(warningRecord.ToString()); if (num >= value && Main.EnableHostActions.Value) { TakeHostAction(targetPlayer, targetPlayerName, triggeringMessage); } } } private static void TakeHostAction(Player targetPlayer, string targetPlayerName, string triggeringMessage) { if ((Object)(object)HostConsole._current == (Object)null || ((NetworkBehaviour)targetPlayer).connectionToClient == null) { return; } string text = Main.HostAction.Value.ToLower(); string text2 = "Player " + targetPlayerName + " (ID: " + targetPlayer._steamID + ") was automatically " + text.ToUpper() + "ed. Reason: " + triggeringMessage; Main.Log.LogWarning((object)text2); try { File.AppendAllText(Main.WarningLogPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [PUNISHMENT] " + text2 + Environment.NewLine); } catch (Exception ex) { Main.Log.LogError((object)("Failed to write punishment to log: " + ex.Message)); } if (Main.PunishmentAnnouncement.Value == "Global" && (Object)(object)HostConsole._current != (Object)null) { HostConsole._current.Init_ServerMessage("[AUTOMOD]: " + text2); } if (text == "ban") { HC_PeerListEntry val = null; if ((Object)(object)HostConsole._current != (Object)null) { foreach (HC_PeerListEntry peerListEntry in HostConsole._current._peerListEntries) { if ((Object)(object)peerListEntry._netId != (Object)null && peerListEntry._netId.netId == ((NetworkBehaviour)targetPlayer).netId) { val = peerListEntry; break; } } } if ((Object)(object)val != (Object)null) { HostConsole._current._selectedPeerEntry = val; HostConsole._current.Ban_Peer(); } else { ((NetworkConnection)((NetworkBehaviour)targetPlayer).connectionToClient).Disconnect(); } } else { ((NetworkConnection)((NetworkBehaviour)targetPlayer).connectionToClient).Disconnect(); } } private static void SaveWarningToFile(string message) { try { File.AppendAllText(Main.WarningLogPath, message + Environment.NewLine); } catch (Exception) { } } } internal static class ModInfo { public const string GUID = "s0apysfederati0n"; public const string NAME = "AutoModeration"; public const string VERSION = "1.2.1"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }