add pdata support, improve filesystem hooks

This commit is contained in:
BobTheBob 2021-07-27 04:27:26 +01:00
parent 80428e6f62
commit 64b8db08ed
9 changed files with 226 additions and 18 deletions

View file

@ -308,6 +308,7 @@
<ClInclude Include="modmanager.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="scriptsrson.h" />
<ClInclude Include="serverauthentication.h" />
<ClInclude Include="sigscanning.h" />
<ClInclude Include="sourceconsole.h" />
<ClInclude Include="sourceinterface.h" />
@ -332,6 +333,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="scriptsrson.cpp" />
<ClCompile Include="serverauthentication.cpp" />
<ClCompile Include="sigscanning.cpp" />
<ClCompile Include="sourceconsole.cpp" />
<ClCompile Include="sourceinterface.cpp" />

View file

@ -88,6 +88,18 @@
<Filter Include="Source Files\Shared\Mods\Compiled">
<UniqueIdentifier>{14fc0931-acad-46ec-a55e-94f4469d4235}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Server">
<UniqueIdentifier>{3d41d3fc-8a3b-4358-b3e8-4f06dc96abfe}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Server\Authentication">
<UniqueIdentifier>{d69760a9-d5ec-4f3e-8f43-f74041654d44}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Server">
<UniqueIdentifier>{365e5c1f-4b2f-4d8b-a1d8-cdef401ca689}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Server\Authentication">
<UniqueIdentifier>{24fd0855-9288-4129-93ba-c6cafdc98d1b}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
@ -519,6 +531,9 @@
<ClInclude Include="scriptsrson.h">
<Filter>Header Files\Shared\Mods\Compiled</Filter>
</ClInclude>
<ClInclude Include="serverauthentication.h">
<Filter>Header Files\Server\Authentication</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
@ -572,6 +587,9 @@
<ClCompile Include="scriptsrson.cpp">
<Filter>Source Files\Shared\Mods\Compiled</Filter>
</ClCompile>
<ClCompile Include="serverauthentication.cpp">
<Filter>Source Files\Server\Authentication</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="include\spdlog\fmt\bundled\LICENSE.rst">

View file

@ -64,14 +64,14 @@ class ConCommand;
// also i sure do hope this size is right because there's a fairly decent chance it isn't
class ConVar
{
private:
public:
// if there are ever crashes caused by modifying custom cvars, check this
unsigned char unknown[0x58];
unsigned char unknown[0x40];
char* m_pszString;
int64_t m_StringLength;
size_t m_StringLength;
float m_fValue;
int32_t m_nValue;
unsigned char unknown2[0x10];
unsigned char unknown2[0x28];
public:
virtual void EngineDestructor(void) {}

View file

@ -8,6 +8,7 @@
#include "concommand.h"
#include "modmanager.h"
#include "filesystem.h"
#include "serverauthentication.h"
#include <iostream>
bool initialised = false;
@ -56,6 +57,7 @@ void InitialiseNorthstar()
}
AddDllLoadCallback("server.dll", InitialiseServerSquirrel);
AddDllLoadCallback("engine.dll", InitialiseServerAuthentication);
AddDllLoadCallback("filesystem_stdio.dll", InitialiseFilesystem);

View file

@ -17,7 +17,12 @@ typedef bool(*ReadFromCacheType)(IFileSystem* filesystem, const char* path, void
ReadFromCacheType readFromCache;
bool ReadFromCacheHook(IFileSystem* filesystem, const char* path, void* result);
typedef void(*AddSearchPathType)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
AddSearchPathType addSearchPathOriginal;
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType);
bool readingOriginalFile;
std::string currentModPath;
SourceInterface<IFileSystem>* g_Filesystem;
void InitialiseFilesystem(HMODULE baseAddress)
@ -28,6 +33,7 @@ void InitialiseFilesystem(HMODULE baseAddress)
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x5CBA0, &ReadFileFromVPKHook, reinterpret_cast<LPVOID*>(&readFileFromVPK));
ENABLER_CREATEHOOK(hook, (*g_Filesystem)->m_vtable->ReadFromCache, &ReadFromCacheHook, reinterpret_cast<LPVOID*>(&readFromCache));
ENABLER_CREATEHOOK(hook, (*g_Filesystem)->m_vtable->AddSearchPath, &AddSearchPathHook, reinterpret_cast<LPVOID*>(&addSearchPathOriginal));
}
std::string ReadVPKFile(const char* path)
@ -60,13 +66,21 @@ std::string ReadVPKOriginalFile(const char* path)
void SetNewModSearchPaths(Mod* mod)
{
// put our new path to the head
// in future we should look into manipulating paths at head manually, might be effort tho
// potentially we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets
// put our new path to the head if we need to read from a different mod path
// in the future we could also determine whether the file we're setting paths for needs a mod dir, or compiled assets
if (mod != nullptr)
(*g_Filesystem)->m_vtable->AddSearchPath(&*(*g_Filesystem), (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
(*g_Filesystem)->m_vtable->AddSearchPath(&*(*g_Filesystem), fs::absolute(COMPILED_ASSETS_PATH).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
{
if ((fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().compare(currentModPath))
{
spdlog::info("changing mod search path from {} to {}", currentModPath, mod->ModDirectory.string());
addSearchPathOriginal(&*(*g_Filesystem), (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
currentModPath = (fs::absolute(mod->ModDirectory) / MOD_OVERRIDE_DIR).string();
}
}
else if (!currentModPath.size()) // if currentModPath isn't set yet, then push compiled to head
addSearchPathOriginal(&*(*g_Filesystem), fs::absolute(COMPILED_ASSETS_PATH).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
bool TryReplaceFile(const char* path)
@ -76,7 +90,7 @@ bool TryReplaceFile(const char* path)
(*g_ModManager).CompileAssetsForFile(path);
// is this efficient? no clue
// is this efficient? could probably be improved
for (ModOverrideFile* modFile : g_ModManager->m_modFiles)
{
if (!modFile->path.compare(fs::path(path).lexically_normal()))
@ -111,4 +125,16 @@ bool ReadFromCacheHook(IFileSystem* filesystem, const char* path, void* result)
return false;
return readFromCache(filesystem, path, result);
}
void AddSearchPathHook(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)
{
addSearchPathOriginal(fileSystem, pPath, pathID, addType);
// make sure current mod paths are at head
if (!strcmp(pathID, "GAME") && currentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
{
addSearchPathOriginal(fileSystem, currentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
addSearchPathOriginal(fileSystem, COMPILED_ASSETS_PATH.string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
}

View file

@ -162,12 +162,6 @@ Mod::Mod(fs::path modDir, char* jsonBuf)
}
}
// vpk stuff
if (fs::exists(modDir / "vpk"))
for (fs::directory_entry file : fs::directory_iterator(modDir / "vpk"))
if (fs::is_regular_file(file) && file.path().extension() == "vpk")
Vpks.push_back(file.path().string());
wasReadSuccessfully = true;
}
@ -239,6 +233,12 @@ void ModManager::LoadMods()
for (ModConVar* convar : mod->ConVars)
if (g_CustomConvars.find(convar->Name) == g_CustomConvars.end()) // make sure convar isn't registered yet, unsure if necessary but idk what behaviour is for defining same convar multiple times
RegisterConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str());
// read vpk paths
if (fs::exists(mod->ModDirectory / "vpk"))
for (fs::directory_entry file : fs::directory_iterator(mod->ModDirectory / "vpk"))
if (fs::is_regular_file(file) && file.path().extension() == "vpk")
mod->Vpks.push_back(file.path().string());
}
// in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised

View file

@ -0,0 +1,130 @@
#include "pch.h"
#include "serverauthentication.h"
#include "convar.h"
#include "hookutils.h"
#include <fstream>
#include <filesystem>
// hooks
typedef void(*RejectClientType)(void* a1, const char* a2, void* player, const char* fmt, ...);
RejectClientType RejectClient;
typedef void*(*CBaseServer__ConnectClientType)(void* server, void* a2, void* a3, uint32_t a4, uint32_t a5, int32_t a6, void* a7, void* a8, char* serverFilter, void* a10, char a11, void* a12, char a13, char a14, void* a15, uint32_t a16, uint32_t a17);
CBaseServer__ConnectClientType CBaseServer__ConnectClient;
typedef char(*CBaseClient__ConnectType)(void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, int a7);
CBaseClient__ConnectType CBaseClient__Connect;
// global vars
ServerAuthenticationManager* g_ServerAuthenticationManager;
ConVar* CVar_ns_auth_allow_insecure;
ConVar* CVar_ns_auth_allow_insecure_write;
ServerAuthenticationManager::ServerAuthenticationManager() : m_authData(std::unordered_map<std::string, AuthData*>())
{}
void ServerAuthenticationManager::AddPlayerAuth(char* authToken, char* uid, char* pdata, size_t pdataSize)
{
}
bool ServerAuthenticationManager::AuthenticatePlayer(__int64 player, char* authToken)
{
// straight up just given up
if (!m_authData.empty() && m_authData.count(authToken))
{
// use stored auth data
AuthData* authData = m_authData[authToken];
// uuid
strcpy((char*)player + 0xF500, authData->uid);
// copy pdata into buffer
memcpy((char*)player + 0x4FA, authData->pdata, authData->pdataSize);
}
else
{
if (!CVar_ns_auth_allow_insecure->m_nValue) // no auth data and insecure connections aren't allowed, so dc the client
return false;
spdlog::info("wtf");
spdlog::info(player);
// no auth data available and insecure connections are allowed, try reading from disk, using authtoken as uid
// uuid
strcpy((char*)(player + 0xF500), authToken);
// try reading pdata file for player
std::string pdataPath = "playerdata/playerdata_";
pdataPath += authToken;
pdataPath += ".pdata";
std::fstream pdataStream(pdataPath, std::ios_base::in);
if (pdataStream.fail()) // file doesn't exist, use placeholder
pdataStream = std::fstream("playerdata/placeholder_playerdata.pdata");
// get file length
pdataStream.seekg(0, pdataStream.end);
int length = pdataStream.tellg();
pdataStream.seekg(0, pdataStream.beg);
// copy pdata into buffer
pdataStream.read((char*)(player + 0x4FA), length);
}
// set persistent data as ready
*(char*)(player + 0x4a0) = (char)0x3;
return true; // auth successful, client stays on
}
// store this in a var so we can use it in CBaseClient::Connect
// this is fine because serverfilter ptr won't decay by the time we use this
char* nextPlayerToken;
void* CBaseServer__ConnectClientHook(void* server, void* a2, void* a3, uint32_t a4, uint32_t a5, int32_t a6, void* a7, void* a8, char* serverFilter, void* a10, char a11, void* a12, char a13, char a14, void* a15, uint32_t a16, uint32_t a17)
{
// auth tokens are sent with serverfilter, can't be accessed from player struct to my knowledge, so have to do this here
nextPlayerToken = serverFilter;
return CBaseServer__ConnectClient(server, a2, a3, a4, a5, a6, a7, a8, serverFilter, a10, a11, a12, a13, a14, a15, a16, a17);
}
char CBaseClient__ConnectHook(void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, int a7)
{
if (!g_ServerAuthenticationManager->AuthenticatePlayer((__int64)self, nextPlayerToken))
{
//RejectClient(nullptr, "Authentication failed", self, "Authentication failed");
//return 0;
}
return CBaseClient__Connect(self, name, netchan_ptr_arg, b_fake_player_arg, a5, Buffer, a7);
}
void InitialiseServerAuthentication(HMODULE baseAddress)
{
g_ServerAuthenticationManager = new ServerAuthenticationManager;
RejectClient = (RejectClientType)((char*)baseAddress + 0x1182E0);
CVar_ns_auth_allow_insecure = RegisterConVar("ns_auth_allow_insecure", "0", FCVAR_GAMEDLL, "Whether this server will allow unauthenicated players to connect");
CVar_ns_auth_allow_insecure_write = RegisterConVar("ns_auth_allow_insecure_write", "0", FCVAR_GAMEDLL, "Whether the pdata of unauthenticated clients will be written to disk when changed");
spdlog::info((void*)CVar_ns_auth_allow_insecure);
spdlog::info((void*)&CVar_ns_auth_allow_insecure->m_nValue);
HookEnabler hook;
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x114430, &CBaseServer__ConnectClientHook, reinterpret_cast<LPVOID*>(&CBaseServer__ConnectClient));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x101740, &CBaseClient__ConnectHook, reinterpret_cast<LPVOID*>(&CBaseClient__Connect));
// patch to disable kicking based on incorrect serverfilter in connectclient, since we repurpose it for use as an auth token
{
void* ptr = (char*)baseAddress + 0x114655;
TempReadWrite rw(ptr);
*((char*)ptr) = (char)0xEB; // jz => jmp
}
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <unordered_map>
#include <string>
struct AuthData
{
char* uid;
// pdata
char* pdata;
size_t pdataSize;
};
class ServerAuthenticationManager
{
public:
std::unordered_map<std::string, AuthData*> m_authData;
public:
ServerAuthenticationManager();
void AddPlayerAuth(char* authToken, char* uid, char* pdata, size_t pdataSize);
bool AuthenticatePlayer(__int64 player, char* authToken);
void WritePersistentData(void* player);
};
void InitialiseServerAuthentication(HMODULE baseAddress);
extern ServerAuthenticationManager* g_ServerAuthenticationManager;

View file

@ -89,18 +89,20 @@ void InitialiseClientSquirrel(HMODULE baseAddress)
void InitialiseServerSquirrel(HMODULE baseAddress)
{
g_ServerSquirrelManager = new SquirrelManager<SERVER>();
g_ServerSquirrelManager->AddFuncRegistration("void", "SavePdataForEntityIndex", "int i", "idk", NSTestFunc);
HookEnabler hook;
ServerSq_compilebuffer = (sq_compilebufferType)((char*)baseAddress + 0x3110);
ServerSq_pushroottable = (sq_pushroottableType)((char*)baseAddress + 0x5840);
ServerSq_call = (sq_callType)((char*)baseAddress + 0x8620);
ServerRegisterSquirrelFunc = (RegisterSquirrelFuncType)((char*)baseAddress + 0x1DDD10);
ServerRegisterSquirrelFunc = (RegisterSquirrelFuncType)((char*)baseAddress + 0x1DD10);
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1FE90, &SQPrintHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerSQPrint)); // server print function
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x260E0, &CreateNewVMHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerCreateNewVM)); // server createnewvm function
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x26E20, &DestroyVMHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerDestroyVM)); // server destroyvm function
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x799E0, &ScriptCompileErrorHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerSQCompileError)); // server compileerror function
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1D5C0, &CallScriptInitCallbackHook<SERVER>, reinterpret_cast<LPVOID*>(&ServerCallScriptInitCallback)); // server callscriptinitcallback function
RegisterConCommand("script", ExecuteCodeCommand<SERVER>, "Executes script code on the server vm", FCVAR_GAMEDLL);
}