From cca9c769ace86c8c40fc4a24e91af4158d593926 Mon Sep 17 00:00:00 2001 From: Sigibert Date: Sat, 27 Jun 2026 15:06:37 +0200 Subject: [PATCH 1/3] feat: add SBPP_BanPlayerBySteamId native for offline player bans SBPP_BanPlayer requires an in-game client index, making it impossible to ban players who disconnect before an automated ban fires. The new native accepts a SteamID string and player name directly, allowing plugins to record a ban in the SBPP database for players who are no longer connected. Implementation: - Checks for an existing active ban first (duplicate-prevention SELECT) - Inserts into sb_bans with length in seconds (iTime * 60) and the IFNULL admin-lookup subquery, matching the pattern used by CommandAddBan - Uses a server-lookup subquery when serverID == -1 (unregistered), or the cached serverID directly - Fires SBPP_OnBanPlayer with iTarget = -1 to signal an offline ban sourcebanspp.inc: - Declares the new native with full doc comment - Adds MarkNativeAsOptional in __pl_sourcebanspp_SetNTVOptional --- .../scripting/include/sourcebanspp.inc | 15 +++ game/addons/sourcemod/scripting/sbpp_main.sp | 120 ++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/game/addons/sourcemod/scripting/include/sourcebanspp.inc b/game/addons/sourcemod/scripting/include/sourcebanspp.inc index f29563c3e..4f534b7c2 100644 --- a/game/addons/sourcemod/scripting/include/sourcebanspp.inc +++ b/game/addons/sourcemod/scripting/include/sourcebanspp.inc @@ -81,6 +81,7 @@ public void __pl_sourcebanspp_SetNTVOptional() { MarkNativeAsOptional("SBBanPlayer"); MarkNativeAsOptional("SBPP_BanPlayer"); + MarkNativeAsOptional("SBPP_BanPlayerBySteamId"); MarkNativeAsOptional("SBPP_ReportPlayer"); } #endif @@ -109,6 +110,20 @@ native void SBBanPlayer(int iAdmin, int iTarget, int iTime, const char[] sReason *********************************************************/ native void SBPP_BanPlayer(int iAdmin, int iTarget, int iTime, const char[] sReason); +/********************************************************* + * Ban a player by SteamID — supports offline/disconnected players. + * Inserts the ban directly into the SourceBans++ database. + * Fires SBPP_OnBanPlayer with iTarget = -1 to indicate an offline ban. + * + * @param iAdmin Client index of the admin (0 = server / automated) + * @param steamId SteamID string, e.g. "STEAM_0:0:12345" + * @param name Player name; may be empty string if unknown + * @param iTime Ban duration in minutes (0 = permanent) + * @param sReason Reason for the ban + * @noreturn + *********************************************************/ +native void SBPP_BanPlayerBySteamId(int iAdmin, const char[] steamId, const char[] name, int iTime, const char[] sReason); + /********************************************************* * Reports a player * diff --git a/game/addons/sourcemod/scripting/sbpp_main.sp b/game/addons/sourcemod/scripting/sbpp_main.sp index 44d52838f..b694a1013 100644 --- a/game/addons/sourcemod/scripting/sbpp_main.sp +++ b/game/addons/sourcemod/scripting/sbpp_main.sp @@ -144,6 +144,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("SBBanPlayer", Native_SBBanPlayer); CreateNative("SBPP_BanPlayer", Native_SBBanPlayer); + CreateNative("SBPP_BanPlayerBySteamId", Native_SBPP_BanPlayerBySteamId); CreateNative("SBPP_ReportPlayer", Native_SBReportPlayer); g_hFwd_OnBanAdded = CreateGlobalForward("SBPP_OnBanPlayer", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_String); @@ -2382,6 +2383,125 @@ public int Native_SBBanPlayer(Handle plugin, int numParams) return true; } +public int Native_SBPP_BanPlayerBySteamId(Handle plugin, int numParams) +{ + if (DB == INVALID_HANDLE) + { + ThrowNativeError(SP_ERROR_NATIVE, "SourceBans++ database is not available."); + return 0; + } + + int admin = GetNativeCell(1); + int iTime = GetNativeCell(4); + + char steamId[MAX_AUTHID_LENGTH], name[MAX_NAME_LENGTH], reason[128]; + GetNativeString(2, steamId, sizeof(steamId)); + GetNativeString(3, name, sizeof(name)); + GetNativeString(5, reason, sizeof(reason)); + + if (reason[0] == '\0') + strcopy(reason, sizeof(reason), "Banned by SourceBans"); + + char adminAuth[MAX_AUTHID_LENGTH], adminIp[16]; + if (!admin || !IsClientInGame(admin)) + { + strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER"); + strcopy(adminIp, sizeof(adminIp), ServerIp); + } + else + { + strcopy(adminAuth, sizeof(adminAuth), g_sSteamIDs[admin]); + strcopy(adminIp, sizeof(adminIp), g_sPlayerIP[admin]); + } + + DataPack pack = new DataPack(); + pack.WriteCell(iTime); + pack.WriteString(reason); + pack.WriteString(steamId); + pack.WriteString(name); + pack.WriteString(adminAuth); + pack.WriteString(adminIp); + + char steamIdEscaped[MAX_AUTHID_LENGTH * 2 + 1]; + DB.Escape(steamId, steamIdEscaped, sizeof(steamIdEscaped)); + + char query[512]; + FormatEx(query, sizeof(query), "SELECT bid FROM %s_bans WHERE type = 0 AND authid = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", + DatabasePrefix, steamIdEscaped); + + DB.Query(DB_OnBanBySteamIdSelect, query, pack, DBPrio_High); + + Call_StartForward(g_hFwd_OnBanAdded); + Call_PushCell(admin); + Call_PushCell(-1); + Call_PushCell(iTime); + Call_PushString(reason); + Call_Finish(); + + return 0; +} + +void DB_OnBanBySteamIdSelect(Database db, DBResultSet results, const char[] error, DataPack pack) +{ + if (results == null) + { + LogToFile(logFile, "[SBPP] BanPlayerBySteamId select failed: %s", error); + delete pack; + return; + } + + pack.Reset(); + int iTime = pack.ReadCell(); + char reason[128], steamId[MAX_AUTHID_LENGTH], name[MAX_NAME_LENGTH], adminAuth[MAX_AUTHID_LENGTH], adminIp[16]; + pack.ReadString(reason, sizeof(reason)); + pack.ReadString(steamId, sizeof(steamId)); + pack.ReadString(name, sizeof(name)); + pack.ReadString(adminAuth, sizeof(adminAuth)); + pack.ReadString(adminIp, sizeof(adminIp)); + delete pack; + + if (results.RowCount > 0) + { + LogToFile(logFile, "[SBPP] BanPlayerBySteamId: %s is already banned, skipping.", steamId); + return; + } + + char steamIdEscaped[MAX_AUTHID_LENGTH * 2 + 1], nameEscaped[MAX_NAME_LENGTH * 2 + 1], reasonEscaped[256]; + DB.Escape(steamId, steamIdEscaped, sizeof(steamIdEscaped)); + DB.Escape(name, nameEscaped, sizeof(nameEscaped)); + DB.Escape(reason, reasonEscaped, sizeof(reasonEscaped)); + + char query[1024]; + if (serverID == -1) + { + FormatEx(query, sizeof(query), "INSERT INTO %s_bans (authid, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \ + ('%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', \ + IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \ + (SELECT sid FROM %s_servers WHERE ip = '%s' AND port = '%s' LIMIT 0,1), ' ')", + DatabasePrefix, steamIdEscaped, nameEscaped, (iTime * 60), (iTime * 60), reasonEscaped, + DatabasePrefix, adminAuth, adminAuth[8], adminIp, + DatabasePrefix, ServerIp, ServerPort); + } + else + { + FormatEx(query, sizeof(query), "INSERT INTO %s_bans (authid, name, created, ends, length, reason, aid, adminIp, sid, country) VALUES \ + ('%s', '%s', UNIX_TIMESTAMP(), UNIX_TIMESTAMP() + %d, %d, '%s', \ + IFNULL((SELECT aid FROM %s_admins WHERE authid = '%s' OR authid REGEXP '^STEAM_[0-9]:%s$'),'0'), '%s', \ + %d, ' ')", + DatabasePrefix, steamIdEscaped, nameEscaped, (iTime * 60), (iTime * 60), reasonEscaped, + DatabasePrefix, adminAuth, adminAuth[8], adminIp, + serverID); + } + + DB.Query(DB_OnBanBySteamIdInsert, query, _, DBPrio_High); +} + +void DB_OnBanBySteamIdInsert(Database db, DBResultSet results, const char[] error, any data) +{ + if (results == null) + LogToFile(logFile, "[SBPP] BanPlayerBySteamId insert failed: %s", error); +} + public int Native_SBReportPlayer(Handle plugin, int numParams) { if (numParams < 3) From f0c0c8294882f7bef1ec0e3d4b10de1cc574b759 Mon Sep 17 00:00:00 2001 From: Sigibert Date: Sat, 27 Jun 2026 23:56:31 +0200 Subject: [PATCH 2/3] fix: validate SteamID2 format before inserting ban SBPP enforces bans on connect using AuthId_Steam2. If a caller passed a SteamID64 or SteamID3 the ban would be stored but never fire. Now throws a native error immediately if the steamId argument does not start with 'STEAM_'. Addresses review comment from rumblefrog on PR #1511. --- game/addons/sourcemod/scripting/sbpp_main.sp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/game/addons/sourcemod/scripting/sbpp_main.sp b/game/addons/sourcemod/scripting/sbpp_main.sp index b694a1013..0201d408d 100644 --- a/game/addons/sourcemod/scripting/sbpp_main.sp +++ b/game/addons/sourcemod/scripting/sbpp_main.sp @@ -2399,6 +2399,12 @@ public int Native_SBPP_BanPlayerBySteamId(Handle plugin, int numParams) GetNativeString(3, name, sizeof(name)); GetNativeString(5, reason, sizeof(reason)); + if (strncmp(steamId, "STEAM_", 6, false) != 0) + { + ThrowNativeError(SP_ERROR_NATIVE, "SBPP_BanPlayerBySteamId: steamId must be in SteamID2 format (STEAM_X:Y:Z), got: %s", steamId); + return 0; + } + if (reason[0] == '\0') strcopy(reason, sizeof(reason), "Banned by SourceBans"); From 97ed2bfc3cf7a1a44372ca9f436bbdc68e27c572 Mon Sep 17 00:00:00 2001 From: Sigibert Date: Thu, 9 Jul 2026 13:26:48 +0200 Subject: [PATCH 3/3] fix: only fire SBPP_OnBanPlayer after the ban INSERT succeeds Addresses review nit on PR #1511 (rumblefrog): the forward was fired synchronously right after issuing the async duplicate-check SELECT, before the ban row was ever written. It now fires from DB_OnBanBySteamIdInsert only when the INSERT query succeeds. --- game/addons/sourcemod/scripting/sbpp_main.sp | 35 +++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/game/addons/sourcemod/scripting/sbpp_main.sp b/game/addons/sourcemod/scripting/sbpp_main.sp index 0201d408d..9da671ec1 100644 --- a/game/addons/sourcemod/scripting/sbpp_main.sp +++ b/game/addons/sourcemod/scripting/sbpp_main.sp @@ -2421,6 +2421,7 @@ public int Native_SBPP_BanPlayerBySteamId(Handle plugin, int numParams) } DataPack pack = new DataPack(); + pack.WriteCell(admin); pack.WriteCell(iTime); pack.WriteString(reason); pack.WriteString(steamId); @@ -2437,13 +2438,6 @@ public int Native_SBPP_BanPlayerBySteamId(Handle plugin, int numParams) DB.Query(DB_OnBanBySteamIdSelect, query, pack, DBPrio_High); - Call_StartForward(g_hFwd_OnBanAdded); - Call_PushCell(admin); - Call_PushCell(-1); - Call_PushCell(iTime); - Call_PushString(reason); - Call_Finish(); - return 0; } @@ -2457,6 +2451,7 @@ void DB_OnBanBySteamIdSelect(Database db, DBResultSet results, const char[] erro } pack.Reset(); + int admin = pack.ReadCell(); int iTime = pack.ReadCell(); char reason[128], steamId[MAX_AUTHID_LENGTH], name[MAX_NAME_LENGTH], adminAuth[MAX_AUTHID_LENGTH], adminIp[16]; pack.ReadString(reason, sizeof(reason)); @@ -2499,13 +2494,35 @@ void DB_OnBanBySteamIdSelect(Database db, DBResultSet results, const char[] erro serverID); } - DB.Query(DB_OnBanBySteamIdInsert, query, _, DBPrio_High); + DataPack fwdPack = new DataPack(); + fwdPack.WriteCell(admin); + fwdPack.WriteCell(iTime); + fwdPack.WriteString(reason); + + DB.Query(DB_OnBanBySteamIdInsert, query, fwdPack, DBPrio_High); } -void DB_OnBanBySteamIdInsert(Database db, DBResultSet results, const char[] error, any data) +void DB_OnBanBySteamIdInsert(Database db, DBResultSet results, const char[] error, DataPack pack) { + pack.Reset(); + int admin = pack.ReadCell(); + int iTime = pack.ReadCell(); + char reason[128]; + pack.ReadString(reason, sizeof(reason)); + delete pack; + if (results == null) + { LogToFile(logFile, "[SBPP] BanPlayerBySteamId insert failed: %s", error); + return; + } + + Call_StartForward(g_hFwd_OnBanAdded); + Call_PushCell(admin); + Call_PushCell(-1); + Call_PushCell(iTime); + Call_PushString(reason); + Call_Finish(); } public int Native_SBReportPlayer(Handle plugin, int numParams)