From 673e203c2c107d58fc8e5fe3afe178eab287992e Mon Sep 17 00:00:00 2001 From: Rushaway Date: Wed, 30 Sep 2026 15:31:54 +0200 Subject: [PATCH] fix(plugin): avoid BanClient before the player entity exists VerifyBan runs from OnClientAuthorized, which can land before OnClientPutInServer. BanClient() resolves the client through ReferenceToIndex(), which returns -1 while the edict has no entity, so SourceMod threw "Client index -1 is invalid" and the banned player was never kicked. Ban the engine auth string with BanIdentity (keeps Synergy's native Steam3 form) and KickClient; when the auth lookup fails or the server is LAN, KickClientEx then ban the IP, matching BanClient's kick-then-addip order. Port of upstream sbpp/sourcebans-pp#1580 (a418884c). Co-Authored-By: Claude Opus 5.5 --- game/addons/sourcemod/scripting/sbpp_main.sp | 30 +++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/game/addons/sourcemod/scripting/sbpp_main.sp b/game/addons/sourcemod/scripting/sbpp_main.sp index 3f23dcb32..668615cef 100644 --- a/game/addons/sourcemod/scripting/sbpp_main.sp +++ b/game/addons/sourcemod/scripting/sbpp_main.sp @@ -1888,15 +1888,31 @@ public void VerifyBan(Database db, DBResultSet results, const char[] error, int } } - // Ban via BanClient() so SourceMod bans with the engine's own auth - // string. The raw "banid " console command is rejected by - // some engines (e.g. Synergy), which left the player unbanned. - // SetGlobalTransTarget() keeps the message in the client's language, - // which KickClient() used to do for us. - char BanReason[256]; + // Ban with the engine's own auth string: the raw "banid " + // console command is rejected by some engines (e.g. Synergy), which + // left the player unbanned. Don't use BanClient() for that though: it + // needs the player entity, which may not exist yet (VerifyBan runs + // from OnClientAuthorized, which can precede OnClientPutInServer), so + // SourceMod threw "Client index -1 is invalid" and the player was + // never kicked. Mirror BanClient's behaviour with entity-independent + // natives instead, falling back to an IP ban on LAN / lookup failure. + // SetGlobalTransTarget() keeps the message in the client's language. + char BanReason[256], gameAuth[MAX_AUTHID_LENGTH]; SetGlobalTransTarget(client); FormatEx(BanReason, sizeof(BanReason), "%t", "Banned Check Site", WebsiteAddress); - BanClient(client, 5, BANFLAG_AUTHID, BanReason, BanReason, "sbpp"); + + if (GetClientAuthId(client, AuthId_Engine, gameAuth, sizeof(gameAuth)) + && BanIdentity(gameAuth, 5, BANFLAG_AUTHID, BanReason, "sbpp")) + { + KickClient(client, "%s", BanReason); + } + else + { + // Kick before addip, as BanClient() does, so the client sees our message. + KickClientEx(client, "%s", BanReason); + if (clientIp[0] != '\0') + BanIdentity(clientIp, 5, BANFLAG_IP, BanReason, "sbpp"); + } return; }