/* * ============================================================================= * Connect Extension * Copyright (C) 2011 Asher Baker (asherkin). All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "extension.h" #include "CDetour/detours.h" #include "sm_namehashset.h" #include "steam/steamclientpublic.h" #include "steam/isteamclient.h" #include #include Connect g_connect; SMEXT_LINK(&g_connect); ConVar connectVersion("connect_version", SMEXT_CONF_VERSION, FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY, SMEXT_CONF_DESCRIPTION " Version"); ConVar g_SvLogging("sv_connect_logging", "0", FCVAR_NOTIFY, "Log connection checks."); ConVar g_SvNoSteam("sv_nosteam", "0", FCVAR_NOTIFY, "Disable steam validation and force steam authentication."); ConVar g_SvForceSteam("sv_forcesteam", "0", FCVAR_NOTIFY, "Force steam authentication."); ConVar g_SvGameDesc("sv_gamedesc_override", "default", FCVAR_NOTIFY, "Overwrite the default game description. Set to 'default' to keep default description."); ConVar g_SvMapName("sv_mapname_override", "default", FCVAR_NOTIFY, "Overwrite the map name. Set to 'default' to keep default name."); IGameConfig *g_pGameConf = NULL; IForward *g_pConnectForward = NULL; class IClient; class CBaseServer; typedef enum EAuthProtocol { k_EAuthProtocolWONCertificate = 1, k_EAuthProtocolHashedCDKey = 2, k_EAuthProtocolSteam = 3, } EAuthProtocol; /* k_EBeginAuthSessionResultOK = 0, // Ticket is valid for this game and this steamID. k_EBeginAuthSessionResultInvalidTicket = 1, // Ticket is not valid. k_EBeginAuthSessionResultDuplicateRequest = 2, // A ticket has already been submitted for this steamID k_EBeginAuthSessionResultInvalidVersion = 3, // Ticket is from an incompatible interface version k_EBeginAuthSessionResultGameMismatch = 4, // Ticket is not for this game k_EBeginAuthSessionResultExpiredTicket = 5, // Ticket has expired */ typedef struct netadr_s { private: typedef enum { NA_NULL = 0, NA_LOOPBACK, NA_BROADCAST, NA_IP, } netadrtype_t; public: netadrtype_t type; unsigned char ip[4]; unsigned short port; } netadr_t; const char *CSteamID::Render() const { static char szSteamID[64]; V_snprintf(szSteamID, sizeof(szSteamID), "STEAM_0:%u:%u", this->GetAccountID() & 1, this->GetAccountID() >> 1); return szSteamID; } class ISteamGameServer; class CSteam3Server { public: void *m_pSteamClient; ISteamGameServer* m_pSteamGameServer; void *m_pSteamGameServerUtils; void *m_pSteamGameServerNetworking; void *m_pSteamGameServerStats; void *m_pSteamHTTP; void *m_pSteamInventory; void *m_pSteamUGC; void *m_pSteamApps; } *g_pSteam3Server; typedef CSteam3Server *(*Steam3ServerFunc)(); class FEmptyClass {}; template union MFPHack { private: void* addr; public: MFPHack(void* addr) { this->addr = addr; } template MFPHack(RetType (randomclass::*mfp)(Args...)) { union { RetType (randomclass::*ptr)(Args...); struct { void* addr; #ifdef __linux__ intptr_t adjustor; #endif } details; } u; u.ptr = mfp; this->addr = u.details.addr; } void SetAddress(void* addr) { this->addr = addr; } void* GetAddress() { return this->addr; } RetType operator()(classcall* ptrThis, Args... args) { union { RetType (FEmptyClass::*ptr)(Args...); struct { void* addr; #ifdef __linux__ intptr_t adjustor; #endif } details; } u; u.details.addr = addr; #ifdef __linux__ u.details.adjustor = 0; #endif return (((FEmptyClass*)ptrThis)->*u.ptr)(args...); } }; CDetour* detourCBaseServer__ConnectClient = nullptr; CDetour *g_Detour_CSteam3Server__OnValidateAuthTicketResponse = NULL; bool g_bSuppressBeginAuthSession = false; CSteamID g_lastClientSteamID; const void* g_lastAuthTicket; int g_lastcbAuthTicket; char passwordBuffer[255]; Steam3ServerFunc g_pSteam3ServerFunc = nullptr; MFPHack g_pRejectConnectionFunc(nullptr); MFPHack g_pBeginAuthSession(nullptr); MFPHack g_pEndAuthSession(nullptr); CSteam3Server *Steam3Server() { if (!g_pSteam3ServerFunc) return NULL; return g_pSteam3ServerFunc(); } SH_DECL_MANUALHOOK3(MHook_BeginAuthSession, 0, 0, 0, EBeginAuthSessionResult, const void *, int, CSteamID); EBeginAuthSessionResult Hook_BeginAuthSession(const void *pAuthTicket, int cbAuthTicket, CSteamID steamID) { if (!g_bSuppressBeginAuthSession) { //g_pSM->LogMessage(myself, "inside Hook_ BeginAuthSession 1"); RETURN_META_VALUE(MRES_IGNORED, k_EBeginAuthSessionResultOK); } g_bSuppressBeginAuthSession = false; if (strcmp(steamID.Render(), g_lastClientSteamID.Render()) == 0 && g_lastAuthTicket == pAuthTicket && g_lastcbAuthTicket == cbAuthTicket) { // Let the server know everything is fine //g_pSM->LogMessage(myself, "inside Hook_ BeginAuthSession 2"); RETURN_META_VALUE(MRES_SUPERCEDE, k_EBeginAuthSessionResultOK); } //g_pSM->LogMessage(myself, "inside Hook_ BeginAuthSession 3"); RETURN_META_VALUE(MRES_IGNORED, k_EBeginAuthSessionResultDuplicateRequest); } struct ValidateAuthTicketResponse_t { enum { k_iCallback = k_iSteamUserCallbacks + 43 }; CSteamID m_SteamID; EAuthSessionResponse m_eAuthSessionResponse; CSteamID m_OwnerSteamID; // different from m_SteamID if borrowed //in dewey voice "can a nigga borrow a m_SteamID?" //the teacher "How is a nigga gonna borrow a m_SteamID? nigga is you gonna give it back?" }; class ConnectClientStorage { public: void* pThis; netadr_t address; int nProtocol; int iChallenge; int iClientChallenge; int nAuthProtocol; char pchName[256]; char pchPassword[256]; char pCookie[256]; int cbCookie; IClient *pClient; uint64 ullSteamID; ValidateAuthTicketResponse_t ValidateAuthTicketResponse; bool GotValidateAuthTicketResponse; bool SteamLegal; bool SteamAuthFailed; void *pvTicket; int cbTicket; ConnectClientStorage() { } ConnectClientStorage(netadr_t address, int nProtocol, int iChallenge, int iClientChallenge, int nAuthProtocol, const char *pchName, const char *pchPassword, const char *pCookie, int cbCookie) { this->address = address; this->nProtocol = nProtocol; this->iChallenge = iChallenge; this->iClientChallenge = iClientChallenge; this->nAuthProtocol = nAuthProtocol; V_strncpy(this->pchName, pchName, 255); V_strncpy(this->pchPassword, pchPassword, 255); memcpy(this->pCookie, pCookie, cbCookie); this->cbCookie = cbCookie; this->pClient = NULL; this->GotValidateAuthTicketResponse = false; this->SteamLegal = false; this->SteamAuthFailed = false; // Calculate and store the ticket pointer this->pvTicket = (void *)((intptr_t)this->pCookie + sizeof(uint64)); this->cbTicket = cbCookie - sizeof(uint64); } }; StringHashMap g_ConnectClientStorage; DETOUR_DECL_MEMBER1(CSteam3Server__OnValidateAuthTicketResponse, int, ValidateAuthTicketResponse_t *, pResponse) { char aSteamID[32]; V_strncpy(aSteamID, pResponse->m_SteamID.Render(), 32); bool SteamLegal = pResponse->m_eAuthSessionResponse == k_EAuthSessionResponseOK; bool force = g_SvNoSteam.GetInt() || g_SvForceSteam.GetInt(); if(!SteamLegal && force) { pResponse->m_eAuthSessionResponse = k_EAuthSessionResponseOK; } ConnectClientStorage Storage; if(g_ConnectClientStorage.retrieve(aSteamID, &Storage)) { if(!Storage.GotValidateAuthTicketResponse) { Storage.GotValidateAuthTicketResponse = true; Storage.ValidateAuthTicketResponse = *pResponse; Storage.SteamLegal = SteamLegal; g_ConnectClientStorage.replace(aSteamID, Storage); } } return DETOUR_MEMBER_CALL(CSteam3Server__OnValidateAuthTicketResponse)(pResponse); } void *g_pLastHookedSteamGameServer = NULL; int g_nBeginAuthSessionOffset = 0; DETOUR_DECL_MEMBER9(CBaseServer__ConnectClient, IClient*, netadr_t&, address, int, nProtocol, int, iChallenge, int, iClientChallenge, int, nAuthProtocol, const char *, pchName, const char *, pchPassword, const char *, pCookie, int, cbCookie) { if (nAuthProtocol != k_EAuthProtocolSteam) { // This is likely a SourceTV client, we don't want to interfere here. return DETOUR_MEMBER_CALL(CBaseServer__ConnectClient)(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie); } if (g_pSteam3Server->m_pSteamGameServer != g_pLastHookedSteamGameServer) { //the hook tends to die, hence its reapplied whenever needed. thats probably very dumb. //does however seem to stop "S3: Client connected with invalid ticket:" for nosteamers. //g_pSM->LogMessage(myself, "Steam GameServer pointer changed! Old: %p, New: %p", g_pLastHookedSteamGameServer, g_pSteam3Server->m_pSteamGameServer); if (g_pLastHookedSteamGameServer != NULL) { // Remove old hook if it exists SH_REMOVE_MANUALHOOK(MHook_BeginAuthSession, g_pLastHookedSteamGameServer, SH_STATIC(Hook_BeginAuthSession), true); } // Add hook to the new object SH_MANUALHOOK_RECONFIGURE(MHook_BeginAuthSession, g_nBeginAuthSessionOffset, 0, 0); SH_ADD_MANUALHOOK(MHook_BeginAuthSession, g_pSteam3Server->m_pSteamGameServer, SH_STATIC(Hook_BeginAuthSession), true); g_pLastHookedSteamGameServer = g_pSteam3Server->m_pSteamGameServer; } if (pCookie == NULL || (size_t)cbCookie < sizeof(uint64)) { g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "#GameUI_ServerRejectInvalidSteamCertLen"); return NULL; } auto steamGameServer = Steam3Server()->m_pSteamGameServer; char ipString[30]; V_snprintf(ipString, sizeof(ipString), "%u.%u.%u.%u", address.ip[0], address.ip[1], address.ip[2], address.ip[3]); V_strncpy(passwordBuffer, pchPassword, 255); uint64 ullSteamID = *(uint64 *)pCookie; g_lastClientSteamID = CSteamID(ullSteamID); void *pvTicket = (void *)((intptr_t)pCookie + sizeof(uint64)); int cbTicket = cbCookie - sizeof(uint64); char aSteamID[32]; V_strncpy(aSteamID, g_lastClientSteamID.Render(), 32); // If client is in async state remove the old object and fake an async retVal // This can happen if the async ClientPreConnectEx takes too long to be called // and the client auto-retries. bool AsyncWaiting = false; bool ExistingSteamid = false; bool SkipEndAuthSession = false; EBeginAuthSessionResult result; ConnectClientStorage Storage(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie); if (g_ConnectClientStorage.retrieve(aSteamID, &Storage)) { ExistingSteamid = true; g_ConnectClientStorage.remove(aSteamID); if (!Storage.SteamAuthFailed) { g_pSM->LogMessage(myself, "inside clientstorage about to do endauthsession for steamID: %s", aSteamID); g_pEndAuthSession(steamGameServer, g_lastClientSteamID); } // Only wait for async on auto-retry, manual retries should go through the full chain // Don't want to leave the client waiting forever if something breaks in the async forward if (Storage.iClientChallenge == iClientChallenge) { AsyncWaiting = true; //reject async nosteamers (althought this statement is not reached even as async nosteamers are rejected with invalid ticket) if (Storage.SteamAuthFailed) { //we are only here in async state when server is 64/64. g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "No slots for nosteamers. server is full"); return NULL; } //async steam player with a clientstorage, use the ticket from the previous clientstorage. g_lastcbAuthTicket = Storage.cbTicket; g_lastAuthTicket = Storage.pvTicket; result = g_pBeginAuthSession(steamGameServer, Storage.pvTicket, Storage.cbTicket, g_lastClientSteamID); } else { //client did a manual retry, if (Storage.SteamAuthFailed) { //its a nosteamer so dont use the clientstorage ticket g_lastcbAuthTicket = cbTicket; g_lastAuthTicket = pvTicket; SkipEndAuthSession = true; result = k_EBeginAuthSessionResultInvalidTicket; } else { //create a new client storage for the steamplayer. Storage = ConnectClientStorage(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie); //use the ticket from the new clientstorage. g_lastcbAuthTicket = Storage.cbTicket; g_lastAuthTicket = Storage.pvTicket; result = g_pBeginAuthSession(steamGameServer, Storage.pvTicket, Storage.cbTicket, g_lastClientSteamID); } } } else { //nosteamer or steam player without clientstorage, g_lastcbAuthTicket = cbTicket; g_lastAuthTicket = pvTicket; //NOTE: if the first connecting client on a map is in k_OnClientPreConnectEx_Reject then this beginAuthSession call can crash the server //however if the first connecting client instead is in k_OnClientPreConnectEx_Accept then the after following //k_OnClientPreConnectEx_Reject will work fine. Ex_Accept is always used until the server is full, hence should Ex_Reject //never be the first to connect on a new map. result = g_pBeginAuthSession(steamGameServer, pvTicket, cbTicket, g_lastClientSteamID); } if (!SkipEndAuthSession) { g_pEndAuthSession(steamGameServer, g_lastClientSteamID); //xen said engine might start its own authsession, so end ours here. } bool NoSteam = g_SvNoSteam.GetInt(); bool SteamAuthFailed = false; //g_pSM->LogMessage(myself, "g_pBeginAuthSession result: %d", result); if (result != k_EBeginAuthSessionResultOK) { if (!NoSteam) { g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "#GameUI_ServerRejectSteam"); return NULL; } Storage.SteamAuthFailed = SteamAuthFailed = true; } if(ExistingSteamid && !AsyncWaiting) { // Another player trying to spoof a Steam ID or game crashed? if(memcmp(address.ip, Storage.address.ip, sizeof(address.ip)) != 0) { // Reject NoSteam players if(SteamAuthFailed) { g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "Steam ID already in use."); return NULL; } // Kick existing player if(Storage.pClient) { Storage.pClient->Disconnect("Same Steam ID connected."); } else { g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "Please try again later."); return NULL; } } } char rejectReason[255]; cell_t retVal = 1; if(AsyncWaiting) { retVal = -1; // Fake async return code when waiting for async call } else { g_pConnectForward->PushString(pchName); g_pConnectForward->PushStringEx(passwordBuffer, 255, SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); g_pConnectForward->PushString(ipString); g_pConnectForward->PushString(g_lastClientSteamID.Render()); g_pConnectForward->PushStringEx(rejectReason, 255, SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); g_pConnectForward->Execute(&retVal); } pchPassword = passwordBuffer; // k_OnClientPreConnectEx_Reject if (retVal == 0) { g_ConnectClientStorage.remove(aSteamID); if (!Storage.SteamAuthFailed) { //calling this on nosteamers does sometimes cause a server explosion. //but not when creating a nosteamer with their first ticket g_pSM->LogMessage(myself, "k_OnClientPreConnectEx_Reject reached about to do endAuthSession. steamID: %s", aSteamID); g_pEndAuthSession(steamGameServer, g_lastClientSteamID); } g_pSM->LogMessage(myself, "k_OnClientPreConnectEx_Reject reached about to do g_pRejectConnectionFunc. steamID: %s", aSteamID); g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, rejectReason); return NULL; } Storage.pThis = this; Storage.ullSteamID = ullSteamID; Storage.SteamAuthFailed = SteamAuthFailed; //puts the storage in the StringHashMap if(!g_ConnectClientStorage.replace(aSteamID, Storage)) { g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "Internal error."); return NULL; } // k_OnClientPreConnectEx_Async if (retVal == -1) { return NULL; } // k_OnClientPreConnectEx_Accept g_bSuppressBeginAuthSession = true; auto client = DETOUR_MEMBER_CALL(CBaseServer__ConnectClient)(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie); g_bSuppressBeginAuthSession = false; Storage.pClient = client; g_ConnectClientStorage.replace(aSteamID, Storage); if (client && SteamAuthFailed) { ValidateAuthTicketResponse_t Response; Response.m_SteamID = g_lastClientSteamID; Response.m_eAuthSessionResponse = k_EAuthSessionResponseAuthTicketInvalid; //nosteamer monkeys Response.m_OwnerSteamID = Response.m_SteamID; DETOUR_MEMBER_MCALL_CALLBACK(CSteam3Server__OnValidateAuthTicketResponse, g_pSteam3Server)(&Response); } return client; } bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late) { char conf_error[255] = ""; if (!gameconfs->LoadGameConfigFile("connect2.games", &g_pGameConf, conf_error, sizeof(conf_error))) { if (conf_error[0]) { snprintf(error, maxlen, "Could not read connect2.games.txt: %s\n", conf_error); } return false; } void* addr; if (!g_pGameConf->GetMemSig("CBaseServer__RejectConnection", &addr) || addr == nullptr) { snprintf(error, maxlen, "Failed to find CBaseServer__RejectConnection function.\n"); return false; } g_pRejectConnectionFunc.SetAddress(addr); #ifndef WIN32 if (!g_pGameConf->GetMemSig("Steam3Server", (void **)(&g_pSteam3ServerFunc)) || !g_pSteam3ServerFunc) { snprintf(error, maxlen, "Failed to find Steam3Server function.\n"); return false; } #else void *address; if (!g_pGameConf->GetMemSig("CBaseServer__CheckMasterServerRequestRestart", &address) || !address) { snprintf(error, maxlen, "Failed to find CBaseServer__CheckMasterServerRequestRestart function.\n"); return false; } int steam3ServerFuncOffset = 0; if (!g_pGameConf->GetOffset("CheckMasterServerRequestRestart_Steam3ServerFuncOffset", &steam3ServerFuncOffset) || steam3ServerFuncOffset == 0) { snprintf(error, maxlen, "Failed to find CheckMasterServerRequestRestart_Steam3ServerFuncOffset offset.\n"); return false; } //META_CONPRINTF("CheckMasterServerRequestRestart: %p\n", address); address = (void *)((intptr_t)address + steam3ServerFuncOffset); g_pSteam3ServerFunc = (Steam3ServerFunc)((intptr_t)address + *((int32_t *)address) + sizeof(int32_t)); //META_CONPRINTF("Steam3Server: %p\n", g_pSteam3ServerFunc); #endif g_pSteam3Server = Steam3Server(); if (!g_pSteam3Server) { snprintf(error, maxlen, "Unable to get Steam3Server singleton.\n"); return false; } if (!g_pSteam3Server->m_pSteamGameServer) { snprintf(error, maxlen, "Unable to get Steam Game Server.\n"); return false; } void** vtable = *((void***)g_pSteam3Server->m_pSteamGameServer); if (!g_pGameConf->GetOffset("ISteamGameServer__BeginAuthSession", &g_nBeginAuthSessionOffset) || g_nBeginAuthSessionOffset == 0) { snprintf(error, maxlen, "Failed to find ISteamGameServer__BeginAuthSession offset.\n"); return false; } g_pBeginAuthSession.SetAddress(vtable[g_nBeginAuthSessionOffset]); SH_MANUALHOOK_RECONFIGURE(MHook_BeginAuthSession, g_nBeginAuthSessionOffset, 0, 0); if (SH_ADD_MANUALHOOK(MHook_BeginAuthSession, g_pSteam3Server->m_pSteamGameServer, SH_STATIC(Hook_BeginAuthSession), true) == 0) { snprintf(error, maxlen, "Failed to setup ISteamGameServer__BeginAuthSession hook.\n"); return false; } g_pLastHookedSteamGameServer = g_pSteam3Server->m_pSteamGameServer; int offset = 0; if (!g_pGameConf->GetOffset("ISteamGameServer__EndAuthSession", &offset) || offset == 0) { snprintf(error, maxlen, "Failed to find ISteamGameServer__EndAuthSession offset.\n"); return false; } g_pEndAuthSession.SetAddress(vtable[offset]); CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf); detourCBaseServer__ConnectClient = DETOUR_CREATE_MEMBER(CBaseServer__ConnectClient, "CBaseServer__ConnectClient"); if (detourCBaseServer__ConnectClient == nullptr) { snprintf(error, maxlen, "Failed to create CBaseServer__ConnectClient detour.\n"); return false; } detourCBaseServer__ConnectClient->EnableDetour(); g_Detour_CSteam3Server__OnValidateAuthTicketResponse = DETOUR_CREATE_MEMBER(CSteam3Server__OnValidateAuthTicketResponse, "CSteam3Server__OnValidateAuthTicketResponse"); if(!g_Detour_CSteam3Server__OnValidateAuthTicketResponse) { snprintf(error, maxlen, "Failed to detour CSteam3Server__OnValidateAuthTicketResponse.\n"); return false; } g_Detour_CSteam3Server__OnValidateAuthTicketResponse->EnableDetour(); g_pConnectForward = g_pForwards->CreateForward("OnClientPreConnectEx", ET_LowEvent, 5, NULL, Param_String, Param_String, Param_String, Param_String, Param_String); return true; } bool Connect::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) { GET_V_IFACE_CURRENT(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); ConVar_Register(0, this); return true; } void Connect::SDK_OnUnload() { g_pForwards->ReleaseForward(g_pConnectForward); gameconfs->CloseGameConfigFile(g_pGameConf); } bool Connect::SDK_OnMetamodUnload(char *error, size_t maxlen) { if (detourCBaseServer__ConnectClient) { detourCBaseServer__ConnectClient->DisableDetour(); delete detourCBaseServer__ConnectClient; } if (g_Detour_CSteam3Server__OnValidateAuthTicketResponse) { g_Detour_CSteam3Server__OnValidateAuthTicketResponse->DisableDetour(); delete g_Detour_CSteam3Server__OnValidateAuthTicketResponse; } return true; } bool Connect::RegisterConCommandBase(ConCommandBase *pCommand) { META_REGCVAR(pCommand); return true; } cell_t SteamClientAuthenticated(IPluginContext *pContext, const cell_t *params) { char *pSteamID; pContext->LocalToString(params[1], &pSteamID); ConnectClientStorage Storage; if(g_ConnectClientStorage.retrieve(pSteamID, &Storage)) { if (g_SvLogging.GetInt()) g_pSM->LogMessage(myself, "%s SteamClientAuthenticated: %d", pSteamID, Storage.SteamLegal); return Storage.SteamLegal; } if (g_SvLogging.GetInt()) g_pSM->LogMessage(myself, "%s SteamClientAuthenticated: FALSE!", pSteamID); return false; } cell_t ClientPreConnectEx(IPluginContext *pContext, const cell_t *params) { char *pSteamID; pContext->LocalToString(params[1], &pSteamID); int retVal = params[2]; char *rejectReason; pContext->LocalToString(params[3], &rejectReason); ConnectClientStorage Storage; if(!g_ConnectClientStorage.retrieve(pSteamID, &Storage)) { return 1; } if(retVal == 0) { g_pSM->LogMessage(myself, "inside ClientPreConnectEx return 0. steamID: %s", pSteamID); g_pRejectConnectionFunc((CBaseServer*)Storage.pThis, Storage.address, Storage.iClientChallenge, rejectReason); return 0; } auto *pClient = DETOUR_MEMBER_MCALL_ORIGINAL(CBaseServer__ConnectClient, Storage.pThis)(Storage.address, Storage.nProtocol, Storage.iChallenge, Storage.iClientChallenge, Storage.nAuthProtocol, Storage.pchName, Storage.pchPassword, Storage.pCookie, Storage.cbCookie); if(!pClient) { return 1; } bool force = g_SvNoSteam.GetInt() || g_SvForceSteam.GetInt(); if(Storage.SteamAuthFailed && force && !Storage.GotValidateAuthTicketResponse) { if (g_SvLogging.GetInt()) g_pSM->LogMessage(myself, "%s Force ValidateAuthTicketResponse", pSteamID); Storage.ValidateAuthTicketResponse.m_SteamID = CSteamID(Storage.ullSteamID); Storage.ValidateAuthTicketResponse.m_eAuthSessionResponse = k_EAuthSessionResponseOK; Storage.ValidateAuthTicketResponse.m_OwnerSteamID = Storage.ValidateAuthTicketResponse.m_SteamID; Storage.GotValidateAuthTicketResponse = true; } // Make sure this is always called in order to verify the client on the server if(Storage.GotValidateAuthTicketResponse) { if (g_SvLogging.GetInt()) g_pSM->LogMessage(myself, "%s Replay ValidateAuthTicketResponse", pSteamID); DETOUR_MEMBER_MCALL_ORIGINAL(CSteam3Server__OnValidateAuthTicketResponse, g_pSteam3Server)(&Storage.ValidateAuthTicketResponse); } return 0; } const sp_nativeinfo_t MyNatives[] = { { "ClientPreConnectEx", ClientPreConnectEx }, { "SteamClientAuthenticated", SteamClientAuthenticated }, { NULL, NULL } }; void Connect::SDK_OnAllLoaded() { sharesys->AddNatives(myself, MyNatives); }