Compare commits

..

13 Commits

Author SHA1 Message Date
Dawid Bepierszcz
ca55c7f1a6 Update build.yml 2026-06-17 02:47:31 +02:00
Dawid Bepierszcz
d870f23b01 Bump version; fix bugs and refactor managers
Minor fixes
2026-06-17 02:46:25 +02:00
Dawid Bepierszcz
a7ad6fce77 .NET10, version bump 2026-06-17 02:21:51 +02:00
Dawid Bepierszcz
eea700bfb4 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.
2026-04-23 22:15:44 +02:00
Dawid Bepierszcz
91615ffc67 Update VERSION 2026-01-27 13:50:27 +01:00
Dawid Bepierszcz
eb9b438315 Update CS2-SimpleAdmin.cs 2026-01-27 13:47:41 +01:00
Dawid Bepierszcz
3d23b8981b Update VERSION 2026-01-27 12:21:07 +01:00
Dawid Bepierszcz
39cbfdab1e Update CS2-SimpleAdmin.cs 2026-01-27 12:20:56 +01:00
Dawid Bepierszcz
4599f08fd7 Update basecommands.cs 2026-01-27 12:20:29 +01:00
Dawid Bepierszcz
4c43f14c82 Update VERSION 2026-01-27 09:52:44 +01:00
Dawid Bepierszcz
f16f8cf1a5 Update CS2-SimpleAdmin.cs 2026-01-27 09:52:31 +01:00
Dawid Bepierszcz
193685826c Update ServerManager.cs 2026-01-27 09:51:12 +01:00
Dawid Bepierszcz
fe73fa9917 Refactor admin and server loading timing logic
Adjusted timers for admin and server data loading to improve reliability and reduce delays. Admin data is now loaded more promptly on map changes and during command execution. ServerManager now initializes the cache after setting the server ID, and redundant admin reloads have been removed. Version bumped to 1.7.8-beta-10.
2026-01-26 01:19:25 +01:00
23 changed files with 235 additions and 446 deletions

View File

@@ -35,7 +35,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 10.0.x
- name: Get Version
id: get_version
@@ -67,28 +67,12 @@ jobs:
- name: Zip Main Build Output
run: zip -r CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip ${{ env.OUTPUT_PATH }}
- name: Extract & Zip StatusBlocker Linux
run: |
mkdir -p statusblocker-linux &&
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-linux.tar.gz -C statusblocker-linux &&
cd statusblocker-linux &&
zip -r ../StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip ./*
- name: Extract & Zip StatusBlocker Windows
run: |
mkdir -p statusblocker-windows &&
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-windows.tar.gz -C statusblocker-windows &&
cd statusblocker-windows &&
zip -r ../StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip ./*
- name: Upload all artifacts
uses: actions/upload-artifact@v4
with:
name: CS2-SimpleAdmin-Build-Artifacts
path: |
CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip
StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip
StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip
publish:
needs: build
@@ -109,8 +93,6 @@ jobs:
with:
artifacts: |
CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip
StatusBlocker-linux-${{ needs.build.outputs.build_version }}.zip
StatusBlocker-windows-${{ needs.build.outputs.build_version }}.zip
name: "CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}"
tag: "build-${{ needs.build.outputs.build_version }}"
body: |
@@ -121,8 +103,5 @@ jobs:
After the first launch, configure the plugin using the JSON config file at:
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
- StatusBlocker:
Place the plugin files directly into the addons directory.
This plugin is a Metamod module for the StealthModule and does not require a subfolder.
Remember to restart or reload your game server after installing and configuring the plugins.

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@ Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSetti
CS2-SimpleAdmin_BanSoundModule — kopia
*.user
CLAUDE.md
/Modules/CS2-SimpleAdmin_BanSoundModule
/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN

View File

@@ -14,7 +14,7 @@ using MySqlConnector;
namespace CS2_SimpleAdmin;
[MinimumApiVersion(300)]
[MinimumApiVersion(369)]
public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdminConfig>
{
internal static CS2_SimpleAdmin Instance { get; private set; } = new();
@@ -22,7 +22,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
public override string ModuleName => "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-9";
public override string ModuleVersion => "1.8.1a";
public override void Load(bool hotReload)
{
@@ -46,7 +46,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
CachedPlayers.Clear();
BotPlayers.Clear();
foreach (var player in Utilities.GetPlayers().Where(p => 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);
@@ -55,14 +55,12 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
};
});
PlayersTimer?.Kill();
PlayersTimer = null;
}
_cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>(GameData.GetSignature("CBasePlayerController_SetPawn"));
SimpleAdminApi = new Api.CS2_SimpleAdminApi();
Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi);
PlayersTimer?.Kill();
PlayersTimer = null;
PlayerManager.CheckPlayersTimer();
@@ -83,9 +81,9 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
Unload(false);
}
AddTimer(6.0f, () => ReloadAdmins(null));
RegisterEvents();
AddTimer(0.5f, RegisterCommands.InitializeCommands);
AddTimer(3.0f, () => ReloadAdmins(null));
if (!CoreConfig.UnlockConCommands)
{
@@ -95,20 +93,11 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
$"to rejoin the server for 60 seconds. " +
$"To enable instant banning, set 'UnlockConCommands': true"
);
_logger?.LogError(
$"⚠️ Warning: 'UnlockConCommands' is disabled in core.json. " +
$"Players will not be automatically banned when kicked and will be able " +
$"to rejoin the server for 60 seconds. " +
$"To enable instant banning, set 'UnlockConCommands': true"
);
}
}
public void OnConfigParsed(CS2_SimpleAdminConfig config)
{
if (System.Diagnostics.Debugger.IsAttached)
Environment.FailFast(":(!");
Helper.UpdateConfig(config);
_logger = Logger;
@@ -164,8 +153,11 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
}
if (missing)
Server.ExecuteCommand($"css_plugins unload {ModuleName}");
{
Server.ExecuteCommand($"css_plugins unload {ModuleDirectory}");
return;
}
Instance = this;
if (Config.DatabaseConfig.DatabaseType.Contains("mysql", StringComparison.CurrentCultureIgnoreCase))

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>CS2_SimpleAdmin</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -19,16 +19,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.361">
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.369">
<PrivateAssets>none</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="MySqlConnector" Version="2.5.0" />
<PackageReference Include="Dapper" Version="2.1.79" />
<PackageReference Include="MySqlConnector" Version="2.6.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageReference Include="System.Linq.Async" Version="7.0.0" />
<PackageReference Include="ZLinq" Version="1.5.4" />
<PackageReference Include="System.Linq.Async" Version="7.0.1" />
<PackageReference Include="ZLinq" Version="1.5.6" />
</ItemGroup>
<ItemGroup>

View File

@@ -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)
{

View File

@@ -501,13 +501,13 @@ public partial class CS2_SimpleAdmin
Task.Run(async () =>
{
await PermissionManager.CrateGroupsJsonFile();
await PermissionManager.CreateGroupsJsonFile();
await PermissionManager.CreateAdminsJsonFile();
var adminsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/admins.json");
var groupsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/groups.json");
await Server.NextWorldUpdateAsync(() =>
await Server.NextWorldUpdateAsync(() =>
{
AddTimer(1, () =>
{
@@ -521,7 +521,7 @@ public partial class CS2_SimpleAdmin
_logger?.LogInformation("Loaded admins!");
});
});
});
});
//_ = _adminManager.GiveAllGroupsFlags();
//_ = _adminManager.GiveAllFlags();

View File

@@ -44,7 +44,7 @@ public partial class CS2_SimpleAdmin
/// <param name="command">Optional command info for logging.</param>
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();
}

View File

@@ -40,6 +40,7 @@ public interface IDatabaseProvider
string GetUpdateBanStatusQuery();
string GetExpireBansQuery(bool multiServer);
string GetExpireIpBansQuery(bool multiServer);
string GetExpireOldPlayerIpsQuery();
// MuteManager
string GetAddMuteQuery(bool includePlayerName);

View File

@@ -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

View File

@@ -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() =>
"""

View File

@@ -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;
@@ -448,13 +448,13 @@ public partial class CS2_SimpleAdmin
private void OnMapStart(string mapName)
{
if (!ServerLoaded || ServerId == null)
AddTimer(2.0f, OnGameServerSteamAPIActivated);
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
AddTimer(5.0f, () => ReloadAdmins(null));
ReloadAdmins(null);
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
if (!ServerLoaded || ServerId == null)
AddTimer(1.5f, OnGameServerSteamAPIActivated);
// AddTimer(34, () =>
// {
@@ -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;

View File

@@ -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<CCSPlayerController> 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<CCSPlayerController> GetValidPlayersWithBots()
@@ -855,26 +857,34 @@ internal static class Helper
}
}
public static void UpdateConfig<T>(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)
@@ -1001,6 +1011,10 @@ public static class PluginInfo
Console.WriteLine("|_______||___| |_| |_||___| |_______||_______||__| |__||______| |_| |_||___| |_| |__|");
Console.WriteLine(" >> Version: " + moduleVersion);
Console.WriteLine(" >> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin");
var _m1 = new List<int> { 70,117,99,107,32,121,111,117,32,114,97,118,105,100,44,32,119,104,121,32,121,111,117,32,108,121,105,110,103,32,97,98,111,117,116,32,110,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,63,32,74,117,115,116,32,116,101,108,108,32,116,114,117,116,104,44,32,110,105,99,101,32,116,114,121,32,116,111,32,112,114,111,106,101,99,116,32,116,97,107,101,111,118,101,114 };
var _m2 = new List<int> { 32,32,58,68,32,40,97,108,108,32,119,104,111,32,116,114,105,101,100,32,116,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,44,32,106,117,115,116,32,119,114,105,116,101,32,109,101,115,115,97,103,101,32,116,111,32,109,101,32,120,68,41 };
Console.WriteLine(" >> " + new string(_m1.Select(c => (char)c).ToArray()));
Console.WriteLine(" >> " + new string(_m2.Select(c => (char)c).ToArray()));
Console.WriteLine(" ");
}
}

View File

@@ -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)

View File

@@ -272,12 +272,12 @@ internal class CacheManager: IDisposable
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
{
ipBans.RemoveAll(b => b.Id == id);
if (ipBans.Count == 0)
_ipIndex.TryRemove(ipUInt, out _);
}
!_ipIndex.TryGetValue(ipUInt, out var ipBans))
continue;
ipBans.RemoveAll(b => b.Id == id);
if (ipBans.Count == 0)
_ipIndex.TryRemove(ipUInt, out _);
}
}
@@ -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;
}
/// <summary>

View File

@@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Modules.Entities;
using Dapper;
using Microsoft.Extensions.Logging;
@@ -12,54 +12,8 @@ namespace CS2_SimpleAdmin.Managers;
public class PermissionManager(IDatabaseProvider? databaseProvider)
{
// Unused for now
//public static readonly ConcurrentDictionary<string, ConcurrentBag<string>> _adminCache = new ConcurrentDictionary<string, ConcurrentBag<string>>();
// public static readonly ConcurrentDictionary<SteamID, DateTime?> AdminCache = new();
public static readonly ConcurrentDictionary<SteamID, (DateTime? ExpirationTime, List<string> Flags)> AdminCache = new();
/*
public async Task<List<(List<string>, int)>> GetAdminFlags(string steamId)
{
DateTime now = Time.ActualDateTime();
await using MySqlConnection connection = await _database.GetConnectionAsync();
string sql = "SELECT flags, immunity, ends FROM sa_admins WHERE player_steamid = @PlayerSteamID AND (ends IS NULL OR ends > @CurrentTime) AND (server_id IS NULL OR server_id = @serverid)";
List<dynamic>? activeFlags = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId }))?.ToList();
if (activeFlags == null)
{
return new List<(List<string>, int)>();
}
List<(List<string>, int)> filteredFlagsWithImmunity = [];
foreach (dynamic flags in activeFlags)
{
if (flags is not IDictionary<string, object> flagsDict)
{
continue;
}
if (!flagsDict.TryGetValue("flags", out var flagsValueObj) || !flagsDict.TryGetValue("immunity", out var immunityValueObj))
{
continue;
}
if (!(flagsValueObj is string flagsValue) || !int.TryParse(immunityValueObj.ToString(), out var immunityValue))
{
continue;
}
//Console.WriteLine($"Flags: {flagsValue}, Immunity: {immunityValue}");
filteredFlagsWithImmunity.Add((flagsValue.Split(',').ToList(), immunityValue));
}
return filteredFlagsWithImmunity;
}
*/
/// <summary>
/// Retrieves all players' flags and associated data asynchronously.
/// </summary>
@@ -100,7 +54,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
string playerName = g.Key.playerName as string ?? string.Empty;
// tutaj zakładamy, że Dapper zwraca już string (nie dynamic)
// Dapper returns string here, not dynamic
var flags = g.Select(r => r.flag as string ?? string.Empty)
.Distinct()
.ToList();
@@ -118,72 +72,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
}
/*
public async Task<Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>>> GetAllGroupsFlags()
{
try
{
await using MySqlConnection connection = await _database.GetConnectionAsync();
string sql = "SELECT group_id FROM sa_groups_servers WHERE server_id = @serverid";
var groupIds = connection.Query<int>(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
sql = @"
SELECT g.group_id, f.flag
FROM sa_groups_flags f
JOIN sa_groups_servers g ON f.group_id = g.group_id
WHERE g.server_id = @serverid";
var groupFlagData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
if (groupIds.Count == 0 || groupFlagData.Count == 0)
{
return [];
}
var groupInfoDictionary = new Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>>();
foreach (var groupId in groupIds)
{
groupInfoDictionary[groupId] = new Tuple<List<string>, List<Tuple<string, DateTime?>>, int>([], [], 0);
}
foreach (var row in groupFlagData)
{
var groupId = (int)row.group_id;
var flag = (string)row.flag;
groupInfoDictionary[groupId].Item1.Add(flag);
}
sql = @"
SELECT a.group_id, a.player_steamid, a.ends, g.immunity, g.name
FROM sa_admins a
JOIN sa_groups g ON a.group_id = g.id
WHERE a.group_id IN @groupIds";
var playerData = (await connection.QueryAsync(sql, new { groupIds })).ToList();
foreach (var row in playerData)
{
var groupId = (int)row.group_id;
var playerSteamid = (string)row.player_steamid;
var ends = row.ends as DateTime?;
var immunity = (int)row.immunity;
groupInfoDictionary[groupId].Item2.Add(new Tuple<string, DateTime?>(playerSteamid, ends));
groupInfoDictionary[groupId] = new Tuple<List<string>, List<Tuple<string, DateTime?>>, int>(groupInfoDictionary[groupId].Item1, groupInfoDictionary[groupId].Item2, immunity);
}
return groupInfoDictionary;
}
catch { }
return [];
}
*/
/// <summary>
/// Retrieves all groups' data including flags and immunity asynchronously.
/// </summary>
@@ -192,13 +80,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
{
if (databaseProvider == null) return [];
await using var connection = await databaseProvider.CreateConnectionAsync();
;
try
{
// var sql = "SELECT group_id FROM sa_groups_servers WHERE (server_id = @serverid OR server_id IS NULL)";
// var groupDataSql = connection.Query<int>(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetGroupsQuery();
var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
if (groupData.Count == 0)
@@ -213,11 +97,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
var flag = (string)row.flag;
var immunity = (int)row.immunity;
// Check if the group name already exists in the dictionary
if (!groupInfoDictionary.TryGetValue(groupName, out (List<string>, int) value))
{
value = ([], immunity);
// If it doesn't exist, add a new entry with an empty list of flags and immunity
groupInfoDictionary[groupName] = value;
}
@@ -238,7 +120,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
/// Creates a JSON file containing groups data asynchronously.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task CrateGroupsJsonFile()
public async Task CreateGroupsJsonFile()
{
var groupsData = await GetAllGroupsData();
var jsonData = new Dictionary<string, object>();
@@ -257,7 +139,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(jsonData, options);
@@ -265,66 +147,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
await File.WriteAllTextAsync(filePath, json);
}
/*
public async Task GiveAllGroupsFlags()
{
Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>> groupFlags = await GetAllGroupsFlags();
foreach (var kvp in groupFlags)
{
var flags = kvp.Value.Item1;
var players = kvp.Value.Item2;
int immunity = kvp.Value.Item3;
foreach (var playerTuple in players)
{
var steamIdStr = playerTuple.Item1;
var ends = playerTuple.Item2;
if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null)
{
if (!_adminCache.ContainsKey(steamId))
{
_adminCache.TryAdd(steamId, ends);
}
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
// Often need to call 2 times
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
}
}
}
}
*/
/*
public async Task GiveAllFlags()
{
List<(string, string, List<string>, int, DateTime?)> allPlayers = await GetAllPlayersFlags();
foreach (var record in allPlayers)
{
string steamIdStr = record.Item1;
List<string> flags = record.Item2;
int immunity = record.Item3;
DateTime? ends = record.Item4;
if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null)
{
if (!_adminCache.ContainsKey(steamId))
{
_adminCache.TryAdd(steamId, ends);
//_adminCacheTimestamps.Add(steamId, ends);
}
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
// Often need to call 2 times
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
}
}
}
*/
/// <summary>
/// Creates a JSON file containing admins data asynchronously.
/// </summary>
@@ -336,25 +158,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
.ToList();
// foreach (var player in allPlayers)
// {
// var (steamId, name, flags, immunity, ends) = player;
//
// Console.WriteLine($"Player SteamID: {steamId}");
// Console.WriteLine($"Player Name: {name}");
// Console.WriteLine($"Flags: {string.Join(", ", flags)}");
// Console.WriteLine($"Immunity: {immunity}");
// Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}");
// Console.WriteLine();
// }
var jsonData = validPlayers
.GroupBy(player => player.name) // Group by player name
.GroupBy(player => player.name)
.ToDictionary(
group => group.Key, // Use the player name as key
group => group.Key,
object (group) =>
{
// Consolidate data for players with same name
var consolidatedData = group.Aggregate(
new
{
@@ -365,16 +174,13 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
},
(acc, player) =>
{
// Merge identities
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
{
acc = acc with { identity = player.identity.ToString() };
}
// Combine immunities by maximum value
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
// Combine flags and groups
acc = acc with
{
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
@@ -383,12 +189,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
return acc;
});
Server.NextWorldUpdate(() =>
{
var keysToRemove = new List<SteamID>();
foreach (var steamId in AdminCache.Keys.ToList())
foreach (var steamId in AdminCache.Keys.ToList())
{
var data = AdminManager.GetPlayerAdminData(steamId);
if (data != null)
@@ -422,40 +228,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
});
// Server.NextFrameAsync(() =>
// {
// for (var index = 0; index < AdminCache.Keys.ToList().Count; index++)
// {
// var steamId = AdminCache.Keys.ToList()[index];
//
// var data = AdminManager.GetPlayerAdminData(steamId);
// if (data != null)
// {
// AdminManager.RemovePlayerPermissions(steamId, AdminCache[steamId].Flags.ToArray());
// AdminManager.RemovePlayerFromGroup(steamId, true, AdminCache[steamId].Flags.ToArray());
// }
//
// if (!AdminCache.TryRemove(steamId, out _)) continue;
//
// if (data == null) continue;
// if (data.Flags.ToList().Count != 0 && data.Groups.ToList().Count != 0)
// continue;
//
// AdminManager.ClearPlayerPermissions(steamId);
// AdminManager.RemovePlayerAdminData(steamId);
// }
//
// foreach (var player in group)
// {
// SteamID.TryParse(player.identity, out var steamId);
// if (steamId == null) continue;
// AdminCache.TryAdd(steamId, (player.ends, player.flags));
// }
// });
return consolidatedData;
});
var options = new JsonSerializerOptions
{
WriteIndented = true,
@@ -464,10 +239,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
var json = JsonSerializer.Serialize(jsonData, options);
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
await File.WriteAllTextAsync(filePath, json);
//await File.WriteAllTextAsync(CS2_SimpleAdmin.Instance.ModuleDirectory + "/data/admins.json", json);
}
/// <summary>
@@ -480,8 +252,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(playerSteamId)) return;
//_adminCache.TryRemove(playerSteamId, out _);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
@@ -510,18 +280,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return;
var now = Time.ActualDateTime();
DateTime? futureTime;
if (time != 0)
futureTime = now.AddMinutes(time);
else
futureTime = null;
DateTime? futureTime = time != 0 ? now.AddMinutes(time) : null;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
// Insert admin into sa_admins table
var insertAdminSql = databaseProvider.GetAddAdminQuery();
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
{
@@ -533,28 +297,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
serverid = globalAdmin ? null : CS2_SimpleAdmin.ServerId
});
// Insert flags into sa_admins_flags table
foreach (var flag in flagsList)
{
// if (flag.StartsWith($"#"))
// {
// // const string sql = "SELECT id FROM `sa_groups` WHERE name = @groupName";
// // var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag });
//
// var sql = databaseProvider.GetGroupIdByNameQuery();
// var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag, CS2_SimpleAdmin.ServerId });
//
// if (groupId != null)
// {
// var updateAdminGroup = "UPDATE `sa_admins` SET group_id = @groupId WHERE id = @adminId";
// await connection.ExecuteAsync(updateAdminGroup, new
// {
// groupId,
// adminId
// });
// }
// }
var insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery();
await connection.ExecuteAsync(insertFlagsSql, new
{
@@ -587,10 +331,10 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return;
await using var connection = await databaseProvider.CreateConnectionAsync();
try
{
// Insert group into sa_groups table
await using var connection = await databaseProvider.CreateConnectionAsync();
var insertGroup = databaseProvider.GetAddGroupQuery();
var groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
{
@@ -598,11 +342,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
immunity
});
// Insert flags into sa_groups_flags table
foreach (var flag in flagsList)
{
var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
await connection.ExecuteAsync(insertFlagsSql, new
{
groupId,
@@ -616,7 +358,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
{
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
});
}
catch (Exception ex)
{
@@ -634,9 +375,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
if (string.IsNullOrEmpty(groupName)) return;
await using var connection = await databaseProvider.CreateConnectionAsync();
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetDeleteGroupQuery();
await connection.ExecuteAsync(sql, new { groupName });
}
@@ -665,4 +406,4 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired admins");
}
}
}
}

View File

@@ -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;
/// <summary>
@@ -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);
}
}
/// <summary>
/// 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.
/// </summary>
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}");
}
}
/// <summary>
/// Periodically checks the status of online players and applies timers for speed, gravity,
/// and penalty expiration validation.

View File

@@ -31,7 +31,7 @@ public class ServerManager
/// </summary>
public void LoadServerData()
{
CS2_SimpleAdmin.Instance.AddTimer(2.0f, () =>
CS2_SimpleAdmin.Instance.AddTimer(1.0f, () =>
{
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
@@ -103,14 +103,12 @@ public class ServerManager
CS2_SimpleAdmin.ServerId = serverId;
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
if (CS2_SimpleAdmin.ServerId != null)
{
await Server.NextWorldUpdateAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
}
CS2_SimpleAdmin.ServerLoaded = true;
CS2_SimpleAdmin.ServerLoaded = true;
if (CS2_SimpleAdmin.Instance.CacheManager != null)
{
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
}
}
catch (Exception ex)
{

View File

@@ -42,7 +42,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
return warnId;
}
catch(Exception e)
catch(Exception)
{
return null;
}

View File

@@ -1 +1 @@
1.7.8-beta-9
1.8.1a

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>CS2_SimpleAdminApi</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.361" />
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.369" />
</ItemGroup>
</Project>

View File

@@ -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);
}
}
}