2022-02-06 16:48:52 +01:00
//=============================================================================//
//
// Purpose: Net system utilities
//
//=============================================================================//
# include "core/stdafx.h"
# include "engine/net.h"
2024-01-12 00:25:19 +01:00
# ifndef _TOOLS
2022-04-09 16:16:40 +02:00
# include "tier1/cvar.h"
2024-04-10 15:29:51 +02:00
# include "tier2/cryptutils.h"
2022-04-02 02:48:54 +02:00
# include "mathlib/color.h"
2023-05-10 00:05:38 +02:00
# include "net.h"
# include "net_chan.h"
2022-04-02 02:48:54 +02:00
# ifndef CLIENT_DLL
2023-05-10 00:05:38 +02:00
# include "server/server.h"
# include "client/client.h"
2022-04-02 02:48:54 +02:00
# endif // !CLIENT_DLL
2024-01-12 00:25:19 +01:00
# endif // !_TOOLS
2022-04-02 02:48:54 +02:00
2024-01-12 00:25:19 +01:00
# ifndef _TOOLS
2024-06-07 22:34:50 +02:00
static void NET_GetKey_f ( )
{
NET_PrintKey ( ) ;
}
2024-02-23 00:12:06 +01:00
static void NET_SetKey_f ( const CCommand & args )
{
if ( args . ArgC ( ) < 2 )
{
return ;
}
NET_SetKey ( args . Arg ( 1 ) ) ;
}
static void NET_GenerateKey_f ( )
{
NET_GenerateKey ( ) ;
}
2024-08-05 00:45:30 +02:00
void NET_UseRandomKeyChanged_f ( IConVar * pConVar , const char * pOldString , float flOldValue , ChangeUserData_t pUserData )
2024-02-24 02:15:09 +01:00
{
if ( ConVar * pConVarRef = g_pCVar - > FindVar ( pConVar - > GetName ( ) ) )
{
if ( strcmp ( pOldString , pConVarRef - > GetString ( ) ) = = NULL )
return ; // Same value.
if ( pConVarRef - > GetBool ( ) )
NET_GenerateKey ( ) ;
else
NET_SetKey ( DEFAULT_NET_ENCRYPTION_KEY ) ;
}
}
ConVar net_useRandomKey ( " net_useRandomKey " , " 1 " , FCVAR_RELEASE , " Use random AES encryption key for game packets. " , false , 0.f , false , 0.f , & NET_UseRandomKeyChanged_f , nullptr ) ;
static ConVar net_tracePayload ( " net_tracePayload " , " 0 " , FCVAR_DEVELOPMENTONLY , " Log the payload of the send/recv datagram to a file on the disk. " ) ;
static ConVar net_encryptionEnable ( " net_encryptionEnable " , " 1 " , FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED , " Use AES encryption on game packets. " ) ;
2024-06-07 22:34:50 +02:00
static ConCommand net_getkey ( " net_getkey " , NET_GetKey_f , " Gets the installed base64 net key " , FCVAR_RELEASE ) ;
2024-02-23 00:12:06 +01:00
static ConCommand net_setkey ( " net_setkey " , NET_SetKey_f , " Sets user specified base64 net key " , FCVAR_RELEASE ) ;
static ConCommand net_generatekey ( " net_generatekey " , NET_GenerateKey_f , " Generates and sets a random base64 net key " , FCVAR_RELEASE ) ;
2022-04-02 02:48:54 +02:00
//-----------------------------------------------------------------------------
// Purpose: hook and log the receive datagram
2022-04-24 19:32:47 +02:00
// Input : iSocket -
// *pInpacket -
// bEncrypted -
// Output : true on success, false otherwise
2022-04-02 02:48:54 +02:00
//-----------------------------------------------------------------------------
2022-04-24 19:32:47 +02:00
bool NET_ReceiveDatagram ( int iSocket , netpacket_s * pInpacket , bool bEncrypted )
2022-04-02 02:48:54 +02:00
{
2024-02-24 02:15:09 +01:00
const bool decryptPacket = ( bEncrypted & & net_encryptionEnable . GetBool ( ) ) ;
2024-02-19 21:18:45 +01:00
const bool result = v_NET_ReceiveDatagram ( iSocket , pInpacket , decryptPacket ) ;
2024-02-24 02:15:09 +01:00
if ( result & & net_tracePayload . GetBool ( ) )
2022-04-02 02:48:54 +02:00
{
// Log received packet data.
2024-02-19 21:18:45 +01:00
HexDump ( " [+] NET_ReceiveDatagram " , " net_trace " ,
2023-04-16 00:36:30 +02:00
pInpacket - > pData , size_t ( pInpacket - > wiresize ) ) ;
2022-04-02 02:48:54 +02:00
}
2024-02-19 21:18:45 +01:00
2022-04-02 02:48:54 +02:00
return result ;
}
//-----------------------------------------------------------------------------
// Purpose: hook and log the send datagram
2022-04-24 19:32:47 +02:00
// Input : s -
// *pPayload -
// iLenght -
// *pAdr -
2022-04-27 18:22:08 +02:00
// bEncrypt -
2022-04-24 19:32:47 +02:00
// Output : outgoing sequence number for this packet
2022-04-02 02:48:54 +02:00
//-----------------------------------------------------------------------------
2023-01-29 15:12:27 +01:00
int NET_SendDatagram ( SOCKET s , void * pPayload , int iLenght , netadr_t * pAdr , bool bEncrypt )
2022-04-02 02:48:54 +02:00
{
2024-02-24 02:15:09 +01:00
const bool encryptPacket = ( bEncrypt & & net_encryptionEnable . GetBool ( ) ) ;
2024-02-19 21:18:45 +01:00
const int result = v_NET_SendDatagram ( s , pPayload , iLenght , pAdr , encryptPacket ) ;
2024-02-24 02:15:09 +01:00
if ( result & & net_tracePayload . GetBool ( ) )
2022-04-02 02:48:54 +02:00
{
// Log transmitted packet data.
2023-04-02 21:51:15 +02:00
HexDump ( " [+] NET_SendDatagram " , " net_trace " , pPayload , size_t ( iLenght ) ) ;
2022-04-02 02:48:54 +02:00
}
2024-02-19 21:18:45 +01:00
2022-04-02 02:48:54 +02:00
return result ;
}
2024-02-19 20:43:22 +01:00
//-----------------------------------------------------------------------------
// Purpose: compresses the input buffer into the output buffer
// Input : *dest -
// *destLen -
// *source -
// sourceLen -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool NET_BufferToBufferCompress ( uint8_t * const dest , size_t * const destLen , uint8_t * const source , const size_t sourceLen )
{
CLZSS lzss ;
uint32_t compLen = ( uint32_t ) sourceLen ;
if ( ! lzss . CompressNoAlloc ( source , ( uint32_t ) sourceLen , dest , & compLen ) )
{
memcpy ( dest , source , sourceLen ) ;
* destLen = sourceLen ;
return false ;
}
* destLen = compLen ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose: decompresses the input buffer into the output buffer
// Input : *source -
// &sourceLen -
// *dest -
// destLen -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
unsigned int NET_BufferToBufferDecompress ( uint8_t * const source , size_t & sourceLen , uint8_t * const dest , const size_t destLen )
{
Assert ( source ) ;
Assert ( sourceLen ) ;
CLZSS lzss ;
if ( lzss . IsCompressed ( source ) )
{
return lzss . SafeUncompress ( source , dest , ( unsigned int ) destLen ) ;
}
return 0 ;
}
2023-04-06 00:28:58 +02:00
//-----------------------------------------------------------------------------
// Purpose: safely decompresses the input buffer into the output buffer
// Input : *lzss -
// *pInput -
// *pOutput -
// unBufSize -
// Output : total decompressed bytes
//-----------------------------------------------------------------------------
2024-02-19 20:43:22 +01:00
unsigned int NET_BufferToBufferDecompress_LZSS ( CLZSS * lzss , unsigned char * pInput , unsigned char * pOutput , unsigned int unBufSize )
2023-04-06 00:28:58 +02:00
{
return lzss - > SafeUncompress ( pInput , pOutput , unBufSize ) ;
}
2023-04-16 11:58:37 +02:00
//-----------------------------------------------------------------------------
// Purpose: configures the network system
//-----------------------------------------------------------------------------
void NET_Config ( )
{
v_NET_Config ( ) ;
g_pNetAdr - > SetPort ( htons ( u_short ( hostport - > GetInt ( ) ) ) ) ;
}
2024-06-07 22:34:50 +02:00
//-----------------------------------------------------------------------------
// Purpose: prints the currently installed encryption key
//-----------------------------------------------------------------------------
void NET_PrintKey ( )
{
Msg ( eDLL_T : : ENGINE , " Installed NetKey: %s'%s%s%s' \n " ,
2025-02-02 14:47:57 +01:00
g_svReset . c_str ( ) , g_svGreyB . c_str ( ) , g_pNetKey - > GetBase64NetKey ( ) , g_svReset . c_str ( ) ) ;
2024-06-07 22:34:50 +02:00
}
2022-04-02 02:48:54 +02:00
//-----------------------------------------------------------------------------
// Purpose: sets the user specified encryption key
2022-08-30 20:04:59 +02:00
// Input : svNetKey -
2022-04-02 02:48:54 +02:00
//-----------------------------------------------------------------------------
2023-02-04 15:59:45 +01:00
void NET_SetKey ( const string & svNetKey )
2022-04-02 02:48:54 +02:00
{
2023-02-12 02:28:03 +01:00
string svTokenizedKey ;
2022-08-30 01:22:53 +02:00
2022-08-30 20:04:59 +02:00
if ( svNetKey . size ( ) = = AES_128_B64_ENCODED_SIZE & &
2023-02-12 02:28:03 +01:00
IsValidBase64 ( svNetKey , & svTokenizedKey ) ) // Results are tokenized by 'IsValidBase64()'.
2022-08-30 20:04:59 +02:00
{
2023-02-12 02:28:03 +01:00
v_NET_SetKey ( g_pNetKey , svTokenizedKey . c_str ( ) ) ;
2024-06-07 22:34:50 +02:00
NET_PrintKey ( ) ;
2022-08-30 20:04:59 +02:00
}
else
{
2022-09-14 01:14:51 +02:00
Error ( eDLL_T : : ENGINE , NO_ERROR , " AES-128 key not encoded or invalid \n " ) ;
2022-08-30 20:04:59 +02:00
}
2022-04-02 02:48:54 +02:00
}
//-----------------------------------------------------------------------------
// Purpose: calculates and sets the encryption key
//-----------------------------------------------------------------------------
void NET_GenerateKey ( )
{
2024-02-24 02:15:09 +01:00
if ( ! net_useRandomKey . GetBool ( ) )
2022-08-30 12:10:07 +02:00
{
2024-02-24 02:15:09 +01:00
net_useRandomKey . SetValue ( 1 ) ;
2022-08-30 12:10:07 +02:00
return ; // Change callback will handle this.
}
2022-04-02 02:48:54 +02:00
2024-04-10 15:29:51 +02:00
uint8_t keyBuf [ AES_128_KEY_SIZE ] ;
const char * errorMsg = nullptr ;
2022-08-30 01:22:53 +02:00
2024-04-10 15:29:51 +02:00
if ( ! Plat_GenerateRandom ( keyBuf , sizeof ( keyBuf ) , errorMsg ) )
2022-04-02 02:48:54 +02:00
{
2024-04-10 15:29:51 +02:00
Error ( eDLL_T : : ENGINE , NO_ERROR , " %s \n " , errorMsg ) ;
2022-04-02 02:48:54 +02:00
return ;
}
2024-04-10 15:29:51 +02:00
NET_SetKey ( Base64Encode ( string ( reinterpret_cast < char * > ( & keyBuf ) , AES_128_KEY_SIZE ) ) ) ;
2022-04-02 02:48:54 +02:00
}
//-----------------------------------------------------------------------------
// Purpose: hook and log the client's signonstate to the console
2022-04-24 19:32:47 +02:00
// Input : *fmt -
// ... -
2022-04-02 02:48:54 +02:00
//-----------------------------------------------------------------------------
void NET_PrintFunc ( const char * fmt , . . . )
{
2022-09-11 23:45:06 +02:00
# ifndef DEDICATED
const static eDLL_T context = eDLL_T : : CLIENT ;
# else // !DEDICATED
const static eDLL_T context = eDLL_T : : SERVER ;
# endif
2022-04-02 02:48:54 +02:00
2023-03-27 02:01:48 +02:00
string result ;
2022-04-02 02:48:54 +02:00
2023-03-27 02:01:48 +02:00
va_list args ;
va_start ( args , fmt ) ;
result = FormatV ( fmt , args ) ;
2022-04-02 02:48:54 +02:00
va_end ( args ) ;
2025-02-09 00:52:32 +01:00
Msg ( context , result . back ( ) = = ' \n ' ? " %s " : " %s \n " , result . c_str ( ) ) ;
2022-04-02 02:48:54 +02:00
}
//-----------------------------------------------------------------------------
// Purpose: disconnect the client and shutdown netchannel
2022-04-24 19:32:47 +02:00
// Input : *pClient -
// nIndex -
// *szReason -
2022-05-12 01:20:19 +02:00
// bBadRep -
// bRemoveNow -
2022-04-02 02:48:54 +02:00
//-----------------------------------------------------------------------------
2022-09-18 23:19:50 +02:00
void NET_RemoveChannel ( CClient * pClient , int nIndex , const char * szReason , uint8_t bBadRep , bool bRemoveNow )
2022-04-02 02:48:54 +02:00
{
# ifndef CLIENT_DLL
if ( ! pClient | | std : : strlen ( szReason ) = = NULL | | ! pClient - > GetNetChan ( ) )
{
return ;
}
2023-05-16 00:44:59 +02:00
pClient - > GetNetChan ( ) - > Shutdown ( szReason , bBadRep , bRemoveNow ) ; // Shutdown NetChannel.
pClient - > Clear ( ) ; // Reset CClient slot.
2022-04-02 02:48:54 +02:00
# endif // !CLIENT_DLL
}
2023-08-21 16:28:04 +02:00
//-----------------------------------------------------------------------------
// Purpose: reads the net message type from buffer
// Input : &outType -
// &buffer -
// Output : true on success, false otherwise
//-----------------------------------------------------------------------------
bool NET_ReadMessageType ( int * outType , bf_read * buffer )
{
* outType = buffer - > ReadUBitLong ( NETMSG_TYPE_BITS ) ;
return ! buffer - > IsOverflowed ( ) ;
}
2024-07-01 00:20:03 +02:00
//-----------------------------------------------------------------------------
// Purpose: checks whether the provided address is the local server.
// Input : &netAdr -
// Output : true if equal, false otherwise
//-----------------------------------------------------------------------------
bool NET_IsRemoteLocal ( const CNetAdr & netAdr )
{
return ( g_pNetAdr - > ComparePort ( netAdr ) & & g_pNetAdr - > CompareAdr ( netAdr ) ) ;
}
2024-01-12 00:25:19 +01:00
# endif // !_TOOLS
2022-02-06 16:48:52 +01:00
//-----------------------------------------------------------------------------
// Purpose: returns the WSA error code
//-----------------------------------------------------------------------------
const char * NET_ErrorString ( int iCode )
{
switch ( iCode )
{
2022-08-09 10:55:31 +02:00
case WSAEINTR : return " WSAEINTR " ;
case WSAEBADF : return " WSAEBADF " ;
case WSAEACCES : return " WSAEACCES " ;
case WSAEFAULT : return " WSAEFAULT " ;
case WSAEINVAL : return " WSAEINVAL " ;
case WSAEMFILE : return " WSAEMFILE " ;
case WSAEWOULDBLOCK : return " WSAEWOULDBLOCK " ;
case WSAEINPROGRESS : return " WSAEINPROGRESS " ;
case WSAEALREADY : return " WSAEALREADY " ;
case WSAENOTSOCK : return " WSAENOTSOCK " ;
case WSAEDESTADDRREQ : return " WSAEDESTADDRREQ " ;
case WSAEMSGSIZE : return " WSAEMSGSIZE " ;
case WSAEPROTOTYPE : return " WSAEPROTOTYPE " ;
case WSAENOPROTOOPT : return " WSAENOPROTOOPT " ;
case WSAEPROTONOSUPPORT : return " WSAEPROTONOSUPPORT " ;
case WSAESOCKTNOSUPPORT : return " WSAESOCKTNOSUPPORT " ;
case WSAEOPNOTSUPP : return " WSAEOPNOTSUPP " ;
case WSAEPFNOSUPPORT : return " WSAEPFNOSUPPORT " ;
case WSAEAFNOSUPPORT : return " WSAEAFNOSUPPORT " ;
case WSAEADDRINUSE : return " WSAEADDRINUSE " ;
case WSAEADDRNOTAVAIL : return " WSAEADDRNOTAVAIL " ;
case WSAENETDOWN : return " WSAENETDOWN " ;
case WSAENETUNREACH : return " WSAENETUNREACH " ;
case WSAENETRESET : return " WSAENETRESET " ;
2022-09-14 13:24:26 +02:00
case WSAECONNABORTED : return " WSAECONNABORTED " ;
2022-08-09 10:55:31 +02:00
case WSAECONNRESET : return " WSAECONNRESET " ;
case WSAENOBUFS : return " WSAENOBUFS " ;
case WSAEISCONN : return " WSAEISCONN " ;
case WSAENOTCONN : return " WSAENOTCONN " ;
case WSAESHUTDOWN : return " WSAESHUTDOWN " ;
case WSAETOOMANYREFS : return " WSAETOOMANYREFS " ;
case WSAETIMEDOUT : return " WSAETIMEDOUT " ;
case WSAECONNREFUSED : return " WSAECONNREFUSED " ;
case WSAELOOP : return " WSAELOOP " ;
case WSAENAMETOOLONG : return " WSAENAMETOOLONG " ;
case WSAEHOSTDOWN : return " WSAEHOSTDOWN " ;
case WSAEHOSTUNREACH : return " WSAEHOSTUNREACH " ;
case WSAENOTEMPTY : return " WSAENOTEMPTY " ;
case WSAEPROCLIM : return " WSAEPROCLIM " ;
case WSAEUSERS : return " WSAEUSERS " ;
case WSAEDQUOT : return " WSAEDQUOT " ;
case WSAESTALE : return " WSAESTALE " ;
case WSAEREMOTE : return " WSAEREMOTE " ;
case WSASYSNOTREADY : return " WSASYSNOTREADY " ;
case WSAVERNOTSUPPORTED : return " WSAVERNOTSUPPORTED " ;
case WSANOTINITIALISED : return " WSANOTINITIALISED " ;
case WSAEDISCON : return " WSAEDISCON " ;
case WSAENOMORE : return " WSAENOMORE " ;
case WSAECANCELLED : return " WSAECANCELLED " ;
case WSAEINVALIDPROCTABLE : return " WSAEINVALIDPROCTABLE " ;
case WSAEINVALIDPROVIDER : return " WSAEINVALIDPROVIDER " ;
case WSAEPROVIDERFAILEDINIT : return " WSAEPROVIDERFAILEDINIT " ;
case WSASYSCALLFAILURE : return " WSASYSCALLFAILURE " ;
case WSASERVICE_NOT_FOUND : return " WSASERVICE_NOT_FOUND " ;
case WSATYPE_NOT_FOUND : return " WSATYPE_NOT_FOUND " ;
case WSA_E_NO_MORE : return " WSA_E_NO_MORE " ;
case WSA_E_CANCELLED : return " WSA_E_CANCELLED " ;
case WSAEREFUSED : return " WSAEREFUSED " ;
case WSAHOST_NOT_FOUND : return " WSAHOST_NOT_FOUND " ;
case WSATRY_AGAIN : return " WSATRY_AGAIN " ;
case WSANO_RECOVERY : return " WSANO_RECOVERY " ;
case WSANO_DATA : return " WSANO_DATA " ;
case WSA_QOS_RECEIVERS : return " WSA_QOS_RECEIVERS " ;
case WSA_QOS_SENDERS : return " WSA_QOS_SENDERS " ;
case WSA_QOS_NO_SENDERS : return " WSA_QOS_NO_SENDERS " ;
case WSA_QOS_NO_RECEIVERS : return " WSA_QOS_NO_RECEIVERS " ;
case WSA_QOS_REQUEST_CONFIRMED : return " WSA_QOS_REQUEST_CONFIRMED " ;
case WSA_QOS_ADMISSION_FAILURE : return " WSA_QOS_ADMISSION_FAILURE " ;
case WSA_QOS_POLICY_FAILURE : return " WSA_QOS_POLICY_FAILURE " ;
case WSA_QOS_BAD_STYLE : return " WSA_QOS_BAD_STYLE " ;
case WSA_QOS_BAD_OBJECT : return " WSA_QOS_BAD_OBJECT " ;
case WSA_QOS_TRAFFIC_CTRL_ERROR : return " WSA_QOS_TRAFFIC_CTRL_ERROR " ;
case WSA_QOS_GENERIC_ERROR : return " WSA_QOS_GENERIC_ERROR " ;
case WSA_QOS_ESERVICETYPE : return " WSA_QOS_ESERVICETYPE " ;
case WSA_QOS_EFLOWSPEC : return " WSA_QOS_EFLOWSPEC " ;
case WSA_QOS_EPROVSPECBUF : return " WSA_QOS_EPROVSPECBUF " ;
case WSA_QOS_EFILTERSTYLE : return " WSA_QOS_EFILTERSTYLE " ;
case WSA_QOS_EFILTERTYPE : return " WSA_QOS_EFILTERTYPE " ;
case WSA_QOS_EFILTERCOUNT : return " WSA_QOS_EFILTERCOUNT " ;
case WSA_QOS_EOBJLENGTH : return " WSA_QOS_EOBJLENGTH " ;
case WSA_QOS_EFLOWCOUNT : return " WSA_QOS_EFLOWCOUNT " ;
2025-02-09 00:51:51 +01:00
case WSA_QOS_EUNKOWNPSOBJ : return " WSA_QOS_EUNKNOWNPSOBJ " ;
2022-08-09 10:55:31 +02:00
case WSA_QOS_EPOLICYOBJ : return " WSA_QOS_EPOLICYOBJ " ;
case WSA_QOS_EFLOWDESC : return " WSA_QOS_EFLOWDESC " ;
case WSA_QOS_EPSFLOWSPEC : return " WSA_QOS_EPSFLOWSPEC " ;
case WSA_QOS_EPSFILTERSPEC : return " WSA_QOS_EPSFILTERSPEC " ;
case WSA_QOS_ESDMODEOBJ : return " WSA_QOS_ESDMODEOBJ " ;
case WSA_QOS_ESHAPERATEOBJ : return " WSA_QOS_ESHAPERATEOBJ " ;
case WSA_QOS_RESERVED_PETYPE : return " WSA_QOS_RESERVED_PETYPE " ;
case WSA_SECURE_HOST_NOT_FOUND : return " WSA_SECURE_HOST_NOT_FOUND " ;
case WSA_IPSEC_NAME_POLICY_ERROR : return " WSA_IPSEC_NAME_POLICY_ERROR " ;
2022-04-02 02:48:54 +02:00
default : return " UNKNOWN_ERROR " ;
2022-02-06 16:48:52 +01:00
}
}
2022-04-02 02:48:54 +02:00
2024-01-12 00:25:19 +01:00
# ifndef _TOOLS
2022-04-02 02:48:54 +02:00
///////////////////////////////////////////////////////////////////////////////
2023-11-26 13:21:20 +01:00
void VNet : : Detour ( const bool bAttach ) const
2022-04-02 02:48:54 +02:00
{
2023-11-26 13:21:20 +01:00
DetourSetup ( & v_NET_Config , & NET_Config , bAttach ) ;
DetourSetup ( & v_NET_ReceiveDatagram , & NET_ReceiveDatagram , bAttach ) ;
DetourSetup ( & v_NET_SendDatagram , & NET_SendDatagram , bAttach ) ;
2024-02-19 20:43:22 +01:00
DetourSetup ( & v_NET_BufferToBufferCompress , & NET_BufferToBufferCompress , bAttach ) ;
DetourSetup ( & v_NET_BufferToBufferDecompress_LZSS , & NET_BufferToBufferDecompress_LZSS , bAttach ) ;
2023-11-26 13:21:20 +01:00
DetourSetup ( & v_NET_PrintFunc , & NET_PrintFunc , bAttach ) ;
2022-04-02 02:48:54 +02:00
}
///////////////////////////////////////////////////////////////////////////////
2023-02-12 11:32:05 +01:00
netadr_t * g_pNetAdr = nullptr ;
2023-02-12 02:15:58 +01:00
netkey_t * g_pNetKey = nullptr ;
2023-02-15 20:47:58 +01:00
double * g_pNetTime = nullptr ;
2024-01-12 00:25:19 +01:00
# endif // !_TOOLS