mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
engine: client: implement steam broker client side
This commit is contained in:
@@ -1056,13 +1056,6 @@ static void CL_WriteSteamTicket( sizebuf_t *send )
|
||||
return;
|
||||
}
|
||||
|
||||
//if( !Q_strcmp( cl_ticket_generator.string, "steam" )
|
||||
//{
|
||||
// i = SteamBroker_InitiateGameConnection( buf, sizeof( buf ));
|
||||
// MSG_WriteBytes( send, buf, i );
|
||||
// return;
|
||||
//}
|
||||
|
||||
s = ID_GetMD5();
|
||||
CRC32_Init( &crc );
|
||||
CRC32_ProcessBuffer( &crc, s, Q_strlen( s ));
|
||||
@@ -1075,6 +1068,40 @@ static void CL_WriteSteamTicket( sizebuf_t *send )
|
||||
*(uint32_t*)(cls.steamid + 4) = LittleLong( ((uint32_t*)buf)[5] );
|
||||
}
|
||||
|
||||
void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen )
|
||||
{
|
||||
const char *name;
|
||||
sizebuf_t send;
|
||||
byte send_buf[2048];
|
||||
char protinfo[MAX_INFO_STRING];
|
||||
|
||||
protinfo[0] = 0;
|
||||
|
||||
Info_SetValueForKey( protinfo, "prot", "3", sizeof( protinfo )); // steam auth type
|
||||
Info_SetValueForKeyf( protinfo, "unique", sizeof( protinfo ), "%i", 0xffffffff );
|
||||
Info_SetValueForKey( protinfo, "raw", "steam", sizeof( protinfo ));
|
||||
CL_GetCDKey( protinfo, sizeof( protinfo ));
|
||||
name = Info_ValueForKey( cls.userinfo, "name" );
|
||||
if( cl_advertise_engine_in_name.value && Q_strnicmp( name, "[Xash3D]", 8 ))
|
||||
Info_SetValueForKeyf( cls.userinfo, "name", sizeof( cls.userinfo ), "[Xash3D]%s", name );
|
||||
|
||||
MSG_Init( &send, "GoldSrcConnect", send_buf, sizeof( send_buf ));
|
||||
MSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET );
|
||||
MSG_WriteStringf( &send, C2S_CONNECT" %i %i \"%s\" \"%s\"\n",
|
||||
PROTOCOL_GOLDSRC_VERSION, challenge, protinfo, cls.userinfo );
|
||||
MSG_SeekToBit( &send, -8, SEEK_CUR ); // rewrite null terminator
|
||||
if( ticket == NULL )
|
||||
CL_WriteSteamTicket( &send );
|
||||
else
|
||||
MSG_WriteBytes( &send, ticket, ticketlen );
|
||||
|
||||
if( MSG_CheckOverflow( &send ))
|
||||
Con_Printf( S_ERROR "%s: %s overflow!\n", __func__, MSG_GetName( &send ) );
|
||||
|
||||
NET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr );
|
||||
Con_Printf( "Trying to connect with GoldSrc 48 protocol\n" );
|
||||
}
|
||||
|
||||
/*
|
||||
=======================
|
||||
CL_SendConnectPacket
|
||||
@@ -1117,33 +1144,24 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
|
||||
Info_SetValueForKey( protinfo, "a", Q_buildarch(), sizeof( protinfo ) );
|
||||
}
|
||||
|
||||
cls.broker_wait = false;
|
||||
|
||||
if( proto == PROTO_GOLDSRC )
|
||||
{
|
||||
const char *name;
|
||||
sizebuf_t send;
|
||||
byte send_buf[2048];
|
||||
// if the cl_ticket_generator is set to "steam" we need to get ticket
|
||||
// from steam broker, which is asynchronous process by nature
|
||||
if( !Q_stricmp( cl_ticket_generator.string, "steam" ))
|
||||
{
|
||||
if( SteamBroker_InitiateGameConnection( adr, challenge ))
|
||||
{
|
||||
// we are waiting for the broker response...
|
||||
cls.broker_wait = true;
|
||||
cls.timestart = Platform_DoubleTime();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Info_SetValueForKey( protinfo, "prot", "3", sizeof( protinfo )); // steam auth type
|
||||
Info_SetValueForKeyf( protinfo, "unique", sizeof( protinfo ), "%i", 0xffffffff );
|
||||
Info_SetValueForKey( protinfo, "raw", "steam", sizeof( protinfo ));
|
||||
CL_GetCDKey( protinfo, sizeof( protinfo ));
|
||||
|
||||
name = Info_ValueForKey( cls.userinfo, "name" );
|
||||
if( cl_advertise_engine_in_name.value && Q_strnicmp( name, "[Xash3D]", 8 ))
|
||||
Info_SetValueForKeyf( cls.userinfo, "name", sizeof( cls.userinfo ), "[Xash3D]%s", name );
|
||||
|
||||
MSG_Init( &send, "GoldSrcConnect", send_buf, sizeof( send_buf ));
|
||||
MSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET );
|
||||
MSG_WriteStringf( &send, C2S_CONNECT" %i %i \"%s\" \"%s\"\n",
|
||||
PROTOCOL_GOLDSRC_VERSION, challenge, protinfo, cls.userinfo );
|
||||
MSG_SeekToBit( &send, -8, SEEK_CUR ); // rewrite null terminator
|
||||
CL_WriteSteamTicket( &send );
|
||||
|
||||
if( MSG_CheckOverflow( &send ))
|
||||
Con_Printf( S_ERROR "%s: %s overflow!\n", __func__, MSG_GetName( &send ) );
|
||||
|
||||
NET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr );
|
||||
Con_Printf( "Trying to connect with GoldSrc 48 protocol\n" );
|
||||
CL_SendGoldSrcConnectPacket( adr, challenge, NULL, 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1320,7 +1338,7 @@ static void CL_CheckForResend( void )
|
||||
else
|
||||
CL_SendBandwidthTest( adr, false );
|
||||
}
|
||||
else
|
||||
else if( !cls.broker_wait )
|
||||
{
|
||||
Con_Printf( "Connecting to %s... (retry #%i)\n", cls.servername, cls.connect_retry );
|
||||
CL_SendGetChallenge( adr );
|
||||
@@ -1686,6 +1704,7 @@ void CL_Disconnect( void )
|
||||
|
||||
// send a disconnect message to the server
|
||||
CL_SendDisconnectMessage( cls.legacymode );
|
||||
SteamBroker_TerminateGameConnection();
|
||||
CL_ClearState ();
|
||||
|
||||
S_StopBackgroundTrack ();
|
||||
@@ -2373,8 +2392,21 @@ static void CL_Challenge( const char *c, netadr_t from )
|
||||
|
||||
// try to autodetect protocol by challenge response
|
||||
if( !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE ))
|
||||
{
|
||||
cls.legacymode = PROTO_GOLDSRC;
|
||||
|
||||
cls.steam_auth = Q_atoi( Cmd_Argv( 2 )) == 3;
|
||||
|
||||
if( Cmd_Argc( ) == 5 && cls.steam_auth )
|
||||
{
|
||||
// arg 2 auth protocol, we only support steam
|
||||
// arg 3 if steam is server's steam id
|
||||
// arg 4 if steam is server's VAC status
|
||||
cls.server_steamid = strtoull( Cmd_Argv( 3 ), NULL, 10 );
|
||||
cls.vac2_secure = Q_atoi( Cmd_Argv( 4 ));
|
||||
}
|
||||
}
|
||||
|
||||
cls.bandwidth_test.challenge = Q_atoi( Cmd_Argv( 1 ));
|
||||
|
||||
if( cls.legacymode == PROTO_CURRENT && cl_test_bandwidth.value && !cls.bandwidth_test.passed )
|
||||
@@ -2537,6 +2569,10 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
|
||||
Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c );
|
||||
|
||||
// server connection
|
||||
if( !Q_strcmp( c, "sb_connect" ))
|
||||
{
|
||||
SteamBroker_HandlePacket( from, msg );
|
||||
}
|
||||
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
|
||||
{
|
||||
CL_ClientConnect( cls.legacymode, c, from );
|
||||
@@ -3637,6 +3673,7 @@ void CL_Init( void )
|
||||
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
|
||||
|
||||
ID_Init();
|
||||
SteamBroker_Init();
|
||||
|
||||
cls.build_num = 0;
|
||||
cls.initialized = true;
|
||||
@@ -3668,6 +3705,7 @@ void CL_Shutdown( void )
|
||||
Mobile_Shutdown ();
|
||||
SCR_Shutdown ();
|
||||
CL_UnloadProgs ();
|
||||
SteamBroker_Shutdown();
|
||||
cls.initialized = false;
|
||||
|
||||
// for client-side VGUI support we use other order
|
||||
@@ -3681,5 +3719,4 @@ void CL_Shutdown( void )
|
||||
R_Shutdown ();
|
||||
|
||||
Con_Shutdown ();
|
||||
|
||||
}
|
||||
|
||||
129
engine/client/cl_steam.c
Normal file
129
engine/client/cl_steam.c
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
cl_steam.c - steam(tm) broker implementation
|
||||
Copyright (C) 2026 Xash3D FWGS contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "common.h"
|
||||
#include "client.h"
|
||||
|
||||
// What is a broker?
|
||||
// From Wikipedia, the free encyclopedia:
|
||||
// "The broker pattern is an architecture pattern that involves the use of an
|
||||
// intermediary software entity, called a "broker", to facilitate communication
|
||||
// between two or more software components. The broker acts as a "middleman"
|
||||
// between the components, allowing them to communicate without being aware of
|
||||
// each other's existence.
|
||||
//
|
||||
// Due to proprietary nature of Steamworks SDK, it cannot be run on same amount
|
||||
// of platforms supported by Xash3D FWGS, neither we can link directly due to
|
||||
// GNU GPLv3 license. However, here comes the broker, by running it (in trusted
|
||||
// network, preferrably) on a machine that has Steam client installed, the
|
||||
// engine can communicate with it, acquiring needed information to log-in into
|
||||
// Steam protected multiplayer servers.
|
||||
static CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_ARCHIVE, "address of steam broker instance" );
|
||||
|
||||
static struct
|
||||
{
|
||||
netadr_t adr;
|
||||
|
||||
int challenge;
|
||||
netadr_t serveradr;
|
||||
} broker;
|
||||
|
||||
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge )
|
||||
{
|
||||
if( NET_NetadrType( &broker.adr ) == NA_UNDEFINED )
|
||||
{
|
||||
if( !NET_StringToAdr( cl_steam_broker_addr.string, &broker.adr ))
|
||||
{
|
||||
Con_Printf( "%s: NET_StringToAdr( %s ) failed\n", __func__, cl_steam_broker_addr.string );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// only ipv4 supported
|
||||
if( NET_NetadrType( &serveradr ) != NA_IP )
|
||||
return false;
|
||||
|
||||
broker.challenge = challenge;
|
||||
broker.serveradr = serveradr;
|
||||
|
||||
// sb_connect <ip:port> <server's steam id> <secure> <challenge>
|
||||
// the message calls
|
||||
char buf[512];
|
||||
int len = Q_snprintf( buf, sizeof( buf ), "sb_connect %s %"PRIu64" %s %d", NET_AdrToString( serveradr ), cls.server_steamid, cls.vac2_secure ? "true" : "false", broker.challenge );
|
||||
|
||||
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SteamBroker_TerminateGameConnection( void )
|
||||
{
|
||||
if( NET_NetadrType( &broker.adr ) == NA_UNDEFINED )
|
||||
return;
|
||||
|
||||
if( NET_NetadrType( &cls.serveradr ) != NA_IP )
|
||||
return;
|
||||
|
||||
if( cls.legacymode != PROTO_GOLDSRC )
|
||||
return;
|
||||
|
||||
// sb_terminate <ip:port> <challenge>quitйгше
|
||||
char buf[512];
|
||||
int len = Q_snprintf( buf, sizeof( buf ), "sb_terminate %s %d", NET_AdrToString( cls.serveradr ), broker.challenge );
|
||||
|
||||
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
|
||||
}
|
||||
|
||||
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg )
|
||||
{
|
||||
// message format
|
||||
// sb_connect\n<4 byte challenge><8 byte steamid><unsigned 4 byte len><len bytes ticket>
|
||||
int challenge;
|
||||
uint32_t len;
|
||||
uint8_t ticket[2048]; // 2048 bytes according to SDK docs
|
||||
|
||||
if( !NET_CompareAdr( from, broker.adr ))
|
||||
return;
|
||||
|
||||
challenge = MSG_ReadLong( msg );
|
||||
|
||||
if( broker.challenge != challenge ) // TODO: print error
|
||||
return;
|
||||
|
||||
MSG_ReadBytes( msg, cls.steamid, sizeof( cls.steamid ));
|
||||
|
||||
len = MSG_ReadDword( msg );
|
||||
if( len > sizeof( ticket )) // TODO: print error, proceed without ticket?
|
||||
return;
|
||||
|
||||
MSG_ReadBytes( msg, ticket, len );
|
||||
|
||||
Con_Printf( "%s: SteamID: %"PRIu64", ticket: [%d, %d, %d, %d...]\n", __func__, *(uint64_t *)cls.steamid, ticket[0], ticket[1], ticket[2], ticket[3] );
|
||||
|
||||
CL_SendGoldSrcConnectPacket( broker.serveradr, challenge, ticket, len );
|
||||
|
||||
cls.broker_wait = false;
|
||||
}
|
||||
|
||||
void SteamBroker_Init( void )
|
||||
{
|
||||
Cvar_RegisterVariable( &cl_steam_broker_addr );
|
||||
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
|
||||
}
|
||||
|
||||
void SteamBroker_Shutdown( void )
|
||||
{
|
||||
}
|
||||
@@ -649,10 +649,16 @@ typedef struct
|
||||
// server's build number (might be zero)
|
||||
int build_num;
|
||||
uint8_t steamid[8];
|
||||
uint64_t server_steamid;
|
||||
|
||||
// whether server allows cheats or not
|
||||
// set differently depending on protocol and extensions
|
||||
qboolean allow_cheats;
|
||||
|
||||
// if server declares steam auth method, we can use our broker software to get the ticket
|
||||
qboolean steam_auth;
|
||||
qboolean broker_wait;
|
||||
qboolean vac2_secure;
|
||||
} client_static_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -777,6 +783,7 @@ int CL_IsDevOverviewMode( void );
|
||||
void CL_SignonReply( connprotocol_t proto );
|
||||
void CL_ClearState( void );
|
||||
void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats );
|
||||
void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen );
|
||||
|
||||
//
|
||||
// cl_demo.c
|
||||
@@ -1194,6 +1201,15 @@ void Mobile_Shutdown( void );
|
||||
//
|
||||
void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F );
|
||||
|
||||
//
|
||||
// cl_steam.c
|
||||
//
|
||||
void SteamBroker_Init( void );
|
||||
void SteamBroker_Shutdown( void );
|
||||
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg );
|
||||
int SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
|
||||
void SteamBroker_TerminateGameConnection( void );
|
||||
|
||||
//
|
||||
// cl_video.c
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user