Implement Valve game directory detection check to prevent incorrect use of mod dlls

This commit is contained in:
Sam V 2024-01-06 22:52:17 +01:00
parent 00fb5c4708
commit ab5735c1cf
5 changed files with 106 additions and 11 deletions

View file

@ -1,5 +1,15 @@
# Half-Life Updated changelog
## Changes in V1.0.0 Release Candidate 004
### Bug fixes
* Disabled GCC optimization that prevents mod dlls from unloading after engine calls dlclose
### Features
* Mods made with this SDK will now shut down if they detect they are being run from a Valve game directory (e.g. by placing the dlls in `Half-Life/valve/cl_dlls` and `Half-Life/valve/dlls`). This is not supported and puts users at risk of being VAC banned. Run mods from their intended location only
## Changes in V1.0.0 Release Candidate 003
### Bug fixes

View file

@ -18,6 +18,8 @@
// this implementation handles the linking of the engine to the DLL
//
#include <SDL2/SDL_messagebox.h>
#include "hud.h"
#include "cl_util.h"
#include "netadr.h"
@ -108,6 +110,28 @@ void DLLEXPORT HUD_PlayerMove(struct playermove_s* ppmove, int server)
PM_Move(ppmove, server);
}
static bool CL_InitClient()
{
EV_HookEvents();
CL_LoadParticleMan();
if (!FileSystem_LoadFileSystem())
{
return false;
}
if (UTIL_IsValveGameDirectory())
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error",
"This mod has detected that it is being run from a Valve game directory which is not supported\n"
"Run this mod from its intended location\n\nThe game will now shut down", nullptr);
return false;
}
// get tracker interface, if any
return true;
}
int DLLEXPORT Initialize(cl_enginefunc_t* pEnginefuncs, int iVersion)
{
gEngfuncs = *pEnginefuncs;
@ -119,15 +143,15 @@ int DLLEXPORT Initialize(cl_enginefunc_t* pEnginefuncs, int iVersion)
memcpy(&gEngfuncs, pEnginefuncs, sizeof(cl_enginefunc_t));
EV_HookEvents();
CL_LoadParticleMan();
const bool result = CL_InitClient();
if (!FileSystem_LoadFileSystem())
if (!result)
{
gEngfuncs.Con_DPrintf("Error initializing client\n");
gEngfuncs.pfnClientCmd("quit\n");
return 0;
}
// get tracker interface, if any
return 1;
}

View file

@ -454,6 +454,23 @@ cvar_t sk_player_leg3 = {"sk_player_leg3", "1"};
// END Cvars for Skill Level settings
static bool SV_InitServer()
{
if (!FileSystem_LoadFileSystem())
{
return false;
}
if (UTIL_IsValveGameDirectory())
{
g_engfuncs.pfnServerPrint("This mod has detected that it is being run from a Valve game directory which is not supported\n"
"Run this mod from its intended location\n\nThe game will now shut down\n");
return false;
}
return true;
}
// Register your console variables here
// This gets called one time when the game is initialied
void GameDLLInit()
@ -465,8 +482,9 @@ void GameDLLInit()
g_footsteps = CVAR_GET_POINTER("mp_footsteps");
g_psv_cheats = CVAR_GET_POINTER("sv_cheats");
if (!FileSystem_LoadFileSystem())
if (!SV_InitServer())
{
g_engfuncs.pfnServerPrint("Error initializing server\n");
//Shut the game down as soon as possible.
SERVER_COMMAND("quit\n");
return;

View file

@ -53,6 +53,7 @@ static CSysModule* g_pFileSystemModule = nullptr;
// The engine's filesystem doesn't provide functions to do this so we have to work around it.
static std::string g_GameDirectory;
static std::string g_ModDirectory;
static std::string g_ModDirectoryName;
static bool FileSystem_InitializeGameDirectory()
{
@ -97,18 +98,17 @@ static bool FileSystem_InitializeGameDirectory()
gameDirectory.shrink_to_fit();
std::string modDirectory;
modDirectory.resize(BufferSize);
g_ModDirectoryName.resize(BufferSize);
#ifdef CLIENT_DLL
modDirectory = gEngfuncs.pfnGetGameDirectory();
g_ModDirectoryName = gEngfuncs.pfnGetGameDirectory();
#else
g_engfuncs.pfnGetGameDir(modDirectory.data());
modDirectory.resize(std::strlen(modDirectory.c_str()));
g_engfuncs.pfnGetGameDir(g_ModDirectoryName.data());
g_ModDirectoryName.resize(std::strlen(g_ModDirectoryName.c_str()));
#endif
g_GameDirectory = std::move(gameDirectory);
g_ModDirectory = g_GameDirectory + DefaultPathSeparatorChar + modDirectory;
g_ModDirectory = g_GameDirectory + DefaultPathSeparatorChar + g_ModDirectoryName;
return true;
}
@ -181,6 +181,11 @@ void FileSystem_FreeFileSystem()
}
}
const std::string& FileSystem_GetModDirectoryName()
{
return g_ModDirectoryName;
}
void FileSystem_FixSlashes(std::string& fileName)
{
std::replace(fileName.begin(), fileName.end(), AlternatePathSeparatorChar, DefaultPathSeparatorChar);
@ -310,3 +315,30 @@ bool FileSystem_WriteTextToFile(const char* fileName, const char* text, const ch
return false;
}
constexpr const char* ValveGameDirectoryPrefixes[] =
{
"valve",
"gearbox",
"bshift",
"ricochet",
"dmc",
"cstrike",
"czero", // Also covers Deleted Scenes (czeror)
"dod",
"tfc"};
bool UTIL_IsValveGameDirectory()
{
const std::string& modDirectoryName = FileSystem_GetModDirectoryName();
for (const auto prefix : ValveGameDirectoryPrefixes)
{
if (strnicmp(modDirectoryName.c_str(), prefix, strlen(prefix)) == 0)
{
return true;
}
}
return false;
}

View file

@ -44,6 +44,11 @@ inline IFileSystem* g_pFileSystem = nullptr;
bool FileSystem_LoadFileSystem();
void FileSystem_FreeFileSystem();
/**
* @brief Returns the mod directory name. Only valid to call after calling FileSystem_LoadFileSystem.
*/
const std::string& FileSystem_GetModDirectoryName();
/**
* @brief Replaces occurrences of ::AlternatePathSeparatorChar with ::DefaultPathSeparatorChar.
*/
@ -102,6 +107,12 @@ std::vector<std::byte> FileSystem_LoadFileIntoBuffer(const char* fileName, FileC
*/
bool FileSystem_WriteTextToFile(const char* fileName, const char* text, const char* pathID = nullptr);
/**
* @brief Returns @c true if the current game directory is that of a Valve game.
* Any directory whose name starts with that of a Valve game's directory name is considered to be one, matching Steam's behavior.
*/
bool UTIL_IsValveGameDirectory();
/**
* @brief Helper class to automatically close the file handle associated with a file.
*/