diff options
-rw-r--r-- | NorthstarDedicatedTest/concommand.cpp | 11 | ||||
-rw-r--r-- | NorthstarDedicatedTest/gameutils.cpp | 3 | ||||
-rw-r--r-- | NorthstarDedicatedTest/gameutils.h | 3 | ||||
-rw-r--r-- | NorthstarDedicatedTest/masterserver.cpp | 134 | ||||
-rw-r--r-- | NorthstarDedicatedTest/masterserver.h | 4 | ||||
-rw-r--r-- | NorthstarDedicatedTest/scriptserverbrowser.cpp | 28 | ||||
-rw-r--r-- | NorthstarDedicatedTest/serverauthentication.cpp | 63 | ||||
-rw-r--r-- | NorthstarDedicatedTest/serverauthentication.h | 10 | ||||
-rw-r--r-- | NorthstarDedicatedTest/tier0.cpp | 13 | ||||
-rw-r--r-- | NorthstarDedicatedTest/tier0.h | 4 |
10 files changed, 261 insertions, 12 deletions
diff --git a/NorthstarDedicatedTest/concommand.cpp b/NorthstarDedicatedTest/concommand.cpp index d97ef2e5..2db46aa2 100644 --- a/NorthstarDedicatedTest/concommand.cpp +++ b/NorthstarDedicatedTest/concommand.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "concommand.h" +#include "gameutils.h" #include <iostream> typedef void(*ConCommandConstructorType)(ConCommand* newCommand, const char* name, void(*callback)(const CCommand&), const char* helpString, int flags, void* parent); @@ -14,7 +15,17 @@ void RegisterConCommand(const char* name, void(*callback)(const CCommand&), cons conCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); } +void SetPlaylistCommand(const CCommand& args) +{ + if (args.ArgC() < 2) + return; + + SetCurrentPlaylist(args.Arg(1)); +} + void InitialiseConCommands(HMODULE baseAddress) { conCommandConstructor = (ConCommandConstructorType)((char*)baseAddress + 0x415F60); + + RegisterConCommand("setplaylist", SetPlaylistCommand, "", FCVAR_NONE); }
\ No newline at end of file diff --git a/NorthstarDedicatedTest/gameutils.cpp b/NorthstarDedicatedTest/gameutils.cpp index fc6b2dc7..add2639c 100644 --- a/NorthstarDedicatedTest/gameutils.cpp +++ b/NorthstarDedicatedTest/gameutils.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "gameutils.h" #include "convar.h" +#include "concommand.h" // cmd.h Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; @@ -14,6 +15,7 @@ ConVar* Cvar_hostport; // playlist stuff GetCurrentPlaylistType GetCurrentPlaylistName; +SetCurrentPlaylistType SetCurrentPlaylist; // uid char* g_LocalPlayerUserID; @@ -28,6 +30,7 @@ void InitialiseEngineGameUtilFunctions(HMODULE baseAddress) Cvar_hostport = (ConVar*)((char*)baseAddress + 0x13FA6070); GetCurrentPlaylistName = (GetCurrentPlaylistType)((char*)baseAddress + 0x18C640); + SetCurrentPlaylist = (SetCurrentPlaylistType)((char*)baseAddress + 0x18EB20); g_LocalPlayerUserID = (char*)baseAddress + 0x13F8E688; } diff --git a/NorthstarDedicatedTest/gameutils.h b/NorthstarDedicatedTest/gameutils.h index ff987467..b637dfa7 100644 --- a/NorthstarDedicatedTest/gameutils.h +++ b/NorthstarDedicatedTest/gameutils.h @@ -84,6 +84,9 @@ extern ConVar* Cvar_hostport; typedef const char*(*GetCurrentPlaylistType)(); extern GetCurrentPlaylistType GetCurrentPlaylistName; +typedef void(*SetCurrentPlaylistType)(const char* playlistName); +extern SetCurrentPlaylistType SetCurrentPlaylist; + // uid extern char* g_LocalPlayerUserID; diff --git a/NorthstarDedicatedTest/masterserver.cpp b/NorthstarDedicatedTest/masterserver.cpp index f5ba55f0..aecf2f1a 100644 --- a/NorthstarDedicatedTest/masterserver.cpp +++ b/NorthstarDedicatedTest/masterserver.cpp @@ -171,6 +171,103 @@ void MasterServerManager::RequestServerList() requestThread.detach(); } +void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken) +{ + // dont wait, just stop if we're trying to do 2 auth requests at once + if (m_authenticatingWithGameServer) + return; + + m_authenticatingWithGameServer = true; + m_scriptAuthenticatingWithGameServer = true; + m_successfullyAuthenticatedWithGameServer = false; + + std::thread requestThread([this, uid, playerToken]() + { + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); + http.set_connection_timeout(20); + + if (auto result = http.Post(fmt::format("/client/auth_with_self?id={}&playerToken={}", uid, playerToken).c_str())) + { + m_successfullyConnected = true; + + rapidjson::Document authInfoJson; + authInfoJson.Parse(result->body.c_str()); + + if (authInfoJson.HasParseError()) + { + spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(authInfoJson.GetParseError())); + goto REQUEST_END_CLEANUP; + } + + if (!authInfoJson.IsObject()) + { + spdlog::error("Failed reading masterserver authentication response: root object is not an object"); + goto REQUEST_END_CLEANUP; + } + + if (authInfoJson.HasMember("error")) + { + spdlog::error("Failed reading masterserver response: got fastify error response"); + spdlog::error(result->body); + goto REQUEST_END_CLEANUP; + } + + if (!authInfoJson["success"].IsTrue()) + { + spdlog::error("Authentication with masterserver failed: \"success\" is not true"); + goto REQUEST_END_CLEANUP; + } + + if (!authInfoJson.HasMember("success") || !authInfoJson.HasMember("id") || !authInfoJson["id"].IsString() || !authInfoJson.HasMember("authToken") || !authInfoJson["authToken"].IsString() || !authInfoJson.HasMember("persistentData") || !authInfoJson["persistentData"].IsArray()) + { + spdlog::error("Failed reading masterserver authentication response: malformed json object"); + goto REQUEST_END_CLEANUP; + } + + AuthData newAuthData; + strncpy(newAuthData.uid, authInfoJson["id"].GetString(), sizeof(newAuthData.uid)); + newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; + + newAuthData.pdataSize = authInfoJson["persistentData"].GetArray().Size(); + newAuthData.pdata = new char[newAuthData.pdataSize]; + //memcpy(newAuthData.pdata, authInfoJson["persistentData"].GetString(), newAuthData.pdataSize); + + int i = 0; + // note: persistentData is a uint8array because i had problems getting strings to behave, it sucks but it's just how it be unfortunately + // potentially refactor later + for (auto& byte : authInfoJson["persistentData"].GetArray()) + { + if (!byte.IsUint() || byte.GetUint() > 255) + { + spdlog::error("Failed reading masterserver authentication response: malformed json object"); + goto REQUEST_END_CLEANUP; + } + + newAuthData.pdata[i++] = byte.GetUint(); + } + + std::lock_guard<std::mutex> guard(g_ServerAuthenticationManager->m_authDataMutex); + g_ServerAuthenticationManager->m_authData.clear(); + g_ServerAuthenticationManager->m_authData.insert(std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); + + m_successfullyAuthenticatedWithGameServer = true; + } + else + { + spdlog::error("Failed authenticating with own server: error {}", result.error()); + m_successfullyConnected = false; + m_successfullyAuthenticatedWithGameServer = false; + m_scriptAuthenticatingWithGameServer = false; + } + + REQUEST_END_CLEANUP: + m_authenticatingWithGameServer = false; + m_scriptAuthenticatingWithGameServer = false; + }); + + requestThread.detach(); +} + void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password) { // dont wait, just stop if we're trying to do 2 auth requests at once @@ -183,6 +280,10 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c std::thread requestThread([this, uid, playerToken, serverId, password]() { + // esnure that any persistence saving is done, so we know masterserver has newest + while (m_savingPersistentData) + Sleep(100); + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); http.set_connection_timeout(20); @@ -385,6 +486,39 @@ void MasterServerManager::UpdateServerPlayerCount(int playerCount) requestThread.detach(); } +void MasterServerManager::WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize) +{ + // dont call this if we don't have a server id + if (!*m_ownServerId) + return; + + m_savingPersistentData = true; + + std::string playerIdTemp(playerId); + std::thread requestThread([this, playerIdTemp, pdata, pdataSize] { + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); + http.set_connection_timeout(10); + + httplib::MultipartFormDataItems requestItems = { + { "pdata", std::string(&pdata[0], pdataSize), "file.pdata", "application/octet-stream"} + }; + + // we dont process this at all atm, maybe do later, but atm not necessary + if (auto result = http.Post(fmt::format("/accounts/write_persistence?id={}", playerIdTemp).c_str(), requestItems)) + { + m_successfullyConnected = true; + } + else + { + m_successfullyConnected = false; + } + + m_savingPersistentData = false; + }); + + requestThread.detach(); +} + void MasterServerManager::RemoveSelfFromServerList() { // dont call this if we don't have a server id diff --git a/NorthstarDedicatedTest/masterserver.h b/NorthstarDedicatedTest/masterserver.h index 17c77a02..ca2df356 100644 --- a/NorthstarDedicatedTest/masterserver.h +++ b/NorthstarDedicatedTest/masterserver.h @@ -37,6 +37,7 @@ class MasterServerManager private: bool m_requestingServerList = false; bool m_authenticatingWithGameServer = false; + bool m_savingPersistentData = false; public: char m_ownServerId[33]; @@ -55,11 +56,12 @@ public: public: void ClearServerList(); void RequestServerList(); + void AuthenticateWithOwnServer(char* uid, char* playerToken); void AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password); void AddSelfToServerList(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password); void UpdateServerMapAndPlaylist(char* map, char* playlist); void UpdateServerPlayerCount(int playerCount); - void WritePlayerPersistentData(char* playerId, char* pdata); + void WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize); void RemoveSelfFromServerList(); }; diff --git a/NorthstarDedicatedTest/scriptserverbrowser.cpp b/NorthstarDedicatedTest/scriptserverbrowser.cpp index 5db5b719..022aa582 100644 --- a/NorthstarDedicatedTest/scriptserverbrowser.cpp +++ b/NorthstarDedicatedTest/scriptserverbrowser.cpp @@ -3,6 +3,7 @@ #include "squirrel.h" #include "masterserver.h" #include "gameutils.h" +#include "serverauthentication.h" // functions for viewing server browser @@ -175,6 +176,11 @@ SQInteger SQ_TryAuthWithServer(void* sqvm) return 0; } + // send off persistent data first, don't worry about server/client stuff, since m_additionalPlayerData should only have entries when we're a local server + // note: this seems like it could create a race condition, test later + for (auto& pair : g_ServerAuthenticationManager->m_additionalPlayerData) + g_ServerAuthenticationManager->WritePersistentData(pair.first); + // do auth g_MasterServerManager->AuthenticateWithServer(g_LocalPlayerUserID, (char*)"", g_MasterServerManager->m_remoteServers[serverIndex].id, (char*)password); @@ -215,6 +221,25 @@ SQInteger SQ_ConnectToAuthedServer(void* sqvm) return 0; } +// void function NSTryAuthWithLocalServer() +SQInteger SQ_TryAuthWithLocalServer(void* sqvm) +{ + // do auth request + g_MasterServerManager->AuthenticateWithOwnServer(g_LocalPlayerUserID, (char*)""); + + return 0; +} + +// void function NSCompleteAuthWithLocalServer() +SQInteger SQ_CompleteAuthWithLocalServer(void* sqvm) +{ + // literally just set serverfilter + // note: this assumes we have no authdata other than our own + Cbuf_AddText(Cbuf_GetCurrentPlayer(), fmt::format("serverfilter {}", g_ServerAuthenticationManager->m_authData.begin()->first).c_str(), cmd_source_t::kCommandSrcCode); + + return 0; +} + void InitialiseScriptServerBrowser(HMODULE baseAddress) { g_UISquirrelManager->AddFuncRegistration("void", "NSRequestServerList", "", "", SQ_RequestServerList); @@ -236,4 +261,7 @@ void InitialiseScriptServerBrowser(HMODULE baseAddress) g_UISquirrelManager->AddFuncRegistration("bool", "NSIsAuthenticatingWithServer", "", "", SQ_IsAuthComplete); g_UISquirrelManager->AddFuncRegistration("bool", "NSWasAuthSuccessful", "", "", SQ_WasAuthSuccessful); g_UISquirrelManager->AddFuncRegistration("void", "NSConnectToAuthedServer", "", "", SQ_ConnectToAuthedServer); + + g_UISquirrelManager->AddFuncRegistration("void", "NSTryAuthWithLocalServer", "", "", SQ_TryAuthWithLocalServer); + g_UISquirrelManager->AddFuncRegistration("void", "NSCompleteAuthWithLocalServer", "", "", SQ_CompleteAuthWithLocalServer); }
\ No newline at end of file diff --git a/NorthstarDedicatedTest/serverauthentication.cpp b/NorthstarDedicatedTest/serverauthentication.cpp index 9df23502..68d98455 100644 --- a/NorthstarDedicatedTest/serverauthentication.cpp +++ b/NorthstarDedicatedTest/serverauthentication.cpp @@ -4,6 +4,7 @@ #include "hookutils.h" #include "masterserver.h" #include "httplib.h" +#include "tier0.h" #include <fstream> #include <filesystem> #include <thread> @@ -172,7 +173,7 @@ void ServerAuthenticationManager::WritePersistentData(void* player) // we use 0x4 internally to mark clients as using remote persistence if (*((char*)player + 0x4A0) == (char)0x4) { - + g_MasterServerManager->WritePlayerPersistentData((char*)player + 0xF500, (char*)player + 0x4FA, m_additionalPlayerData[player].pdataSize); } else if (CVar_ns_auth_allow_insecure_write->m_nValue) { @@ -183,8 +184,6 @@ void ServerAuthenticationManager::WritePersistentData(void* player) // auth hooks -int playerCount = 0; // temp - // store these in vars so we can use them in CBaseClient::Connect // this is fine because ptrs won't decay by the time we use this, just don't use it outside of cbaseclient::connect char* nextPlayerToken; @@ -209,7 +208,14 @@ char CBaseClient__ConnectHook(void* self, char* name, __int64 netchan_ptr_arg, c else if (!g_ServerAuthenticationManager->AuthenticatePlayer(self, nextPlayerUid, nextPlayerToken)) CBaseClient__Disconnect(self, 1, "Authentication Failed"); - playerCount++; + if (!g_ServerAuthenticationManager->m_additionalPlayerData.count(self)) + { + AdditionalPlayerData additionalData; + additionalData.pdataSize = g_ServerAuthenticationManager->m_authData[nextPlayerToken].pdataSize; + additionalData.usingLocalPdata = *((char*)self + 0x4a0) == (char)0x3; + + g_ServerAuthenticationManager->m_additionalPlayerData.insert(std::make_pair(self, additionalData)); + } return ret; } @@ -221,7 +227,7 @@ void CBaseClient__ActivatePlayerHook(void* self) if (*((char*)self + 0x4A0) >= (char)0x3 && !g_ServerAuthenticationManager->RemovePlayerAuthData(self)) { g_ServerAuthenticationManager->WritePersistentData(self); - g_MasterServerManager->UpdateServerPlayerCount(playerCount); + g_MasterServerManager->UpdateServerPlayerCount(g_ServerAuthenticationManager->m_additionalPlayerData.size()); } CBaseClient__ActivatePlayer(self); @@ -237,11 +243,20 @@ void CBaseClient__DisconnectHook(void* self, uint32_t unknownButAlways1, const c vsprintf(buf, reason, va); va_end(va); - // dcing, write persistent data - g_ServerAuthenticationManager->WritePersistentData(self); - g_ServerAuthenticationManager->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case - g_MasterServerManager->UpdateServerPlayerCount(playerCount = std::max(playerCount - 1, 0)); + // this reason is used while connecting to a local server, hacky, but just ignore it + if (strcmp(reason, "Connection closing")) + { + // dcing, write persistent data + g_ServerAuthenticationManager->WritePersistentData(self); + g_ServerAuthenticationManager->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case + } + + if (g_ServerAuthenticationManager->m_additionalPlayerData.count(self)) + { + g_ServerAuthenticationManager->m_additionalPlayerData.erase(self); + g_MasterServerManager->UpdateServerPlayerCount(g_ServerAuthenticationManager->m_additionalPlayerData.size()); + } CBaseClient__Disconnect(self, unknownButAlways1, buf); } @@ -249,6 +264,26 @@ void CBaseClient__DisconnectHook(void* self, uint32_t unknownButAlways1, const c // maybe this should be done outside of auth code, but effort to refactor rn and it sorta fits char CGameClient__ExecuteStringCommandHook(void* self, uint32_t unknown, const char* pCommandString) { + if (CVar_sv_quota_stringcmdspersecond->m_nValue != -1) + { + // note: this isn't super perfect, legit clients can trigger it in lobby, mostly good enough tho imo + // https://github.com/perilouswithadollarsign/cstrike15_src/blob/f82112a2388b841d72cb62ca48ab1846dfcc11c8/engine/sv_client.cpp#L1513 + if (Plat_FloatTime() - g_ServerAuthenticationManager->m_additionalPlayerData[self].lastClientCommandQuotaStart >= 1.0f) + { + // reset quota + g_ServerAuthenticationManager->m_additionalPlayerData[self].lastClientCommandQuotaStart = Plat_FloatTime(); + g_ServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota = 0; + } + + g_ServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota++; + if (g_ServerAuthenticationManager->m_additionalPlayerData[self].numClientCommandsInQuota > CVar_sv_quota_stringcmdspersecond->m_nValue) + { + // too many stringcmds, dc player + CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands"); + return false; + } + } + // todo later, basically just limit to CVar_sv_quota_stringcmdspersecond->m_nValue stringcmds per client per second return CGameClient__ExecuteStringCommand(self, unknown, pCommandString); } @@ -260,7 +295,7 @@ void InitialiseServerAuthentication(HMODULE baseAddress) 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"); // literally just stolen from a fix valve used in csgo - CVar_sv_quota_stringcmdspersecond = RegisterConVar("sv_quota_stringcmdspersecond", "40", FCVAR_NONE, "How many string commands per second clients are allowed to submit, 0 to disallow all string commands"); + CVar_sv_quota_stringcmdspersecond = RegisterConVar("sv_quota_stringcmdspersecond", "60", FCVAR_NONE, "How many string commands per second clients are allowed to submit, 0 to disallow all string commands"); Cvar_ns_player_auth_port = RegisterConVar("Cvar_ns_player_auth_port", "8081", FCVAR_GAMEDLL, ""); HookEnabler hook; @@ -287,4 +322,12 @@ void InitialiseServerAuthentication(HMODULE baseAddress) *((char*)ptr + 5) = (char)0x90; // nop extra byte we no longer use } + + // patch to allow same of multiple account + { + void* ptr = (char*)baseAddress + 0x114510; + 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 index b26ce2b2..38008e9b 100644 --- a/NorthstarDedicatedTest/serverauthentication.h +++ b/NorthstarDedicatedTest/serverauthentication.h @@ -13,6 +13,15 @@ struct AuthData size_t pdataSize; }; +struct AdditionalPlayerData +{ + bool usingLocalPdata; + size_t pdataSize; + + double lastClientCommandQuotaStart = 0; + int numClientCommandsInQuota = 0; +}; + class ServerAuthenticationManager { private: @@ -21,6 +30,7 @@ private: public: std::mutex m_authDataMutex; std::unordered_map<std::string, AuthData> m_authData; + std::unordered_map<void*, AdditionalPlayerData> m_additionalPlayerData; bool m_runningPlayerAuthThread = false; public: diff --git a/NorthstarDedicatedTest/tier0.cpp b/NorthstarDedicatedTest/tier0.cpp index 717432bf..ad533a9c 100644 --- a/NorthstarDedicatedTest/tier0.cpp +++ b/NorthstarDedicatedTest/tier0.cpp @@ -73,4 +73,17 @@ void Error(const char* fmt, ...) // tier0Func(buf); //else std::cout << "FATAL ERROR " << buf << std::endl; +} + +typedef double(*Tier0FloatTime)(); +double Plat_FloatTime() +{ + Tier0FloatTime tier0Func = (Tier0FloatTime)ResolveTier0Function("Plat_FloatTime"); + + if (tier0Func) + { + return tier0Func(); + } + else + return 0.0f; }
\ No newline at end of file diff --git a/NorthstarDedicatedTest/tier0.h b/NorthstarDedicatedTest/tier0.h index 97a2d753..07d91fe5 100644 --- a/NorthstarDedicatedTest/tier0.h +++ b/NorthstarDedicatedTest/tier0.h @@ -24,4 +24,6 @@ void operator delete(void* p) throw(); // actual function defs // would've liked to resolve these at compile time, but we load before tier0 so not really possible -void Error(const char* fmt, ...);
\ No newline at end of file +void Error(const char* fmt, ...); + +double Plat_FloatTime();
\ No newline at end of file |