203 lines
7.6 KiB
SourcePawn
203 lines
7.6 KiB
SourcePawn
#include <sourcemod>
|
|
#include <dhooks>
|
|
#include <ripext>
|
|
#include <sdktools>
|
|
|
|
#pragma semicolon 1
|
|
#pragma newdecls required
|
|
|
|
GameData gamedatafile; //Handle to the gamedata
|
|
|
|
char originalConVar[1024]; //original sv_downloadurl value
|
|
char g_cBackupURLS[2][1024];
|
|
char g_cIPaddresses[MAXPLAYERS + 1][256];
|
|
StringMap g_SteamIDRotations;
|
|
bool g_bDoesIndexHaveBZ2[sizeof(g_cBackupURLS)]; //must have same size as g_cBackupURLS
|
|
bool g_bRotated[MAXPLAYERS + 1];
|
|
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://uk-fastdl.unloze.com/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", "https://uk-fastdl.unloze.com/css_ze/", "the second backup fastdl URL")), Cvar_backupurl2);
|
|
cvar1.GetString(g_cBackupURLS[1], sizeof(g_cBackupURLS[]));
|
|
delete cvar1;
|
|
|
|
//we only use one backup fastdl but you could potentially have several.
|
|
|
|
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!");
|
|
|
|
|
|
HookEvent("player_disconnect", Event_PlayerDisconnect, EventHookMode_Pre);
|
|
|
|
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);
|
|
}
|
|
g_bRotated[client] = false;
|
|
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");
|
|
|
|
HTTPRequest hRequest = new HTTPRequest(sRequest);
|
|
|
|
// ripext has no HEAD verb (SteamWorks' k_EHTTPMethodHEAD has no
|
|
// equivalent native here). Ask for just the first byte via a Range
|
|
// header on a GET instead, so we still avoid downloading the whole
|
|
// (potentially large) .bsp.bz2 just to check whether it exists.
|
|
// A server that honors Range answers 206/200 without the full body;
|
|
// one that doesn't just falls back to sending the whole file.
|
|
hRequest.SetHeader("Range", "bytes=0-0");
|
|
hRequest.Get(OnTransferComplete, i);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void OnTransferComplete(HTTPResponse response, any url_index)
|
|
{
|
|
if (response.Status == HTTPStatus_OK || response.Status == HTTPStatus_PartialContent)
|
|
{
|
|
g_bDoesIndexHaveBZ2[url_index] = true;
|
|
}
|
|
else
|
|
{
|
|
g_bDoesIndexHaveBZ2[url_index] = false;
|
|
}
|
|
}
|
|
|
|
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(newValue);
|
|
}
|
|
|
|
public void Cvar_backupurl2(ConVar convar, const char[] oldValue, const char[] newValue)
|
|
{
|
|
convar.SetString(newValue);
|
|
}
|
|
|
|
public void OnConfigsExecuted()
|
|
{
|
|
downloadurl = FindConVar("sv_downloadurl");
|
|
downloadurl.GetString(originalConVar, sizeof(originalConVar));
|
|
}
|
|
|
|
public MRESReturn buildConVarMessageDetCallback_Pre(Handle hParams) //Second callback
|
|
{
|
|
for (int client = 1; client <= MaxClients; client++)
|
|
{
|
|
if (IsClientConnected(client))
|
|
{
|
|
//LogMessage("client %N in buildConVarMessageDetCallback_Pre", client);
|
|
char clientIPAddress[64];
|
|
GetClientIP(client, clientIPAddress, sizeof(clientIPAddress));
|
|
|
|
for (int i = 0; i < sizeof(g_cIPaddresses); i++)
|
|
{
|
|
if (StrEqual(g_cIPaddresses[i], clientIPAddress) && !g_bRotated[client])
|
|
{
|
|
g_bRotated[client] = true; //its only from disconnecting that the clients needs to be rotated again by matching IP.
|
|
int rotated = -1;
|
|
g_SteamIDRotations.GetValue(clientIPAddress, rotated);
|
|
|
|
//0 = ovh uk unloze. 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.
|
|
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 default sv_downloadurl on %s. using %s instead.", client, 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);
|
|
}
|