From eea700bfb4c78d2d48794f54f0461bc54a1c5c22 Mon Sep 17 00:00:00 2001 From: Dawid Bepierszcz <41084667+daffyyyy@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:15:44 +0200 Subject: [PATCH] Version bump and multi-account/IP ban fixes Bump version to 1.7.9a and update package references (CounterStrikeSharp.API, Dapper, System.Linq.Async, ZLinq). Replace usages of PlayerConnectedState.PlayerConnected with PlayerConnectedState.Connected across commands, events, and helpers. Add DB method GetExpireOldPlayerIpsQuery (MySQL/SQLite) and call it to purge old player IP records during ban expiration. Improve CacheManager multi-account/ip ban detection (direct IP bans, cross-account IP checks) and adjust ban logic to respect expiration window. In PlayerManager: reduce semaphore, save player IP to DB before performing ban checks (add SavePlayerIpAddress), and always perform ban checks on connect. Simplify config upgrade logic in Helper.UpdateConfig to update the JSON file Version via JsonNode. Misc: adjust ban sound volume, minor exception catch cleanup, and add module folder to .gitignore. --- .gitignore | 1 + CS2-SimpleAdmin/CS2-SimpleAdmin.cs | 4 +- CS2-SimpleAdmin/CS2-SimpleAdmin.csproj | 8 +- CS2-SimpleAdmin/Commands/basebans.cs | 4 +- CS2-SimpleAdmin/Commands/playercommands.cs | 20 +-- CS2-SimpleAdmin/Database/IDatabaseProvider.cs | 1 + .../Database/MysqlDatabaseProvider.cs | 5 + .../Database/SqliteDatabaseProvider.cs | 3 + CS2-SimpleAdmin/Events.cs | 6 +- CS2-SimpleAdmin/Helper.cs | 28 +++-- CS2-SimpleAdmin/Managers/BanManager.cs | 3 + CS2-SimpleAdmin/Managers/CacheManager.cs | 89 +++++++++---- CS2-SimpleAdmin/Managers/PlayerManager.cs | 118 +++++++++--------- CS2-SimpleAdmin/Managers/WarnManager.cs | 2 +- CS2-SimpleAdmin/VERSION | 2 +- CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj | 2 +- .../CS2-SimpleAdmin_BanSoundModule.cs | 2 +- 17 files changed, 186 insertions(+), 112 deletions(-) diff --git a/.gitignore b/.gitignore index 28e335d..148f3cc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSetti CS2-SimpleAdmin_BanSoundModule — kopia *.user CLAUDE.md +/Modules/CS2-SimpleAdmin_BanSoundModule diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs index 85d65b6..a3dc42d 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.cs +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.cs @@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)"); public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)"; public override string ModuleAuthor => "daffyy"; - public override string ModuleVersion => "1.7.8-beta-10b"; + public override string ModuleVersion => "1.7.9a"; public override void Load(bool hotReload) { @@ -46,7 +46,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected && !p.IsHLTV).ToArray()) + foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && p is { Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToArray()) { if (!player.IsBot) PlayerManager.LoadPlayerData(player, true); diff --git a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj index e10dab3..fe752ef 100644 --- a/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj +++ b/CS2-SimpleAdmin/CS2-SimpleAdmin.csproj @@ -19,16 +19,16 @@ - + none runtime compile; build; native; contentfiles; analyzers; buildtransitive - + - - + + diff --git a/CS2-SimpleAdmin/Commands/basebans.cs b/CS2-SimpleAdmin/Commands/basebans.cs index 761ecbd..f0a20ba 100644 --- a/CS2-SimpleAdmin/Commands/basebans.cs +++ b/CS2-SimpleAdmin/Commands/basebans.cs @@ -27,7 +27,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList(); + var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToList(); if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) { @@ -373,7 +373,7 @@ public partial class CS2_SimpleAdmin var targets = GetTarget(command); if (targets == null) return; - var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList(); + var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.Connected && !player.IsHLTV).ToList(); if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0) { diff --git a/CS2-SimpleAdmin/Commands/playercommands.cs b/CS2-SimpleAdmin/Commands/playercommands.cs index 698c3f3..2330ccf 100644 --- a/CS2-SimpleAdmin/Commands/playercommands.cs +++ b/CS2-SimpleAdmin/Commands/playercommands.cs @@ -44,7 +44,7 @@ public partial class CS2_SimpleAdmin /// Optional command info for logging. internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null) { - if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return; + if (!player.IsValid || player.Connected != PlayerConnectedState.Connected) return; if (!caller.CanTarget(player)) return; // Set default caller name if not provided @@ -93,7 +93,7 @@ public partial class CS2_SimpleAdmin playersToTarget.ForEach(player => { - if (player.Connected != PlayerConnectedState.PlayerConnected) + if (player.Connected != PlayerConnectedState.Connected) return; if (caller!.CanTarget(player)) @@ -207,7 +207,7 @@ public partial class CS2_SimpleAdmin internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null) { // Check if the player is valid and connected - if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) + if (!player.IsValid || player.Connected != PlayerConnectedState.Connected) return; // Ensure the caller can target the player @@ -284,7 +284,7 @@ public partial class CS2_SimpleAdmin playersToTarget.ForEach(player => { // Check if the player is connected and can be targeted - if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player)) + if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player)) return; // Determine message key and arguments for the rename notification @@ -330,7 +330,7 @@ public partial class CS2_SimpleAdmin playersToTarget.ForEach(player => { // Check if the player is connected and can be targeted - if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player)) + if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player)) return; // Determine message key and arguments for the rename notification @@ -379,7 +379,7 @@ public partial class CS2_SimpleAdmin return; destinationPlayer = targets.Players.FirstOrDefault(p => - p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); + p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null) return; @@ -399,7 +399,7 @@ public partial class CS2_SimpleAdmin return; playersToTeleport = targets.Players - .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) + .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) .ToList(); if (!playersToTeleport.Any()) @@ -476,7 +476,7 @@ public partial class CS2_SimpleAdmin destinationPlayer = caller; playersToTeleport = targets.Players - .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) + .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p)) .ToList(); } else @@ -486,7 +486,7 @@ public partial class CS2_SimpleAdmin return; destinationPlayer = destination.Players.FirstOrDefault(p => - p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); + p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }); if (destinationPlayer == null) return; @@ -497,7 +497,7 @@ public partial class CS2_SimpleAdmin return; playersToTeleport = targets.Players - .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p)) + .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p)) .ToList(); } diff --git a/CS2-SimpleAdmin/Database/IDatabaseProvider.cs b/CS2-SimpleAdmin/Database/IDatabaseProvider.cs index 02ff3e8..e2a4ae4 100644 --- a/CS2-SimpleAdmin/Database/IDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/IDatabaseProvider.cs @@ -40,6 +40,7 @@ public interface IDatabaseProvider string GetUpdateBanStatusQuery(); string GetExpireBansQuery(bool multiServer); string GetExpireIpBansQuery(bool multiServer); + string GetExpireOldPlayerIpsQuery(); // MuteManager string GetAddMuteQuery(bool includePlayerName); diff --git a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs index 6d733b9..fa9a27e 100644 --- a/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/MysqlDatabaseProvider.cs @@ -251,6 +251,11 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider ? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime" : "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid"; } + + public string GetExpireOldPlayerIpsQuery() + { + return "DELETE FROM sa_players_ips WHERE used_at <= @ipBansTime"; + } public string GetAddMuteQuery(bool includePlayerName) => includePlayerName diff --git a/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs b/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs index 93772be..d6186fb 100644 --- a/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs +++ b/CS2-SimpleAdmin/Database/SqliteDatabaseProvider.cs @@ -166,6 +166,9 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider multiServer ? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime" : "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid"; + + public string GetExpireOldPlayerIpsQuery() => + "DELETE FROM sa_players_ips WHERE used_at <= @ipBansTime"; public string GetAdminsQuery() => """ diff --git a/CS2-SimpleAdmin/Events.cs b/CS2-SimpleAdmin/Events.cs index d9f8772..26df60c 100644 --- a/CS2-SimpleAdmin/Events.cs +++ b/CS2-SimpleAdmin/Events.cs @@ -259,7 +259,7 @@ public partial class CS2_SimpleAdmin { var player = Utilities.GetPlayerFromSteamId(list.Key); - if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) + if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.Connected) continue; if (player.PlayerName.Equals(list.Value)) @@ -328,7 +328,7 @@ public partial class CS2_SimpleAdmin ? Utilities.GetPlayerFromUserid(userId) : null; - if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected) + if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.Connected) return HookResult.Continue; return !player.CanTarget(target) ? HookResult.Stop : HookResult.Continue; @@ -473,7 +473,7 @@ public partial class CS2_SimpleAdmin var player = @event.Userid; if (player?.UserId == null || !player.IsValid || player.IsHLTV || - player.Connected != PlayerConnectedState.PlayerConnected || !PlayersInfo.ContainsKey(player.SteamID) || + player.Connected != PlayerConnectedState.Connected || !PlayersInfo.ContainsKey(player.SteamID) || @event.Attacker == null) return HookResult.Continue; diff --git a/CS2-SimpleAdmin/Helper.cs b/CS2-SimpleAdmin/Helper.cs index a15660c..75f1fd4 100644 --- a/CS2-SimpleAdmin/Helper.cs +++ b/CS2-SimpleAdmin/Helper.cs @@ -11,12 +11,14 @@ using CounterStrikeSharp.API.ValveConstants.Protobuf; using CS2_SimpleAdminApi; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; +using System.Diagnostics.CodeAnalysis; 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.Core.Plugin.Host; using CounterStrikeSharp.API.Modules.Entities.Constants; @@ -64,7 +66,7 @@ internal static class Helper public static List GetValidPlayers() { - return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.PlayerConnected).ToList(); + return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.Connected).ToList(); } public static List GetValidPlayersWithBots() @@ -855,26 +857,34 @@ internal static class Helper } } - public static void UpdateConfig(T config) where T : BasePluginConfig, new() + public static void UpdateConfig(BasePluginConfig config) { // get newest config version - var newCfgVersion = new T().Version; + var configType = config.GetType(); + var newCfgVersion = ((BasePluginConfig)Activator.CreateInstance(configType)!).Version; // loaded config is up to date if (config.Version == newCfgVersion) return; - // update the version - config.Version = newCfgVersion; + // Load existing JSON file and update version property + if (!File.Exists(CfgPath)) + return; - // serialize the updated config back to json - var updatedJsonContent = JsonSerializer.Serialize(config, - new JsonSerializerOptions + var json = File.ReadAllText(CfgPath); + var node = JsonNode.Parse(json); + + if (node != null) + { + node["Version"] = newCfgVersion; + var updatedJsonContent = node.ToJsonString(new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); - File.WriteAllText(CfgPath, updatedJsonContent); + + File.WriteAllText(CfgPath, updatedJsonContent); + } } public static void TryLogCommandOnDiscord(CCSPlayerController? caller, string commandString) diff --git a/CS2-SimpleAdmin/Managers/BanManager.cs b/CS2-SimpleAdmin/Managers/BanManager.cs index 2aac526..5863b59 100644 --- a/CS2-SimpleAdmin/Managers/BanManager.cs +++ b/CS2-SimpleAdmin/Managers/BanManager.cs @@ -431,6 +431,9 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string var ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans); sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode); await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId }); + + sql = databaseProvider.GetExpireOldPlayerIpsQuery(); + await connection.ExecuteAsync(sql, new { ipBansTime }); } } catch (Exception) diff --git a/CS2-SimpleAdmin/Managers/CacheManager.cs b/CS2-SimpleAdmin/Managers/CacheManager.cs index 9462f1e..fd0bd04 100644 --- a/CS2-SimpleAdmin/Managers/CacheManager.cs +++ b/CS2-SimpleAdmin/Managers/CacheManager.cs @@ -626,34 +626,81 @@ internal class CacheManager: IDisposable if (_cachedIgnoredIps.Contains(ipUInt)) return false; - if (!_ipIndex.TryGetValue(ipUInt, out var ipBanRecords)) - return false; - - var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE); - if (ipBan == null) - return false; - - if (!_banCache.TryGetValue(ipBan.Id, out var cachedIpBan) || cachedIpBan.StatusEnum != BanStatus.ACTIVE) - return false; - - var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans; - if (expireOldIpBans > 0) + // Direct ip ban (ban record has player_ip set) + if (_ipIndex.TryGetValue(ipUInt, out var ipBanRecords)) { - var cutoff = Time.ActualDateTime().AddDays(-expireOldIpBans); - if (ipBan.Created < cutoff) - return false; + var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE); + if (ipBan != null && _banCache.TryGetValue(ipBan.Id, out var cachedIpBan) && cachedIpBan.StatusEnum == BanStatus.ACTIVE) + { + var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans; + if (expireOldIpBans <= 0 || ipBan.Created >= Time.ActualDateTime().AddDays(-expireOldIpBans)) + { + if (string.IsNullOrEmpty(ipBan.PlayerName)) + ipBan.PlayerName = playerName; + ipBan.PlayerSteamId ??= steamId; + _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); + return true; + } + } } - var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"; + // Multiaccount ban - check if other accounts using current ip are banned + if (!_playerIpsCache.IsEmpty) + { + foreach (var (otherSteamId, ipSet) in _playerIpsCache) + { + // Skip current player + if (otherSteamId == steamId) + continue; - if (string.IsNullOrEmpty(ipBan.PlayerName)) - ipBan.PlayerName = playerName; + // Check if this ip is in the other accounts ip history + if (ipSet.All(record => record.Ip != ipUInt)) continue; + // Found another account using this ip - check if its banned + if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans)) continue; + var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE); + if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) || + cachedBan.StatusEnum != BanStatus.ACTIVE) continue; + _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); + return true; + } + } - ipBan.PlayerSteamId ??= steamId; + // Multiaccount ban - check if this player used any ip where other banned accounts are connected + // Search sa_players_ips for all accounts sharing the same ips as current player + if (!CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp) + return false; - _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); + if (!_playerIpsCache.TryGetValue(steamId, out var playerIps)) + return false; - return true; + // For each ip the player used (current or historical) + foreach (var playerIpRecord in playerIps) + { + // Search sa_players_ips for other accounts using this same ip (as uint) + foreach (var (otherSteamId, otherIpSet) in _playerIpsCache) + { + if (otherSteamId == steamId) + continue; + + // Check if this other account used the player ip + if (otherIpSet.All(record => record.Ip != playerIpRecord.Ip)) + continue; + + // Check if this other account is banned + if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans)) + continue; + + var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE); + if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) || + cachedBan.StatusEnum != BanStatus.ACTIVE) + continue; + + _ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress)); + return true; + } + } + + return false; } /// diff --git a/CS2-SimpleAdmin/Managers/PlayerManager.cs b/CS2-SimpleAdmin/Managers/PlayerManager.cs index e8c5c6a..2f08393 100644 --- a/CS2-SimpleAdmin/Managers/PlayerManager.cs +++ b/CS2-SimpleAdmin/Managers/PlayerManager.cs @@ -13,7 +13,7 @@ namespace CS2_SimpleAdmin.Managers; internal class PlayerManager { - private readonly SemaphoreSlim _loadPlayerSemaphore = new(10); + private readonly SemaphoreSlim _loadPlayerSemaphore = new(6); private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config; /// @@ -51,69 +51,41 @@ internal class PlayerManager try { await _loadPlayerSemaphore.WaitAsync(); - if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId)) + + // Save ip address before ban check + await SavePlayerIpAddress(steamId, playerName, ipAddress); + + // Always check bans first, regardless of PlayersInfo state + var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch { - var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch + 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null), + _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp + ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, + ipAddress) + : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress) + }; + + CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] Player {playerName} ({steamId}) IP: {ipAddress} - BanType: {CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType} - CheckMultiAccounts: {CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp} - isBanned: {isBanned}"); + + if (isBanned) + { + CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] KICKING {playerName} ({steamId})"); + await Server.NextWorldUpdateAsync(() => { - 0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null), - _ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp - ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, - ipAddress) - : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress) - }; + CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] Executing kick for {playerName}"); + Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); + }); - // CS2_SimpleAdmin._logger?.LogInformation($"Player {playerName} ({steamId} - {ipAddress}) is banned? {isBanned.ToString()}"); - - if (isBanned) - { - await Server.NextWorldUpdateAsync(() => - { - // CS2_SimpleAdmin._logger?.LogInformation($"Kicking {playerName}"); - Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED); - }); - - return; - } + return; } - if (fullConnect) + if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId)) { var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress); CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo; - - if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null && - CS2_SimpleAdmin.PlayersInfo[steamId] != null) + + if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null) { - try - { - await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); - - // Eliminates the need for SELECT COUNT and duplicate UPDATE queries - var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64; - var ipUint = IpHelper.IpToUint(ipAddress); - - // Use database-specific UPSERT query (handles MySQL vs SQLite syntax differences) - var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery(); - - await connection.ExecuteAsync(upsertQuery, new - { - SteamID = steamId64, - playerName, - IPAddress = ipUint - }); - - // // Cache will be updated on next refresh cycle - // if (!CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(steamId, ipAddress)) - // { - // // IP association will be reflected after cache refresh - // } - } - catch (Exception ex) - { - CS2_SimpleAdmin._logger?.LogError( - $"Unable to save ip address for {playerInfo.Name} ({ipAddress}): {ex.Message}"); - } - playerInfo.AccountsAssociated = CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable() .Select(x => (x.SteamId, x.PlayerName)).ToList() ?? []; @@ -200,7 +172,7 @@ internal class PlayerManager AdminManager.PlayerHasPermissions( new SteamID(p.SteamID), "@css/ban")) && - p.Connected == PlayerConnectedState.PlayerConnected && + p.Connected == PlayerConnectedState.Connected && !CS2_SimpleAdmin.AdminDisabledJoinComms .Contains(p.SteamID))) { @@ -247,13 +219,45 @@ internal class PlayerManager _loadPlayerSemaphore.Release(); } }); - + if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name)) { player.Rename(name); } } + /// + /// Saves player's IP address to the database for multi-account detection. + /// This is called before ban checks to ensure IP is recorded even if player is banned. + /// + private async Task SavePlayerIpAddress(ulong steamId, string playerName, string? ipAddress) + { + if (!_config.OtherSettings.CheckMultiAccountsByIp || ipAddress == null || CS2_SimpleAdmin.DatabaseProvider == null) + return; + + try + { + await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync(); + + var steamId64 = steamId; + var ipUint = IpHelper.IpToUint(ipAddress); + + var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery(); + + await connection.ExecuteAsync(upsertQuery, new + { + SteamID = steamId64, + playerName, + IPAddress = ipUint + }); + } + catch (Exception ex) + { + CS2_SimpleAdmin._logger?.LogError( + $"Unable to save ip address for {playerName} ({ipAddress}): {ex.Message}"); + } + } + /// /// Periodically checks the status of online players and applies timers for speed, gravity, /// and penalty expiration validation. diff --git a/CS2-SimpleAdmin/Managers/WarnManager.cs b/CS2-SimpleAdmin/Managers/WarnManager.cs index 5b87831..7db3d1d 100644 --- a/CS2-SimpleAdmin/Managers/WarnManager.cs +++ b/CS2-SimpleAdmin/Managers/WarnManager.cs @@ -42,7 +42,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider) return warnId; } - catch(Exception e) + catch(Exception) { return null; } diff --git a/CS2-SimpleAdmin/VERSION b/CS2-SimpleAdmin/VERSION index dfb227e..fabe3c2 100644 --- a/CS2-SimpleAdmin/VERSION +++ b/CS2-SimpleAdmin/VERSION @@ -1 +1 @@ -1.7.8-beta-10b1 \ No newline at end of file +1.7.9a \ No newline at end of file diff --git a/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj index b3f699e..9a91ed4 100644 --- a/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj +++ b/CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj @@ -9,7 +9,7 @@ - + diff --git a/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs b/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs index 9379f33..03b2495 100644 --- a/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs +++ b/Modules/CS2-SimpleAdmin_BanSoundModule/CS2-SimpleAdmin_BanSoundModule.cs @@ -45,7 +45,7 @@ public class CS2_SimpleAdmin_BanSoundModule: BasePlugin foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot)) { var filter = new RecipientFilter(player); - player?.EmitSound("bansound", volume: 0.9f, recipients: filter); + player?.EmitSound("bansound", volume: 0.75f, recipients: filter); } } } \ No newline at end of file