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