aboutsummaryrefslogtreecommitdiff
path: root/primedev/server/auth
diff options
context:
space:
mode:
authorJack <66967891+ASpoonPlaysGames@users.noreply.github.com>2023-12-27 00:32:01 +0000
committerGitHub <noreply@github.com>2023-12-27 01:32:01 +0100
commitf5ab6fb5e8be7b73e6003d4145081d5e0c0ce287 (patch)
tree90f2c6a4885dbd181799e2325cf33588697674e1 /primedev/server/auth
parentbb8ed59f6891b1196c5f5bbe7346cd171c8215fa (diff)
downloadNorthstarLauncher-f5ab6fb5e8be7b73e6003d4145081d5e0c0ce287.tar.gz
NorthstarLauncher-f5ab6fb5e8be7b73e6003d4145081d5e0c0ce287.zip
Folder restructuring from primedev (#624)v1.21.2-rc3v1.21.2
Copies of over the primedev folder structure for easier cherry-picking of further changes Co-authored-by: F1F7Y <filip.bartos07@proton.me>
Diffstat (limited to 'primedev/server/auth')
-rw-r--r--primedev/server/auth/bansystem.cpp224
-rw-r--r--primedev/server/auth/bansystem.h19
-rw-r--r--primedev/server/auth/serverauthentication.cpp380
-rw-r--r--primedev/server/auth/serverauthentication.h58
4 files changed, 681 insertions, 0 deletions
diff --git a/primedev/server/auth/bansystem.cpp b/primedev/server/auth/bansystem.cpp
new file mode 100644
index 00000000..a45cde93
--- /dev/null
+++ b/primedev/server/auth/bansystem.cpp
@@ -0,0 +1,224 @@
+#include "bansystem.h"
+#include "serverauthentication.h"
+#include "core/convar/concommand.h"
+#include "server/r2server.h"
+#include "engine/r2engine.h"
+#include "client/r2client.h"
+#include "config/profile.h"
+
+#include <filesystem>
+
+const char* BANLIST_PATH_SUFFIX = "/banlist.txt";
+const char BANLIST_COMMENT_CHAR = '#';
+
+ServerBanSystem* g_pBanSystem;
+
+void ServerBanSystem::OpenBanlist()
+{
+ std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt");
+
+ if (!banlistStream.fail())
+ {
+ std::string line;
+ while (std::getline(banlistStream, line))
+ {
+ // ignore line if first char is # or line is empty
+ if (line == "" || line.front() == BANLIST_COMMENT_CHAR)
+ continue;
+
+ // remove tabs which shouldnt be there but maybe someone did the funny
+ line.erase(std::remove(line.begin(), line.end(), '\t'), line.end());
+ // remove spaces to allow for spaces before uids
+ line.erase(std::remove(line.begin(), line.end(), ' '), line.end());
+
+ // check if line is empty to allow for newlines in the file
+ if (line == "")
+ continue;
+
+ // for inline comments like: 123123123 #banned for unfunny
+ std::string uid = line.substr(0, line.find(BANLIST_COMMENT_CHAR));
+
+ m_vBannedUids.push_back(strtoull(uid.c_str(), nullptr, 10));
+ }
+
+ banlistStream.close();
+ }
+
+ // open write stream for banlist // dont do this to allow for all time access
+ // m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app);
+}
+
+void ServerBanSystem::ReloadBanlist()
+{
+ std::ifstream fsBanlist(GetNorthstarPrefix() + "/banlist.txt");
+
+ if (!fsBanlist.fail())
+ {
+ std::string line;
+ // since we wanna use this as the reload func we need to clear the list
+ m_vBannedUids.clear();
+ while (std::getline(fsBanlist, line))
+ m_vBannedUids.push_back(strtoull(line.c_str(), nullptr, 10));
+
+ fsBanlist.close();
+ }
+}
+
+void ServerBanSystem::ClearBanlist()
+{
+ m_vBannedUids.clear();
+
+ // reopen the file, don't provide std::ofstream::app so it clears on open
+ m_sBanlistStream.close();
+ m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary);
+ m_sBanlistStream.close();
+}
+
+void ServerBanSystem::BanUID(uint64_t uid)
+{
+ // checking if last char is \n to make sure uids arent getting fucked
+ std::ifstream fsBanlist(GetNorthstarPrefix() + "/banlist.txt");
+ std::string content((std::istreambuf_iterator<char>(fsBanlist)), (std::istreambuf_iterator<char>()));
+ fsBanlist.close();
+
+ m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app);
+ if (content.back() != '\n')
+ m_sBanlistStream << std::endl;
+
+ m_vBannedUids.push_back(uid);
+ m_sBanlistStream << std::to_string(uid) << std::endl;
+ m_sBanlistStream.close();
+ spdlog::info("{} was banned", uid);
+}
+
+void ServerBanSystem::UnbanUID(uint64_t uid)
+{
+ auto findResult = std::find(m_vBannedUids.begin(), m_vBannedUids.end(), uid);
+ if (findResult == m_vBannedUids.end())
+ return;
+
+ m_vBannedUids.erase(findResult);
+
+ std::vector<std::string> banlistText;
+ std::ifstream fs_readBanlist(GetNorthstarPrefix() + "/banlist.txt");
+
+ if (!fs_readBanlist.fail())
+ {
+ std::string line;
+ while (std::getline(fs_readBanlist, line))
+ {
+ // support for comments and newlines added in https://github.com/R2Northstar/NorthstarLauncher/pull/227
+
+ std::string modLine = line; // copy the line into a free var that we can fuck with, line will be the original
+
+ // remove tabs which shouldnt be there but maybe someone did the funny
+ modLine.erase(std::remove(modLine.begin(), modLine.end(), '\t'), modLine.end());
+ // remove spaces to allow for spaces before uids
+ modLine.erase(std::remove(modLine.begin(), modLine.end(), ' '), modLine.end());
+
+ // ignore line if first char is # or empty line, just add it
+ if (line.front() == BANLIST_COMMENT_CHAR || modLine == "")
+ {
+ banlistText.push_back(line);
+ continue;
+ }
+
+ // for inline comments like: 123123123 #banned for unfunny
+ std::string lineUid = line.substr(0, line.find(BANLIST_COMMENT_CHAR));
+ // have to erase spaces or else inline comments will fuck up the uid finding
+ lineUid.erase(std::remove(lineUid.begin(), lineUid.end(), '\t'), lineUid.end());
+ lineUid.erase(std::remove(lineUid.begin(), lineUid.end(), ' '), lineUid.end());
+
+ // if the uid in the line is the uid we wanna unban
+ if (std::to_string(uid) == lineUid)
+ {
+ // comment the uid out
+ line.insert(0, "# ");
+
+ // add a comment with unban date
+ // not necessary but i feel like this makes it better
+ std::time_t t = std::time(0);
+ std::tm* now = std::localtime(&t);
+
+ std::ostringstream unbanComment;
+
+ //{y}/{m}/{d} {h}:{m}
+ unbanComment << " # unban date: ";
+ unbanComment << now->tm_year + 1900 << "-"; // this lib is so fucking awful
+ unbanComment << std::setw(2) << std::setfill('0') << now->tm_mon + 1 << "-";
+ unbanComment << std::setw(2) << std::setfill('0') << now->tm_mday << " ";
+ unbanComment << std::setw(2) << std::setfill('0') << now->tm_hour << ":";
+ unbanComment << std::setw(2) << std::setfill('0') << now->tm_min;
+
+ line.append(unbanComment.str());
+ }
+
+ banlistText.push_back(line);
+ }
+
+ fs_readBanlist.close();
+ }
+
+ // open write stream for banlist // without append so we clear the file
+ if (m_sBanlistStream.is_open())
+ m_sBanlistStream.close();
+ m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary);
+
+ for (std::string updatedLine : banlistText)
+ m_sBanlistStream << updatedLine << std::endl;
+
+ m_sBanlistStream.close();
+ spdlog::info("{} was unbanned", uid);
+}
+
+bool ServerBanSystem::IsUIDAllowed(uint64_t uid)
+{
+ uint64_t localPlayerUserID = strtoull(g_pLocalPlayerUserID, nullptr, 10);
+ if (localPlayerUserID == uid)
+ return true;
+
+ ReloadBanlist(); // Reload to have up to date list on join
+ return std::find(m_vBannedUids.begin(), m_vBannedUids.end(), uid) == m_vBannedUids.end();
+}
+
+void ConCommand_ban(const CCommand& args)
+{
+ if (args.ArgC() < 2)
+ return;
+
+ for (int i = 0; i < g_pGlobals->m_nMaxClients; i++)
+ {
+ CBaseClient* player = &g_pClientArray[i];
+
+ if (!strcmp(player->m_Name, args.Arg(1)) || !strcmp(player->m_UID, args.Arg(1)))
+ {
+ g_pBanSystem->BanUID(strtoull(player->m_UID, nullptr, 10));
+ CBaseClient__Disconnect(player, 1, "Banned from server");
+ break;
+ }
+ }
+}
+
+void ConCommand_unban(const CCommand& args)
+{
+ if (args.ArgC() < 2)
+ return;
+
+ // assumedly the player being unbanned here wasn't already connected, so don't need to iterate over players or anything
+ g_pBanSystem->UnbanUID(strtoull(args.Arg(1), nullptr, 10));
+}
+
+void ConCommand_clearbanlist(const CCommand& args)
+{
+ g_pBanSystem->ClearBanlist();
+}
+
+ON_DLL_LOAD_RELIESON("engine.dll", BanSystem, ConCommand, (CModule module))
+{
+ g_pBanSystem = new ServerBanSystem;
+ g_pBanSystem->OpenBanlist();
+
+ RegisterConCommand("ban", ConCommand_ban, "bans a given player by uid or name", FCVAR_GAMEDLL);
+ RegisterConCommand("unban", ConCommand_unban, "unbans a given player by uid", FCVAR_GAMEDLL);
+ RegisterConCommand("clearbanlist", ConCommand_clearbanlist, "clears all uids on the banlist", FCVAR_GAMEDLL);
+}
diff --git a/primedev/server/auth/bansystem.h b/primedev/server/auth/bansystem.h
new file mode 100644
index 00000000..d6ac5a4f
--- /dev/null
+++ b/primedev/server/auth/bansystem.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <fstream>
+
+class ServerBanSystem
+{
+private:
+ std::ofstream m_sBanlistStream;
+ std::vector<uint64_t> m_vBannedUids;
+
+public:
+ void OpenBanlist();
+ void ReloadBanlist();
+ void ClearBanlist();
+ void BanUID(uint64_t uid);
+ void UnbanUID(uint64_t uid);
+ bool IsUIDAllowed(uint64_t uid);
+};
+
+extern ServerBanSystem* g_pBanSystem;
diff --git a/primedev/server/auth/serverauthentication.cpp b/primedev/server/auth/serverauthentication.cpp
new file mode 100644
index 00000000..0d46426f
--- /dev/null
+++ b/primedev/server/auth/serverauthentication.cpp
@@ -0,0 +1,380 @@
+#include "serverauthentication.h"
+#include "shared/exploit_fixes/ns_limits.h"
+#include "core/convar/cvar.h"
+#include "core/convar/convar.h"
+#include "masterserver/masterserver.h"
+#include "server/serverpresence.h"
+#include "engine/hoststate.h"
+#include "bansystem.h"
+#include "core/convar/concommand.h"
+#include "dedicated/dedicated.h"
+#include "config/profile.h"
+#include "core/tier0.h"
+#include "engine/r2engine.h"
+#include "client/r2client.h"
+#include "server/r2server.h"
+
+#include <fstream>
+#include <filesystem>
+#include <string>
+#include <thread>
+
+AUTOHOOK_INIT()
+
+// global vars
+ServerAuthenticationManager* g_pServerAuthentication;
+CBaseServer__RejectConnectionType CBaseServer__RejectConnection;
+
+void ServerAuthenticationManager::AddRemotePlayer(std::string token, uint64_t uid, std::string username, std::string pdata)
+{
+ std::string uidS = std::to_string(uid);
+
+ RemoteAuthData newAuthData {};
+ strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), uidS.c_str(), uidS.length());
+ strncpy_s(newAuthData.username, sizeof(newAuthData.username), username.c_str(), username.length());
+ newAuthData.pdata = new char[pdata.length()];
+ newAuthData.pdataSize = pdata.length();
+ memcpy(newAuthData.pdata, pdata.c_str(), newAuthData.pdataSize);
+
+ std::lock_guard<std::mutex> guard(m_AuthDataMutex);
+ m_RemoteAuthenticationData[token] = newAuthData;
+}
+
+void ServerAuthenticationManager::AddPlayer(CBaseClient* pPlayer, const char* pToken)
+{
+ PlayerAuthenticationData additionalData;
+
+ auto remoteAuthData = m_RemoteAuthenticationData.find(pToken);
+ if (remoteAuthData != m_RemoteAuthenticationData.end())
+ additionalData.pdataSize = remoteAuthData->second.pdataSize;
+ else
+ additionalData.pdataSize = PERSISTENCE_MAX_SIZE;
+
+ additionalData.usingLocalPdata = pPlayer->m_iPersistenceReady == ePersistenceReady::READY_INSECURE;
+
+ m_PlayerAuthenticationData.insert(std::make_pair(pPlayer, additionalData));
+}
+
+void ServerAuthenticationManager::RemovePlayer(CBaseClient* pPlayer)
+{
+ if (m_PlayerAuthenticationData.count(pPlayer))
+ m_PlayerAuthenticationData.erase(pPlayer);
+}
+
+bool ServerAuthenticationManager::VerifyPlayerName(const char* pAuthToken, const char* pName, char pOutVerifiedName[64])
+{
+ std::lock_guard<std::mutex> guard(m_AuthDataMutex);
+
+ // always use name from masterserver if available
+ // use of strncpy_s here should verify that this is always nullterminated within valid buffer size
+ auto authData = m_RemoteAuthenticationData.find(pAuthToken);
+ if (authData != m_RemoteAuthenticationData.end() && *authData->second.username)
+ strncpy_s(pOutVerifiedName, 64, authData->second.username, 63);
+ else
+ strncpy_s(pOutVerifiedName, 64, pName, 63);
+
+ // now, check that whatever name we have is actually valid
+ // first, make sure it's >1 char
+ if (!*pOutVerifiedName)
+ return false;
+
+ // next, make sure it's within a valid range of ascii characters
+ for (int i = 0; pOutVerifiedName[i]; i++)
+ {
+ if (pOutVerifiedName[i] < 32 || pOutVerifiedName[i] > 126)
+ return false;
+ }
+
+ return true;
+}
+
+bool ServerAuthenticationManager::IsDuplicateAccount(CBaseClient* pPlayer, const char* pPlayerUid)
+{
+ if (m_bAllowDuplicateAccounts)
+ return false;
+
+ bool bHasUidPlayer = false;
+ for (int i = 0; i < g_pGlobals->m_nMaxClients; i++)
+ if (&g_pClientArray[i] != pPlayer && !strcmp(pPlayerUid, g_pClientArray[i].m_UID))
+ return true;
+
+ return false;
+}
+
+bool ServerAuthenticationManager::CheckAuthentication(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken)
+{
+ std::string sUid = std::to_string(iUid);
+
+ // check whether this player's authentication is valid but don't actually write anything to the player, we'll do that later
+ // if we don't need auth this is valid
+ if (Cvar_ns_auth_allow_insecure->GetBool())
+ return true;
+
+ // local server that doesn't need auth (probably sp) and local player
+ if (m_bStartingLocalSPGame && !strcmp(sUid.c_str(), g_pLocalPlayerUserID))
+ return true;
+
+ // don't allow duplicate accounts
+ if (IsDuplicateAccount(pPlayer, sUid.c_str()))
+ return false;
+
+ std::lock_guard<std::mutex> guard(m_AuthDataMutex);
+ auto authData = m_RemoteAuthenticationData.find(pAuthToken);
+ if (authData != m_RemoteAuthenticationData.end() && !strcmp(sUid.c_str(), authData->second.uid))
+ return true;
+
+ return false;
+}
+
+void ServerAuthenticationManager::AuthenticatePlayer(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken)
+{
+ // for bot players, generate a new uid
+ if (pPlayer->m_bFakePlayer)
+ iUid = 0; // is this a good way of doing things :clueless:
+
+ std::string sUid = std::to_string(iUid);
+
+ // copy uuid
+ strcpy(pPlayer->m_UID, sUid.c_str());
+
+ std::lock_guard<std::mutex> guard(m_AuthDataMutex);
+ auto authData = m_RemoteAuthenticationData.find(pAuthToken);
+ if (authData != m_RemoteAuthenticationData.end())
+ {
+ // if we're resetting let script handle the reset with InitPersistentData() on connect
+ if (!m_bForceResetLocalPlayerPersistence || strcmp(sUid.c_str(), g_pLocalPlayerUserID))
+ {
+ // copy pdata into buffer
+ memcpy(pPlayer->m_PersistenceBuffer, authData->second.pdata, authData->second.pdataSize);
+ }
+
+ // set persistent data as ready
+ pPlayer->m_iPersistenceReady = ePersistenceReady::READY_REMOTE;
+ }
+ // we probably allow insecure at this point, but make sure not to write anyway if not insecure
+ else if (Cvar_ns_auth_allow_insecure->GetBool() || pPlayer->m_bFakePlayer)
+ {
+ // set persistent data as ready
+ // note: actual placeholder persistent data is populated in script with InitPersistentData()
+ pPlayer->m_iPersistenceReady = ePersistenceReady::READY_INSECURE;
+ }
+}
+
+bool ServerAuthenticationManager::RemovePlayerAuthData(CBaseClient* pPlayer)
+{
+ 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(pPlayer->m_UID, 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(pPlayer->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(CBaseClient* pPlayer)
+{
+ if (pPlayer->m_iPersistenceReady == ePersistenceReady::READY_REMOTE)
+ {
+ g_pMasterServerManager->WritePlayerPersistentData(
+ pPlayer->m_UID, (const char*)pPlayer->m_PersistenceBuffer, m_PlayerAuthenticationData[pPlayer].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;
+
+// clang-format off
+AUTOHOOK(CBaseServer__ConnectClient, engine.dll + 0x114430,
+void*,, (
+ void* self,
+ void* addr,
+ void* a3,
+ uint32_t a4,
+ uint32_t a5,
+ int32_t a6,
+ void* a7,
+ char* playerName,
+ char* serverFilter,
+ void* a10,
+ char a11,
+ void* a12,
+ char a13,
+ char a14,
+ int64_t uid,
+ uint32_t a16,
+ uint32_t a17))
+// clang-format on
+{
+ // 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(self, addr, a3, a4, a5, a6, a7, playerName, serverFilter, a10, a11, a12, a13, a14, uid, a16, a17);
+}
+
+ConVar* Cvar_ns_allowuserclantags;
+
+// clang-format off
+AUTOHOOK(CBaseClient__Connect, engine.dll + 0x101740,
+bool,, (CBaseClient* self, char* pName, void* pNetChannel, char bFakePlayer, void* a5, char pDisconnectReason[256], void* a7))
+// clang-format on
+{
+ const char* pAuthenticationFailure = nullptr;
+ char pVerifiedName[64];
+
+ if (!bFakePlayer)
+ {
+ if (!g_pServerAuthentication->VerifyPlayerName(pNextPlayerToken, pName, pVerifiedName))
+ pAuthenticationFailure = "Invalid Name.";
+ else if (!g_pBanSystem->IsUIDAllowed(iNextPlayerUid))
+ pAuthenticationFailure = "Banned From server.";
+ else if (!g_pServerAuthentication->CheckAuthentication(self, iNextPlayerUid, pNextPlayerToken))
+ pAuthenticationFailure = "Authentication Failed.";
+ }
+ else // need to copy name for bots still
+ strncpy_s(pVerifiedName, pName, 63);
+
+ if (pAuthenticationFailure)
+ {
+ spdlog::info("{}'s (uid {}) connection was rejected: \"{}\"", pName, iNextPlayerUid, pAuthenticationFailure);
+
+ strncpy_s(pDisconnectReason, 256, pAuthenticationFailure, 255);
+ return false;
+ }
+
+ // try to actually connect the player
+ if (!CBaseClient__Connect(self, pVerifiedName, pNetChannel, bFakePlayer, a5, pDisconnectReason, a7))
+ return false;
+
+ // we already know this player's authentication data is legit, actually write it to them now
+ g_pServerAuthentication->AuthenticatePlayer(self, iNextPlayerUid, pNextPlayerToken);
+
+ g_pServerAuthentication->AddPlayer(self, pNextPlayerToken);
+ g_pServerLimits->AddPlayer(self);
+
+ return true;
+}
+
+// clang-format off
+AUTOHOOK(CBaseClient__ActivatePlayer, engine.dll + 0x100F80,
+void,, (CBaseClient* self))
+// clang-format on
+{
+ // 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 >= 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);
+}
+
+// clang-format off
+AUTOHOOK(_CBaseClient__Disconnect, engine.dll + 0x1012C0,
+void,, (CBaseClient* self, uint32_t unknownButAlways1, const char* pReason, ...))
+// clang-format on
+{
+ // 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);
+
+ memset(self->m_PersistenceBuffer, 0, g_pServerAuthentication->m_PlayerAuthenticationData[self].pdataSize);
+ g_pServerAuthentication->RemovePlayerAuthData(self); // won't do anything 99% of the time, but just in case
+
+ g_pServerAuthentication->RemovePlayer(self);
+ g_pServerLimits->RemovePlayer(self);
+ }
+
+ g_pServerPresence->SetPlayerCount(g_pServerAuthentication->m_PlayerAuthenticationData.size());
+
+ _CBaseClient__Disconnect(self, unknownButAlways1, buf);
+}
+
+void ConCommand_ns_resetpersistence(const CCommand& args)
+{
+ if (*g_pServerState == 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_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");
+
+ CBaseServer__RejectConnection = module.Offset(0x1182E0).RCast<CBaseServer__RejectConnectionType>();
+
+ if (CommandLine()->CheckParm("-allowdupeaccounts"))
+ {
+ // patch to allow same of multiple account
+ module.Offset(0x114510).Patch("EB");
+
+ g_pServerAuthentication->m_bAllowDuplicateAccounts = true;
+ }
+}
diff --git a/primedev/server/auth/serverauthentication.h b/primedev/server/auth/serverauthentication.h
new file mode 100644
index 00000000..996d20e1
--- /dev/null
+++ b/primedev/server/auth/serverauthentication.h
@@ -0,0 +1,58 @@
+#pragma once
+#include "core/convar/convar.h"
+#include "engine/r2engine.h"
+#include <unordered_map>
+#include <string>
+
+struct RemoteAuthData
+{
+ char uid[33];
+ char username[64];
+
+ // pdata
+ char* pdata;
+ size_t pdataSize;
+};
+
+struct PlayerAuthenticationData
+{
+ bool usingLocalPdata;
+ size_t pdataSize;
+ bool needPersistenceWriteOnLeave = true;
+};
+
+typedef int64_t (*CBaseServer__RejectConnectionType)(void* a1, unsigned int a2, void* a3, const char* a4, ...);
+extern CBaseServer__RejectConnectionType CBaseServer__RejectConnection;
+
+class ServerAuthenticationManager
+{
+public:
+ ConVar* Cvar_ns_erase_auth_info;
+ ConVar* Cvar_ns_auth_allow_insecure;
+ ConVar* Cvar_ns_auth_allow_insecure_write;
+
+ std::mutex m_AuthDataMutex;
+ std::unordered_map<std::string, RemoteAuthData> m_RemoteAuthenticationData;
+ std::unordered_map<CBaseClient*, PlayerAuthenticationData> m_PlayerAuthenticationData;
+
+ bool m_bAllowDuplicateAccounts = false;
+ bool m_bNeedLocalAuthForNewgame = false;
+ bool m_bForceResetLocalPlayerPersistence = false;
+ bool m_bStartingLocalSPGame = false;
+
+public:
+ void AddRemotePlayer(std::string token, uint64_t uid, std::string username, std::string pdata);
+
+ void AddPlayer(CBaseClient* pPlayer, const char* pAuthToken);
+ void RemovePlayer(CBaseClient* pPlayer);
+
+ bool VerifyPlayerName(const char* pAuthToken, const char* pName, char pOutVerifiedName[64]);
+ bool IsDuplicateAccount(CBaseClient* pPlayer, const char* pUid);
+ bool CheckAuthentication(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken);
+
+ void AuthenticatePlayer(CBaseClient* pPlayer, uint64_t iUid, char* pAuthToken);
+ bool RemovePlayerAuthData(CBaseClient* pPlayer);
+ void WritePersistentData(CBaseClient* pPlayer);
+};
+
+extern ServerAuthenticationManager* g_pServerAuthentication;