using InnerNet; using System; using System.IO; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Reflection; using UnityEngine; // https://github.com/EnhancedNetwork/TownofHost-Enhanced/blob/main/Modules/BanManager.cs namespace AmongUsRevamped; public static class BanManager { public static readonly string DataPath = #if ANDROID Application.persistentDataPath; #else "."; #endif public static string RemoveHtmlTags(this string str) => Regex.Replace(str, "<[^>]*?>", ""); private static readonly string DenyNameListPath = $"{DataPath}/AUR-DATA/Deniednames.txt"; private static string BanListPath = $"{DataPath}/AUR-DATA/Banlist.txt"; private static string BanWordPath = $"{DataPath}/AUR-DATA/Deniedwords.txt"; public static string VipListPath = $"{DataPath}/AUR-DATA/VIP.txt"; public static string ModeratorListPath = $"{DataPath}/AUR-DATA/Moderator.txt"; public static string AdminListPath = $"{DataPath}/AUR-DATA/Admin.txt"; private static string TemplatePath = $"{DataPath}/AUR-DATA/Templates.txt"; private static string StartWordsPath = $"{DataPath}/AUR-DATA/Startwords.txt"; public static Dictionary Templates = new(); public static void Init() { try { if (!Directory.Exists($"{DataPath}/AUR-DATA")) Directory.CreateDirectory($"{DataPath}/AUR-DATA"); CheckFile(DenyNameListPath, Translator.Get("bannameWelcome"), "DeniedNames.txt"); CheckFile(BanListPath, Translator.Get("banlistWelcome"), "Banlist.txt"); CheckFile(BanWordPath, Translator.Get("banwordWelcome"), "DeniedWords.txt"); CheckFile(VipListPath, Translator.Get("vipWelcome"), "VIP.txt"); CheckFile(ModeratorListPath, Translator.Get("moderatorWelcome"), "Moderator.txt"); CheckFile(AdminListPath, Translator.Get("adminWelcome"), "Admin.txt"); CheckFile(TemplatePath, Translator.Get("templateWelcome"), "Templates.txt"); CheckFile(StartWordsPath, Translator.Get("startWordsWelcome"), "StartWords.txt"); LoadTemplates(); } catch (Exception ex) { Logger.Exception(ex, "BanManager"); } } private static string GetResourcesTxt(string path) { var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); stream.Position = 0; using StreamReader reader = new(stream, Encoding.UTF8); return reader.ReadToEnd(); } public static string GetHashedPuid(this ClientData player) { if (player == null) return ""; string puid = player.ProductUserId; return GetHashedPuid(puid); } public static string GetHashedPuid(string puid) { using SHA256 sha256 = SHA256.Create(); // get sha-256 hash byte[] sha256Bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(puid)); string sha256Hash = BitConverter.ToString(sha256Bytes).Replace("-", "").ToLower(); // pick front 5 and last 4 return string.Concat(sha256Hash.AsSpan(0, 5), sha256Hash.AsSpan(sha256Hash.Length - 4)); } public static void AddBanPlayer(ClientData player) { if (!AmongUsClient.Instance.AmHost || player == null) return; if (!CheckBanList(player?.FriendCode, player?.GetHashedPuid())) { if (player?.GetHashedPuid() != "" && player?.GetHashedPuid() != null && player?.GetHashedPuid() != "e3b0cb855") { var additionalInfo = ""; File.AppendAllText(BanListPath, $"{player?.FriendCode},{player?.GetHashedPuid()},{player.PlayerName.RemoveHtmlTags()}{additionalInfo}\n"); Logger.SendInGame($"Added {player?.PlayerName.RemoveHtmlTags()}/{player?.FriendCode}/{player?.GetHashedPuid()} to the BanList"); } else Logger.Info($"Failed to add player {player?.PlayerName.RemoveHtmlTags()}/{player?.FriendCode}/{player?.GetHashedPuid()} to the BanList", "AddBanPlayer"); } } public static void CheckBanPlayer(ClientData player) { if (!AmongUsClient.Instance.AmHost) return; string friendcode = player?.FriendCode; // Check file BanList.txt if (CheckBanList(friendcode, player?.GetHashedPuid())) { AmongUsClient.Instance.KickPlayer(player.Id, true); Logger.Info($"{player.PlayerName} was in the BanList and has been banned", "BanListBan"); return; } } public static bool CheckBanList(string code, string hashedpuid = "") { bool OnlyCheckPuid = false; if (code == "" && hashedpuid != "") OnlyCheckPuid = true; else if (code == "") return false; string noDiscrim = ""; if (code.Contains('#')) { int index = code.IndexOf('#'); noDiscrim = code[..index]; } try { if (!Directory.Exists($"{DataPath}/AUR-DATA")) Directory.CreateDirectory($"{DataPath}/AUR-DATA"); if (!File.Exists(BanListPath)) File.Create(BanListPath).Close(); using StreamReader sr = new(BanListPath); string line; while ((line = sr.ReadLine()) != null) { if (line.StartsWith("#")) continue; if (line == "") continue; if (!OnlyCheckPuid) { if (line.Contains(code)) return true; if (!string.IsNullOrEmpty(noDiscrim) && !line.Contains('#') && line.Contains(noDiscrim)) return true; } if (line.Contains(hashedpuid)) return true; } } catch (Exception ex) { Logger.Exception(ex, "CheckBanList"); } return false; } public static bool IsPlayerInDenyName(ClientData client, string name) { if (name == "" || !AmongUsClient.Instance.AmHost) return false; var deniedNames = File.ReadAllLines(DenyNameListPath).Where(x => !x.StartsWith("#")); if (deniedNames.Where(code => !string.IsNullOrWhiteSpace(code)).Any(code => name.Contains(code, StringComparison.OrdinalIgnoreCase))) { AmongUsClient.Instance.KickPlayer(client.Id, false); Logger.Info($" {name} was kicked because their name was in DenyNameList.txt", "Kick"); Logger.SendInGame($"{name} was kicked because their name was in DenyNameList.txt"); return true; } else return false; } public static bool IsWordBanned(PlayerControl player, string input) { if (input == "" || !AmongUsClient.Instance.AmHost || Utils.CheckAccessLevel(player.Data.FriendCode) > 0 || !Options.EnableDeniedWords.GetBool()) return false; int clientId = player.Data.ClientId; var bannedWords = File.ReadAllLines(BanWordPath).Where(x => !x.StartsWith("#")); if ((Options.DeniedWordsStrength.GetBool() && bannedWords.Where(code => !string.IsNullOrWhiteSpace(code)).Any(code => input.Contains(code, StringComparison.OrdinalIgnoreCase))) || (!Options.DeniedWordsStrength.GetBool() && bannedWords.Where(code => !string.IsNullOrWhiteSpace(code)).Any(code => input.Equals(code, StringComparison.OrdinalIgnoreCase)))) { if (!Main.SayDeniedWordTimes.ContainsKey(clientId)) { Main.SayDeniedWordTimes.Add(clientId, 0); } Main.SayDeniedWordTimes[clientId]++; if (Main.SayDeniedWordTimes[clientId] >= Options.DeniedWordsToKick.GetInt()) { if (Options.DeniedWordsAsBan.GetBool()) { AmongUsClient.Instance.KickPlayer(clientId, true); Logger.SendInGame(Translator.Get("banWordBan", player.Data.PlayerName, Options.DeniedWordsToKick.GetInt())); } else { AmongUsClient.Instance.KickPlayer(clientId, false); Logger.SendInGame(Translator.Get("banWordKick", player.Data.PlayerName, Options.DeniedWordsToKick.GetInt())); } Logger.Info(Translator.Get("banWordKick", player.Data.PlayerName, Options.DeniedWordsToKick.GetInt()), "DeniedWordKick"); } return true; } else return false; } public static bool IsStartWord(PlayerControl player, string input) { if (input == "" || !AmongUsClient.Instance.AmHost || Utils.CheckAccessLevel(player.Data.FriendCode) > 0 || !Options.AutoKickStart.GetBool() || !Utils.IsLobby) return false; int clientId = player.Data.ClientId; var startWords = File.ReadAllLines(StartWordsPath).Where(x => !x.StartsWith("#")); if ((Options.AutoKickStartStrength.GetBool() && startWords.Where(code => !string.IsNullOrWhiteSpace(code)).Any(code => input.Contains(code, StringComparison.OrdinalIgnoreCase))) || (!Options.AutoKickStartStrength.GetBool() && startWords.Where(code => !string.IsNullOrWhiteSpace(code)).Any(code => input.Equals(code, StringComparison.OrdinalIgnoreCase)))) { if (!Main.SayStartTimes.ContainsKey(clientId)) { Main.SayStartTimes.Add(clientId, 0); } Main.SayStartTimes[clientId]++; if (Main.SayStartTimes[clientId] >= Options.AutoKickStartTimes.GetInt()) { if (Options.AutoKickStartAsBan.GetBool()) { AmongUsClient.Instance.KickPlayer(clientId, true); Logger.SendInGame(Translator.Get("startWordBan", player.Data.PlayerName, Options.AutoKickStartTimes.GetInt())); } else { AmongUsClient.Instance.KickPlayer(clientId, false); Logger.SendInGame(Translator.Get("startWordKick", player.Data.PlayerName, Options.AutoKickStartTimes.GetInt())); } Logger.Info(Translator.Get("startWordKick", player.Data.PlayerName, Options.AutoKickStartTimes.GetInt()), "StartWordKick"); } return true; } else return false; } [HarmonyPatch(typeof(BanMenu), nameof(BanMenu.Select))] class BanMenuSelectPatch { public static void Postfix(BanMenu __instance, int clientId) { ClientData recentClient = AmongUsClient.Instance.GetRecentClient(clientId); if (recentClient == null) return; if (!BanManager.CheckBanList(recentClient?.FriendCode, recentClient?.GetHashedPuid())) __instance.BanButton.GetComponent().SetEnabledColors(); } } public static void LoadTemplates() { Templates.Clear(); if (!File.Exists(TemplatePath)) return; foreach (var line in File.ReadAllLines(TemplatePath)) { if (string.IsNullOrWhiteSpace(line)) continue; if (line.StartsWith("#")) continue; int splitIndex = line.IndexOf(':'); if (splitIndex == -1) continue; string key = line[..splitIndex].Trim().ToLower(); string value = line[(splitIndex + 1)..].Trim(); if (!Templates.ContainsKey(key)) Templates.Add(key, value); } } private static void CheckFile(string path, string defaultContent, string name) { try { if (!File.Exists(path)) { Logger.Warn($"Creating a new {name} file", "BanManager"); File.WriteAllText(path, defaultContent); } } catch (Exception ex) { Logger.Exception(ex, "BanManager"); } } public static string CheckTemplate(string text) { Dictionary variables = new() { { "ModVersion", Main.ModVersion }, { "\\n", Environment.NewLine }, { "FirstDeath", NormalGameEndChecker.firstDeath } }; foreach (var kvp in variables) { text = text.Replace($"{{{kvp.Key}}}", kvp.Value); } text = text.Replace("\\n", Environment.NewLine); return text; } }