diff --git a/fastdl-rotator/gamedata/fastdl_rotater.games.txt b/fastdl-rotator/gamedata/fastdl_rotater.games.txt new file mode 100644 index 0000000..0506a1d --- /dev/null +++ b/fastdl-rotator/gamedata/fastdl_rotater.games.txt @@ -0,0 +1,31 @@ +"Games" +{ + "cstrike" + { + "Signatures" + { + "CBaseClient::SendServerInfo" + { + "library" "engine" + "linux" "@_ZN11CBaseClient14SendServerInfoEv" + } + "Host_BuildConVarUpdateMessage" + { + "library" "engine" + "linux" "@_Z29Host_BuildConVarUpdateMessageP13NET_SetConVarib" + } + "CBaseClientState::Disconnect" + { + "library" "engine" + "linux" "@_ZN16CBaseClientState10DisconnectEPKcb" + } + } + "Offsets" + { + "CBaseClient::GetPlayerSlot" + { + "linux" "3" + } + } + } +} diff --git a/fastdl-rotator/scripting/fastdl_rotater.sp b/fastdl-rotator/scripting/fastdl_rotater.sp new file mode 100644 index 0000000..631ad93 --- /dev/null +++ b/fastdl-rotator/scripting/fastdl_rotater.sp @@ -0,0 +1,217 @@ +#include +#include +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +Handle hPlayerSlot = INVALID_HANDLE; +GameData gamedatafile; //Handle to the gamedata + +char originalConVar[1024]; //original sv_downloadurl value +char g_cBackupURLS[2][1024]; +char g_cIPaddresses[MAXPLAYERS + 1][256]; +int g_icurrentClient; +StringMap g_SteamIDRotations; +bool g_bDoesIndexHaveBZ2[sizeof(g_cBackupURLS)]; //must have same size as g_cBackupURLS +ConVar downloadurl; // sv_downloadurl + +public Plugin myinfo = +{ + name = "Fastdownload rotater", + description = "Rotates fastdl used for clients when missing map errors happen", + author = "jenz", + url = "", + version = "1.0" +}; + +public void OnPluginStart() +{ + g_SteamIDRotations = new StringMap(); + ConVar cvar; + HookConVarChange((cvar = CreateConVar("sm_first_backup_fastdl_url", "https://fastdl.nide.gg/css_ze/", "the first backup fastdl URL")), Cvar_backupurl1); + cvar.GetString(g_cBackupURLS[0], sizeof(g_cBackupURLS[])); + delete cvar; + + ConVar cvar1; + HookConVarChange((cvar1 = CreateConVar("sm_second_backup_fastdl_url", "http://uk-fastdl.unloze.com/css_ze/", "the second backup fastdl URL")), Cvar_backupurl2); + cvar1.GetString(g_cBackupURLS[1], sizeof(g_cBackupURLS[])); + delete cvar1; + + downloadurl = FindConVar("sv_downloadurl"); //Save original downloadurl, so we can send it to clients who we cant locate + downloadurl.GetString(originalConVar, sizeof(originalConVar)); + + gamedatafile = LoadGameConfigFile("fastdl_rotater.games"); + + if(gamedatafile == null) + SetFailState("Cannot load fastdl_rotater.games.txt! Make sure you have it installed!"); + + Handle detourBuildConVarMessage = DHookCreateDetour(Address_Null, CallConv_CDECL, ReturnType_Void, ThisPointer_Ignore); + if(detourBuildConVarMessage == null) + SetFailState("Failed to create detour for Host_BuildConVarUpdateMessage!"); + + if(!DHookSetFromConf(detourBuildConVarMessage, gamedatafile, SDKConf_Signature, "Host_BuildConVarUpdateMessage")) + SetFailState("Failed to load Host_BuildConVarUpdateMessage signature from gamedata!"); + + DHookAddParam(detourBuildConVarMessage, HookParamType_Unknown); + DHookAddParam(detourBuildConVarMessage, HookParamType_Int); + DHookAddParam(detourBuildConVarMessage, HookParamType_Bool); + + if(!DHookEnableDetour(detourBuildConVarMessage, false, buildConVarMessageDetCallback_Pre)) + SetFailState("Failed to detour Host_BuildConVarUpdateMessage PreHook!"); + + + Handle detourSendServerInfo = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Bool, ThisPointer_Address); + if(detourSendServerInfo == null) + SetFailState("Failed to create detour for CBaseClient::SendServerInfo!"); + + if(!DHookSetFromConf(detourSendServerInfo, gamedatafile, SDKConf_Signature, "CBaseClient::SendServerInfo")) + SetFailState("Failed to load CBaseClient::SendServerInfo signature from gamedata!"); + + if(!DHookEnableDetour(detourSendServerInfo, false, sendServerInfoDetCallback_Pre)) + SetFailState("Failed to detour CBaseClient::SendServerInfo PreHook!"); + + HookEvent("player_disconnect", Event_PlayerDisconnect, EventHookMode_Pre); + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gamedatafile, SDKConf_Virtual, "CBaseClient::GetPlayerSlot"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + hPlayerSlot = EndPrepSDKCall(); + + OnMapStart(); +} + +public Action Event_PlayerDisconnect(Handle event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(GetEventInt(event, "userid")); + char reason[256]; + GetEventString(event, "reason", reason, sizeof(reason)); + //requires net_disconnect_reason 1 + if (StrContains(reason, "Map is missing") != -1) + { + GetClientIP(client, g_cIPaddresses[client], sizeof(g_cIPaddresses[])); + + int rotated = -1; + g_SteamIDRotations.GetValue(g_cIPaddresses[client], rotated); + rotated++; + g_SteamIDRotations.SetValue(g_cIPaddresses[client], rotated, true); + } + return Plugin_Continue; +} + +public void OnMapStart() +{ + //http request to the backup fastdl urls to verify if they have the bz2 files. + char sRequest[512]; + char map[256]; + GetCurrentMap(map, sizeof(map)); + for (int i = 0; i < sizeof(g_cBackupURLS);i++) + { + g_bDoesIndexHaveBZ2[i] = false; //resetting to false until the http header finished + FormatEx(sRequest, sizeof(sRequest), "%s%s%s%s", g_cBackupURLS[i], "maps/", map, ".bsp.bz2"); + + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodHEAD, sRequest); + if (!hRequest || + !SteamWorks_SetHTTPCallbacks(hRequest, OnTransferComplete) || + !SteamWorks_SetHTTPRequestContextValue(hRequest, i) || + !SteamWorks_SendHTTPRequest(hRequest)) + { + delete hRequest; + } + } +} + +//---------------------------------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------------------------------- +public int OnTransferComplete(Handle hRequest, bool bFailure, bool bSuccessful, EHTTPStatusCode eStatusCode, int url_index) +{ + if (bFailure || !bSuccessful || eStatusCode != k_EHTTPStatusCode200OK) + { + g_bDoesIndexHaveBZ2[url_index] = false; + } + else + { + g_bDoesIndexHaveBZ2[url_index] = true; + } + delete hRequest; + return 0; +} + +public void OnMapEnd() +{ + for (int i = 0; i <= MaxClients; i++) + { + Format(g_cIPaddresses[i], sizeof(g_cIPaddresses[]), ""); + } + g_SteamIDRotations = new StringMap(); +} + +public void Cvar_backupurl1(ConVar convar, const char[] oldValue, const char[] newValue) +{ + convar.SetString(g_cBackupURLS[0]); +} + +public void Cvar_backupurl2(ConVar convar, const char[] oldValue, const char[] newValue) +{ + convar.SetString(g_cBackupURLS[1]); +} + +public void OnConfigsExecuted() +{ + downloadurl = FindConVar("sv_downloadurl"); + downloadurl.GetString(originalConVar, sizeof(originalConVar)); +} + +public MRESReturn sendServerInfoDetCallback_Pre(Address pointer, Handle hReturn, Handle hParams) //First callback +{ + int client; + client = view_as(SDKCall(hPlayerSlot, pointer)) + 1; //we just use linux anyways, no reason to check OS. + g_icurrentClient = client; + return MRES_Ignored; +} + +public MRESReturn buildConVarMessageDetCallback_Pre(Handle hParams) //Second callback +{ + if ((g_icurrentClient == 0 || g_icurrentClient > MaxClients)) return MRES_Ignored; + + char clientIPAddress[64]; //IP of the connecting client + GetClientIP(g_icurrentClient, clientIPAddress, sizeof(clientIPAddress)); + + for (int i = 0; i < sizeof(g_cIPaddresses); i++) + { + if (StrEqual(g_cIPaddresses[i], clientIPAddress)) + { + int rotated = -1; + g_SteamIDRotations.GetValue(clientIPAddress, rotated); + + //0 = hetzner nide. 1 = ovh uk unloze. 2 = default sv_downloadurl again. so not overwriting anything + rotated = rotated % 3; + + //the bz2 file does not exist on the backup urls so rotate to the next working one. for example hetzner nide might not have but ovh uk has. + while (rotated < sizeof(g_cBackupURLS) && !g_bDoesIndexHaveBZ2[rotated]) + { + rotated++; + } + if (rotated < sizeof(g_cBackupURLS)) + { + char map[256]; + GetCurrentMap(map, sizeof(map)); + g_SteamIDRotations.SetValue(clientIPAddress, rotated, true); //adapting in case of increment. + LogMessage("client %N had missing map with cloudflare on %s. using %s instead.", g_icurrentClient, map, g_cBackupURLS[rotated]); + setConVarValue(g_cBackupURLS[rotated]); + } + break; + } + } + return MRES_Ignored; +} + +void setConVarValue(char[] value) //Sets the actual ConVar value +{ + int oldflags = GetConVarFlags(downloadurl); + SetConVarFlags(downloadurl, oldflags &~ FCVAR_REPLICATED); + SetConVarString(downloadurl, value, true, false); + SetConVarFlags(downloadurl, oldflags|FCVAR_REPLICATED); +}