Compare commits

...
Author SHA1 Message Date
jenz 420a56c8c4 adding extra logging messages as a delay mechanism so nosteamers netaddress have enough time to be ready 2026-08-28 11:07:43 +01:00
jenz 5e262c573a copy pasting the password from the forward back to the connect extension so that the webclient can have it be used 2026-07-24 10:01:37 +01:00
jenz df4b1b0aa7 did not need this any more 2026-07-23 15:00:35 +01:00
jenz dfc25da57a moved webclient name generation over to the connect extension. fixed pending steam ID. name is tracked with cvar value. name also sets the steamID 2026-07-23 14:58:45 +01:00
jenz 4c7ec14d52 finally removed the logging and removed the duplicate case from begin auth session hook. probably fine 2026-06-25 14:32:41 +01:00
jenz 0aab8e0bd6 adding support for switching steamID for the nosteam webclients 2026-06-05 23:54:25 +01:00
jenz 86c714fb1b updated gamedata and updated extension to kick dead left over sessions that would be caused by being blocked by ISP on first connect to to server and then on second connect using a VPN instead. also changes how nosteamers attempting to spoof are handled so that a nosteamer could change IP address on a reconnect and still be allowed in 2026-03-19 15:38:02 +00:00
jenz 3a126db081 re-applying beginauthsession and endauthsession 2026-02-16 14:14:42 +00:00
jenz 0b5306f689 i am still keeping the log messages enabled for now. this extra begin auth session check should still work for nosteamers while being able to re-authenticate steam players with steam 2026-02-15 18:37:52 +00:00
jenz 00c4ee1521 to hell with it, committing the log messages 2026-01-27 23:27:50 +00:00
jenz aa115c645e adding a few infos in readme 2026-01-26 13:42:57 +00:00
jenz 723d6e07b9 actual changes applied 2026-01-26 07:54:21 +00:00
9 changed files with 927 additions and 349 deletions
+1
View File
@@ -3,3 +3,4 @@ Containerfile
.venv
safetyhook
.env
secret.h
+11
View File
@@ -31,3 +31,14 @@ Note that this function is called before the client has a client index on the se
# [Builds](https://builds.limetech.io/?project=connect)
# helpful commands for building on the unloze machine
## the environment
export PATH=":/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11/bin:/home/gameservers/.local/bin":/sbin:/bin:
export SOURCEMOD=/home/gameservers/automate/sourcemod_1.13.0.7207
export MMSOURCE=/home/gameservers/automate/mmsource-1.13
## building
rm -r -f build; mkdir build; cd build; python ../configure.py --sdks css --targets=x86 --enable-optimize; ambuild; cd ..
+1 -1
View File
@@ -40,7 +40,7 @@ for cxx_task in Extension.extensions:
pdblog.write(cxx_task.debug.path + '\n')
pdblog.close()
CopyFiles('connect.games.txt', folders['addons/sourcemod/gamedata'])
CopyFiles('connect2.games.txt', folders['addons/sourcemod/gamedata'])
CopyFiles('connect.inc', folders['addons/sourcemod/scripting/include'])
CopyFiles('connect.sp', folders['addons/sourcemod/scripting'])
+10
View File
@@ -4,7 +4,17 @@
#define _connect_included
enum EConnect
{
k_OnClientPreConnectEx_Reject = 0,
k_OnClientPreConnectEx_Accept = 1,
k_OnClientPreConnectEx_Async = -1
};
forward bool OnClientPreConnectEx(const char[] name, char password[255], const char[] ip, const char[] steamID, char rejectReason[255]);
native bool ClientPreConnectEx(const char[] sSteam32ID, EConnect RetVal, char sRejectReason[255]);
native bool SteamClientAuthenticated(const char[] sSteam32ID);
public Extension __ext_Connect =
{
+9 -11
View File
@@ -64,23 +64,21 @@
"CBaseServer__CheckMasterServerRequestRestart"
{
// "%cMasterRequestRestart"
"library" "engine"
"windows" "\x55\x8B\xEC\x83\xEC\x1C\x53\x57\x33\xD2"
"windows64" "\x4C\x8B\xDC\x49\x89\x5B\x2A\x49\x89\x6B\x2A\x56\x57\x41\x54\x41\x56\x41\x57\x48\x83\xEC\x60\x48\x8B\x05\x2A\x2A\x2A\x2A\x48\x8D\x1D"
}
}
}
"tf"
{
"Signatures"
{
"CBaseServer__CheckMasterServerRequestRestart"
"CSteam3Server__OnValidateAuthTicketResponse"
{
"library" "engine"
// "%cMasterRequestRestart"
"windows" "\x55\x8B\xEC\x83\xEC\x18\x53\x57\x33\xD2\x8B\xF9\x8B\x0D\x2A\x2A\x2A\x2A\x89\x55\xE8\x89\x55\xEC\x89\x55\xF4\x8B\x41\x08"
"windows64" "\x4C\x8B\xDC\x49\x89\x5B\x10\x49\x89\x6B\x18\x56\x57\x41\x54\x41\x56\x41\x57\x48\x83\xEC\x60\x48\x8B\x05\x2A\x83\x20\x00"
"linux" "@_ZN13CSteam3Server28OnValidateAuthTicketResponseEP28ValidateAuthTicketResponse_t"
}
"CBaseClient__SetSteamID"
{
"library" "engine"
"linux" "@_ZN11CBaseClient10SetSteamIDERK8CSteamID"
}
}
}
+611 -67
View File
@@ -19,8 +19,13 @@
#include "extension.h"
#include "CDetour/detours.h"
#include "sm_namehashset.h"
#include "steam/steamclientpublic.h"
#include "steam/isteamclient.h"
#include <iclient.h>
#include <ISDKTools.h>
#include <iserver.h>
#include "secret.h"
#include <string>
Connect g_connect;
@@ -28,9 +33,19 @@ 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.");
ConVar g_SvSetSteamIDIPS("sv_set_steam_id_ips", "default", FCVAR_NOTIFY, "IP's allowed for webclient nosteamers");
ConVar g_SvWebclientName("sv_webclient_name", "", FCVAR_NOTIFY, "Generated name for the currently connecting webclient player.");
IGameConfig *g_pGameConf = NULL;
IForward *g_pConnectForward = NULL;
IServer *iserver = NULL;
ISDKTools *g_pSDKTools = NULL;
class IClient;
class CBaseServer;
@@ -42,34 +57,14 @@ typedef enum EAuthProtocol
k_EAuthProtocolSteam = 3,
} EAuthProtocol;
#if SOURCE_ENGINE == SE_LEFT4DEAD || SOURCE_ENGINE == SE_LEFT4DEAD2
typedef enum EBeginAuthSessionResult
{
/*
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
} EBeginAuthSessionResult;
#endif
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
{
@@ -157,10 +152,9 @@ public:
};
CDetour* detourCBaseServer__ConnectClient = nullptr;
bool g_bSuppressBeginAuthSession = false;
CSteamID g_lastClientSteamID;
const void* g_lastAuthTicket;
int g_lastcbAuthTicket;
CDetour *g_Detour_CSteam3Server__OnValidateAuthTicketResponse = NULL;
CDetour *g_Detour_CBaseClient__SetSteamID = NULL;
char passwordBuffer[255];
@@ -177,27 +171,254 @@ CSteam3Server *Steam3Server()
return g_pSteam3ServerFunc();
}
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;
bool ManualRetry;
bool Webclient;
bool SuppressBeginAuthSession;
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;
this->ManualRetry = false;
this->Webclient = false;
this->SuppressBeginAuthSession = false;
// Calculate and store the ticket pointer
this->pvTicket = (void *)((intptr_t)this->pCookie + sizeof(uint64));
this->cbTicket = cbCookie - sizeof(uint64);
}
};
StringHashMap<ConnectClientStorage> g_ConnectClientStorage;
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)
ConnectClientStorage Storage;
g_ConnectClientStorage.retrieve(steamID.Render(), &Storage);
if (!Storage.SuppressBeginAuthSession)
{
//g_pSM->LogMessage(myself, "inside Hook_ BeginAuthSession 1. %s", steamID.Render());
RETURN_META_VALUE(MRES_IGNORED, k_EBeginAuthSessionResultOK);
}
g_bSuppressBeginAuthSession = false;
Storage.SuppressBeginAuthSession = false;
g_ConnectClientStorage.replace(steamID.Render(), Storage);
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, "You alright ;)");
//g_pSM->LogMessage(myself, "inside Hook_ BeginAuthSession 2. %s", steamID.Render());
RETURN_META_VALUE(MRES_SUPERCEDE, k_EBeginAuthSessionResultOK);
}
//ai generated function
uint64 GenerateSteamIDFromName(const char *pchName)
{
uint64 hash = 14695981039346656037ULL;
for (const char *p = pchName; *p; p++)
{
hash ^= (uint64)(unsigned char)*p;
hash *= 1099511628211ULL;
}
RETURN_META_VALUE(MRES_IGNORED, k_EBeginAuthSessionResultDuplicateRequest);
uint32 W = hash & 1;
// Range within existing Steam accounts: 1 to 700,000,000
uint32 Z = 1 + (uint32)((hash >> 1) % 699999999);
uint64 steamID64 = 0x0110000100000000ULL | ((uint64)Z << 1) | W;
return steamID64;
}
//AI generated function
static uint32_t HashString(const char *str)
{
uint32_t hash = 5381;
while (*str)
{
hash = ((hash << 5) + hash) + (unsigned char)*str;
hash = hash & 0x7fffffff;
str++;
}
return hash;
}
//AI generated function
void GenerateNickNameFromPassword(const char *pchPassword, const char *salt, char *outName, size_t outSize)
{
static const char *adjectives[] = {
"Fast","Slow","Red","Blue","Green","Dark","Bright","Wild",
"Cool","Hot","Big","Small","Sharp","Brave","Swift","Bold"
};
static const char *nouns[] = {
"Wolf","Eagle","Tiger","Bear","Fox","Lion","Hawk","Shark",
"Snake","Raven","Storm","Rock","Fire","Ice","Wind","Steel"
};
char salted[256];
V_snprintf(salted, sizeof(salted), "%s%s", pchPassword, salt);
uint32_t h1 = HashString(salted);
char salted1[256];
V_snprintf(salted1, sizeof(salted1), "%s1", salted);
uint32_t h2 = HashString(salted1);
char salted2[256];
V_snprintf(salted2, sizeof(salted2), "%s2", salted);
uint32_t h3 = HashString(salted2);
V_snprintf(outName, outSize, "%s %s %u",
adjectives[h1 % 16],
nouns[h2 % 16],
h3 % 9999
);
}
DETOUR_DECL_MEMBER1(CBaseClient__SetSteamID, void, const CSteamID &, steamID)
{
char aSteamID[32];
V_strncpy(aSteamID, steamID.Render(), 32);
ConnectClientStorage storage;
if (g_ConnectClientStorage.retrieve(aSteamID, &storage))
{
if (storage.SteamAuthFailed)
{
char IpsAllowed[255];
V_strncpy(IpsAllowed, g_SvSetSteamIDIPS.GetString(), 255);
char ipString[30];
V_snprintf(ipString, sizeof(ipString), "%u.%u.%u.%u", storage.address.ip[0], storage.address.ip[1], storage.address.ip[2], storage.address.ip[3]);
if (g_SvLogging.GetInt())
smutils->LogMessage(myself, "inside SetSteamID steamAuthFailed. IpsAllowed: %s. ipString: %s", IpsAllowed, ipString);
//connects from the IP's designated for the webclients.
if (strcmp(IpsAllowed, ipString) == 0)
{
//changes the storages pchName
GenerateNickNameFromPassword(storage.pchPassword, NAME_SALT, storage.pchName, sizeof(storage.pchName));
uint64 steamID64 = GenerateSteamIDFromName(storage.pchName);
CSteamID newSteamID = CSteamID(steamID64);
//smutils->LogMessage(myself, "Generated SteamID64: %llu for name: %s", steamID64, storage.pchName);
//smutils->LogMessage(myself, "SteamID render: %s. password. %s", newSteamID.Render(), storage.pchPassword);
storage.ullSteamID = steamID64;
storage.Webclient = true;
g_ConnectClientStorage.replace(aSteamID, storage);
DETOUR_MEMBER_CALL(CBaseClient__SetSteamID)(newSteamID);
return;
}
}
}
DETOUR_MEMBER_CALL(CBaseClient__SetSteamID)(steamID);
}
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);
}
//2026 march 17th: resolve (STEAM UserID [xxxxxx] is already in use on this server)
//we handle it here in the detour before CheckForDuplicateSteamID() is called.
//confirm that this client actually did a manual retry
if (Storage.ManualRetry)
{
if (!Storage.pClient || !Storage.pClient->IsConnected())
{
return DETOUR_MEMBER_CALL(CSteam3Server__OnValidateAuthTicketResponse)(pResponse);
}
USERID_t clientID = Storage.pClient->GetNetworkID();
//two scenarios related to nosteam trying to spoof steam players.
//1: nosteamer trying to spoof active steam player on the server. should already be handled by MEMBER9 function comparing IP address and rejecting if nosteam
//2: nosteamer trying to spoof steam player not on the server right now. handled by PlayerManager_Connection kicking nosteamers with message "Trying to join with a legitimate steamid while not authenticated with steam."
for (int i = 0; i < iserver->GetClientCount(); i++)
{
IClient* pSlot = iserver->GetClient(i);
if (pSlot && pSlot->IsConnected() && pSlot != Storage.pClient)
{
USERID_t slotID = pSlot->GetNetworkID();
if (slotID.idtype == IDTYPE_STEAM && clientID.idtype == IDTYPE_STEAM)
{
if (slotID.steamid == clientID.steamid)
{
//g_pSM->LogMessage(myself, "Disconnecting idle left over session for steamID: %s", aSteamID);
pSlot->Disconnect("Disconnecting dead left over session.");
break;
}
}
}
}
}
}
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)
@@ -206,6 +427,35 @@ DETOUR_DECL_MEMBER9(CBaseServer__ConnectClient, IClient*, netadr_t&, address, in
return DETOUR_MEMBER_CALL(CBaseServer__ConnectClient)(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie);
}
//g_pSM->LogMessage(myself, "entered MEMBER9");
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);
void** vtable = *((void***)g_pSteam3Server->m_pSteamGameServer);
g_pBeginAuthSession.SetAddress(vtable[g_nBeginAuthSessionOffset]);
int endAuthOffset = 0;
if (g_pGameConf->GetOffset("ISteamGameServer__EndAuthSession", &endAuthOffset))
{
g_pEndAuthSession.SetAddress(vtable[endAuthOffset]);
}
g_pLastHookedSteamGameServer = g_pSteam3Server->m_pSteamGameServer;
}
if (pCookie == NULL || (size_t)cbCookie < sizeof(uint64))
{
g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "#GameUI_ServerRejectInvalidSteamCertLen");
@@ -216,62 +466,244 @@ DETOUR_DECL_MEMBER9(CBaseServer__ConnectClient, IClient*, netadr_t&, address, in
char ipString[30];
V_snprintf(ipString, sizeof(ipString), "%u.%u.%u.%u", address.ip[0], address.ip[1], address.ip[2], address.ip[3]);
char IpsAllowed[255];
V_strncpy(IpsAllowed, g_SvSetSteamIDIPS.GetString(), 255);
//you think i am kidding but without this extra log message will ipString end up being empty for nosteamers.
//probably because of netadr_t& address not being processed yet as is. So these logging messages add enough delay.
smutils->LogMessage(myself, "IpsAllowed: %s. ipString: %s", IpsAllowed, ipString);
smutils->LogMessage(myself, "IpsAllowed: %s. ipString: %s", IpsAllowed, ipString);
smutils->LogMessage(myself, "IpsAllowed: %s. ipString: %s", IpsAllowed, ipString);
V_strncpy(passwordBuffer, pchPassword, 255);
uint64 ullSteamID = *(uint64 *)pCookie;
void *pvTicket = (void *)((intptr_t)pCookie + sizeof(uint64));
int cbTicket = cbCookie - sizeof(uint64);
CSteamID ClientSteamID = CSteamID(ullSteamID);
char aSteamID[32];
V_strncpy(aSteamID, ClientSteamID.Render(), 32);
g_lastClientSteamID = CSteamID(ullSteamID);
g_lastcbAuthTicket = cbTicket;
g_lastAuthTicket = pvTicket;
// 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;
EBeginAuthSessionResult result;
ConnectClientStorage Storage(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie);
if (g_ConnectClientStorage.retrieve(aSteamID, &Storage))
{
//g_pSM->LogMessage(myself, "entered has clientstorage. %s", aSteamID);
ExistingSteamid = true;
g_ConnectClientStorage.remove(aSteamID);
// 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)
{
//g_pSM->LogMessage(myself, "entered async retry. %s", aSteamID);
AsyncWaiting = true;
//reject async nosteamers or async steam player who is not authenticated
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.
result = g_pBeginAuthSession(steamGameServer, Storage.pvTicket, Storage.cbTicket, ClientSteamID);
}
else
{
//client did a manual retry,
//create new auth storage with fresh ticket and challenge
Storage = ConnectClientStorage(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie);
//g_pSM->LogMessage(myself, "entered manual retry. %s", aSteamID);
result = g_pBeginAuthSession(steamGameServer, Storage.pvTicket, Storage.cbTicket, ClientSteamID);
}
}
else
{
result = g_pBeginAuthSession(steamGameServer, Storage.pvTicket, Storage.cbTicket, ClientSteamID);
//g_pSM->LogMessage(myself, "without client storage after beginauth session. %s", aSteamID);
}
//g_pSM->LogMessage(myself, "about to do endAuthSession. %s", aSteamID);
g_pEndAuthSession(steamGameServer, ClientSteamID); //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. %s", result, aSteamID);
// Validate steam ticket
EBeginAuthSessionResult result = g_pBeginAuthSession(steamGameServer, pvTicket, cbTicket, g_lastClientSteamID);
if (result != k_EBeginAuthSessionResultOK)
{
if (!NoSteam)
{
g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "#GameUI_ServerRejectSteam");
return NULL;
}
Storage.SteamAuthFailed = SteamAuthFailed = true;
}
if(ExistingSteamid && !AsyncWaiting)
{
Storage.ManualRetry = true;
// Another player trying to spoof a Steam ID or game crashed?
if(memcmp(address.ip, Storage.address.ip, sizeof(address.ip)) != 0)
{
// Is a nosteam player
if(SteamAuthFailed)
{
//if the nosteam player is trying to spoof a steam player reject the nosteamer.
if (!Storage.SteamAuthFailed)
{
//g_pSM->LogMessage(myself, "about to do rejectconnection 1. %s", aSteamID);
g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "Steam ID already in use.");
return NULL;
}
//a nosteamer connecting the first time could be blocked by their ISP and then on the second connect be using a VPN
//so if the original nosteamer is not connected and ingame we allow the nosteamer to take over the spot
//a nosteamer spoofing another nosteamer that is not ingame is fine too.
if (Storage.pClient && Storage.pClient->IsConnected() && Storage.pClient->IsActive())
{
//g_pSM->LogMessage(myself, "about to do rejectconnection 2. %s", aSteamID);
g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "Steam ID already in use.");
return NULL;
}
}
// Kick existing player
if(Storage.pClient)
{
//g_pSM->LogMessage(myself, "about to do disconnect. %s", aSteamID);
Storage.pClient->Disconnect("Same Steam ID connected.");
}
else
{
//g_pSM->LogMessage(myself, "about to do rejectconnection 3. %s", aSteamID);
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->PushString(ClientSteamID.Render());
g_pConnectForward->PushStringEx(rejectReason, 255, SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
cell_t retVal = 1;
g_pConnectForward->Execute(&retVal);
}
pchPassword = passwordBuffer;
//overwrite it to the storage so the webclient can use its value assigned by nosteam celt plugin inside the SetSteamID detour.
V_strncpy(Storage.pchPassword, passwordBuffer, sizeof(Storage.pchPassword));
// k_OnClientPreConnectEx_Reject
if (retVal == 0)
{
g_pEndAuthSession(steamGameServer, g_lastClientSteamID);
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, ClientSteamID);
}
//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;
}
pchPassword = passwordBuffer;
Storage.pThis = this;
Storage.ullSteamID = ullSteamID;
Storage.SteamAuthFailed = SteamAuthFailed;
g_bSuppressBeginAuthSession = true;
auto client = DETOUR_MEMBER_CALL(CBaseServer__ConnectClient)(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie);
g_bSuppressBeginAuthSession = false;
if (client == nullptr)
//puts the storage in the StringHashMap
if(!g_ConnectClientStorage.replace(aSteamID, Storage))
{
g_pEndAuthSession(steamGameServer, g_lastClientSteamID);
g_pRejectConnectionFunc((CBaseServer*)this, address, iClientChallenge, "Internal error.");
return NULL;
}
// k_OnClientPreConnectEx_Async
if (retVal == -1)
{
return NULL;
}
// k_OnClientPreConnectEx_Accept
//g_pSM->LogMessage(myself, "about to do DETOUR_MEMBER_CALL. %s", aSteamID);
Storage.SuppressBeginAuthSession = true;
g_ConnectClientStorage.replace(aSteamID, Storage);
auto client = DETOUR_MEMBER_CALL(CBaseServer__ConnectClient)(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie);
//g_pSM->LogMessage(myself, "finished DETOUR_MEMBER_CALL. %s", aSteamID);
//doing another retrieve in case of SetSteamID for webclient.
ConnectClientStorage storage_setSteamID;
g_ConnectClientStorage.retrieve(aSteamID, &storage_setSteamID);
if (storage_setSteamID.Webclient)
{
storage_setSteamID.SuppressBeginAuthSession = false;
CSteamID newSteamID = CSteamID(storage_setSteamID.ullSteamID);
//smutils->LogMessage(myself, "Generated name after webclient check: %s", storage_setSteamID.pchName);
g_SvWebclientName.SetValue(storage_setSteamID.pchName);
storage_setSteamID.pClient = client;
//smutils->LogMessage(myself, "SteamID render after webclient check: %s", newSteamID.Render());
g_ConnectClientStorage.replace(newSteamID.Render(), storage_setSteamID);
g_ConnectClientStorage.remove(aSteamID);
if (client) //webclient is always nosteamer.
{
ValidateAuthTicketResponse_t Response;
Response.m_SteamID = newSteamID;
Response.m_eAuthSessionResponse = k_EAuthSessionResponseAuthTicketInvalid; //nosteamer monkeys
Response.m_OwnerSteamID = Response.m_SteamID;
//g_pSM->LogMessage(myself, "about to do OnValidateAuthTicketResponse webclient. %s", newSteamID.Render());
DETOUR_MEMBER_MCALL_CALLBACK(CSteam3Server__OnValidateAuthTicketResponse, g_pSteam3Server)(&Response);
}
}
else
{
Storage.SuppressBeginAuthSession = false;
Storage.pClient = client;
g_ConnectClientStorage.replace(aSteamID, Storage);
if (client && SteamAuthFailed)
{
ValidateAuthTicketResponse_t Response;
Response.m_SteamID = ClientSteamID;
Response.m_eAuthSessionResponse = k_EAuthSessionResponseAuthTicketInvalid; //nosteamer monkeys
Response.m_OwnerSteamID = Response.m_SteamID;
//g_pSM->LogMessage(myself, "about to do OnValidateAuthTicketResponse. %s", aSteamID);
DETOUR_MEMBER_MCALL_CALLBACK(CSteam3Server__OnValidateAuthTicketResponse, g_pSteam3Server)(&Response);
}
}
//g_pSM->LogMessage(myself, "MEMBER 9 end. %s", aSteamID);
return client;
}
bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late)
{
char conf_error[255] = "";
if (!gameconfs->LoadGameConfigFile("connect.games", &g_pGameConf, conf_error, sizeof(conf_error)))
if (!gameconfs->LoadGameConfigFile("connect2.games", &g_pGameConf, conf_error, sizeof(conf_error)))
{
if (conf_error[0])
{
snprintf(error, maxlen, "Could not read connect.games.txt: %s\n", conf_error);
snprintf(error, maxlen, "Could not read connect2.games.txt: %s\n", conf_error);
}
return false;
}
@@ -324,30 +756,24 @@ bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late)
return false;
}
/*
META_CONPRINTF("ISteamGameServer: %p\n", g_pSteam3Server->m_pSteamGameServer);
META_CONPRINTF("ISteamUtils: %p\n", g_pSteam3Server->m_pSteamGameServerUtils);
META_CONPRINTF("ISteamNetworking: %p\n", g_pSteam3Server->m_pSteamGameServerNetworking);
META_CONPRINTF("ISteamGameServerStats: %p\n", g_pSteam3Server->m_pSteamGameServerStats);
*/
void** vtable = *((void***)g_pSteam3Server->m_pSteamGameServer);
int offset = 0;
if (!g_pGameConf->GetOffset("ISteamGameServer__BeginAuthSession", &offset) || offset == 0)
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[offset]);
g_pBeginAuthSession.SetAddress(vtable[g_nBeginAuthSessionOffset]);
SH_MANUALHOOK_RECONFIGURE(MHook_BeginAuthSession, offset, 0, 0);
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;
offset = 0;
int offset = 0;
if (!g_pGameConf->GetOffset("ISteamGameServer__EndAuthSession", &offset) || offset == 0)
{
snprintf(error, maxlen, "Failed to find ISteamGameServer__EndAuthSession offset.\n");
@@ -365,6 +791,22 @@ bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late)
}
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_Detour_CBaseClient__SetSteamID = DETOUR_CREATE_MEMBER(CBaseClient__SetSteamID, "CBaseClient__SetSteamID");
if(!g_Detour_CBaseClient__SetSteamID)
{
snprintf(error, maxlen, "Failed to detour CBaseClient__SetSteamID.\n");
return false;
}
g_Detour_CBaseClient__SetSteamID->EnableDetour();
g_pConnectForward = g_pForwards->CreateForward("OnClientPreConnectEx", ET_LowEvent, 5, NULL, Param_String, Param_String, Param_String, Param_String, Param_String);
return true;
@@ -373,7 +815,6 @@ bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late)
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;
@@ -393,6 +834,17 @@ bool Connect::SDK_OnMetamodUnload(char *error, size_t maxlen)
detourCBaseServer__ConnectClient->DisableDetour();
delete detourCBaseServer__ConnectClient;
}
if (g_Detour_CSteam3Server__OnValidateAuthTicketResponse)
{
g_Detour_CSteam3Server__OnValidateAuthTicketResponse->DisableDetour();
delete g_Detour_CSteam3Server__OnValidateAuthTicketResponse;
}
if (g_Detour_CBaseClient__SetSteamID)
{
g_Detour_CBaseClient__SetSteamID->DisableDetour();
delete g_Detour_CBaseClient__SetSteamID;
}
return true;
}
@@ -403,3 +855,95 @@ bool Connect::RegisterConCommandBase(ConCommandBase *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()
{
SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools);
iserver = g_pSDKTools->GetIServer();
if (!iserver) {
smutils->LogError(myself, "Failed to get IServer interface from SDKTools!");
return;
}
sharesys->AddNatives(myself, MyNatives);
}
+12
View File
@@ -55,6 +55,13 @@ public:
*/
//virtual void SDK_OnPauseChange(bool paused);
/**
* @brief This is called once all known extensions have been loaded.
* Note: It is is a good idea to add natives here, if any are provided.
*/
virtual void SDK_OnAllLoaded();
/**
* @brief this is called when Core wants to know if your extension is working.
*
@@ -110,6 +117,11 @@ public: \
ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type) = NULL; \
ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name)
#define DETOUR_MEMBER_MCALL_CALLBACK(name, classptr) \
((name##Class *)classptr->*(&name##Class::name))
#define DETOUR_MEMBER_MCALL_ORIGINAL(name, classptr) \
((name##Class *)classptr->*(name##Class::name##_Actual))
struct mfpDetails {
void *addr;
intptr_t adjustor;
+2
View File
@@ -0,0 +1,2 @@
#pragma once
#define NAME_SALT "your-salt-here"
+2 -2
View File
@@ -40,8 +40,8 @@
#define SMEXT_CONF_NAME "Connect"
#define SMEXT_CONF_DESCRIPTION "Forward for early connection"
#define SMEXT_CONF_VERSION SM_FULL_VERSION
#define SMEXT_CONF_AUTHOR "Asher \"asherkin\" Baker"
#define SMEXT_CONF_URL "http://limetech.org/"
#define SMEXT_CONF_AUTHOR "Asher \"asherkin\" Baker + jenz + zacade"
#define SMEXT_CONF_URL "https://git.unloze.com/UNLOZE/sm-ext-connect"
#define SMEXT_CONF_LOGTAG "CONNECT"
#define SMEXT_CONF_LICENSE "GPL"
#define SMEXT_CONF_DATESTRING __DATE__