From 2d91202469f57c38faacec22cc073a3d538aba67 Mon Sep 17 00:00:00 2001 From: jenz Date: Fri, 24 Jul 2026 11:03:05 +0200 Subject: [PATCH] forwards finally detect the webclient, handling password and retry through the plugin for the webclient --- CELT_VOICE/scripting/nosteam_celt_audio.sp | 185 +++++++++++++++++++-- 1 file changed, 171 insertions(+), 14 deletions(-) diff --git a/CELT_VOICE/scripting/nosteam_celt_audio.sp b/CELT_VOICE/scripting/nosteam_celt_audio.sp index 758f1db..9f96832 100644 --- a/CELT_VOICE/scripting/nosteam_celt_audio.sp +++ b/CELT_VOICE/scripting/nosteam_celt_audio.sp @@ -4,8 +4,10 @@ #include #include #include +#include #tryinclude #include +#include #include #pragma semicolon 1 @@ -14,20 +16,22 @@ Handle g_hCheckMutes = null; Handle g_hCookieTorchMuted = null; -bool g_bWasChecked[MAXPLAYERS + 1]; bool g_bWebclient[MAXPLAYERS + 1]; - +bool g_bReRunWebClientName; bool g_bSkipInfection = false; +char g_sLastWebclientIp[64]; + public Plugin myinfo = { name = "NoSteam CELT Voice override", author = "jenz", - version = "1.0" + version = "1.5" }; public void OnPluginStart() { + Format(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), ""); g_hCheckMutes = CreateTimer(2.0, check_mutes, _, TIMER_REPEAT); g_hCookieTorchMuted = RegClientCookie("torch_muted", "is torch muted", CookieAccess_Protected); } @@ -63,14 +67,6 @@ public Action check_mutes(Handle timer, any data) { continue; } - if (!g_bWasChecked[i]) - { - //this exists for the webclient. the webclient is skipping all connection related forwards. hence a manual check like this is needed for the webclient. - char dummy[64]; - OnClientAuthorized(i, dummy); - g_bWebclient[i] = true; - g_bSkipInfection = GetConVarInt(FindConVar("zr_infect_mzombie_ratio")) > 10 ? true : false; - } if (!PM_IsPlayerSteam(i)) { set_nosteam_listen_override(i); @@ -89,8 +85,15 @@ public Action check_mutes(Handle timer, any data) //PrintToChatAll("i: %N. j: %N. isMuted: %i", i, j, isMuted); ClientMutedOtherClient(i, j, isMuted); } + if (g_bReRunWebClientName && g_bWebclient[i]) + { + char dummy[64]; + OnClientAuthorized(i, dummy); + g_bReRunWebClientName = false; + } } + //sourceTV special case, index 0. for (int j = 1; j <= MaxClients; j++) { @@ -111,23 +114,90 @@ public Action check_mutes(Handle timer, any data) ClientMutedOtherClient(j, 0, false); } } + + //check if webclient has new IP every 2 seconds through endpoint. + PollWebclientCurrentIp(); return Plugin_Continue; } +//AI slop +public void SendWebclientNameToSignaling(const char[] webclientName) +{ + char url[256]; + // Adjust port/path to match your signaling servers actual endpoint + FormatEx(url, sizeof(url), "http://127.0.0.1:3000/webclient-name"); + + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, url); + if (hRequest == null) + { + return; + } + + // Send the name as a POST body parameter + SteamWorks_SetHTTPRequestGetOrPostParameter(hRequest, "name", webclientName); + + if (!SteamWorks_SetHTTPCallbacks(hRequest, OnWebclientNamePosted) || + !SteamWorks_SendHTTPRequest(hRequest)) + { + delete hRequest; + } +} + +public int OnWebclientNamePosted(Handle hRequest, bool bFailure, bool bRequestSuccessful, + EHTTPStatusCode eStatusCode, any data) +{ + // Fire-and-forget: we dont care about the response, just clean up. + delete hRequest; + return 0; +} + +public void OnMapStart() +{ + g_bReRunWebClientName = true; +} + public void OnClientDisconnect(int client) { - g_bWasChecked[client] = false; g_bWebclient[client] = false; } -public void OnClientAuthorized(int client, const char[] auth) +public void OnClientPostAdminCheck(int client) { - g_bWasChecked[client] = true; g_bWebclient[client] = false; if (IsFakeClient(client)) { return; } + + char sIP[32]; + GetClientIP(client, sIP, sizeof(sIP)); + + char allowed_ips[128]; + ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips"); + if (sv_set_steam_id_ips != null) + { + sv_set_steam_id_ips.GetString(allowed_ips, sizeof(allowed_ips)); + if (StrEqual(sIP, allowed_ips)) + { + g_bWebclient[client] = true; + g_bSkipInfection = GetConVarInt(FindConVar("zr_infect_mzombie_ratio")) > 10 ? true : false; + + //this works fine with one webclient only existing. + char webclientName[64]; + GetConVarString(FindConVar("sv_webclient_name"), webclientName, sizeof(webclientName)); + SetClientName(client, webclientName); + SendWebclientNameToSignaling(webclientName); + } + } +} + +public void OnClientAuthorized(int client, const char[] auth) +{ + if (IsFakeClient(client)) + { + return; + } + SetClientNoSteam(client, false); if (!PM_IsPlayerSteam(client)) { @@ -183,6 +253,93 @@ public Action Timer_SendVoiceInit(Handle timer, int Serial) return Plugin_Handled; } +/** + * Polls the signaling server for the current webclient controller's real IP. + * If the IP has changed since the last + * check and a webclient player is currently connected, forces them to + * reconnect with the new password via ClientCommand. + **/ +public void PollWebclientCurrentIp() +{ + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, "http://127.0.0.1:3000/webclient-current-ip"); + if (hRequest == null) + { + return; + } + + if (!SteamWorks_SetHTTPCallbacks(hRequest, OnWebclientCurrentIpReceived) || + !SteamWorks_SendHTTPRequest(hRequest)) + { + delete hRequest; + } +} + +public int OnWebclientCurrentIpReceived(Handle hRequest, bool bFailure, bool bRequestSuccessful, + EHTTPStatusCode eStatusCode, any data) +{ + if (bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) + { + delete hRequest; + return 0; + } + + int bodySize; + bool gotSize = SteamWorks_GetHTTPResponseBodySize(hRequest, bodySize); + + char newIp[64]; + newIp[0] = '\0'; + + if (gotSize && bodySize > 0 && bodySize < sizeof(newIp)) + { + SteamWorks_GetHTTPResponseBodyData(hRequest, newIp, bodySize); + } + + delete hRequest; + + if (newIp[0] == '\0') + { + return 0; + } + + if (StrEqual(newIp, g_sLastWebclientIp)) + { + return 0; + } + + for (int i = 1; i <= MaxClients; i++) + { + if (IsValidClient(i) && g_bWebclient[i]) + { + strcopy(g_sLastWebclientIp, sizeof(g_sLastWebclientIp), newIp); + LogMessage("Forcing webclient reconnect for client %N with new IP: %s", i, newIp); + ClientCommand(i, "retry"); + break; + } + } + return 0; +} + +public EConnect OnClientPreConnectEx(const char[] sName, char sPassword[255], const char[] sIP, const char[] sSteam32ID, char sRejectReason[255]) +{ + char allowedIp[64]; + ConVar sv_set_steam_id_ips = FindConVar("sv_set_steam_id_ips"); + if (sv_set_steam_id_ips != null) + { + sv_set_steam_id_ips.GetString(allowedIp, sizeof(allowedIp)); + //this is to handle that the connect extension receives the password parameter for the webclient as its real connecting IP. + //works both for the client command retry and for the case that somebody presses the connect button in the clients Menu as the same password + //is applied again. + if (StrEqual(sIP, allowedIp) && strlen(g_sLastWebclientIp) > 0) + { + strcopy(sPassword, 255, g_sLastWebclientIp); + LogMessage("Backfilled empty password for webclient with cached IP: %s", g_sLastWebclientIp); + } + } + // ET_LowEvent takes the LOWEST value returned across all plugins hooking this forward. + //this plugin returns 1 (Accept), meanwhile the reserved slot plugin still might return 0 (reject) or -1 (async) to overrule the decision from this return value. + return k_OnClientPreConnectEx_Accept; +} + stock bool IsValidClient(int client) { if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))