diff options
-rw-r--r-- | NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj | 2 | ||||
-rw-r--r-- | NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters | 18 | ||||
-rw-r--r-- | NorthstarDedicatedTest/convar.h | 8 | ||||
-rw-r--r-- | NorthstarDedicatedTest/dllmain.cpp | 2 | ||||
-rw-r--r-- | NorthstarDedicatedTest/filesystem.cpp | 40 | ||||
-rw-r--r-- | NorthstarDedicatedTest/modmanager.cpp | 12 | ||||
-rw-r--r-- | NorthstarDedicatedTest/serverauthentication.cpp | 130 | ||||
-rw-r--r-- | NorthstarDedicatedTest/serverauthentication.h | 28 | ||||
-rw-r--r-- | NorthstarDedicatedTest/squirrel.cpp | 4 |
9 files changed, 226 insertions, 18 deletions
diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj index ef652d9d..87ea35a2 100644 --- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj +++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj @@ -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" /> diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters index 7bd160c6..f6ff5453 100644 --- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters +++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters @@ -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"> diff --git a/NorthstarDedicatedTest/convar.h b/NorthstarDedicatedTest/convar.h index 2c1e21a9..adfd585f 100644 --- a/NorthstarDedicatedTest/convar.h +++ b/NorthstarDedicatedTest/convar.h @@ -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) {} diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp index 303afcfc..cc40a00c 100644 --- a/NorthstarDedicatedTest/dllmain.cpp +++ b/NorthstarDedicatedTest/dllmain.cpp @@ -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); diff --git a/NorthstarDedicatedTest/filesystem.cpp b/NorthstarDedicatedTest/filesystem.cpp index aa6975fe..04b8c98e 100644 --- a/NorthstarDedicatedTest/filesystem.cpp +++ b/NorthstarDedicatedTest/filesystem.cpp @@ -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); + } }
\ No newline at end of file diff --git a/NorthstarDedicatedTest/modmanager.cpp b/NorthstarDedicatedTest/modmanager.cpp index 66fff17f..ea16f591 100644 --- a/NorthstarDedicatedTest/modmanager.cpp +++ b/NorthstarDedicatedTest/modmanager.cpp @@ -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 diff --git a/NorthstarDedicatedTest/serverauthentication.cpp b/NorthstarDedicatedTest/serverauthentication.cpp new file mode 100644 index 00000000..5e27e9a5 --- /dev/null +++ b/NorthstarDedicatedTest/serverauthentication.cpp @@ -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 + } +}
\ No newline at end of file diff --git a/NorthstarDedicatedTest/serverauthentication.h b/NorthstarDedicatedTest/serverauthentication.h new file mode 100644 index 00000000..75187221 --- /dev/null +++ b/NorthstarDedicatedTest/serverauthentication.h @@ -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;
\ No newline at end of file diff --git a/NorthstarDedicatedTest/squirrel.cpp b/NorthstarDedicatedTest/squirrel.cpp index 15c144c5..b57e22ae 100644 --- a/NorthstarDedicatedTest/squirrel.cpp +++ b/NorthstarDedicatedTest/squirrel.cpp @@ -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); } |