From 082f3893215797268d7ac8c1000ebe371f276af7 Mon Sep 17 00:00:00 2001 From: BobTheBob9 Date: Tue, 16 Aug 2022 01:12:59 +0100 Subject: lots of stuff idk --- NorthstarDLL/serverauthentication.cpp | 761 +++++++++++++++++----------------- 1 file changed, 390 insertions(+), 371 deletions(-) (limited to 'NorthstarDLL/serverauthentication.cpp') diff --git a/NorthstarDLL/serverauthentication.cpp b/NorthstarDLL/serverauthentication.cpp index a8aa678d..953321e6 100644 --- a/NorthstarDLL/serverauthentication.cpp +++ b/NorthstarDLL/serverauthentication.cpp @@ -1,371 +1,390 @@ -#include "pch.h" -#include "serverauthentication.h" -#include "limits.h" -#include "cvar.h" -#include "convar.h" -#include "masterserver.h" -#include "serverpresence.h" -#include "hoststate.h" -#include "bansystem.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 -#include -#include - -AUTOHOOK_INIT() - -const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; - -// global vars -ServerAuthenticationManager* g_pServerAuthentication; - -void ServerAuthenticationManager::StartPlayerAuthServer() -{ - if (m_bRunningPlayerAuthThread) - { - spdlog::warn("ServerAuthenticationManager::StartPlayerAuthServer was called while m_bRunningPlayerAuthThread is true"); - return; - } - - 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( - [this] - { - // 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( - "/verify", - [](const httplib::Request& request, httplib::Response& response) - { response.set_content(AUTHSERVER_VERIFY_STRING, "text/plain"); }); - - m_PlayerAuthServer.Post( - "/authenticate_incoming_player", - [this](const httplib::Request& request, httplib::Response& response) - { - if (!request.has_param("id") || !request.has_param("authToken") || request.body.size() >= R2::PERSISTENCE_MAX_SIZE || - !request.has_param("serverAuthToken") || - strcmp( - g_pMasterServerManager->m_sOwnServerAuthToken, - request.get_param_value("serverAuthToken") - .c_str())) - { - response.set_content("{\"success\":false}", "application/json"); - return; - } - - 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 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()); - }); - - serverThread.detach(); -} - -void ServerAuthenticationManager::StopPlayerAuthServer() -{ - if (!m_bRunningPlayerAuthThread) - { - spdlog::warn("ServerAuthenticationManager::StopPlayerAuthServer was called while m_bRunningPlayerAuthThread is false"); - return; - } - - m_bRunningPlayerAuthThread = false; - m_PlayerAuthServer.stop(); -} - -void ServerAuthenticationManager::AddPlayerData(R2::CBaseClient* player, const char* pToken) -{ - PlayerAuthenticationData additionalData; - additionalData.pdataSize = m_RemoteAuthenticationData[pToken].pdataSize; - additionalData.usingLocalPdata = player->m_iPersistenceReady == R2::ePersistenceReady::READY_INSECURE; - - m_PlayerAuthenticationData.insert(std::make_pair(player, additionalData)); -} - -void ServerAuthenticationManager::VerifyPlayerName(R2::CBaseClient* player, char* authToken, char* name) -{ - std::lock_guard guard(m_AuthDataMutex); - - if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) - { - RemoteAuthData authData = m_RemoteAuthenticationData[authToken]; - - bool nameAccepted = (!*authData.username || !strcmp(name, authData.username)); - - 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_s(name, 64, authData.username, 63); - } - } -} - -bool ServerAuthenticationManager::AuthenticatePlayer(R2::CBaseClient* player, uint64_t uid, char* authToken) -{ - std::string strUid = std::to_string(uid); - std::lock_guard guard(m_AuthDataMutex); - - bool authFail = true; - if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) - { - // 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 - { - // copy uuid - strcpy(player->m_UID, strUid.c_str()); - - // if we're resetting let script handle the reset - if (!m_bForceResetLocalPlayerPersistence || strcmp(authData.uid, R2::g_pLocalPlayerUserID)) - { - // copy pdata into buffer - memcpy(player->m_PersistenceBuffer, authData.pdata, authData.pdataSize); - } - - // set persistent data as ready - player->m_iPersistenceReady = R2::ePersistenceReady::READY_REMOTE; - authFail = false; - } - } - - if (authFail) - { - if (CVar_ns_auth_allow_insecure->GetBool()) - { - // copy uuid - strcpy((char*)player + 0xF500, strUid.c_str()); - - // set persistent data as ready - // note: actual persistent data is populated in script with InitPersistentData() - player->m_iPersistenceReady = R2::ePersistenceReady::READY_INSECURE; - return true; - } - else - return false; - } - - return true; // auth successful, client stays on -} - -bool ServerAuthenticationManager::RemovePlayerAuthData(R2::CBaseClient* player) -{ - 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(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_RemoteAuthenticationData) - { - if (!strcmp(player->m_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 guard(m_AuthDataMutex); - - delete[] auth.second.pdata; - m_RemoteAuthenticationData.erase(auth.first); - return true; - } - } - - return false; -} - -void ServerAuthenticationManager::WritePersistentData(R2::CBaseClient* player) -{ - if (player->m_iPersistenceReady == R2::ePersistenceReady::READY_REMOTE) - { - g_pMasterServerManager->WritePlayerPersistentData( - player->m_UID, (const char*)player->m_PersistenceBuffer, m_PlayerAuthenticationData[player].pdataSize); - } - else if (CVar_ns_auth_allow_insecure_write->GetBool()) - { - // todo: write pdata to disk here - } -} - -// 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 calls from cbaseclient::connectclient -char* pNextPlayerToken; -uint64_t iNextPlayerUid; - -AUTOHOOK(CBaseServer__ConnectClient, engine.dll + 0x114430, -void*,, ( - 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)) -{ - // auth tokens are sent with serverfilter, can't be accessed from player struct to my knowledge, so have to do this here - pNextPlayerToken = serverFilter; - iNextPlayerUid = uid; - - return CBaseServer__ConnectClient(server, a2, a3, a4, a5, a6, a7, a8, serverFilter, a10, a11, a12, a13, a14, uid, a16, a17); -} - -AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740, -bool,, (R2::CBaseClient* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7)) -{ - // try changing name before all else - g_pServerAuthentication->VerifyPlayerName(self, pNextPlayerToken, name); - - // try to auth player, dc if it fails - // 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); - if (!ret) - return ret; - - if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid)) - { - R2::CBaseClient__Disconnect(self, 1, "Banned from server"); - return ret; - } - - if (strlen(name) >= 64) // fix for name overflow bug - R2::CBaseClient__Disconnect(self, 1, "Invalid name"); - else if ( - !g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken) && - g_pMasterServerManager->m_bRequireClientAuth) - R2::CBaseClient__Disconnect(self, 1, "Authentication Failed"); - - g_pServerAuthentication->AddPlayerData(self, pNextPlayerToken); - g_pServerLimits->AddPlayer(self); - - return ret; -} - -AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80, -void,, (R2::CBaseClient* self)) -{ - // 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 (self->m_iPersistenceReady >= R2::ePersistenceReady::READY && !g_pServerAuthentication->RemovePlayerAuthData(self)) - { - g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = false; - g_pServerAuthentication->WritePersistentData(self); - g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); - } - - CBaseClient__ActivatePlayer(self); -} - -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, 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(pReason, "Connection closing")) - { - spdlog::info("Player {} disconnected: \"{}\"", self->m_Name, buf); - - // dcing, write persistent data - 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_pServerAuthentication->m_PlayerAuthenticationData.count(self)) - { - g_pServerAuthentication->m_PlayerAuthenticationData.erase(self); - g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); - } - - _CBaseClient__Disconnect(self, unknownButAlways1, buf); -} - -void ConCommand_ns_resetpersistence(const CCommand& args) -{ - 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_pServerAuthentication->m_bForceResetLocalPlayerPersistence = true; -} - -ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, (ConCommand, ConVar), (CModule module)) -{ - AUTOHOOK_DISPATCH() - - g_pServerAuthentication = new ServerAuthenticationManager; - - 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"); - 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"); - 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"); - - 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 - module.Offset(0x114655).Patch("EB"); - - // patch to disable fairfight marking players as cheaters and kicking them - module.Offset(0x101012).Patch("E9 90 00"); - - // patch to allow same of multiple account - module.Offset(0x114510).Patch("EB"); -} \ No newline at end of file +#include "pch.h" +#include "serverauthentication.h" +#include "limits.h" +#include "cvar.h" +#include "convar.h" +#include "masterserver.h" +#include "serverpresence.h" +#include "hoststate.h" +#include "maxplayers.h" +#include "bansystem.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 +#include +#include + +AUTOHOOK_INIT() + +const char* AUTHSERVER_VERIFY_STRING = "I am a northstar server!"; + +// global vars +ServerAuthenticationManager* g_pServerAuthentication; + +void ServerAuthenticationManager::StartPlayerAuthServer() +{ + if (m_bRunningPlayerAuthThread) + { + spdlog::warn("ServerAuthenticationManager::StartPlayerAuthServer was called while m_bRunningPlayerAuthThread is true"); + return; + } + + 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( + [this] + { + // 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( + "/verify", + [](const httplib::Request& request, httplib::Response& response) + { response.set_content(AUTHSERVER_VERIFY_STRING, "text/plain"); }); + + m_PlayerAuthServer.Post( + "/authenticate_incoming_player", + [this](const httplib::Request& request, httplib::Response& response) + { + if (!request.has_param("id") || !request.has_param("authToken") || request.body.size() >= R2::PERSISTENCE_MAX_SIZE || + !request.has_param("serverAuthToken") || + strcmp( + g_pMasterServerManager->m_sOwnServerAuthToken, + request.get_param_value("serverAuthToken") + .c_str())) + { + response.set_content("{\"success\":false}", "application/json"); + return; + } + + 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 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()); + }); + + serverThread.detach(); +} + +void ServerAuthenticationManager::StopPlayerAuthServer() +{ + if (!m_bRunningPlayerAuthThread) + { + spdlog::warn("ServerAuthenticationManager::StopPlayerAuthServer was called while m_bRunningPlayerAuthThread is false"); + return; + } + + m_bRunningPlayerAuthThread = false; + m_PlayerAuthServer.stop(); +} + +void ServerAuthenticationManager::AddPlayerData(R2::CBaseClient* player, const char* pToken) +{ + PlayerAuthenticationData additionalData; + additionalData.pdataSize = m_RemoteAuthenticationData[pToken].pdataSize; + additionalData.usingLocalPdata = player->m_iPersistenceReady == R2::ePersistenceReady::READY_INSECURE; + + m_PlayerAuthenticationData.insert(std::make_pair(player, additionalData)); +} + +void ServerAuthenticationManager::VerifyPlayerName(R2::CBaseClient* player, char* authToken, char* name) +{ + std::lock_guard guard(m_AuthDataMutex); + + if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) + { + RemoteAuthData authData = m_RemoteAuthenticationData[authToken]; + + bool nameAccepted = (!*authData.username || !strcmp(name, authData.username)); + + 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_s(name, 64, authData.username, 63); + } + } +} + +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 guard(m_AuthDataMutex); + + // copy uuid + strcpy(player->m_UID, strUid.c_str()); + + bool authFail = true; + if (!m_RemoteAuthenticationData.empty() && m_RemoteAuthenticationData.count(std::string(authToken))) + { + if (!CheckDuplicateAccounts(player)) + return false; + + // 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 + { + // if we're resetting let script handle the reset + if (!m_bForceResetLocalPlayerPersistence || strcmp(authData.uid, R2::g_pLocalPlayerUserID)) + { + // copy pdata into buffer + memcpy(player->m_PersistenceBuffer, authData.pdata, authData.pdataSize); + } + + // set persistent data as ready + player->m_iPersistenceReady = R2::ePersistenceReady::READY_REMOTE; + authFail = false; + } + } + + if (authFail) + { + 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; + } + + return true; // auth successful, client stays on +} + +bool ServerAuthenticationManager::RemovePlayerAuthData(R2::CBaseClient* player) +{ + 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(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_RemoteAuthenticationData) + { + if (!strcmp(player->m_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 guard(m_AuthDataMutex); + + delete[] auth.second.pdata; + m_RemoteAuthenticationData.erase(auth.first); + return true; + } + } + + return false; +} + +void ServerAuthenticationManager::WritePersistentData(R2::CBaseClient* player) +{ + if (player->m_iPersistenceReady == R2::ePersistenceReady::READY_REMOTE) + { + g_pMasterServerManager->WritePlayerPersistentData( + player->m_UID, (const char*)player->m_PersistenceBuffer, m_PlayerAuthenticationData[player].pdataSize); + } + else if (CVar_ns_auth_allow_insecure_write->GetBool()) + { + // todo: write pdata to disk here + } +} + +// 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 calls from cbaseclient::connectclient +char* pNextPlayerToken; +uint64_t iNextPlayerUid; + +AUTOHOOK(CBaseServer__ConnectClient, engine.dll + 0x114430, +void*,, ( + 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)) +{ + // auth tokens are sent with serverfilter, can't be accessed from player struct to my knowledge, so have to do this here + pNextPlayerToken = serverFilter; + iNextPlayerUid = uid; + + return CBaseServer__ConnectClient(server, a2, a3, a4, a5, a6, a7, a8, serverFilter, a10, a11, a12, a13, a14, uid, a16, a17); +} + +AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740, +bool,, (R2::CBaseClient* self, char* name, __int64 netchan_ptr_arg, char b_fake_player_arg, __int64 a5, char* Buffer, void* a7)) +{ + // try changing name before all else + g_pServerAuthentication->VerifyPlayerName(self, pNextPlayerToken, name); + + // try to auth player, dc if it fails + // 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); + if (!ret) + return ret; + + if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid)) + { + R2::CBaseClient__Disconnect(self, 1, "Banned from server"); + return ret; + } + + if (strlen(name) >= 64) // fix for name overflow bug + R2::CBaseClient__Disconnect(self, 1, "Invalid name"); + else if ( + !g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken) && + g_pMasterServerManager->m_bRequireClientAuth) + R2::CBaseClient__Disconnect(self, 1, "Authentication Failed"); + + g_pServerAuthentication->AddPlayerData(self, pNextPlayerToken); + g_pServerLimits->AddPlayer(self); + + return ret; +} + +AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80, +void,, (R2::CBaseClient* self)) +{ + // 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 (self->m_iPersistenceReady >= R2::ePersistenceReady::READY && !g_pServerAuthentication->RemovePlayerAuthData(self)) + { + g_pServerAuthentication->m_bForceResetLocalPlayerPersistence = false; + g_pServerAuthentication->WritePersistentData(self); + g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); + } + + CBaseClient__ActivatePlayer(self); +} + +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, 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(pReason, "Connection closing")) + { + spdlog::info("Player {} disconnected: \"{}\"", self->m_Name, buf); + + // dcing, write persistent data + 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_pServerAuthentication->m_PlayerAuthenticationData.count(self)) + { + g_pServerAuthentication->m_PlayerAuthenticationData.erase(self); + g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size()); + } + + _CBaseClient__Disconnect(self, unknownButAlways1, buf); +} + +void ConCommand_ns_resetpersistence(const CCommand& args) +{ + 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_pServerAuthentication->m_bForceResetLocalPlayerPersistence = true; +} + +ON_DLL_LOAD_RELIESON("engine.dll", ServerAuthentication, (ConCommand, ConVar), (CModule module)) +{ + AUTOHOOK_DISPATCH() + + g_pServerAuthentication = new ServerAuthenticationManager; + + 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"); + 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"); + 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"); + + 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 + 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")) + { + // patch to allow same of multiple account + module.Offset(0x114510).Patch("EB"); + + g_pServerAuthentication->m_bAllowDuplicateAccounts = true; + } +} -- cgit v1.2.3