aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDLL/serverauthentication.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'NorthstarDLL/serverauthentication.cpp')
-rw-r--r--NorthstarDLL/serverauthentication.cpp761
1 files changed, 390 insertions, 371 deletions
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 <fstream>
-#include <filesystem>
-#include <thread>
-
-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<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());
- });
-
- 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<std::mutex> 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<std::mutex> 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<std::mutex> 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 <fstream>
+#include <filesystem>
+#include <thread>
+
+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<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());
+ });
+
+ 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
+ }
+}