using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.RegularExpressions; using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Plugin.Host; using CounterStrikeSharp.API.Core.Translations; using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API.Modules.Commands; using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Entities; using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.ValveConstants.Protobuf; using CS2_SimpleAdmin.Managers; using CS2_SimpleAdminApi; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using ZLinq; namespace CS2_SimpleAdmin; internal static class Helper { private static readonly string AssemblyName = Assembly.GetExecutingAssembly().GetName().Name ?? ""; private static readonly string CfgPath = $"{Server.GameDirectory}/csgo/addons/counterstrikesharp/configs/plugins/{AssemblyName}/{AssemblyName}.json"; private delegate nint CNetworkSystemUpdatePublicIp(nint a1); private static CNetworkSystemUpdatePublicIp? _networkSystemUpdatePublicIp; public static bool IsDebugBuild { get { #if DEBUG return true; #else return false; #endif } } public static string GetCallerName(CCSPlayerController? caller) { if (caller != null && !string.IsNullOrEmpty(caller.PlayerName)) { return caller.PlayerName; } return CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console"; } public static string GetReasonOrUnknown(string? reason) { if (!string.IsNullOrWhiteSpace(reason)) { return reason; } return CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; } /// /// Joins the command arguments from on as the penalty reason, "Unknown" when blank. /// public static string GetArgsReason(CommandInfo command, int firstArg) { string reason = string.Empty; if (command.ArgCount > firstArg) { reason = string.Join(" ", Enumerable.Range(firstArg, command.ArgCount - firstArg).Select(command.GetArg)).Trim(); } return GetReasonOrUnknown(reason); } public static PlayerInfo? GetAdminInfo(CCSPlayerController? caller) { if (caller == null || !caller.UserId.HasValue) { return null; } return CS2_SimpleAdmin.PlayersInfo[caller.SteamID]; } public static string GetLogName(CCSPlayerController player) { if (string.IsNullOrEmpty(player.PlayerName)) { return player.SteamID.ToString(); } return player.PlayerName; } public static string Truncate(string value, int maxLength) { if (value.Length > maxLength) { return value[..maxLength]; } return value; } public static bool HasCommandPermission(CCSPlayerController admin, string command, string defaultFlag) { SteamID steamId = new SteamID(admin.SteamID); if (AdminManager.CommandIsOverriden(command)) { return AdminManager.PlayerHasPermissions(steamId, AdminManager.GetPermissionOverrides(command)); } return AdminManager.PlayerHasPermissions(steamId, defaultFlag); } public static List GetPlayerFromName(string name) { return GetValidPlayers().FindAll(x => x.PlayerName.Equals(name, StringComparison.OrdinalIgnoreCase)); } public static CCSPlayerController? GetPlayerFromSteamid64(ulong steamid) { return GetValidPlayers().FirstOrDefault(x => x.SteamID == steamid); } public static List GetPlayerFromIp(string ipAddress) { return CS2_SimpleAdmin.CachedPlayers.FindAll(x => x.IpAddress != null && x.IpAddress.Split(":")[0].Equals(ipAddress)); } public static List GetValidPlayers() { return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.Connected).ToList(); } public static List GetValidPlayersWithBots() { return CS2_SimpleAdmin.CachedPlayers.Concat(CS2_SimpleAdmin.BotPlayers).AsValueEnumerable().ToList(); } // public static bool IsValidSteamId64(string input) // { // const string pattern = @"^\d{17}$"; // return Regex.IsMatch(input, pattern); // } public static bool ValidateSteamId(string input, out SteamID? steamId) { steamId = null; if (string.IsNullOrEmpty(input)) { return false; } if (!SteamID.TryParse(input, out SteamID? parsedSteamId)) { return false; } steamId = parsedSteamId; return true; } public static bool IsValidIp(string input) { const string pattern = @"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; return Regex.IsMatch(input, pattern); } public static void GivePlayerFlags(SteamID? steamid, List? flags = null, uint immunity = 0) { try { if (steamid == null || (flags == null && immunity == 0)) { return; } if (flags == null) { return; } foreach (string? flag in flags.Where(flag => !string.IsNullOrEmpty(flag))) { if (flag.StartsWith($"@")) { //Console.WriteLine($"Adding permission {flag} to SteamID {steamid}"); AdminManager.AddPlayerPermissions(steamid, flag); } else if (flag.StartsWith($"#")) { //Console.WriteLine($"Adding SteamID {steamid} to group {flag}"); AdminManager.AddPlayerToGroup(steamid, flag); } } AdminManager.SetPlayerImmunity(steamid, immunity); } catch { } } public static void KickPlayer(int userId, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0) { CCSPlayerController? player = Utilities.GetPlayerFromUserid(userId); if (player == null || !player.IsValid || player.IsHLTV) { return; } if (player.UserId.HasValue && CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out PlayerInfo? value)) { value.WaitingForKick = true; } // player.CommitSuicide(true, true); player.VoiceFlags = VoiceFlags.Muted; CCSPlayerPawn? playerPawn = player.PlayerPawn.Value; if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE) { playerPawn.Freeze(); playerPawn.Colorize(255, 0, 0); CPlayer_WeaponServices? weaponServices = playerPawn.WeaponServices; if (weaponServices == null) { return; } foreach (CHandle _weap in weaponServices.MyWeapons) { CBasePlayerWeapon? weapon = _weap.Value; ; if (weapon == null || !weapon.IsValid) { continue; } if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot")) { continue; } weapon.NextPrimaryAttackTick = Server.TickCount + 999; weapon.NextSecondaryAttackTick = Server.TickCount + 999; Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick"); Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick"); } } if (delay > 0) { CS2_SimpleAdmin.Instance.AddTimer(delay, () => { if (!player.IsValid || player.IsHLTV) { return; } // Server.ExecuteCommand($"kickid {player.UserId}"); playerPawn?.Colorize(); player.Disconnect(reason); }); } else { // Server.ExecuteCommand($"kickid {player.UserId}"); playerPawn?.Colorize(); player.Disconnect(reason); } if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED) { Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}"); } // if (!string.IsNullOrEmpty(reason)) // { // var escapeChars = reason.IndexOfAny([';', '|']); // // if (escapeChars != -1) // { // reason = reason[..escapeChars]; // } // } // // Server.ExecuteCommand($"kickid {userId} {reason}"); } public static void KickPlayer(CCSPlayerController player, NetworkDisconnectionReason reason = NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKED, int delay = 0) { if (!player.IsValid || player.IsHLTV) { return; } if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(player.SteamID, out PlayerInfo? value)) { if (value.WaitingForKick) { return; } value.WaitingForKick = true; } player.VoiceFlags = VoiceFlags.Muted; CCSPlayerPawn? playerPawn = player.PlayerPawn.Value; if (playerPawn != null && playerPawn.LifeState == (int)LifeState_t.LIFE_ALIVE) { playerPawn.Freeze(); playerPawn.Colorize(255, 0, 0); CPlayer_WeaponServices? weaponServices = playerPawn.WeaponServices; if (weaponServices == null) { return; } foreach (CHandle _weap in weaponServices.MyWeapons) { CBasePlayerWeapon? weapon = _weap.Value; ; if (weapon == null || !weapon.IsValid) { continue; } if (weapon.DesignerName.Contains("c4") || weapon.DesignerName.Contains("healthshot")) { continue; } weapon.NextPrimaryAttackTick = Server.TickCount + 999; weapon.NextSecondaryAttackTick = Server.TickCount + 999; Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick"); Utilities.SetStateChanged(weapon, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick"); } } if (delay > 0) { CS2_SimpleAdmin.Instance.AddTimer(delay, () => { if (!player.IsValid || player.IsHLTV) { return; } // if (!string.IsNullOrEmpty(reason)) // { // var escapeChars = reason.IndexOfAny([';', '|']); // // if (escapeChars != -1) // { // reason = reason[..escapeChars]; // } // } // // Server.ExecuteCommand($"kickid {player.UserId}"); player.Disconnect(reason); }); } else { // Server.ExecuteCommand($"kickid {player.UserId}"); player.Disconnect(reason); } if (CS2_SimpleAdmin.UnlockedCommands && reason == NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED) { Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}"); } // if (!string.IsNullOrEmpty(reason)) // { // var escapeChars = reason.IndexOfAny([';', '|']); // // if (escapeChars != -1) // { // reason = reason[..escapeChars]; // } // } // // Server.ExecuteCommand($"kickid {userId} {reason}"); } public static int ParsePenaltyTime(string time) { time = time.ToLower(); if (string.IsNullOrWhiteSpace(time) || !time.Any(char.IsDigit)) { // CS2_SimpleAdmin._logger?.LogError("Time string cannot be null or empty."); return -1; } if (time.Equals($"0")) { return 0; } Dictionary timeUnits = new Dictionary { { "m", 1 }, // Minute { "h", 60 }, // Hour { "d", 1440 }, // Day (24 * 60) { "w", 10080 }, // Week (7 * 24 * 60) { "mo", 43200 }, // Month (30 * 24 * 60) { "y", 525600 } // Year (365 * 24 * 60) }; // Check if the input is purely numeric (e.g., "10" for 10 minutes) if (int.TryParse(time, out int numericMinutes)) { return numericMinutes; } int totalMinutes = 0; Regex regex = new Regex(@"(\d+)([a-z]+)"); MatchCollection matches = regex.Matches(time); foreach (Match match in matches) { int value = int.Parse(match.Groups[1].Value); // Numeric part string unit = match.Groups[2].Value; // Unit part if (timeUnits.TryGetValue(unit, out int minutesPerUnit)) { totalMinutes += value * minutesPerUnit; } else { throw new ArgumentException($"Invalid time unit '{unit}' in time string.", nameof(time)); } } if (totalMinutes > 0) { return totalMinutes; } return -1; } public static void PrintToCenterAll(string message) { Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false }).ToList().ForEach(controller => { controller.PrintToCenter(message); }); } internal static void LogCommand(CCSPlayerController? caller, CommandInfo command) { if (CS2_SimpleAdmin._localizer == null) { return; } string playerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer["sa_console"]; string hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer["sa_unknown"]; CS2_SimpleAdmin.Instance.Logger.LogInformation($"{CS2_SimpleAdmin._localizer[ "sa_discord_log_command", playerName, command.GetCommandString]}".Replace("HOSTNAME", hostname).Replace("**", "")); SendDiscordLogMessage(caller, command, CS2_SimpleAdmin._localizer); } internal static void LogCommand(CCSPlayerController? caller, string command) { if (CS2_SimpleAdmin._localizer == null) { return; } string playerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer["sa_console"]; ConVar? hostnameCvar = ConVar.Find("hostname"); string hostname = hostnameCvar?.StringValue ?? CS2_SimpleAdmin._localizer["sa_unknown"]; CS2_SimpleAdmin.Instance.Logger.LogInformation($"{CS2_SimpleAdmin._localizer["sa_discord_log_command", playerName, command]}".Replace("HOSTNAME", hostname).Replace("**", "")); SendDiscordLogMessage(caller, command, CS2_SimpleAdmin._localizer); } /*public static IEnumerable GenerateEmbedsDiscord(string title, string description, string thumbnailUrl, Color color, string[] fieldNames, string[] fieldValues, bool[] inlineFlags) { var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; var address = $"{ConVar.Find("ip")?.StringValue}:{ConVar.Find("hostport")!.GetPrimitiveValue()}"; description = description.Replace("{hostname}", hostname); description = description.Replace("{address}", address); var embed = new EmbedBuilder { Title = title, Description = description, ThumbnailUrl = thumbnailUrl, Color = color, }; for (var i = 0; i < fieldNames.Length; i++) { fieldValues[i] = fieldValues[i].Replace("{hostname}", hostname ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"); fieldValues[i] = fieldValues[i].Replace("{address}", address ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"); embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]); if ((i + 1) % 2 == 0 && i < fieldNames.Length - 1) { embed.AddField("\u200b", "\u200b"); } } return new List { embed.Build() }; }*/ private static void SendDiscordLogMessage(CCSPlayerController? caller, CommandInfo command, IStringLocalizer? localizer) { if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || localizer == null) { return; } string communityUrl; if (caller != null) { communityUrl = "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">"; } else { communityUrl = ""; } string callerName = Helper.GetCallerName(caller); _ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command.GetCommandString])); } private static void SendDiscordLogMessage(CCSPlayerController? caller, string command, IStringLocalizer? localizer) { if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || localizer == null) { return; } string communityUrl; if (caller != null) { communityUrl = "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">"; } else { communityUrl = ""; } string callerName = Helper.GetCallerName(caller); _ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord(localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", command])); } /// /// Resolves the admin name one recipient should see, honoring ShowActivityType: /// on type 0 and 1 only admins see the real name, everyone else sees the generic ADMIN label. /// The generic and console labels are uppercased so they read as labels beside real player names. /// /// The player the message is being sent to. /// Name of the admin who ran the command, or null for console. internal static string ResolveActivityName(CCSPlayerController recipient, string? callerName) { bool hideFromPlayers = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType is 0 or 1; if (hideFromPlayers && !AdminManager.PlayerHasPermissions(new SteamID(recipient.SteamID), "@css/kick") && !AdminManager.PlayerHasPermissions(new SteamID(recipient.SteamID), "@css/ban")) { return (CS2_SimpleAdmin._localizer?["sa_admin"] ?? "Admin").ToString().ToUpperInvariant(); } return callerName ?? (CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console").ToString().ToUpperInvariant(); } public static void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs) { string[] publishActions = ["ban", "gag", "silence", "mute"]; if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) { return; } if (CS2_SimpleAdmin._localizer == null) { return; } if (string.IsNullOrWhiteSpace(callerName)) { callerName = CS2_SimpleAdmin._localizer["sa_console"]; } string[] formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray(); if (!dontPublish && publishActions.Any(messageKey.Contains)) { CS2_SimpleAdmin.SimpleAdminApi?.OnAdminShowActivityEvent(messageKey, callerName, dontPublish, messageArgs); } // // Replace placeholder based on showActivityType // for (var i = 0; i < formattedMessageArgs.Length; i++) // { // var arg = formattedMessageArgs[i]; // Convert argument to string if not null // // Replace "CALLER" placeholder in the argument string // formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch // { // 1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]), // 2 => arg.Replace("CALLER", callerName ?? "Console"), // _ => arg // }; // } IEnumerable validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false }); if (!validPlayers.Any()) { return; } if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3) { validPlayers = validPlayers.Where(c => AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban")); } foreach (CCSPlayerController? controller in validPlayers.ToList()) { string[] currentMessageArgs = (string[])formattedMessageArgs.Clone(); // Replace "CALLER" placeholder based on showActivityType and whether the recipient is an admin string shownName = ResolveActivityName(controller, callerName); for (int i = 0; i < currentMessageArgs.Length; i++) { currentMessageArgs[i] = currentMessageArgs[i].Replace("CALLER", shownName); } // Send the localized message to each player controller.SendLocalizedMessage(CS2_SimpleAdmin._localizer, messageKey, currentMessageArgs.Cast().ToArray()); } } /// /// Shows admin activity with a custom translated message (for modules with their own localizer). /// public static void ShowAdminActivityTranslated(string translatedMessage, string? callerName = null, bool dontPublish = false) { if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) { return; } if (CS2_SimpleAdmin._localizer == null) { return; } if (string.IsNullOrWhiteSpace(callerName)) { callerName = CS2_SimpleAdmin._localizer["sa_console"]; } IEnumerable validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false }); if (!validPlayers.Any()) { return; } if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3) { validPlayers = validPlayers.Where(c => AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban")); } foreach (CCSPlayerController? controller in validPlayers.ToList()) { // Only admins see who did it when showActivityType is 1 string? visibleCallerName = callerName; if (!AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/kick") && !AdminManager.PlayerHasPermissions(new SteamID(controller.SteamID), "@css/ban")) { visibleCallerName = CS2_SimpleAdmin._localizer["sa_admin"]; } // Replace "CALLER" placeholder based on showActivityType string message = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch { 1 => translatedMessage.Replace("CALLER", visibleCallerName), _ => translatedMessage.Replace("CALLER", callerName ?? CS2_SimpleAdmin._localizer["sa_console"]), }; // Send the pre-translated message to the player controller.PrintToChat(message); } } /// /// Shows admin activity using module's localizer for per-player language support. /// Each player receives the message in their configured language using SendLocalizedMessage. /// public static void ShowAdminActivityLocalized(IStringLocalizer moduleLocalizer, string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs) { if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 0) { return; } if (CS2_SimpleAdmin._localizer == null) { return; } if (string.IsNullOrWhiteSpace(callerName)) { callerName = CS2_SimpleAdmin._localizer["sa_console"]; } string[] formattedMessageArgs = messageArgs.Select(arg => arg.ToString() ?? string.Empty).ToArray(); IEnumerable validPlayers = GetValidPlayers().Where(c => c is { IsValid: true, IsBot: false }); if (!validPlayers.Any()) { return; } if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType == 3) { validPlayers = validPlayers.Where(c => AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/kick") || AdminManager.PlayerHasPermissions(new SteamID(c.SteamID), "@css/ban")); } foreach (CCSPlayerController? controller in validPlayers.ToList()) { string[] currentMessageArgs = (string[])formattedMessageArgs.Clone(); // Replace "CALLER" placeholder based on showActivityType string shownName = ResolveActivityName(controller, callerName); for (int i = 0; i < currentMessageArgs.Length; i++) { currentMessageArgs[i] = currentMessageArgs[i].Replace("CALLER", shownName); } // Send the localized message to each player using their language controller.SendLocalizedMessage(moduleLocalizer, messageKey, currentMessageArgs.Cast().ToArray()); } } public static void DisplayCenterMessage( CCSPlayerController player, string messageKey, string? callerName = null, params object[] messageArgs) { if (CS2_SimpleAdmin._localizer == null) { return; } // Determine the localized message key string localizedMessageKey = $"{messageKey}"; string[] formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray(); // Replace placeholder based on showActivityType for (int i = 0; i < formattedMessageArgs.Length; i++) { string arg = formattedMessageArgs[i]; // Convert argument to string if not null // Replace "CALLER" placeholder in the argument string formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch { 1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]), 2 => arg.Replace("CALLER", callerName ?? "Console"), _ => arg }; } // Print the localized message to the center of the screen for the player using (new WithTemporaryCulture(player.GetLanguage())) { player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast().ToArray()]); } } private static string ConvertMinutesToTime(int minutes) { TimeSpan time = TimeSpan.FromMinutes(minutes); if (time.Days > 0) { return $"{time.Days}d {time.Hours}h {time.Minutes}m"; } if (time.Hours > 0) { return $"{time.Hours}h {time.Minutes}m"; } return $"{time.Minutes}m"; } public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, CCSPlayerController? target, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer) { if (localizer == null) { return; } DiscordPenaltySetting[] penaltySetting = penalty switch { PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings, PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyMuteSettings, PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyGagSettings, PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltySilenceSettings, PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyWarnSettings, _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null) }; string? webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value; if (string.IsNullOrEmpty(webhookUrl)) { return; } const string defaultCommunityUrl = ""; string callerCommunityUrl; if (caller != null) { callerCommunityUrl = $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>"; } else { callerCommunityUrl = defaultCommunityUrl; } string targetCommunityUrl; if (target != null) { targetCommunityUrl = $"<{new SteamID(target.SteamID).ToCommunityUrl()}>"; } else { targetCommunityUrl = defaultCommunityUrl; } string callerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console"; string targetName = target?.PlayerName ?? localizer["sa_unknown"]; string targetSteamId; if (target != null) { targetSteamId = new SteamID(target.SteamID).SteamId64.ToString(); } else { targetSteamId = localizer["sa_unknown"]; } DateTime futureTime = Time.ActualDateTime().AddMinutes(duration); long futureUnixTimestamp = new DateTimeOffset(futureTime).ToUnixTimeSeconds(); string time; if (penaltySetting.FirstOrDefault(s => s.Name.Equals("Time"))?.Value == "{relative}") { if (duration != 0) { time = $""; } else { time = localizer["sa_permanent"]; } } else { if (duration != 0) { time = ConvertMinutesToTime(duration); } else { time = localizer["sa_permanent"]; } } string[] fieldNames = [ localizer["sa_player"], localizer["sa_steamid"], localizer["sa_duration"], localizer["sa_reason"], localizer["sa_admin"]]; string[] fieldValues = [ $"[{targetName}]({targetCommunityUrl})", $"||{targetSteamId}||", time, reason, $"[{callerName}]({callerCommunityUrl})" ]; bool[] inlineFlags = [true, true, true, false, false]; string hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"]; string? colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value; string colorHex; if (string.IsNullOrWhiteSpace(colorValue)) { colorHex = "#FFFFFF"; } else { colorHex = colorValue.Trim(); } Embed embed = new Embed { Color = DiscordManager.ColorFromHex(colorHex), Title = penalty switch { PenaltyType.Ban => localizer["sa_discord_penalty_ban"], PenaltyType.Mute => localizer["sa_discord_penalty_mute"], PenaltyType.Gag => localizer["sa_discord_penalty_gag"], PenaltyType.Silence => localizer["sa_discord_penalty_silence"], PenaltyType.Warn => localizer["sa_discord_penalty_warn"], _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null) }, Description = $"{hostname}", ThumbnailUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ThumbnailUrl"))?.Value, ImageUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ImageUrl"))?.Value, Footer = new Footer { Text = penaltySetting.FirstOrDefault(s => s.Name.Equals("Footer"))?.Value }, Timestamp = Time.ActualDateTime().ToUniversalTime().ToString("o"), }; for (int i = 0; i < fieldNames.Length; i++) { embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]); } Task.Run(async () => { try { await new DiscordManager(webhookUrl).SendEmbedAsync(embed); } catch (Exception ex) { // Log or handle the exception CS2_SimpleAdmin._logger?.LogError("Unable to send discord webhook: {exception}", ex.Message); } }); } public static void SendDiscordPenaltyMessage(CCSPlayerController? caller, string steamId, string reason, int duration, PenaltyType penalty, IStringLocalizer? localizer) { if (localizer == null) { return; } DiscordPenaltySetting[] penaltySetting = penalty switch { PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyBanSettings, PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyMuteSettings, PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyGagSettings, PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltySilenceSettings, PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.Discord.DiscordPenaltyWarnSettings, _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null) }; string? webhookUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("Webhook"))?.Value; if (string.IsNullOrEmpty(webhookUrl)) { return; } const string defaultCommunityUrl = ""; string callerCommunityUrl; if (caller != null) { callerCommunityUrl = $"<{new SteamID(caller.SteamID).ToCommunityUrl()}>"; } else { callerCommunityUrl = defaultCommunityUrl; } string targetCommunityUrl = $"<{new SteamID(ulong.Parse(steamId)).ToCommunityUrl()}>"; string callerName = caller?.PlayerName ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console"; string targetName = steamId; string targetSteamId = steamId; DateTime futureTime = Time.ActualDateTime().AddMinutes(duration); long futureUnixTimestamp = new DateTimeOffset(futureTime).ToUnixTimeSeconds(); string time; if (penaltySetting.FirstOrDefault(s => s.Name.Equals("Time"))?.Value == "{relative}") { if (duration != 0) { time = $""; } else { time = localizer["sa_permanent"]; } } else { if (duration != 0) { time = ConvertMinutesToTime(duration); } else { time = localizer["sa_permanent"]; } } string[] fieldNames = [ localizer["sa_player"], localizer["sa_steamid"], localizer["sa_duration"], localizer["sa_reason"], localizer["sa_admin"]]; string[] fieldValues = [ $"[{targetName}]({targetCommunityUrl})", $"||{targetSteamId}||", time, reason, $"[{callerName}]({callerCommunityUrl})" ]; bool[] inlineFlags = [true, true, true, false, false]; string hostname = ConVar.Find("hostname")?.StringValue ?? localizer["sa_unknown"]; string? colorValue = penaltySetting.FirstOrDefault(s => s.Name.Equals("Color"))?.Value; string colorHex; if (string.IsNullOrWhiteSpace(colorValue)) { colorHex = "#FFFFFF"; } else { colorHex = colorValue.Trim(); } Embed embed = new Embed { Color = DiscordManager.ColorFromHex(colorHex), Title = penalty switch { PenaltyType.Ban => localizer["sa_discord_penalty_ban"], PenaltyType.Mute => localizer["sa_discord_penalty_mute"], PenaltyType.Gag => localizer["sa_discord_penalty_gag"], PenaltyType.Silence => localizer["sa_discord_penalty_silence"], PenaltyType.Warn => localizer["sa_discord_penalty_warn"], _ => throw new ArgumentOutOfRangeException(nameof(penalty), penalty, null) }, Description = $"{hostname}", ThumbnailUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ThumbnailUrl"))?.Value, ImageUrl = penaltySetting.FirstOrDefault(s => s.Name.Equals("ImageUrl"))?.Value, Footer = new Footer { Text = penaltySetting.FirstOrDefault(s => s.Name.Equals("Footer"))?.Value }, Timestamp = Time.ActualDateTime().ToUniversalTime().ToString("o"), }; for (int i = 0; i < fieldNames.Length; i++) { embed.AddField(fieldNames[i], fieldValues[i], inlineFlags[i]); } Task.Run(async () => { try { await new DiscordManager(webhookUrl).SendEmbedAsync(embed); } catch (Exception ex) { Console.WriteLine(ex); } }); } private static string GenerateMessageDiscord(string message) { string hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; string address = $"{ConVar.Find("ip")?.StringValue}:{ConVar.Find("hostport")!.GetPrimitiveValue()}"; message = message.Replace("HOSTNAME", hostname); message = message.Replace("ADDRESS", address); return message; } public static string[] SeparateLines(string message) { return message.Split(["\r\n", "\r", "\n"], StringSplitOptions.None); } public static string CenterMessage(string message) => $"⠀⠀⠀⠀⠀⠀⠀⠀{message}⠀⠀⠀⠀⠀⠀⠀⠀"; public static string GetServerIp() { nint networkSystem = NativeAPI.GetValveInterface(0, "NetworkSystemVersion001"); unsafe { if (_networkSystemUpdatePublicIp == null) { nint funcPtr = *(nint*)(*(nint*)(networkSystem) + 256); _networkSystemUpdatePublicIp = Marshal.GetDelegateForFunctionPointer(funcPtr); } /* struct netadr_t { uint32_t type uint8_t ip[4] uint16_t port } */ // + 4 to skip type, because the size of uint32_t is 4 bytes byte* ipBytes = (byte*)(_networkSystemUpdatePublicIp(networkSystem) + 4); // port is always 0, use the one from convar "hostport" return $"{ipBytes[0]}.{ipBytes[1]}.{ipBytes[2]}.{ipBytes[3]}"; } } public static void UpdateConfig(BasePluginConfig config) { // get newest config version Type configType = config.GetType(); int newCfgVersion = ((BasePluginConfig)Activator.CreateInstance(configType)!).Version; // loaded config is up to date if (config.Version == newCfgVersion) { return; } // Load existing JSON file and update version property if (!File.Exists(CfgPath)) { return; } string json = File.ReadAllText(CfgPath); JsonNode? node = JsonNode.Parse(json); if (node != null) { node["Version"] = newCfgVersion; string updatedJsonContent = node.ToJsonString(new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); File.WriteAllText(CfgPath, updatedJsonContent); } } public static void TryLogCommandOnDiscord(CCSPlayerController? caller, string commandString) { if (CS2_SimpleAdmin.DiscordWebhookClientLog == null || CS2_SimpleAdmin._localizer == null) { return; } if (caller != null && !caller.IsValid) { caller = null; } string callerName = Helper.GetCallerName(caller); string communityUrl; if (caller != null) { communityUrl = "<" + new SteamID(caller.SteamID).ToCommunityUrl() + ">"; } else { communityUrl = ""; } _ = CS2_SimpleAdmin.DiscordWebhookClientLog.SendMessageAsync(GenerateMessageDiscord( CS2_SimpleAdmin._localizer["sa_discord_log_command", $"[{callerName}]({communityUrl})", commandString])); } internal static IPluginManager? GetPluginManager() { // Access the singleton instance of Application Application applicationInstance = Application.Instance; // Use Reflection to access the private _pluginManager field FieldInfo? pluginManagerField = typeof(Application).GetField("_pluginManager", BindingFlags.NonPublic | BindingFlags.Instance); IPluginManager? pluginManager = pluginManagerField?.GetValue(applicationInstance) as IPluginManager; return pluginManager; } } public static class PluginInfo { internal static async Task CheckVersion(string localVersion, ILogger logger) { ShowAd(localVersion); const string versionUrl = "https://raw.githubusercontent.com/daffyyyy/CS2-SimpleAdmin/main/CS2-SimpleAdmin/VERSION"; HttpClient client = CS2_SimpleAdmin.HttpClient; client.Timeout = TimeSpan.FromSeconds(3); try { HttpResponseMessage response = await client.GetAsync(versionUrl); if (response.IsSuccessStatusCode) { string remoteVersion = await response.Content.ReadAsStringAsync(); remoteVersion = remoteVersion.Trim(); int comparisonResult = string.CompareOrdinal(localVersion, remoteVersion); switch (comparisonResult) { case < 0: logger.LogWarning("Plugin is outdated! Check https://github.com/daffyyyy/CS2-SimpleAdmin"); break; case > 0: logger.LogInformation("Probably dev version detected"); break; default: logger.LogInformation("Plugin is up to date"); break; } } else { logger.LogWarning($"Failed to check version. Status Code: {response.StatusCode}"); } } catch (HttpRequestException ex) { logger.LogError(ex, "Failed to connect to the version server."); } catch (Exception ex) { logger.LogError(ex, "An error occurred while checking version."); } } private static void ShowAd(string moduleVersion) { Console.WriteLine(" "); Console.WriteLine(" _______ ___ __ __ _______ ___ _______ _______ ______ __ __ ___ __ _ "); Console.WriteLine("| || | | |_| || || | | || _ || | | |_| || | | | | |"); Console.WriteLine("| _____|| | | || _ || | | ___|| |_| || _ || || | | |_| |"); Console.WriteLine("| |_____ | | | || |_| || | | |___ | || | | || || | | |"); Console.WriteLine("|_____ || | | || ___|| |___ | ___|| || |_| || || | | _ |"); Console.WriteLine(" _____| || | | ||_|| || | | || |___ | _ || || ||_|| || | | | | |"); Console.WriteLine("|_______||___| |_| |_||___| |_______||_______||__| |__||______| |_| |_||___| |_| |__|"); Console.WriteLine(" >> Version: " + moduleVersion); Console.WriteLine(" >> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin"); Console.WriteLine(" "); } } public class SchemaString(TSchemaClass instance, string member) : NativeObject(Schema.GetSchemaValue(instance.Handle, typeof(TSchemaClass).Name, member)) where TSchemaClass : NativeObject { public unsafe void Set(string str) { byte[] bytes = GetStringBytes(str); for (int i = 0; i < bytes.Length; i++) { Unsafe.Write((void*)(Handle.ToInt64() + i), bytes[i]); } Unsafe.Write((void*)(Handle.ToInt64() + bytes.Length), 0); } private static byte[] GetStringBytes(string str) { return Encoding.UTF8.GetBytes(str); } } public static class Time { public static DateTime ActualDateTime() { if (CS2_SimpleAdmin.Instance.Config.DatabaseConfig.DatabaseType.ToLower().Equals("sqlite")) { return DateTime.UtcNow; } string timezoneId = CS2_SimpleAdmin.Instance.Config.Timezone; DateTime utcNow = DateTime.UtcNow; try { TimeZoneInfo timezone = TimeZoneInfo.FindSystemTimeZoneById(timezoneId); DateTime userTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, timezone); return userTime; } catch (TimeZoneNotFoundException) { CS2_SimpleAdmin._logger?.LogWarning($"Time zone '{timezoneId}' not found. Returning UTC time."); return utcNow; } catch (InvalidTimeZoneException) { CS2_SimpleAdmin._logger?.LogWarning($"Time zone '{timezoneId}' is invalid. Returning UTC time."); return utcNow; } } } public static class WeaponHelper { private static readonly Lazy> WeaponsEnumCache = new(BuildEnumMemberMap); private static Dictionary BuildEnumMemberMap() { Dictionary dictionary = new Dictionary(); foreach (FieldInfo field in typeof(CsItem).GetFields(BindingFlags.Public | BindingFlags.Static)) { EnumMemberAttribute? attribute = field.GetCustomAttribute(); if (attribute?.Value == null) { continue; } if (field.GetValue(null) is not CsItem csItem) { continue; } object? enumValue = field.GetValue(null); dictionary.TryAdd(attribute.Value, csItem); } return dictionary; } public static CsItem? GetEnumFromWeaponName(string weaponName) { if (WeaponsEnumCache.Value.TryGetValue(weaponName, out CsItem csItem)) { return csItem; } return null; } public static List<(string EnumMemberValue, CsItem EnumValue)> GetWeaponsByPartialName(string input) { // Normalize input for case-insensitive comparison string normalizedInput = input.ToLowerInvariant(); // Find all matching weapons based on the input List<(string EnumMemberValue, CsItem EnumValue)> matchingWeapons = WeaponsEnumCache.Value .Where(kvp => kvp.Key.Contains(normalizedInput, StringComparison.InvariantCultureIgnoreCase)) .Select(kvp => (EnumMemberValue: kvp.Key, EnumValue: kvp.Value)) .ToList(); // Check for an exact match first (string EnumMemberValue, CsItem EnumValue) exactMatch = matchingWeapons .FirstOrDefault(m => m.EnumMemberValue.Equals(input, StringComparison.OrdinalIgnoreCase)); if (exactMatch.EnumMemberValue != null) { // Return a list containing only the exact match return [exactMatch]; } // If no exact match, get all matches that start with the input List<(string EnumMemberValue, CsItem EnumValue)> filteredWeapons = matchingWeapons .Where(m => m.EnumMemberValue.StartsWith(normalizedInput, StringComparison.InvariantCultureIgnoreCase)) .ToList(); return filteredWeapons; // Return all relevant matches for the partial input } } public static class IpHelper { public static uint IpToUint(string ipAddress) { if (string.IsNullOrWhiteSpace(ipAddress)) { throw new ArgumentException("IP address cannot be null or empty.", nameof(ipAddress)); } if (!System.Net.IPAddress.TryParse(ipAddress, out IPAddress? ip)) { throw new FormatException($"Invalid IP address format: {ipAddress}"); } // Ensure it's IPv4 (IPv6 will throw) if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) { throw new FormatException("Only IPv4 addresses are supported."); } byte[] bytes = ip.GetAddressBytes(); if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); // Ensure big-endian (network order) } return BitConverter.ToUInt32(bytes, 0); } public static bool TryConvertIpToUint(string ipString, out uint ipUint) { ipUint = 0; if (string.IsNullOrWhiteSpace(ipString)) { return false; } if (!System.Net.IPAddress.TryParse(ipString, out IPAddress? ipAddress)) { return false; } byte[] bytes = ipAddress.GetAddressBytes(); if (bytes.Length != 4) { return false; } ipUint = IpToUint(ipString); return true; } public static string UintToIp(uint ipAddress) { byte[] bytes = BitConverter.GetBytes(ipAddress).Reverse().ToArray(); return new System.Net.IPAddress(bytes).ToString(); } }