diff options
Diffstat (limited to 'NorthstarDLL/serverauthentication.cpp')
-rw-r--r-- | NorthstarDLL/serverauthentication.cpp | 690 |
1 files changed, 162 insertions, 528 deletions
diff --git a/NorthstarDLL/serverauthentication.cpp b/NorthstarDLL/serverauthentication.cpp index ac1985cf..e7a7ff00 100644 --- a/NorthstarDLL/serverauthentication.cpp +++ b/NorthstarDLL/serverauthentication.cpp @@ -1,99 +1,44 @@ #include "pch.h" #include "serverauthentication.h" +#include "limits.h" #include "cvar.h" #include "convar.h" -#include "hookutils.h" #include "masterserver.h" -#include "httplib.h" -#include "gameutils.h" +#include "serverpresence.h" +#include "hoststate.h" +#include "maxplayers.h" #include "bansystem.h" -#include "miscserverscript.h" #include "concommand.h" #include "dedicated.h" +#include "nsprefix.h" +#include "tier0.h" +#include "r2engine.h" +#include "r2client.h" +#include "r2server.h" + +#include "httplib.h" + #include <fstream> #include <filesystem> #include <thread> -#include "configurables.h" -#include "NSMem.h" - -const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; -// This convar defines whether to log all client commands -ConVar* Cvar_ns_should_log_all_clientcommands; +AUTOHOOK_INIT() -// hook types - -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, - int64_t uid, - uint32_t a16, - uint32_t a17); -CBaseServer__ConnectClientType CBaseServer__ConnectClient; - -typedef bool (*CBaseClient__ConnectType)( - void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7); -CBaseClient__ConnectType CBaseClient__Connect; - -typedef void (*CBaseClient__ActivatePlayerType)(void* self); -CBaseClient__ActivatePlayerType CBaseClient__ActivatePlayer; - -CBaseClient__DisconnectType CBaseClient__Disconnect; - -typedef char (*CGameClient__ExecuteStringCommandType)(void* self, uint32_t unknown, const char* pCommandString); -CGameClient__ExecuteStringCommandType CGameClient__ExecuteStringCommand; - -typedef char (*__fastcall CNetChan___ProcessMessagesType)(void* self, void* buf); -CNetChan___ProcessMessagesType CNetChan___ProcessMessages; - -typedef char (*CBaseClient__SendServerInfoType)(void* self); -CBaseClient__SendServerInfoType CBaseClient__SendServerInfo; - -typedef bool (*ProcessConnectionlessPacketType)(void* a1, netpacket_t* packet); -ProcessConnectionlessPacketType ProcessConnectionlessPacket; - -typedef void (*CServerGameDLL__OnReceivedSayTextMessageType)(void* self, unsigned int senderClientIndex, const char* message, char unknown); -CServerGameDLL__OnReceivedSayTextMessageType CServerGameDLL__OnReceivedSayTextMessage; - -typedef void (*ConCommand__DispatchType)(ConCommand* command, const CCommand& args, void* a3); -ConCommand__DispatchType ConCommand__Dispatch; +const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; // global vars -ServerAuthenticationManager* g_ServerAuthenticationManager; - -ConVar* Cvar_ns_player_auth_port; -ConVar* Cvar_ns_erase_auth_info; -ConVar* CVar_ns_auth_allow_insecure; -ConVar* CVar_ns_auth_allow_insecure_write; -ConVar* CVar_sv_quota_stringcmdspersecond; -ConVar* Cvar_net_chan_limit_mode; -ConVar* Cvar_net_chan_limit_msec_per_sec; -ConVar* Cvar_sv_querylimit_per_sec; -ConVar* Cvar_sv_max_chat_messages_per_sec; - -ConVar* Cvar_net_datablock_enabled; +ServerAuthenticationManager* g_pServerAuthentication; void ServerAuthenticationManager::StartPlayerAuthServer() { - if (m_runningPlayerAuthThread) + if (m_bRunningPlayerAuthThread) { - spdlog::warn("ServerAuthenticationManager::StartPlayerAuthServer was called while m_runningPlayerAuthThread is true"); + spdlog::warn("ServerAuthenticationManager::StartPlayerAuthServer was called while m_bRunningPlayerAuthThread is true"); return; } - m_runningPlayerAuthThread = true; + g_pServerPresence->SetAuthPort(Cvar_ns_player_auth_port->GetInt()); // set auth port for presence + m_bRunningPlayerAuthThread = true; // listen is a blocking call so thread this std::thread serverThread( @@ -101,61 +46,44 @@ void ServerAuthenticationManager::StartPlayerAuthServer() { // this is just a super basic way to verify that servers have ports open, masterserver will try to read this before ensuring // server is legit - m_playerAuthServer.Get( + m_PlayerAuthServer.Get( "/verify", [](const httplib::Request& request, httplib::Response& response) { response.set_content(AUTHSERVER_VERIFY_STRING, "text/plain"); }); - m_playerAuthServer.Post( + m_PlayerAuthServer.Post( "/authenticate_incoming_player", [this](const httplib::Request& request, httplib::Response& response) { - // can't just do request.remote_addr == Cvar_ns_masterserver_hostname->GetString() because the cvar can be a url, gotta - // resolve an ip from it for comparisons - // unsigned long remoteAddr = inet_addr(request.remote_addr.c_str()); - // - // char* addrPtr = Cvar_ns_masterserver_hostname->GetString(); - // char* typeStart = strstr(addrPtr, "://"); - // if (typeStart) - // addrPtr = typeStart + 3; - // hostent* resolvedRemoteAddr = gethostbyname((const char*)addrPtr); - - if (!request.has_param("id") || !request.has_param("authToken") || request.body.size() >= 65335 || + if (!request.has_param("id") || !request.has_param("authToken") || request.body.size() >= R2::PERSISTENCE_MAX_SIZE || !request.has_param("serverAuthToken") || strcmp( - g_MasterServerManager->m_ownServerAuthToken, + g_pMasterServerManager->m_sOwnServerAuthToken, request.get_param_value("serverAuthToken") - .c_str())) // || !resolvedRemoteAddr || ((in_addr**)resolvedRemoteAddr->h_addr_list)[0]->S_un.S_addr != - // remoteAddr) + .c_str())) { response.set_content("{\"success\":false}", "application/json"); return; } - // Log playername and UID from request - spdlog::info( - "Player \"{}\" with UID \"{}\" requested to join", - request.get_param_value("username").c_str(), - request.get_param_value("id").c_str()); - - AuthData newAuthData {}; - strncpy(newAuthData.uid, request.get_param_value("id").c_str(), sizeof(newAuthData.uid)); - newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; - - strncpy(newAuthData.username, request.get_param_value("username").c_str(), sizeof(newAuthData.username)); - newAuthData.username[sizeof(newAuthData.username) - 1] = 0; + RemoteAuthData newAuthData {}; + strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), request.get_param_value("id").c_str(), sizeof(newAuthData.uid) - 1); + strncpy_s( + newAuthData.username, + sizeof(newAuthData.username), request.get_param_value("username").c_str(), + sizeof(newAuthData.username) - 1); newAuthData.pdataSize = request.body.size(); newAuthData.pdata = new char[newAuthData.pdataSize]; memcpy(newAuthData.pdata, request.body.c_str(), newAuthData.pdataSize); - std::lock_guard<std::mutex> guard(m_authDataMutex); - m_authData.insert(std::make_pair(request.get_param_value("authToken"), newAuthData)); + std::lock_guard<std::mutex> guard(m_AuthDataMutex); + m_RemoteAuthenticationData.insert(std::make_pair(request.get_param_value("authToken"), newAuthData)); response.set_content("{\"success\":true}", "application/json"); }); - m_playerAuthServer.listen("0.0.0.0", Cvar_ns_player_auth_port->GetInt()); + m_PlayerAuthServer.listen("0.0.0.0", Cvar_ns_player_auth_port->GetInt()); }); serverThread.detach(); @@ -163,144 +91,125 @@ void ServerAuthenticationManager::StartPlayerAuthServer() void ServerAuthenticationManager::StopPlayerAuthServer() { - if (!m_runningPlayerAuthThread) + if (!m_bRunningPlayerAuthThread) { - spdlog::warn("ServerAuthenticationManager::StopPlayerAuthServer was called while m_runningPlayerAuthThread is false"); + spdlog::warn("ServerAuthenticationManager::StopPlayerAuthServer was called while m_bRunningPlayerAuthThread is false"); return; } - m_runningPlayerAuthThread = false; - m_playerAuthServer.stop(); + m_bRunningPlayerAuthThread = false; + m_PlayerAuthServer.stop(); } -char* ServerAuthenticationManager::VerifyPlayerName(void* player, char* authToken, char* name) +void ServerAuthenticationManager::AddPlayerData(R2::CBaseClient* player, const char* pToken) { - std::lock_guard<std::mutex> guard(m_authDataMutex); + PlayerAuthenticationData additionalData; + additionalData.pdataSize = m_RemoteAuthenticationData[pToken].pdataSize; + additionalData.usingLocalPdata = player->m_iPersistenceReady == R2::ePersistenceReady::READY_INSECURE; - if (!m_authData.empty() && m_authData.count(std::string(authToken))) + m_PlayerAuthenticationData.insert(std::make_pair(player, additionalData)); +} + +void ServerAuthenticationManager::VerifyPlayerName(R2::CBaseClient* player, char* authToken, char* name) +{ + std::lock_guard<std::mutex> guard(m_AuthDataMutex); + + if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) { - AuthData authData = m_authData[authToken]; + RemoteAuthData authData = m_RemoteAuthenticationData[authToken]; bool nameAccepted = (!*authData.username || !strcmp(name, authData.username)); - if (!nameAccepted && g_MasterServerManager->m_bRequireClientAuth && !CVar_ns_auth_allow_insecure->GetInt()) + if (!nameAccepted && g_pMasterServerManager->m_bRequireClientAuth && !CVar_ns_auth_allow_insecure->GetInt()) { // limit name length to 64 characters just in case something changes, this technically shouldn't be needed given the master // server gets usernames from origin but we have it just in case - strncpy(name, authData.username, 64); - name[63] = 0; + strncpy_s(name, 64, authData.username, 63); } } - return name; } -bool ServerAuthenticationManager::AuthenticatePlayer(void* player, int64_t uid, char* authToken) +bool ServerAuthenticationManager::CheckDuplicateAccounts(R2::CBaseClient* player) +{ + if (m_bAllowDuplicateAccounts) + return true; + + bool bHasUidPlayer = false; + for (int i = 0; i < R2::GetMaxPlayers(); i++) + if (&R2::g_pClientArray[i] != player && !strcmp(R2::g_pClientArray[i].m_UID, player->m_UID)) + return false; + + return true; +} + +bool ServerAuthenticationManager::AuthenticatePlayer(R2::CBaseClient* player, uint64_t uid, char* authToken) { std::string strUid = std::to_string(uid); - std::lock_guard<std::mutex> guard(m_authDataMutex); + std::lock_guard<std::mutex> guard(m_AuthDataMutex); + + // copy uuid + strcpy(player->m_UID, strUid.c_str()); bool authFail = true; - if (!m_authData.empty() && m_authData.count(std::string(authToken))) + if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) { - // use stored auth data - AuthData authData = m_authData[authToken]; + if (!CheckDuplicateAccounts(player)) + return false; - // Log playnername and UID from request - spdlog::info("Comparing connecting UID \"{}\" against stored UID from ms auth request \"{}\"", strUid.c_str(), authData.uid); + // use stored auth data + RemoteAuthData authData = m_RemoteAuthenticationData[authToken]; if (!strcmp(strUid.c_str(), authData.uid)) // connecting client's uid is the same as auth's uid { - authFail = false; - // uuid - strcpy((char*)player + 0xF500, strUid.c_str()); - - // reset from disk if we're doing that - if (m_bForceReadLocalPlayerPersistenceFromDisk && !strcmp(authData.uid, g_LocalPlayerUserID)) - { - std::fstream pdataStream(GetNorthstarPrefix() + "/placeholder_playerdata.pdata", std::ios_base::in); - - if (!pdataStream.fail()) - { - // get file length - pdataStream.seekg(0, pdataStream.end); - auto length = pdataStream.tellg(); - pdataStream.seekg(0, pdataStream.beg); - - // copy pdata into buffer - pdataStream.read((char*)player + 0x4FA, length); - } - else // fallback to remote pdata if no local default - memcpy((char*)player + 0x4FA, authData.pdata, authData.pdataSize); - } - else + // if we're resetting let script handle the reset + if (!m_bForceResetLocalPlayerPersistence || strcmp(authData.uid, R2::g_pLocalPlayerUserID)) { // copy pdata into buffer - memcpy((char*)player + 0x4FA, authData.pdata, authData.pdataSize); + memcpy(player->m_PersistenceBuffer, authData.pdata, authData.pdataSize); } - // set persistent data as ready, we use 0x4 internally to mark the client as using remote persistence - *((char*)player + 0x4a0) = (char)0x4; + // set persistent data as ready + player->m_iPersistenceReady = R2::ePersistenceReady::READY_REMOTE; + authFail = false; } } if (authFail) { - // set persistent data as ready, we use 0x3 internally to mark the client as using local persistence - *((char*)player + 0x4a0) = (char)0x3; - - // no auth data and insecure connections aren't allowed, so dc the client - if (!CVar_ns_auth_allow_insecure->GetBool() && strncmp(GetCurrentPlaylistName(), "solo", 5) != 0) + if (CVar_ns_auth_allow_insecure->GetBool()) + { + // set persistent data as ready + // note: actual placeholder persistent data is populated in script with InitPersistentData() + player->m_iPersistenceReady = R2::ePersistenceReady::READY_INSECURE; + return true; + } + else return false; - - // insecure connections are allowed, try reading from disk - // uuid - strcpy((char*)player + 0xF500, strUid.c_str()); - - // try reading pdata file for player - std::string pdataPath = GetNorthstarPrefix() + "/playerdata_"; - pdataPath += strUid; - pdataPath += ".pdata"; - - std::fstream pdataStream(pdataPath, std::ios_base::in); - if (pdataStream.fail()) // file doesn't exist, use placeholder - pdataStream = std::fstream(GetNorthstarPrefix() + "/placeholder_playerdata.pdata", std::ios_base::in); - - // get file length - pdataStream.seekg(0, pdataStream.end); - auto length = pdataStream.tellg(); - pdataStream.seekg(0, pdataStream.beg); - - // copy pdata into buffer - pdataStream.read((char*)player + 0x4FA, length); - - pdataStream.close(); } return true; // auth successful, client stays on } -bool ServerAuthenticationManager::RemovePlayerAuthData(void* player) +bool ServerAuthenticationManager::RemovePlayerAuthData(R2::CBaseClient* player) { - if (!Cvar_ns_erase_auth_info->GetBool()) + if (!Cvar_ns_erase_auth_info->GetBool()) // keep auth data forever return false; // hack for special case where we're on a local server, so we erase our own newly created auth data on disconnect - if (m_bNeedLocalAuthForNewgame && !strcmp((char*)player + 0xF500, g_LocalPlayerUserID)) + if (m_bNeedLocalAuthForNewgame && !strcmp(player->m_UID, R2::g_pLocalPlayerUserID)) return false; // we don't have our auth token at this point, so lookup authdata by uid - for (auto& auth : m_authData) + for (auto& auth : m_RemoteAuthenticationData) { - if (!strcmp((char*)player + 0xF500, auth.second.uid)) + if (!strcmp(player->m_UID, auth.second.uid)) { - // Log UID - spdlog::info("Erasing auth data from UID \"{}\"", auth.second.uid); // pretty sure this is fine, since we don't iterate after the erase // i think if we iterated after it'd be undefined behaviour tho - std::lock_guard<std::mutex> guard(m_authDataMutex); + std::lock_guard<std::mutex> guard(m_AuthDataMutex); delete[] auth.second.pdata; - m_authData.erase(auth.first); + m_RemoteAuthenticationData.erase(auth.first); return true; } } @@ -308,13 +217,12 @@ bool ServerAuthenticationManager::RemovePlayerAuthData(void* player) return false; } -void ServerAuthenticationManager::WritePersistentData(void* player) +void ServerAuthenticationManager::WritePersistentData(R2::CBaseClient* player) { - // we use 0x4 internally to mark clients as using remote persistence - if (*((char*)player + 0x4A0) == (char)0x4) + if (player->m_iPersistenceReady == R2::ePersistenceReady::READY_REMOTE) { - g_MasterServerManager->WritePlayerPersistentData( - (char*)player + 0xF500, (char*)player + 0x4FA, m_additionalPlayerData[player].pdataSize); + g_pMasterServerManager->WritePlayerPersistentData( + player->m_UID, (const char*)player->m_PersistenceBuffer, m_PlayerAuthenticationData[player].pdataSize); } else if (CVar_ns_auth_allow_insecure_write->GetBool()) { @@ -322,29 +230,15 @@ void ServerAuthenticationManager::WritePersistentData(void* player) } } -bool ServerAuthenticationManager::CheckPlayerChatRatelimit(void* player) -{ - if (Plat_FloatTime() - m_additionalPlayerData[player].lastSayTextLimitStart >= 1.0) - { - m_additionalPlayerData[player].lastSayTextLimitStart = Plat_FloatTime(); - m_additionalPlayerData[player].sayTextLimitCount = 0; - } - - if (m_additionalPlayerData[player].sayTextLimitCount >= Cvar_sv_max_chat_messages_per_sec->GetInt()) - return false; - - m_additionalPlayerData[player].sayTextLimitCount++; - return true; -} - // auth hooks // 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; -uint64_t nextPlayerUid; +// this is fine because ptrs won't decay by the time we use this, just don't use it outside of calls from cbaseclient::connectclient +char* pNextPlayerToken; +uint64_t iNextPlayerUid; -void* CBaseServer__ConnectClientHook( +AUTOHOOK(CBaseServer__ConnectClient, engine.dll + 0x114430, +void*,, ( void* server, void* a2, void* a3, @@ -361,395 +255,135 @@ void* CBaseServer__ConnectClientHook( char a14, int64_t uid, uint32_t a16, - uint32_t a17) + 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; - nextPlayerUid = uid; - - // Random UID log - spdlog::info("CBaseServer__ConnectClientHook says UID \"{}\"", uid); + pNextPlayerToken = serverFilter; + iNextPlayerUid = uid; return CBaseServer__ConnectClient(server, a2, a3, a4, a5, a6, a7, a8, serverFilter, a10, a11, a12, a13, a14, uid, a16, a17); } -bool CBaseClient__ConnectHook(void* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7) +AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740, +bool,, (R2::CBaseClient* self, char* name, void* netchan_ptr_arg, char b_fake_player_arg, void* a5, char* Buffer, void* a7)) { // try changing name before all else - name = g_ServerAuthenticationManager->VerifyPlayerName(self, nextPlayerToken, name); + g_pServerAuthentication->VerifyPlayerName(self, pNextPlayerToken, name); // try to auth player, dc if it fails - // we connect irregardless of auth, because returning bad from this function can fuck client state p bad + // we connect regardless of auth, because returning bad from this function can fuck client state p bad bool ret = CBaseClient__Connect(self, name, netchan_ptr_arg, b_fake_player_arg, a5, Buffer, a7); - - // Another UID log - spdlog::info("CBaseClient__ConnectHook says UID \"{}\"", nextPlayerUid); - if (!ret) return ret; - if (!g_ServerBanSystem->IsUIDAllowed(nextPlayerUid)) + if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid)) { - CBaseClient__Disconnect(self, 1, "Banned from server"); + R2::CBaseClient__Disconnect(self, 1, "Banned from server"); return ret; } if (strlen(name) >= 64) // fix for name overflow bug - CBaseClient__Disconnect(self, 1, "Invalid name"); - else if ( - !g_ServerAuthenticationManager->AuthenticatePlayer(self, nextPlayerUid, nextPlayerToken) && - g_MasterServerManager->m_bRequireClientAuth) - CBaseClient__Disconnect(self, 1, "Authentication Failed"); + R2::CBaseClient__Disconnect(self, 1, "Invalid name"); + else if (!g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken) && + g_pServerAuthentication->m_bRequireClientAuth) + R2::CBaseClient__Disconnect(self, 1, "Authentication Failed"); - 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)); - - g_ServerAuthenticationManager->m_additionalPlayerData[self].uid = nextPlayerUid; - } + g_pServerAuthentication->AddPlayerData(self, pNextPlayerToken); + g_pServerLimits->AddPlayer(self); return ret; } -void CBaseClient__ActivatePlayerHook(void* self) +AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80, +void,, (R2::CBaseClient* self)) { - bool uidMatches = false; - if (g_ServerAuthenticationManager->m_additionalPlayerData.count(self)) - { - std::string strUid = std::to_string(g_ServerAuthenticationManager->m_additionalPlayerData[self].uid); - if (!strcmp(strUid.c_str(), (char*)self + 0xF500)) // connecting client's uid is the same as auth's uid - { - uidMatches = true; - } - } - if (!uidMatches) - { - CBaseClient__Disconnect(self, 1, "Authentication Failed"); - return; - } - // if we're authed, write our persistent data // RemovePlayerAuthData returns true if it removed successfully, i.e. on first call only, and we only want to write on >= second call // (since this func is called on map loads) - if (*((char*)self + 0x4A0) >= (char)0x3 && !g_ServerAuthenticationManager->RemovePlayerAuthData(self)) + if (self->m_iPersistenceReady >= R2::ePersistenceReady::READY && !g_pServerAuthentication->RemovePlayerAuthData(self)) { - g_ServerAuthenticationManager->m_bForceReadLocalPlayerPersistenceFromDisk = false; - g_ServerAuthenticationManager->WritePersistentData(self); - g_MasterServerManager->UpdateServerPlayerCount(g_ServerAuthenticationManager->m_additionalPlayerData.size()); + g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = false; + g_pServerAuthentication->WritePersistentData(self); + g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); } - // Log UID - spdlog::info("In CBaseClient__ActivatePlayerHook, activating UID \"{}\"", (char*)self + 0xF500); CBaseClient__ActivatePlayer(self); } -void CBaseClient__DisconnectHook(void* self, uint32_t unknownButAlways1, const char* reason, ...) +AUTOHOOK(_CBaseClient__Disconnect, engine.dll + 0x1012C0, +void,, (R2::CBaseClient* self, uint32_t unknownButAlways1, const char* pReason, ...)) { // have to manually format message because can't pass varargs to original func char buf[1024]; va_list va; - va_start(va, reason); - vsprintf(buf, reason, va); + va_start(va, pReason); + vsprintf(buf, pReason, va); va_end(va); // this reason is used while connecting to a local server, hacky, but just ignore it - if (strcmp(reason, "Connection closing")) + if (strcmp(pReason, "Connection closing")) { - spdlog::info("Player {} disconnected: \"{}\"", (char*)self + 0x16, buf); + spdlog::info("Player {} disconnected: \"{}\"", self->m_Name, buf); // dcing, write persistent data - if (g_ServerAuthenticationManager->m_additionalPlayerData[self].needPersistenceWriteOnLeave) - g_ServerAuthenticationManager->WritePersistentData(self); - g_ServerAuthenticationManager->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case + if (g_pServerAuthentication->m_PlayerAuthenticationData[self].needPersistenceWriteOnLeave) + g_pServerAuthentication->WritePersistentData(self); + g_pServerAuthentication->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case } - if (g_ServerAuthenticationManager->m_additionalPlayerData.count(self)) + if (g_pServerAuthentication->m_PlayerAuthenticationData.count(self)) { - g_ServerAuthenticationManager->m_additionalPlayerData.erase(self); - g_MasterServerManager->UpdateServerPlayerCount(g_ServerAuthenticationManager->m_additionalPlayerData.size()); + g_pServerAuthentication->m_PlayerAuthenticationData.erase(self); + g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); } - CBaseClient__Disconnect(self, unknownButAlways1, buf); + _CBaseClient__Disconnect(self, unknownButAlways1, buf); } -// maybe this should be done outside of auth code, but effort to refactor rn and it sorta fits -typedef bool (*CCommand__TokenizeType)(CCommand& self, const char* pCommandString, cmd_source_t commandSource); -CCommand__TokenizeType CCommand__Tokenize; - -char CGameClient__ExecuteStringCommandHook(void* self, uint32_t unknown, const char* pCommandString) +void ConCommand_ns_resetpersistence(const CCommand& args) { - // Only log clientcommands if the convar `ns_should_log_all_clientcommands` equals 1 - if (Cvar_ns_should_log_all_clientcommands->GetBool()) - { - spdlog::info("{} (UID: {}) executed command: \"{}\"", (char*)self + 0x16, (char*)self + 0xF500, pCommandString); - } - - if (CVar_sv_quota_stringcmdspersecond->GetInt() != -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.0) - { - // 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->GetInt()) - { - // too many stringcmds, dc player - CBaseClient__Disconnect(self, 1, "Sent too many stringcmd commands"); - return false; - } - } - - // verify the command we're trying to execute is FCVAR_CLIENTCMD_CAN_EXECUTE, if it's a concommand - char* commandBuf[1040]; // assumedly this is the size of CCommand since we don't have an actual constructor - memset(commandBuf, 0, sizeof(commandBuf)); - CCommand tempCommand = *(CCommand*)&commandBuf; - - if (!CCommand__Tokenize(tempCommand, pCommandString, cmd_source_t::kCommandSrcCode) || !tempCommand.ArgC()) - return false; - - ConCommand* command = g_pCVar->FindCommand(tempCommand.Arg(0)); - - // if the command doesn't exist pass it on to ExecuteStringCommand for script clientcommands and stuff - if (command && !command->IsFlagSet(FCVAR_CLIENTCMD_CAN_EXECUTE)) - { - // ensure FCVAR_GAMEDLL concommands without FCVAR_CLIENTCMD_CAN_EXECUTE can't be executed by remote clients - if (IsDedicated()) - return false; - - if (strcmp((char*)self + 0xF500, g_LocalPlayerUserID)) - return false; - } - - // todo later, basically just limit to CVar_sv_quota_stringcmdspersecond->GetInt() stringcmds per client per second - return CGameClient__ExecuteStringCommand(self, unknown, pCommandString); -} - -char __fastcall CNetChan___ProcessMessagesHook(void* self, void* buf) -{ - double startTime = Plat_FloatTime(); - char ret = CNetChan___ProcessMessages(self, buf); - - // check processing limits, unless we're in a level transition - if (g_pHostState->m_iCurrentState == HostState_t::HS_RUN && ThreadInServerFrameThread()) - { - // player that sent the message - void* sender = *(void**)((char*)self + 368); - - // if no sender, return - // relatively certain this is fine? - if (!sender || !g_ServerAuthenticationManager->m_additionalPlayerData.count(sender)) - return ret; - - // reset every second - if (startTime - g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart >= 1.0 || - g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart == -1.0) - { - g_ServerAuthenticationManager->m_additionalPlayerData[sender].lastNetChanProcessingLimitStart = startTime; - g_ServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime = 0.0; - } - g_ServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime += - (Plat_FloatTime() * 1000) - (startTime * 1000); - - if (g_ServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime >= - Cvar_net_chan_limit_msec_per_sec->GetInt()) - { - spdlog::warn( - "Client {} hit netchan processing limit with {}ms of processing time this second (max is {})", - (char*)sender + 0x16, - g_ServerAuthenticationManager->m_additionalPlayerData[sender].netChanProcessingLimitTime, - Cvar_net_chan_limit_msec_per_sec->GetInt()); - - // nonzero = kick, 0 = warn, but never kick local player - if (Cvar_net_chan_limit_mode->GetInt() && strcmp(g_LocalPlayerUserID, (char*)sender + 0xF500)) - { - CBaseClient__Disconnect(sender, 1, "Exceeded net channel processing limit"); - return false; - } - } - } - - return ret; -} - -bool bWasWritingStringTableSuccessful; - -void CBaseClient__SendServerInfoHook(void* self) -{ - bWasWritingStringTableSuccessful = true; - CBaseClient__SendServerInfo(self); - if (!bWasWritingStringTableSuccessful) - CBaseClient__Disconnect( - self, 1, "Overflowed CNetworkStringTableContainer::WriteBaselines, try restarting your client and reconnecting"); -} - -bool ProcessConnectionlessPacketHook(void* a1, netpacket_t* packet) -{ - if (packet->adr.type == NA_IP && - (!(packet->data[4] == 'N' && Cvar_net_datablock_enabled->GetBool()) || !Cvar_net_datablock_enabled->GetBool())) - { - // bad lookup: optimise later tm - UnconnectedPlayerSendData* sendData = nullptr; - for (UnconnectedPlayerSendData& foundSendData : g_ServerAuthenticationManager->m_unconnectedPlayerSendData) - { - if (!memcmp(packet->adr.ip, foundSendData.ip, 16)) - { - sendData = &foundSendData; - break; - } - } - - if (!sendData) - { - sendData = &g_ServerAuthenticationManager->m_unconnectedPlayerSendData.emplace_back(); - memcpy(sendData->ip, packet->adr.ip, 16); - } - - if (Plat_FloatTime() < sendData->timeoutEnd) - return false; - - if (Plat_FloatTime() - sendData->lastQuotaStart >= 1.0) - { - sendData->lastQuotaStart = Plat_FloatTime(); - sendData->packetCount = 0; - } - - sendData->packetCount++; - - if (sendData->packetCount >= Cvar_sv_querylimit_per_sec->GetInt()) - { - spdlog::warn( - "Client went over connectionless ratelimit of {} per sec with packet of type {}", - Cvar_sv_querylimit_per_sec->GetInt(), - packet->data[4]); - - // timeout for a minute - sendData->timeoutEnd = Plat_FloatTime() + 60.0; - return false; - } - } - - return ProcessConnectionlessPacket(a1, packet); -} - -void ResetPdataCommand(const CCommand& args) -{ - if (*sv_m_State == server_state_t::ss_active) + if (*R2::g_pServerState == R2::server_state_t::ss_active) { spdlog::error("ns_resetpersistence must be entered from the main menu"); return; } spdlog::info("resetting persistence on next lobby load..."); - g_ServerAuthenticationManager->m_bForceReadLocalPlayerPersistenceFromDisk = true; + g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = true; } -void InitialiseServerAuthentication(HMODULE baseAddress) +ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, (ConCommand, ConVar), (CModule module)) { - g_ServerAuthenticationManager = new ServerAuthenticationManager; + AUTOHOOK_DISPATCH() + + g_pServerAuthentication = new ServerAuthenticationManager; - Cvar_ns_erase_auth_info = + g_pServerAuthentication->Cvar_ns_player_auth_port = new ConVar("ns_player_auth_port", "8081", FCVAR_GAMEDLL, ""); + g_pServerAuthentication->Cvar_ns_erase_auth_info = new ConVar("ns_erase_auth_info", "1", FCVAR_GAMEDLL, "Whether auth info should be erased from this server on disconnect or crash"); - CVar_ns_auth_allow_insecure = + g_pServerAuthentication->CVar_ns_auth_allow_insecure = new ConVar("ns_auth_allow_insecure", "0", FCVAR_GAMEDLL, "Whether this server will allow unauthenicated players to connect"); - CVar_ns_auth_allow_insecure_write = new ConVar( + g_pServerAuthentication->CVar_ns_auth_allow_insecure_write = new ConVar( "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 = new ConVar( - "sv_quota_stringcmdspersecond", - "60", - FCVAR_GAMEDLL, - "How many string commands per second clients are allowed to submit, 0 to disallow all string commands"); - // https://blog.counter-strike.net/index.php/2019/07/24922/ but different because idk how to check what current tick number is - Cvar_net_chan_limit_mode = - new ConVar("net_chan_limit_mode", "0", FCVAR_GAMEDLL, "The mode for netchan processing limits: 0 = log, 1 = kick"); - Cvar_net_chan_limit_msec_per_sec = new ConVar( - "net_chan_limit_msec_per_sec", - "100", - FCVAR_GAMEDLL, - "Netchannel processing is limited to so many milliseconds, abort connection if exceeding budget"); - Cvar_ns_should_log_all_clientcommands = - new ConVar("ns_should_log_all_clientcommands", "0", FCVAR_NONE, "Whether to log all clientcommands"); - Cvar_ns_player_auth_port = new ConVar("ns_player_auth_port", "8081", FCVAR_GAMEDLL, ""); - Cvar_sv_querylimit_per_sec = new ConVar("sv_querylimit_per_sec", "15", FCVAR_GAMEDLL, ""); - Cvar_sv_max_chat_messages_per_sec = new ConVar("sv_max_chat_messages_per_sec", "5", FCVAR_GAMEDLL, ""); - - Cvar_net_datablock_enabled = g_pCVar->FindVar("net_datablock_enabled"); - - RegisterConCommand("ns_resetpersistence", ResetPdataCommand, "resets your pdata when you next enter the lobby", FCVAR_NONE); - - 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)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x100F80, &CBaseClient__ActivatePlayerHook, reinterpret_cast<LPVOID*>(&CBaseClient__ActivatePlayer)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x1012C0, &CBaseClient__DisconnectHook, reinterpret_cast<LPVOID*>(&CBaseClient__Disconnect)); - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x1022E0, - &CGameClient__ExecuteStringCommandHook, - reinterpret_cast<LPVOID*>(&CGameClient__ExecuteStringCommand)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x2140A0, &CNetChan___ProcessMessagesHook, reinterpret_cast<LPVOID*>(&CNetChan___ProcessMessages)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x104FB0, &CBaseClient__SendServerInfoHook, reinterpret_cast<LPVOID*>(&CBaseClient__SendServerInfo)); - ENABLER_CREATEHOOK( - hook, (char*)baseAddress + 0x117800, &ProcessConnectionlessPacketHook, reinterpret_cast<LPVOID*>(&ProcessConnectionlessPacket)); - - CCommand__Tokenize = (CCommand__TokenizeType)((char*)baseAddress + 0x418380); - - uintptr_t ba = (uintptr_t)baseAddress; + "Whether the pdata of unauthenticated clients will be written to disk when changed"); + RegisterConCommand( + "ns_resetpersistence", ConCommand_ns_resetpersistence, "resets your pdata when you next enter the lobby", FCVAR_NONE); + // patch to disable kicking based on incorrect serverfilter in connectclient, since we repurpose it for use as an auth token - { - NSMem::BytePatch( - ba + 0x114655, - "EB" // jz => jmp - ); - } - + module.Offset(0x114655).Patch("EB"); + // patch to disable fairfight marking players as cheaters and kicking them + module.Offset(0x101012).Patch("E9 90 00"); + + if (Tier0::CommandLine()->CheckParm("-allowdupeaccounts")) { - NSMem::BytePatch( - ba + 0x101012, - "E9 90 00" // jz => jmp - ); - } - - // patch to allow same of multiple account - if (CommandLine()->CheckParm("-allowdupeaccounts")) - { - NSMem::BytePatch( - ba + 0x114510, - "EB" // jz => jmp - ); - } - - // patch to set bWasWritingStringTableSuccessful in CNetworkStringTableContainer::WriteBaselines if it fails - { - uintptr_t writeAddress = (uintptr_t)(&bWasWritingStringTableSuccessful - (ba + 0x234EDC)); - - auto addr = ba + 0x234ED2; - NSMem::BytePatch(addr, "C7 05"); - NSMem::BytePatch(addr + 2, (BYTE*)&writeAddress, sizeof(writeAddress)); - - NSMem::BytePatch(addr + 6, "00 00 00 00"); + // patch to allow same of multiple account + module.Offset(0x114510).Patch("EB"); - NSMem::NOP(addr + 10, 5); + g_pServerAuthentication->m_bAllowDuplicateAccounts = true; } } |