using System; using System.Collections.Concurrent; 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 System.Text.RegularExpressions; using Alpha; using Alpha.Core.Command; using Alpha.Core.Util; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Hush.Core; using Hush.Core.Commands; using Hush.Patches; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using PurrNet; using PurrNet.Packing; using UnityEngine; [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("AndrewLin")] [assembly: AssemblyConfiguration("Publish")] [assembly: AssemblyDescription("Hush: A mod for On-Together with in-game utility commands including scheduled hushers (/hushmein, /hushlocalin, /hushglobalin), and more. Use /hushhelp")] [assembly: AssemblyFileVersion("0.1.14.0")] [assembly: AssemblyInformationalVersion("0.1.14+6d8ff45e787a49ad993a6bf3e2e6d91d41bd5c79")] [assembly: AssemblyProduct("AndrewLin.Hush")] [assembly: AssemblyTitle("AndrewLin.Hush")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/andrewlimforfun/ot-mods")] [assembly: AssemblyVersion("0.1.14.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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; } } } namespace Hush { public static class BuildInfo { public const string Version = "0.1.14"; } [BepInPlugin("com.andrewlin.ontogether.hush", "Hush", "0.1.14")] public class HushPlugin : BaseUnityPlugin { private static ManualLogSource? _logger; private static readonly ConcurrentQueue _mainThreadQueue = new ConcurrentQueue(); public const string ModGUID = "com.andrewlin.ontogether.hush"; public const string ModName = "Hush"; public const string ModVersion = "0.1.14"; public static ConfigEntry? EnableFeature { get; private set; } public static ConfigEntry? ShowCommand { get; private set; } public static ConfigEntry? FilterActionConfig { get; private set; } public static ConfigEntry? CensorCharConfig { get; private set; } public static ConfigEntry? FilterConfigPathConfig { get; private set; } public static ChatFilterManager? FilterManager { get; private set; } public static PlayerMuteManager? MuteManager { get; private set; } public static BanManager? BanManager { get; private set; } public static bool VerboseLogging { get { return HushSettings.VerboseLogging; } set { HushSettings.VerboseLogging = value; } } public static string FilterConfigPath => FilterConfigPathConfig?.Value ?? Path.Combine(Paths.ConfigPath, "com.andrewlin.ontogether.hush.filter.json"); public static string MutesConfigPath => Path.Combine(Paths.ConfigPath, "com.andrewlin.ontogether.hush.mutes.json"); public static void SaveFilter() { if (FilterManager == null) { return; } try { FilterManager.Save(FilterConfigPath); } catch (Exception ex) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogError((object)("Failed to save filter config: " + ex.Message)); } } } public static void SaveMutes() { if (MuteManager == null) { return; } try { MuteManager.Save(MutesConfigPath); } catch (Exception ex) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogError((object)("Failed to save mutes: " + ex.Message)); } } } public static void RunOnMainThread(Action action) { ConfigEntry? enableFeature = EnableFeature; if (enableFeature != null && enableFeature.Value) { _mainThreadQueue.Enqueue(action); } } private void Awake() { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Expected O, but got Unknown _logger = ((BaseUnityPlugin)this).Logger; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Hush v0.1.14 is loaded!"); InitConfig(); FilterManager = new ChatFilterManager(); FilterManager.Load(FilterConfigPath); if (FilterActionConfig != null) { FilterManager.Action = FilterActionConfig.Value; FilterActionConfig.SettingChanged += delegate { if (FilterManager != null) { FilterManager.Action = FilterActionConfig.Value; } }; } if (CensorCharConfig != null) { if (CensorCharConfig.Value.Length == 1) { FilterManager.CensorChar = CensorCharConfig.Value[0]; } CensorCharConfig.SettingChanged += delegate { if (FilterManager != null && CensorCharConfig.Value.Length == 1) { FilterManager.CensorChar = CensorCharConfig.Value[0]; } }; } MuteManager = new PlayerMuteManager(); MuteManager.Load(MutesConfigPath); BanManager = new BanManager(); Harmony val = new Harmony("com.andrewlin.ontogether.hush"); val.PatchAll(typeof(TextChannelManagerPatch)); ChatCommandManager commandManager = AlphaPlugin.CommandManager; if (commandManager != null) { commandManager.Register((IChatCommand)(object)new HushToggleCommand()); } ChatCommandManager commandManager2 = AlphaPlugin.CommandManager; if (commandManager2 != null) { commandManager2.Register((IChatCommand)(object)new HushAddWordCommand()); } ChatCommandManager commandManager3 = AlphaPlugin.CommandManager; if (commandManager3 != null) { commandManager3.Register((IChatCommand)(object)new HushRemoveWordCommand()); } ChatCommandManager commandManager4 = AlphaPlugin.CommandManager; if (commandManager4 != null) { commandManager4.Register((IChatCommand)(object)new HushAddPatternCommand()); } ChatCommandManager commandManager5 = AlphaPlugin.CommandManager; if (commandManager5 != null) { commandManager5.Register((IChatCommand)(object)new HushRemovePatternCommand()); } ChatCommandManager commandManager6 = AlphaPlugin.CommandManager; if (commandManager6 != null) { commandManager6.Register((IChatCommand)(object)new HushGetWordsCommand()); } ChatCommandManager commandManager7 = AlphaPlugin.CommandManager; if (commandManager7 != null) { commandManager7.Register((IChatCommand)(object)new HushGetPatternsCommand()); } ChatCommandManager commandManager8 = AlphaPlugin.CommandManager; if (commandManager8 != null) { commandManager8.Register((IChatCommand)(object)new HushFilterActionCommand()); } ChatCommandManager commandManager9 = AlphaPlugin.CommandManager; if (commandManager9 != null) { commandManager9.Register((IChatCommand)(object)new HushCensorCharCommand()); } ChatCommandManager commandManager10 = AlphaPlugin.CommandManager; if (commandManager10 != null) { commandManager10.Register((IChatCommand)(object)new HushLoadFilterCommand()); } ChatCommandManager commandManager11 = AlphaPlugin.CommandManager; if (commandManager11 != null) { commandManager11.Register((IChatCommand)(object)new HushMuteCommand()); } ChatCommandManager commandManager12 = AlphaPlugin.CommandManager; if (commandManager12 != null) { commandManager12.Register((IChatCommand)(object)new HushUnmuteCommand()); } ChatCommandManager commandManager13 = AlphaPlugin.CommandManager; if (commandManager13 != null) { commandManager13.Register((IChatCommand)(object)new HushTempMuteCommand()); } ChatCommandManager commandManager14 = AlphaPlugin.CommandManager; if (commandManager14 != null) { commandManager14.Register((IChatCommand)(object)new HushGetMutesCommand()); } ChatCommandManager commandManager15 = AlphaPlugin.CommandManager; if (commandManager15 != null) { commandManager15.Register((IChatCommand)(object)new HushDelegateAddCommand()); } ChatCommandManager commandManager16 = AlphaPlugin.CommandManager; if (commandManager16 != null) { commandManager16.Register((IChatCommand)(object)new HushDelegateRemoveCommand()); } ChatCommandManager commandManager17 = AlphaPlugin.CommandManager; if (commandManager17 != null) { commandManager17.Register((IChatCommand)(object)new HushDelegateListCommand()); } ChatCommandManager commandManager18 = AlphaPlugin.CommandManager; if (commandManager18 != null) { commandManager18.Register((IChatCommand)(object)new HushVersionCommand()); } ChatCommandManager commandManager19 = AlphaPlugin.CommandManager; if (commandManager19 != null) { commandManager19.Register((IChatCommand)(object)new HushLogVerboseCommand()); } ChatCommandManager commandManager20 = AlphaPlugin.CommandManager; if (commandManager20 != null) { commandManager20.Register((IChatCommand)(object)new HushBanCommand()); } ChatCommandManager commandManager21 = AlphaPlugin.CommandManager; if (commandManager21 != null) { commandManager21.Register((IChatCommand)(object)new HushBanOfflineCommand()); } ChatCommandManager commandManager22 = AlphaPlugin.CommandManager; if (commandManager22 != null) { commandManager22.Register((IChatCommand)(object)new HushUnbanCommand()); } ChatCommandManager commandManager23 = AlphaPlugin.CommandManager; if (commandManager23 != null) { commandManager23.Register((IChatCommand)(object)new HushGetBansCommand()); } } private void InitConfig() { EnableFeature = ((BaseUnityPlugin)this).Config.Bind("General", "EnableFeature", true, "Enable or disable the mod feature."); ShowCommand = ((BaseUnityPlugin)this).Config.Bind("General", "ShowCommand", false, "Show the command in chat when used."); FilterActionConfig = ((BaseUnityPlugin)this).Config.Bind("Filter", "Action", FilterAction.Censor, "How the filter handles matched words: Censor (replace with asterisks) or Block (suppress entire message)."); CensorCharConfig = ((BaseUnityPlugin)this).Config.Bind("Filter", "CensorChar", "*", "Character used to replace matched words when in Censor mode."); FilterConfigPathConfig = ((BaseUnityPlugin)this).Config.Bind("Filter", "ConfigPath", Path.Combine(Paths.ConfigPath, "com.andrewlin.ontogether.hush.filter.json"), "Path to the filter word list JSON file."); } private void Update() { ConfigEntry? enableFeature = EnableFeature; if (enableFeature == null || !enableFeature.Value) { return; } MuteManager?.Tick(); Action result; while (_mainThreadQueue.TryDequeue(out result)) { try { result(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Main thread action failed: " + ex.Message)); } } } private void OnDestroy() { } } public static class MyPluginInfo { public const string PLUGIN_GUID = "AndrewLin.Hush"; public const string PLUGIN_NAME = "AndrewLin.Hush"; public const string PLUGIN_VERSION = "0.1.14"; } } namespace Hush.Patches { [HarmonyPatch(typeof(TextChannelManager))] public static class TextChannelManagerPatch { private static readonly ManualLogSource _log = Logger.CreateLogSource("Hush.TCMP"); [HarmonyPatch("HandleRPCGenerated_0")] [HarmonyPrefix] public static bool HandleRPCGenerated_0_Prefix(ref RPCPacket packet, RPCInfo info, bool asServer) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0481: Unknown result type (might be due to invalid IL or missing references) //IL_0483: Unknown result type (might be due to invalid IL or missing references) if (!asServer) { return true; } BitPacker val = BitPackerPool.Get(false); val.WriteBitDataWithoutConsumingIt(packet.data); val.ResetPositionAndMode(true); byte[] bytes = null; Packer.Read(val, ref bytes); byte[] array = null; Packer.Read(val, ref array); bool flag = false; Packer.Read(val, ref flag); Vector3 val2 = default(Vector3); Packer.Read(val, ref val2); string text = null; Packer.Read(val, ref text); val.Dispose(); string text2 = Encoding.Unicode.GetString(array); string text3 = ChatUtils.CleanTMPTags(text2); if (HushSettings.VerboseLogging) { _log.LogDebug((object)("[Server] HandleRPCGenerated_0: intercept " + text3 + " (" + text + ")")); } if (HushPlugin.MuteManager == null) { _log.LogWarning((object)"[Server] MuteManager is null - mute check skipped"); } else { if (HushPlugin.MuteManager.IsMuted(text)) { ChatUtils.AddGlobalNotification("Muted message from " + text2 + " (" + text + ")."); _log.LogInfo((object)("[Server] Blocked message from muted player " + text3 + " (" + text + ").")); return false; } if (HushSettings.VerboseLogging) { _log.LogDebug((object)("[Server] " + text3 + " (" + text + ") is not muted, proceeding")); } } string text4 = Encoding.Unicode.GetString(bytes); if (text4.Trim().StartsWith("hush:", StringComparison.Ordinal)) { PlayerMuteManager? muteManager = HushPlugin.MuteManager; if (muteManager != null && muteManager.IsDelegate(text)) { _log.LogInfo((object)("[Relay] Relay accepted from delegate " + text3 + " (" + text + "): " + text4)); try { ExecuteRelay(text4, text); } catch (Exception arg) { _log.LogError((object)$"[Relay] Unhandled exception executing relay from {text3} ({text}): {arg}"); } } else if (PlayerLists.IsAdmin(text)) { _log.LogInfo((object)("[Relay] Relay accepted from admin " + text3 + ": " + text4)); try { ExecuteRelay(text4, text); } catch (Exception arg2) { _log.LogError((object)$"[Relay] Unhandled exception executing relay from admin {text3} ({text}): {arg2}"); } } else { _log.LogWarning((object)("[Relay] Relay rejected from non-delegate " + text3 + " (" + text + "): " + text4)); } return false; } ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager == null || !filterManager.Enabled) { return true; } FilterResult filterResult = filterManager.Apply(text4); if (filterResult.WasBlocked) { ChatUtils.AddGlobalNotification("Filter blocked message from " + text2 + " (" + text + ")."); _log.LogInfo((object)("[Server] Blocked message from " + text3 + " (" + text + "): \"" + text4 + "\"")); return false; } if (!filterResult.WasModified) { return true; } _log.LogInfo((object)("[Server] Censored message from " + text3 + " (" + text + ").")); byte[] bytes2 = Encoding.Unicode.GetBytes(filterResult.Text); BitPacker val3 = BitPackerPool.Get(false); Packer.Write(val3, bytes2); Packer.Write(val3, array); Packer.Write(val3, flag); Packer.Write(val3, val2); Packer.Write(val3, text); BitData data = default(BitData); ((BitData)(ref data))..ctor(val3); val3.ResetPositionAndMode(true); packet.data = data; return true; } private static void ExecuteRelay(string payload, string senderSteamId) { PlayerMuteManager mutes = HushPlugin.MuteManager; if (mutes != null) { RelayExecutor relayExecutor = new RelayExecutor(mutes, (Func)((string id, string name) => HushPlugin.BanManager?.Ban(id, name) ?? false), (Func)((string id) => mutes.Unmute(id)), (Func)delegate(string id) { PlayerDetail obj = PlayerUtils.FindPlayerBySteamID(id); return (obj != null) ? obj.UserNameClean : null; }, (Func)((string q) => PlayerUtils.FindPlayerByQuery(q)?.SteamID), (Action)ChatUtils.AddGlobalNotification, (Action)HushPlugin.SaveMutes, _log); relayExecutor.Execute(payload, senderSteamId); } } private static string FormatDuration(TimeSpan ts) { return DurationFormatter.Format(ts); } [HarmonyPatch("OnChannelMessageReceived")] [HarmonyPrefix] public static bool OnChannelMessageReceived_Prefix(string message, string playerID) { if (string.IsNullOrEmpty(message)) { return true; } if (message.Trim().StartsWith("hush:", StringComparison.Ordinal)) { TextChannelManager i = NetworkSingleton.I; if (i != null && ((NetworkIdentity)i).isServer) { _log.LogWarning((object)("[Relay] Backup path triggered - relay escaped RPC intercept. sender=(" + playerID + "): " + message)); try { ExecuteRelay(message, playerID); } catch (Exception arg) { _log.LogError((object)$"[Relay] Backup path unhandled exception from ({playerID}): {arg}"); } } } ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager == null || !filterManager.Enabled) { return true; } FilterResult filterResult = filterManager.Apply(message); if (filterResult.WasBlocked) { _log.LogInfo((object)("Suppressed notification for blocked message: " + message)); } return !filterResult.WasBlocked; } [HarmonyPatch("AddMessageUI")] [HarmonyPrefix] public static bool AddMessageUI_Prefix(ref string text) { if (string.IsNullOrEmpty(text)) { return true; } ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager == null || !filterManager.Enabled) { return true; } FilterResult filterResult = filterManager.Apply(text); if (filterResult.WasBlocked) { _log.LogInfo((object)("Blocked message in UI: " + filterResult.Text)); return false; } if (filterResult.WasModified) { _log.LogInfo((object)("Censored message in UI: " + filterResult.Text)); text = filterResult.Text; } return true; } } } namespace Hush.Core { public class BanManager { private readonly ManualLogSource _log = Logger.CreateLogSource("Hush.BM"); public bool IsBanned(string steamId) { return MonoSingleton.I.BanData.BanServerPlayers.Contains(steamId); } public bool Ban(string steamId, string displayName) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) BanData banData = MonoSingleton.I.BanData; if (banData.BanServerPlayers.Contains(steamId)) { return false; } banData.BanServerPlayers.Add(steamId); banData.BanServerPlayerNicks.Add(displayName); MonoSingleton.I.SaveBanData(); _log.LogInfo((object)("Banned: " + displayName + " (" + steamId + ")")); PlayerPanelController i = NetworkSingleton.I; TextChannelManager i2 = NetworkSingleton.I; if ((Object)(object)i != (Object)null && (Object)(object)i2 != (Object)null) { int num = i.PlayerSteamIDs.IndexOf(steamId); if (num >= 0) { i2.MainPlayerController.BanRPC(i.PlayerIDs[num], true, default(RPCInfo)); } } return true; } public bool BanOffline(string steamId, string displayName) { BanData banData = MonoSingleton.I.BanData; if (banData.BanServerPlayers.Contains(steamId)) { return false; } banData.BanServerPlayers.Add(steamId); banData.BanServerPlayerNicks.Add(displayName); MonoSingleton.I.SaveBanData(); _log.LogInfo((object)("Offline banned: " + displayName + " (" + steamId + ")")); return true; } public bool Unban(string steamId) { BanData banData = MonoSingleton.I.BanData; int num = banData.BanServerPlayers.IndexOf(steamId); if (num < 0) { return false; } string text = banData.BanServerPlayerNicks[num]; banData.BanServerPlayers.RemoveAt(num); banData.BanServerPlayerNicks.RemoveAt(num); MonoSingleton.I.SaveBanData(); _log.LogInfo((object)("Unbanned: " + text + " (" + steamId + ")")); return true; } public List<(string SteamId, string Nick)> GetBans() { BanData banData = MonoSingleton.I.BanData; List<(string, string)> list = new List<(string, string)>(banData.BanServerPlayers.Count); for (int i = 0; i < banData.BanServerPlayers.Count; i++) { list.Add((banData.BanServerPlayers[i], banData.BanServerPlayerNicks[i])); } return list; } } public enum FilterAction { Censor, Block } public readonly struct FilterResult { public readonly bool WasModified; public readonly bool WasBlocked; public readonly string Text; private FilterResult(string text, bool wasModified, bool wasBlocked) { Text = text; WasModified = wasModified; WasBlocked = wasBlocked; } public static FilterResult Unchanged(string text) { return new FilterResult(text, wasModified: false, wasBlocked: false); } public static FilterResult Censored(string text) { return new FilterResult(text, wasModified: true, wasBlocked: false); } public static FilterResult Blocked() { return new FilterResult(string.Empty, wasModified: false, wasBlocked: true); } } public class ChatFilterManager { private class FilterConfig { public List? Words { get; set; } public List? Patterns { get; set; } } private readonly ManualLogSource _log = Logger.CreateLogSource("Hush.CFM"); private readonly HashSet _blockedWords = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _rawPatterns = new HashSet(StringComparer.Ordinal); private Regex? _pattern; public FilterAction Action { get; set; } = FilterAction.Censor; public char CensorChar { get; set; } = '*'; public bool Enabled { get; set; } = true; public int Count => _blockedWords.Count + _rawPatterns.Count; public bool Add(string word) { if (string.IsNullOrWhiteSpace(word)) { return false; } if (!_blockedWords.Add(word.Trim())) { return false; } _log.LogInfo((object)("Added word: \"" + word.Trim() + "\"")); RebuildPattern(); return true; } public bool Remove(string word) { if (string.IsNullOrWhiteSpace(word)) { return false; } if (!_blockedWords.Remove(word.Trim())) { return false; } _log.LogInfo((object)("Removed word: \"" + word.Trim() + "\"")); RebuildPattern(); return true; } public bool AddPattern(string regexPattern) { if (string.IsNullOrWhiteSpace(regexPattern)) { return false; } string text = regexPattern.Trim(); try { new Regex(text); } catch (ArgumentException ex) { _log.LogWarning((object)("Rejected invalid regex \"" + text + "\": " + ex.Message)); return false; } if (!_rawPatterns.Add(text)) { return false; } _log.LogInfo((object)("Added pattern: \"" + text + "\"")); RebuildPattern(); return true; } public bool RemovePattern(string regexPattern) { if (string.IsNullOrWhiteSpace(regexPattern)) { return false; } string text = regexPattern.Trim(); if (!_rawPatterns.Remove(text)) { return false; } _log.LogInfo((object)("Removed pattern: \"" + text + "\"")); RebuildPattern(); return true; } public void Clear() { int count = Count; _blockedWords.Clear(); _rawPatterns.Clear(); _pattern = null; _log.LogInfo((object)$"Filter cleared ({count} entries removed)."); } public string[] GetWords() { string[] array = new string[_blockedWords.Count]; _blockedWords.CopyTo(array); return array; } public string[] GetPatterns() { string[] array = new string[_rawPatterns.Count]; _rawPatterns.CopyTo(array); return array; } public FilterResult Apply(string message) { if (!Enabled || string.IsNullOrEmpty(message)) { return FilterResult.Unchanged(message); } Regex pattern = _pattern; FilterAction action = Action; char censorChar = CensorChar; if (pattern == null) { return FilterResult.Unchanged(message); } if (action == FilterAction.Block) { if (pattern.IsMatch(message)) { return FilterResult.Blocked(); } return FilterResult.Unchanged(message); } string text = pattern.Replace(message, (Match match) => new string(censorChar, match.Value.Length)); if (string.Equals(text, message, StringComparison.Ordinal)) { return FilterResult.Unchanged(message); } return FilterResult.Censored(text); } public void Save(string filePath) { FilterConfig filterConfig = new FilterConfig { Words = new List(_blockedWords), Patterns = new List(_rawPatterns) }; string contents = JsonConvert.SerializeObject((object)filterConfig, (Formatting)1); File.WriteAllText(filePath, contents, Encoding.UTF8); if (HushSettings.VerboseLogging) { _log.LogInfo((object)$"Saved filter config ({_blockedWords.Count} words, {_rawPatterns.Count} patterns) to: {filePath}"); } } public void Load(string filePath) { if (!File.Exists(filePath)) { if (HushSettings.VerboseLogging) { _log.LogInfo((object)"No filter config file found, starting fresh."); } return; } FilterConfig filterConfig; try { string text = File.ReadAllText(filePath, Encoding.UTF8); filterConfig = JsonConvert.DeserializeObject(text); } catch (Exception ex) { _log.LogWarning((object)("Failed to parse filter config: " + ex.Message)); return; } if (filterConfig == null) { return; } _blockedWords.Clear(); _rawPatterns.Clear(); if (filterConfig.Words != null) { foreach (string word in filterConfig.Words) { if (!string.IsNullOrWhiteSpace(word)) { _blockedWords.Add(word.Trim()); } } } if (filterConfig.Patterns != null) { foreach (string pattern in filterConfig.Patterns) { if (!string.IsNullOrWhiteSpace(pattern)) { string text2 = pattern.Trim(); try { new Regex(text2); _rawPatterns.Add(text2); } catch (ArgumentException) { _log.LogWarning((object)("Skipped invalid regex in config: \"" + text2 + "\"")); } } } } RebuildPattern(); if (HushSettings.VerboseLogging) { _log.LogInfo((object)$"Loaded filter config: {_blockedWords.Count} words, {_rawPatterns.Count} patterns."); } } private void RebuildPattern() { bool flag = _blockedWords.Count > 0; bool flag2 = _rawPatterns.Count > 0; if (!flag && !flag2) { _pattern = null; return; } StringBuilder stringBuilder = new StringBuilder(); if (flag) { stringBuilder.Append("\\b(?:"); bool flag3 = true; foreach (string blockedWord in _blockedWords) { if (!flag3) { stringBuilder.Append('|'); } stringBuilder.Append(Regex.Escape(blockedWord)); flag3 = false; } stringBuilder.Append(")\\b"); } foreach (string rawPattern in _rawPatterns) { if (stringBuilder.Length > 0) { stringBuilder.Append('|'); } stringBuilder.Append("(?:"); stringBuilder.Append(rawPattern); stringBuilder.Append(')'); } _pattern = new Regex(stringBuilder.ToString(), RegexOptions.IgnoreCase | RegexOptions.Compiled); if (HushSettings.VerboseLogging) { _log.LogInfo((object)$"Pattern rebuilt: {Count} active entries."); } } } public static class DurationFormatter { public static string Format(TimeSpan ts) { if (ts.TotalSeconds < 60.0) { return $"{(int)ts.TotalSeconds}s"; } if (ts.TotalMinutes < 60.0) { return $"{(int)ts.TotalMinutes}m"; } if (ts.TotalHours < 24.0) { string text = $"{ts.Hours}h"; return (ts.Minutes > 0) ? $"{text} {ts.Minutes}m" : text; } return $"{(int)ts.TotalDays}d"; } } public static class HushSettings { public static bool VerboseLogging { get; set; } } public class PlayerMuteManager { private class MuteConfig { public List? PermaMuted { get; set; } public Dictionary? TimedMutes { get; set; } public List? Delegates { get; set; } } private readonly ManualLogSource _log = Logger.CreateLogSource("Hush.PMM"); private readonly HashSet _permaMuted = new HashSet(StringComparer.Ordinal); private readonly Dictionary _timedMutes = new Dictionary(StringComparer.Ordinal); private readonly HashSet _delegates = new HashSet(StringComparer.Ordinal); public bool Mute(string steamId) { string text = DisplayId(steamId); if (_timedMutes.Remove(steamId) && HushSettings.VerboseLogging) { _log.LogDebug((object)("Mute: removed existing timed mute for " + text)); } if (!_permaMuted.Add(steamId)) { if (HushSettings.VerboseLogging) { _log.LogDebug((object)("Mute: " + text + " was already permanently muted (no-op)")); } return false; } _log.LogInfo((object)("Permanently muted: " + text)); return true; } public bool MuteFor(string steamId, TimeSpan duration) { string text = DisplayId(steamId); if (duration <= TimeSpan.Zero) { _log.LogWarning((object)$"MuteFor: invalid duration {duration} for {text} - ignored"); return false; } if (_permaMuted.Remove(steamId) && HushSettings.VerboseLogging) { _log.LogDebug((object)("MuteFor: removed existing permanent mute for " + text)); } bool flag = _timedMutes.ContainsKey(steamId); _timedMutes[steamId] = DateTime.UtcNow + duration; if (flag && HushSettings.VerboseLogging) { _log.LogDebug((object)("MuteFor: extended/replaced existing timed mute for " + text)); } _log.LogInfo((object)$"Timed muted: {text} until {_timedMutes[steamId]:u} ({duration.TotalSeconds:0}s)"); return true; } public bool Unmute(string steamId) { bool flag = _permaMuted.Remove(steamId); bool flag2 = _timedMutes.Remove(steamId); if (flag || flag2) { _log.LogInfo((object)$"Unmuted: {DisplayId(steamId)} (wasPerma={flag}, wasTimed={flag2})"); return true; } if (HushSettings.VerboseLogging) { _log.LogDebug((object)("Unmute: " + DisplayId(steamId) + " was not muted (no-op)")); } return false; } public bool IsMuted(string steamId) { if (_permaMuted.Contains(steamId)) { if (HushSettings.VerboseLogging) { _log.LogDebug((object)("IsMuted: " + DisplayId(steamId) + " → true (permanent)")); } return true; } if (_timedMutes.TryGetValue(steamId, out var value)) { bool flag = value > DateTime.UtcNow; if (HushSettings.VerboseLogging) { _log.LogDebug((object)$"IsMuted: {DisplayId(steamId)} → {flag} (timed, expires {value:u}, remaining {(value - DateTime.UtcNow).TotalSeconds:0}s)"); } return flag; } if (HushSettings.VerboseLogging) { _log.LogDebug((object)("IsMuted: " + DisplayId(steamId) + " → false (not in either list)")); } return false; } public bool IsDelegate(string steamId) { return _delegates.Contains(steamId); } public bool AddDelegate(string steamId) { if (!_delegates.Add(steamId)) { return false; } _log.LogInfo((object)("Added mute delegate: " + DisplayId(steamId))); return true; } public bool RemoveDelegate(string steamId) { if (!_delegates.Remove(steamId)) { return false; } _log.LogInfo((object)("Removed mute delegate: " + DisplayId(steamId))); return true; } public IReadOnlyCollection GetDelegates() { return _delegates; } public void Tick() { if (_timedMutes.Count == 0) { return; } DateTime now = DateTime.UtcNow; List list = (from kv in _timedMutes where kv.Value <= now select kv.Key).ToList(); foreach (string item in list) { _timedMutes.Remove(item); string text = DisplayId(item); ChatUtils.AddGlobalNotification("Timed mute expired for player " + text + "."); _log.LogInfo((object)("Timed mute expired: " + text)); } } public List<(string SteamId, DateTime? Expiry)> GetMutes() { List<(string, DateTime?)> list = new List<(string, DateTime?)>(); foreach (string item in _permaMuted) { list.Add((item, null)); } DateTime utcNow = DateTime.UtcNow; foreach (KeyValuePair timedMute in _timedMutes) { if (timedMute.Value > utcNow) { list.Add((timedMute.Key, timedMute.Value)); } } return list; } public void Save(string filePath) { MuteConfig muteConfig = new MuteConfig { PermaMuted = new List(_permaMuted), TimedMutes = _timedMutes.Where>((KeyValuePair kv) => kv.Value > DateTime.UtcNow).ToDictionary((KeyValuePair kv) => kv.Key, (KeyValuePair kv) => kv.Value.ToString("o")), Delegates = new List(_delegates) }; string contents = JsonConvert.SerializeObject((object)muteConfig, (Formatting)1); File.WriteAllText(filePath, contents, Encoding.UTF8); if (HushSettings.VerboseLogging) { _log.LogInfo((object)("Saved mute config to: " + filePath)); } } public void Load(string filePath) { if (!File.Exists(filePath)) { if (HushSettings.VerboseLogging) { _log.LogInfo((object)"No mute config found, starting fresh."); } return; } MuteConfig muteConfig; try { string text = File.ReadAllText(filePath, Encoding.UTF8); muteConfig = JsonConvert.DeserializeObject(text); } catch (Exception ex) { _log.LogWarning((object)("Failed to parse mute config: " + ex.Message)); return; } if (muteConfig == null) { return; } _permaMuted.Clear(); _timedMutes.Clear(); _delegates.Clear(); if (muteConfig.PermaMuted != null) { foreach (string item in muteConfig.PermaMuted) { if (!string.IsNullOrWhiteSpace(item)) { _permaMuted.Add(item.Trim()); } } } if (muteConfig.TimedMutes != null) { DateTime utcNow = DateTime.UtcNow; foreach (KeyValuePair timedMute in muteConfig.TimedMutes) { if (DateTime.TryParse(timedMute.Value, null, DateTimeStyles.RoundtripKind, out var result) && result > utcNow) { _timedMutes[timedMute.Key] = result; } } } if (muteConfig.Delegates != null) { foreach (string @delegate in muteConfig.Delegates) { if (!string.IsNullOrWhiteSpace(@delegate)) { _delegates.Add(@delegate.Trim()); } } } if (HushSettings.VerboseLogging) { _log.LogInfo((object)$"Loaded mute config: {_permaMuted.Count} permanent, {_timedMutes.Count} timed, {_delegates.Count} delegates."); } } private string DisplayId(string steamId) { try { PlayerDetail val = PlayerUtils.FindPlayerBySteamID(steamId); return (val != null) ? (val.UserName + " (" + steamId + ")") : steamId; } catch { return steamId; } } } public class RelayExecutor { private readonly PlayerMuteManager _mutes; private readonly Func _ban; private readonly Func _unmute; private readonly Func _resolveName; private readonly Func _resolveQuery; private readonly Action _notify; private readonly Action _saveMutes; private readonly ManualLogSource _log; public RelayExecutor(PlayerMuteManager mutes, Func ban, Func unmute, Func resolveName, Func resolveQuery, Action notify, Action saveMutes, ManualLogSource log) { _mutes = mutes; _ban = ban; _unmute = unmute; _resolveName = resolveName; _resolveQuery = resolveQuery; _notify = notify; _saveMutes = saveMutes; _log = log; } public bool Execute(string text, string senderSteamId) { if (text == null || !text.StartsWith("hush:", StringComparison.Ordinal)) { _log.LogWarning((object)("[Relay] Missing 'hush:' prefix from " + senderSteamId + ": " + text)); return false; } string payload = text.Substring("hush:".Length); ParsedRelayCommand parsedRelayCommand = RelayParser.Parse(payload); if (!parsedRelayCommand.IsValid) { _log.LogWarning((object)("[Relay] " + parsedRelayCommand.Error + " from " + senderSteamId)); return false; } string text2 = _resolveName(senderSteamId) ?? senderSteamId; string text3; string text4; if (SteamUtils.IsSteamID(parsedRelayCommand.TargetSteamId)) { text3 = parsedRelayCommand.TargetSteamId; text4 = _resolveName(text3); } else { string text5 = _resolveQuery(parsedRelayCommand.TargetSteamId); if (text5 == null) { _log.LogWarning((object)("[Relay] Could not resolve player query \"" + parsedRelayCommand.TargetSteamId + "\" from " + senderSteamId + ".")); return false; } text3 = text5; text4 = _resolveName(text3); _log.LogInfo((object)("[Relay] Resolved query \"" + parsedRelayCommand.TargetSteamId + "\" → " + (text4 ?? text3) + " (" + text3 + ")")); } string text6 = ((text4 != null) ? (text4 + " (" + text3 + ")") : text3); switch (parsedRelayCommand.Type) { case RelayCommandType.TimedMute: { _mutes.MuteFor(text3, TimeSpan.FromSeconds(parsedRelayCommand.DurationSeconds)); _saveMutes(); string text7 = DurationFormatter.Format(TimeSpan.FromSeconds(parsedRelayCommand.DurationSeconds)); _notify("Hush: delegate " + text2 + " muted " + text6 + " for " + text7 + "."); _log.LogInfo((object)$"[Relay] {text2} ({senderSteamId}) muted {text6} for {parsedRelayCommand.DurationSeconds}s."); return true; } case RelayCommandType.Ban: if (_ban(text3, text4 ?? text3)) { _notify("Hush: delegate " + text2 + " banned " + text6 + "."); _log.LogInfo((object)("[Relay] " + text2 + " (" + senderSteamId + ") banned " + text6 + ".")); } return true; case RelayCommandType.Unmute: if (_unmute(text3)) { _saveMutes(); _notify("Hush: delegate " + text2 + " unmuted " + text6 + "."); _log.LogInfo((object)("[Relay] " + text2 + " (" + senderSteamId + ") unmuted " + text6 + ".")); } return true; default: _log.LogWarning((object)("[Relay] Unhandled command type from " + senderSteamId + ".")); return false; } } } public enum RelayCommandType { TimedMute, Ban, Unmute, Unknown } public readonly struct ParsedRelayCommand { public readonly bool IsValid; public readonly RelayCommandType Type; public readonly string TargetSteamId; public readonly int DurationSeconds; public readonly string? Error; private ParsedRelayCommand(RelayCommandType type, string targetSteamId, int durationSeconds) { IsValid = true; Type = type; TargetSteamId = targetSteamId; DurationSeconds = durationSeconds; Error = null; } private ParsedRelayCommand(string error) { IsValid = false; Type = RelayCommandType.Unknown; TargetSteamId = string.Empty; DurationSeconds = 0; Error = error; } internal static ParsedRelayCommand Valid(RelayCommandType type, string targetId, int duration = 0) { return new ParsedRelayCommand(type, targetId, duration); } internal static ParsedRelayCommand Invalid(string error) { return new ParsedRelayCommand(error); } } public static class RelayParser { public static ParsedRelayCommand Parse(string payload) { if (string.IsNullOrEmpty(payload)) { return ParsedRelayCommand.Invalid("Empty payload"); } int num = payload.IndexOf(':'); if (num < 0) { return ParsedRelayCommand.Invalid("Malformed payload: " + payload); } string text = payload.Substring(0, num); string text2 = payload.Substring(num + 1); switch (text) { case "tmute": { int num2 = text2.LastIndexOf(':'); if (num2 < 0) { return ParsedRelayCommand.Invalid("Bad tmute args: " + text2); } string text3 = text2.Substring(0, num2); if (string.IsNullOrEmpty(text3)) { return ParsedRelayCommand.Invalid("Empty tmute target: " + text2); } if (!int.TryParse(text2.Substring(num2 + 1), out var result) || result <= 0) { return ParsedRelayCommand.Invalid("Bad duration: " + text2); } return ParsedRelayCommand.Valid(RelayCommandType.TimedMute, text3, result); } case "ban": if (string.IsNullOrEmpty(text2)) { return ParsedRelayCommand.Invalid("Empty ban target"); } return ParsedRelayCommand.Valid(RelayCommandType.Ban, text2); case "unmute": if (string.IsNullOrEmpty(text2)) { return ParsedRelayCommand.Invalid("Empty unmute target"); } return ParsedRelayCommand.Valid(RelayCommandType.Unmute, text2); default: return ParsedRelayCommand.Invalid("Unknown command: " + text); } } } } namespace Hush.Core.Commands { public class HushAddPatternCommand : IChatCommand, IComparable { public string Name => "hushaddpattern"; public string ShortName => "hap"; public string Description => "Add a raw regex pattern to the Hush filter. Usage: /hushaddpattern (e.g. (?i)f+u+c+k)"; public string Namespace => "hush"; public void Execute(string[] args) { if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushaddpattern Example: /hushaddpattern (?i)f+u+c+k"); return; } string text = string.Join(" ", args); ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager != null) { if (filterManager.AddPattern(text)) { HushPlugin.SaveFilter(); ChatUtils.AddGlobalNotification("Hush: added pattern \"" + text + "\"."); } else { ChatUtils.AddGlobalNotification("Hush: pattern \"" + text + "\" is invalid or already in the list."); } } } } public class HushAddWordCommand : IChatCommand, IComparable { public string Name => "hushaddword"; public string ShortName => "haw"; public string Description => "Add a literal word to the Hush filter. Usage: /hushaddword "; public string Namespace => "hush"; public void Execute(string[] args) { if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushaddword "); return; } string text = string.Join(" ", args); ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager != null) { if (filterManager.Add(text)) { HushPlugin.SaveFilter(); ChatUtils.AddGlobalNotification("Hush: added word \"" + text + "\"."); } else { ChatUtils.AddGlobalNotification("Hush: \"" + text + "\" is already in the list."); } } } } public class HushBanCommand : IChatCommand, IComparable { public string Name => "hushban"; public string ShortName => "hb"; public string Description => "Ban an online player. Host executes directly; delegates relay to host. Usage: /hushban "; public string Namespace => "hush"; public void Execute(string[] args) { //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushban "); return; } string text = string.Join(" ", args); PlayerDetail val = PlayerUtils.FindPlayerByQuery(text); if (val == null) { ChatUtils.AddGlobalNotification("Hush: player not found: \"" + text + "\""); return; } string playerSteamID = SteamUtils.GetPlayerSteamID(); if (PlayerUtils.GetHost()?.SteamID == playerSteamID) { BanManager banManager = HushPlugin.BanManager; if (banManager != null) { if (banManager.Ban(val.SteamID, val.UserName)) { ChatUtils.AddGlobalNotification("Hush: banned " + val.UserName + "."); } else { ChatUtils.AddGlobalNotification("Hush: " + val.UserName + " is already banned."); } } return; } HushPlugin.BanManager?.BanOffline(val.SteamID, val.UserName); TextChannelManager i = NetworkSingleton.I; if ((Object)(object)i == (Object)null) { ChatUtils.AddGlobalNotification("Hush: not connected - cannot relay ban request."); return; } string s = "hush:ban:" + val.SteamID; Vector3 val2 = (((Object)(object)i.MainPlayer != (Object)null) ? i.MainPlayer.position : Vector3.zero); i.SendMessageAsync(Encoding.Unicode.GetBytes(s), Encoding.Unicode.GetBytes(i.UserName ?? string.Empty), false, val2, playerSteamID, default(RPCInfo)); ChatUtils.AddGlobalNotification("Hush: ban request relayed for " + val.UserName + "."); } } public class HushBanOfflineCommand : IChatCommand, IComparable { public string Name => "hushbanoffline"; public string ShortName => "hbo"; public string Description => "Ban a player by Steam ID without them being online (host only). Usage: /hushbanoffline "; public string Namespace => "hush"; public void Execute(string[] args) { if (!HushMuteCommand.HostGuard()) { return; } if (args.Length < 2) { ChatUtils.AddGlobalNotification("Usage: /hushbanoffline "); return; } string text = args[0].Trim(); string text2 = string.Join(" ", args, 1, args.Length - 1); if (!Regex.IsMatch(text, "^\\d{17}$")) { ChatUtils.AddGlobalNotification("Hush: invalid Steam ID \"" + text + "\". Must be a 17-digit number."); return; } BanManager banManager = HushPlugin.BanManager; if (banManager != null) { if (banManager.BanOffline(text, text2)) { ChatUtils.AddGlobalNotification("Hush: banned " + text2 + " (" + text + ")."); } else { ChatUtils.AddGlobalNotification("Hush: " + text + " is already banned."); } } } } public class HushCensorCharCommand : IChatCommand, IComparable { public const string CMD = "hushcensorchar"; public string Name => "hushcensorchar"; public string ShortName => "hcc"; public string Description => "Set Hush censor character: /hushcensorchar ."; public string Namespace => "hush"; public void Execute(string[] args) { if (HushPlugin.CensorCharConfig != null) { if (args.Length == 0 || args[0].Length != 1) { ChatUtils.AddGlobalNotification("Usage: /hushcensorchar . Supply exactly one character."); return; } HushPlugin.CensorCharConfig.Value = args[0][0].ToString(); ChatUtils.AddGlobalNotification($"Hush censor character set to '{args[0][0]}'."); } } } public class HushDelegateAddCommand : IChatCommand, IComparable { public string Name => "hushdelegateadd"; public string ShortName => "hda"; public string Description => "Add a player to the mute-delegate whitelist (host only). Usage: /hushdelegateadd "; public string Namespace => "hush"; public void Execute(string[] args) { if (!HushMuteCommand.HostGuard()) { return; } if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushdelegateadd "); return; } string text = string.Join(" ", args); PlayerDetail val = PlayerUtils.FindPlayerByQuery(text); if (val == null) { ChatUtils.AddGlobalNotification("Hush: player not found: \"" + text + "\""); return; } PlayerMuteManager muteManager = HushPlugin.MuteManager; if (muteManager != null) { if (muteManager.AddDelegate(val.SteamID)) { HushPlugin.SaveMutes(); ChatUtils.AddGlobalNotification("Hush: " + val.UserNameClean + " added as mute delegate."); } else { ChatUtils.AddGlobalNotification("Hush: " + val.UserNameClean + " is already a delegate."); } } } } public class HushDelegateListCommand : IChatCommand, IComparable { public string Name => "hushdelegatelist"; public string ShortName => "hdl"; public string Description => "List all mute delegates (host only). Usage: /hushdelegatelist"; public string Namespace => "hush"; public void Execute(string[] args) { if (!HushMuteCommand.HostGuard()) { return; } PlayerMuteManager muteManager = HushPlugin.MuteManager; if (muteManager == null) { return; } IReadOnlyCollection delegates = muteManager.GetDelegates(); if (delegates.Count == 0) { ChatUtils.AddGlobalNotification("Hush: no mute delegates configured."); return; } StringBuilder stringBuilder = new StringBuilder($"Hush: {delegates.Count} mute delegate(s):"); foreach (string item in delegates) { PlayerDetail val = PlayerUtils.FindPlayerBySteamID(item); string text = ((val != null) ? (val.UserNameClean + " (" + item + ")") : item); stringBuilder.Append("\n " + text); } ChatUtils.AddGlobalNotification(stringBuilder.ToString()); } } public class HushDelegateRemoveCommand : IChatCommand, IComparable { public string Name => "hushdelegateremove"; public string ShortName => "hdr"; public string Description => "Remove a player from the mute-delegate whitelist (host only). Usage: /hushdelegateremove "; public string Namespace => "hush"; public void Execute(string[] args) { if (!HushMuteCommand.HostGuard()) { return; } if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushdelegateremove "); return; } string text = string.Join(" ", args); PlayerDetail val = PlayerUtils.FindPlayerByQuery(text); string steamId = val?.SteamID ?? text; string text2 = ((val != null) ? val.UserNameClean : null) ?? text; PlayerMuteManager muteManager = HushPlugin.MuteManager; if (muteManager != null) { if (muteManager.RemoveDelegate(steamId)) { HushPlugin.SaveMutes(); ChatUtils.AddGlobalNotification("Hush: " + text2 + " removed from mute delegates."); } else { ChatUtils.AddGlobalNotification("Hush: " + text2 + " is not a delegate."); } } } } public class HushFilterActionCommand : IChatCommand, IComparable { public const string CMD = "hushfilteraction"; public string Name => "hushfilteraction"; public string ShortName => "hfa"; public string Description => "Set Hush filter action command: /hushfilteraction . Valid actions: block, censor."; public string Namespace => "hush"; public void Execute(string[] args) { if (HushPlugin.FilterActionConfig != null) { FilterAction result; if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushfilteraction . Valid actions: block, censor."); } else if (Enum.TryParse(args[0], ignoreCase: true, out result)) { HushPlugin.FilterActionConfig.Value = result; ChatUtils.AddGlobalNotification($"Hush filter action set to {result}."); } else { ChatUtils.AddGlobalNotification("Invalid action. Valid actions: block, censor."); } } } } public class HushGetBansCommand : IChatCommand, IComparable { public string Name => "hushgetbans"; public string ShortName => "hgb"; public string Description => "List all banned players (host only). Usage: /hushgetbans"; public string Namespace => "hush"; public void Execute(string[] args) { if (!HushMuteCommand.HostGuard()) { return; } BanManager banManager = HushPlugin.BanManager; if (banManager == null) { return; } List<(string, string)> bans = banManager.GetBans(); if (bans.Count == 0) { ChatUtils.AddGlobalNotification("Hush: no players are currently banned."); return; } StringBuilder stringBuilder = new StringBuilder($"Hush: {bans.Count} banned player(s):"); foreach (var (text, text2) in bans) { stringBuilder.Append("\n " + text2 + " (" + text + ")"); } ChatUtils.AddGlobalNotification(stringBuilder.ToString()); } } public class HushGetMutesCommand : IChatCommand, IComparable { public string Name => "hushgetmutes"; public string ShortName => "hgm"; public string Description => "List all currently muted players (host only). Usage: /hushgetmutes"; public string Namespace => "hush"; public void Execute(string[] args) { if (!HushMuteCommand.HostGuard()) { return; } PlayerMuteManager muteManager = HushPlugin.MuteManager; if (muteManager == null) { return; } List<(string, DateTime?)> mutes = muteManager.GetMutes(); if (mutes.Count == 0) { ChatUtils.AddGlobalNotification("Hush: no players are currently muted."); return; } StringBuilder stringBuilder = new StringBuilder($"Hush: {mutes.Count} muted player(s):"); foreach (var item3 in mutes) { string item = item3.Item1; DateTime? item2 = item3.Item2; PlayerDetail val = PlayerUtils.FindPlayerBySteamID(item); string text = ((val != null) ? val.UserNameClean : null) ?? item; string text2 = ((!item2.HasValue) ? "permanent" : $"until {item2.Value.ToLocalTime():HH:mm:ss}"); stringBuilder.Append("\n " + text + " - " + text2); } ChatUtils.AddGlobalNotification(stringBuilder.ToString()); } } public class HushGetPatternsCommand : IChatCommand, IComparable { public string Name => "hushgetpatterns"; public string ShortName => "hgp"; public string Description => "List all raw regex patterns currently in the Hush filter."; public string Namespace => "hush"; public void Execute(string[] args) { ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager != null) { string[] patterns = filterManager.GetPatterns(); if (patterns.Length == 0) { ChatUtils.AddGlobalNotification("Hush: no patterns in the filter."); return; } Array.Sort(patterns, (IComparer?)StringComparer.Ordinal); ChatUtils.AddGlobalNotification(string.Format("Hush patterns ({0}): {1}", patterns.Length, string.Join(", ", patterns))); } } } public class HushGetWordsCommand : IChatCommand, IComparable { public string Name => "hushgetwords"; public string ShortName => "hgw"; public string Description => "List all literal words currently in the Hush filter."; public string Namespace => "hush"; public void Execute(string[] args) { ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager != null) { string[] words = filterManager.GetWords(); if (words.Length == 0) { ChatUtils.AddGlobalNotification("Hush: no words in the filter."); return; } Array.Sort(words, (IComparer?)StringComparer.OrdinalIgnoreCase); ChatUtils.AddGlobalNotification(string.Format("Hush words ({0}): {1}", words.Length, string.Join(", ", words))); } } } public class HushLoadFilterCommand : IChatCommand, IComparable { public const string CMD = "hushloadfilter"; public string Name => "hushloadfilter"; public string ShortName => "hlf"; public string Description => "Reload the Hush filter word list from disk: /hushloadfilter."; public string Namespace => "hush"; public void Execute(string[] args) { if (HushPlugin.FilterManager != null) { HushPlugin.FilterManager.Load(HushPlugin.FilterConfigPath); ChatUtils.AddGlobalNotification($"Hush filter reloaded ({HushPlugin.FilterManager.Count} entries)."); } } } public class HushLogVerboseCommand : IChatCommand, IComparable { public const string CMD = "hushlogverbose"; public string Name => "hushlogverbose"; public string ShortName => "hlv"; public string Description => "Toggle verbose BepInEx logging for Hush."; public string Namespace => "hush"; public void Execute(string[] args) { HushPlugin.VerboseLogging = !HushPlugin.VerboseLogging; ChatUtils.AddGlobalNotification("Hush verbose logging is now " + (HushPlugin.VerboseLogging ? "enabled" : "disabled") + "."); } } public class HushMuteCommand : IChatCommand, IComparable { public string Name => "hushmute"; public string ShortName => "hmu"; public string Description => "Permanently mute a player (host only). Usage: /hushmute "; public string Namespace => "hush"; public void Execute(string[] args) { if (!HostGuard()) { return; } if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushmute "); return; } string text = string.Join(" ", args); PlayerDetail val = PlayerUtils.FindPlayerByQuery(text); if (val == null) { ChatUtils.AddGlobalNotification("Hush: player not found: \"" + text + "\""); return; } PlayerMuteManager muteManager = HushPlugin.MuteManager; if (muteManager != null) { if (muteManager.Mute(val.SteamID)) { HushPlugin.SaveMutes(); ChatUtils.AddGlobalNotification("Hush: permanently muted " + val.UserNameClean + "."); } else { ChatUtils.AddGlobalNotification("Hush: " + val.UserNameClean + " is already permanently muted."); } } } internal static bool HostGuard() { if (PlayerUtils.GetHost()?.SteamID == SteamUtils.GetPlayerSteamID()) { return true; } ChatUtils.AddGlobalNotification("Hush: only the host can mute players."); return false; } } public class HushRemovePatternCommand : IChatCommand, IComparable { public string Name => "hushremovepattern"; public string ShortName => "hrp"; public string Description => "Remove a raw regex pattern from the Hush filter. Usage: /hushremovepattern "; public string Namespace => "hush"; public void Execute(string[] args) { if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushremovepattern "); return; } string text = string.Join(" ", args); ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager != null) { if (filterManager.RemovePattern(text)) { HushPlugin.SaveFilter(); ChatUtils.AddGlobalNotification("Hush: removed pattern \"" + text + "\"."); } else { ChatUtils.AddGlobalNotification("Hush: pattern \"" + text + "\" was not in the list."); } } } } public class HushRemoveWordCommand : IChatCommand, IComparable { public string Name => "hushremoveword"; public string ShortName => "hrw"; public string Description => "Remove a literal word from the Hush filter. Usage: /hushremoveword "; public string Namespace => "hush"; public void Execute(string[] args) { if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushremoveword "); return; } string text = string.Join(" ", args); ChatFilterManager filterManager = HushPlugin.FilterManager; if (filterManager != null) { if (filterManager.Remove(text)) { HushPlugin.SaveFilter(); ChatUtils.AddGlobalNotification("Hush: removed word \"" + text + "\"."); } else { ChatUtils.AddGlobalNotification("Hush: \"" + text + "\" was not in the list."); } } } } public class HushTempMuteCommand : IChatCommand, IComparable { public string Name => "hushtempmute"; public string ShortName => "htm"; public string Description => "Temporarily mute a player. Host executes immediately; delegates relay to host. Usage: /hushtempmute e.g. /hushtempmute bob 10m"; public string Namespace => "hush"; public void Execute(string[] args) { //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) if (args.Length < 2) { ChatUtils.AddGlobalNotification("Usage: /hushtmute e.g. /hushtmute bob 10m"); return; } string text = args[^1]; string text2 = string.Join(" ", args, 0, args.Length - 1); PlayerDetail val = PlayerUtils.FindPlayerByQuery(text2); if (val == null) { ChatUtils.AddGlobalNotification("Hush: player not found: \"" + text2 + "\""); return; } TimeSpan timeSpan = default(TimeSpan); if (!TimeUtils.TryParseDuration(text, ref timeSpan) || timeSpan <= TimeSpan.Zero) { ChatUtils.AddGlobalNotification("Hush: invalid duration \"" + text + "\". Use e.g. 10m, 1h30m, 30s."); return; } PlayerMuteManager muteManager = HushPlugin.MuteManager; if (muteManager == null) { return; } string playerSteamID = SteamUtils.GetPlayerSteamID(); if (PlayerUtils.GetHost()?.SteamID == playerSteamID) { muteManager.MuteFor(val.SteamID, timeSpan); HushPlugin.SaveMutes(); ChatUtils.AddGlobalNotification("Hush: muted " + val.UserNameClean + " for " + FormatDuration(timeSpan) + "."); } else { TextChannelManager i = NetworkSingleton.I; if ((Object)(object)i == (Object)null) { ChatUtils.AddGlobalNotification("Hush: not connected - cannot relay mute request."); return; } string s = $"hush:tmute:{val.SteamID}:{(int)timeSpan.TotalSeconds}"; Vector3 val2 = (((Object)(object)i.MainPlayer != (Object)null) ? i.MainPlayer.position : Vector3.zero); i.SendMessageAsync(Encoding.Unicode.GetBytes(s), Encoding.Unicode.GetBytes(i.UserName ?? string.Empty), false, val2, SteamUtils.GetPlayerSteamID(), default(RPCInfo)); ChatUtils.AddGlobalNotification("Hush: mute request sent for " + val.UserNameClean + "."); } } private static string FormatDuration(TimeSpan ts) { if (ts.TotalSeconds < 60.0) { return $"{(int)ts.TotalSeconds}s"; } if (ts.TotalMinutes < 60.0) { return $"{(int)ts.TotalMinutes}m"; } if (ts.TotalHours < 24.0) { string text = $"{ts.Hours}h"; return (ts.Minutes > 0) ? $"{text} {ts.Minutes}m" : text; } return $"{(int)ts.TotalDays}d"; } } public class HushToggleCommand : IChatCommand, IComparable { public const string CMD = "hushtoggle"; public string Name => "hushtoggle"; public string ShortName => "ht"; public string Description => "Toggle Hush feature on/off. "; public string Namespace => "hush"; public void Execute(string[] args) { if (HushPlugin.EnableFeature != null) { HushPlugin.EnableFeature.Value = !HushPlugin.EnableFeature.Value; ChatUtils.AddGlobalNotification("Hush feature is now " + (HushPlugin.EnableFeature.Value ? "enabled" : "disabled") + "."); } } } public class HushUnbanCommand : IChatCommand, IComparable { public string Name => "hushunban"; public string ShortName => "hub"; public string Description => "Unban a player (host only). Accepts player query or raw Steam ID. Usage: /hushunban "; public string Namespace => "hush"; public void Execute(string[] args) { if (!HushMuteCommand.HostGuard()) { return; } if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushunban "); return; } string text = string.Join(" ", args); BanManager banManager = HushPlugin.BanManager; if (banManager != null) { PlayerDetail val = PlayerUtils.FindPlayerByQuery(text); string steamId = val?.SteamID ?? text; string text2 = ((val != null) ? val.UserNameClean : null) ?? text; if (banManager.Unban(steamId)) { ChatUtils.AddGlobalNotification("Hush: unbanned " + text2 + "."); } else { ChatUtils.AddGlobalNotification("Hush: " + text2 + " is not banned."); } } } } public class HushUnmuteCommand : IChatCommand, IComparable { public string Name => "hushunmute"; public string ShortName => "hum"; public string Description => "Unmute a player. Host executes directly; delegates relay to host. Usage: /hushunmute "; public string Namespace => "hush"; public void Execute(string[] args) { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) if (args.Length == 0) { ChatUtils.AddGlobalNotification("Usage: /hushunmute "); return; } string text = string.Join(" ", args); PlayerDetail val = PlayerUtils.FindPlayerByQuery(text); string text2 = val?.SteamID ?? text; string text3 = ((val != null) ? val.UserNameClean : null) ?? text; if (PlayerUtils.GetHost()?.SteamID == SteamUtils.GetPlayerSteamID()) { PlayerMuteManager muteManager = HushPlugin.MuteManager; if (muteManager != null) { if (muteManager.Unmute(text2)) { HushPlugin.SaveMutes(); ChatUtils.AddGlobalNotification("Hush: unmuted " + text3 + "."); } else { ChatUtils.AddGlobalNotification("Hush: " + text3 + " is not muted."); } } } else { TextChannelManager i = NetworkSingleton.I; if ((Object)(object)i == (Object)null) { ChatUtils.AddGlobalNotification("Hush: not connected - cannot relay unmute request."); return; } string s = "hush:unmute:" + text2; Vector3 val2 = (((Object)(object)i.MainPlayer != (Object)null) ? i.MainPlayer.position : Vector3.zero); i.SendMessageAsync(Encoding.Unicode.GetBytes(s), Encoding.Unicode.GetBytes(i.UserName ?? string.Empty), false, val2, SteamUtils.GetPlayerSteamID(), default(RPCInfo)); ChatUtils.AddGlobalNotification("Hush: unmute request sent for " + text3 + "."); } } } public class HushVersionCommand : IChatCommand, IComparable { public string Name => "hushversion"; public string ShortName => "hv"; public string Description => "Print the installed Hush version."; public string Namespace => "hush"; public void Execute(string[] args) { ChatUtils.AddGlobalNotification("Hush v0.1.14"); } } }