From 155c221a7016d464ef535d8c0ed5fc0c686f4848 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:43:22 +0100 Subject: [PATCH] Engine: rebuild CClientState::ProcessCreateStringTable() Rebuild CClientState::ProcessCreateStringTable() and add notes for multiple 'potential' exploit vectors that are currently in place. Also add wrappers for compressing/decompressing net packets. --- src/common/netmessages.h | 18 +++++ src/engine/client/clientstate.cpp | 101 ++++++++++++++++++++++++++++ src/engine/client/clientstate.h | 19 +++++- src/engine/net.cpp | 54 ++++++++++++++- src/engine/net.h | 16 ++++- src/engine/networkstringtable.h | 33 ++++++--- src/public/inetchannel.h | 16 +++++ src/public/networkstringtabledefs.h | 76 +++++++++++++++++++++ 8 files changed, 318 insertions(+), 15 deletions(-) create mode 100644 src/public/networkstringtabledefs.h diff --git a/src/common/netmessages.h b/src/common/netmessages.h index 23307b27..e94ed440 100644 --- a/src/common/netmessages.h +++ b/src/common/netmessages.h @@ -185,6 +185,24 @@ public: /////////////////////////////////////////////////////////////////////////////////////// // server messages: /////////////////////////////////////////////////////////////////////////////////////// +class SVC_CreateStringTable : public CNetMessage +{ +public: + const char* m_szTableName; + int m_nMaxEntries; + int m_nNumEntries; + char m_bUserDataFixedSize; + char _padding0[3]; + int m_nUserDataSize; + int m_nUserDataSizeBits; + int m_nDictFlags; + int m_nLength; + bf_read m_DataIn; + bf_write m_DataOut; + char m_bDataCompressed; + char m_szTableNameBuffer[260]; +}; + class SVC_Print : public CNetMessage { public: diff --git a/src/engine/client/clientstate.cpp b/src/engine/client/clientstate.cpp index 701fac61..b3a2c644 100644 --- a/src/engine/client/clientstate.cpp +++ b/src/engine/client/clientstate.cpp @@ -12,6 +12,7 @@ #include "tier0/frametask.h" #include "engine/common.h" #include "engine/host.h" +#include "engine/host_cmd.h" #ifndef CLIENT_DLL #include "engine/server/server.h" #endif // !CLIENT_DLL @@ -173,6 +174,12 @@ bool CClientState::VProcessServerTick(CClientState* thisptr, SVC_ServerTick* msg } } +//------------------------------------------------------------------------------ +// Purpose: processes string commands sent from server +// Input : *thisptr - +// *msg - +// Output : true on success, false otherwise +//------------------------------------------------------------------------------ bool CClientState::_ProcessStringCmd(CClientState* thisptr, NET_StringCmd* msg) { CClientState* const thisptr_ADJ = thisptr->GetShiftedBasePointer(); @@ -207,6 +214,99 @@ bool CClientState::_ProcessStringCmd(CClientState* thisptr, NET_StringCmd* msg) return true; } +//------------------------------------------------------------------------------ +// Purpose: create's string tables from string table data sent from server +// Input : *thisptr - +// *msg - +// Output : true on success, false otherwise +//------------------------------------------------------------------------------ +bool CClientState::_ProcessCreateStringTable(CClientState* thisptr, SVC_CreateStringTable* msg) +{ + CClientState* const cl = thisptr->GetShiftedBasePointer(); + + if (!cl->IsConnected()) + return false; + + CNetworkStringTableContainer* const container = cl->m_StringTableContainer; + + // Must have a string table container at this point! + if (!container) + { + Assert(0); + + COM_ExplainDisconnection(true, "String table container missing.\n"); + v_Host_Disconnect(true); + + return false; + } + + container->AllowCreation(true); + const ssize_t startbit = msg->m_DataIn.GetNumBitsRead(); + + CNetworkStringTable* const table = (CNetworkStringTable*)container->CreateStringTable(false, msg->m_szTableName, + msg->m_nMaxEntries, msg->m_nUserDataSize, msg->m_nUserDataSizeBits, msg->m_nDictFlags); + + table->SetTick(cl->GetServerTickCount()); + CClientState__HookClientStringTable(cl, msg->m_szTableName); + + if (msg->m_bDataCompressed) + { + // TODO[ AMOS ]: check sizes before proceeding to decode + // the string tables + unsigned int msgUncompressedSize = msg->m_DataIn.ReadLong(); + unsigned int msgCompressedSize = msg->m_DataIn.ReadLong(); + + size_t uncompressedSize = msgUncompressedSize; + size_t compressedSize = msgCompressedSize; + + bool bSuccess = false; + + // TODO[ AMOS ]: this could do better. The engine does UINT_MAX-3 + // which doesn't look very great. Clamp to more reasonable values + // than UINT_MAX-3 or UINT_MAX/2? The largest string tables sent + // are settings layout string tables which are roughly 256KiB + // compressed with LZSS. perhaps clamp this to something like 16MiB? + if (msg->m_DataIn.TotalBytesAvailable() > 0 && + msgCompressedSize <= (unsigned int)msg->m_DataIn.TotalBytesAvailable() && + msgCompressedSize < UINT_MAX / 2 && msgUncompressedSize < UINT_MAX / 2) + { + // allocate buffer for uncompressed data, align to 4 bytes boundary + uint8_t* const uncompressedBuffer = new uint8_t[PAD_NUMBER(msgUncompressedSize, 4)]; + uint8_t* const compressedBuffer = new uint8_t[PAD_NUMBER(msgCompressedSize, 4)]; + + msg->m_DataIn.ReadBytes(compressedBuffer, msgCompressedSize); + + // uncompress data + bSuccess = NET_BufferToBufferDecompress(compressedBuffer, compressedSize, uncompressedBuffer, uncompressedSize); + bSuccess &= (uncompressedSize == msgUncompressedSize); + + if (bSuccess) + { + bf_read data(uncompressedBuffer, (int)uncompressedSize); + table->ParseUpdate(data, msg->m_nNumEntries); + } + + delete[] uncompressedBuffer; + delete[] compressedBuffer; + } + + if (!bSuccess) + { + Assert(false); + DevWarning(eDLL_T::CLIENT, "%s: Received malformed string table message!\n", __FUNCTION__); + } + } + else + { + table->ParseUpdate(msg->m_DataIn, msg->m_nNumEntries); + } + + container->AllowCreation(false); + const ssize_t endbit = msg->m_DataIn.GetNumBitsRead(); + + return (endbit - startbit) == msg->m_nLength; +} + //------------------------------------------------------------------------------ // Purpose: get authentication token for current connection context // Input : *connectParams - @@ -295,6 +395,7 @@ void VClientState::Detour(const bool bAttach) const DetourSetup(&CClientState__ConnectionClosing, &CClientState::VConnectionClosing, bAttach); DetourSetup(&CClientState__ProcessStringCmd, &CClientState::_ProcessStringCmd, bAttach); DetourSetup(&CClientState__ProcessServerTick, &CClientState::VProcessServerTick, bAttach); + DetourSetup(&CClientState__ProcessCreateStringTable, &CClientState::_ProcessCreateStringTable, bAttach); DetourSetup(&CClientState__Connect, &CClientState::VConnect, bAttach); } diff --git a/src/engine/client/clientstate.h b/src/engine/client/clientstate.h index b954fea4..106272ac 100644 --- a/src/engine/client/clientstate.h +++ b/src/engine/client/clientstate.h @@ -5,6 +5,7 @@ #include "public/inetmsghandler.h" #include "public/isnapshotmgr.h" #include "engine/net_chan.h" +#include "engine/networkstringtable.h" #include "engine/debugoverlay.h" #include "engine/clockdriftmgr.h" #include "engine/framesnapshot.h" @@ -38,8 +39,10 @@ public: // Hook statics. static void VConnectionClosing(CClientState* thisptr, const char* szReason); static bool _ProcessStringCmd(CClientState* thisptr, NET_StringCmd* msg); static bool VProcessServerTick(CClientState* thisptr, SVC_ServerTick* msg); + static bool _ProcessCreateStringTable(CClientState* thisptr, SVC_CreateStringTable* msg); static void VConnect(CClientState* thisptr, connectparams_t* connectParams); + public: bool IsPaused() const; bool IsActive(void) const; @@ -63,7 +66,12 @@ public: protected: FORCEINLINE CClientState* GetShiftedBasePointer(void) { - char* const pShifted = reinterpret_cast(this) - 0x10; // Shifted due to compiler optimizations. + // NOTE: you must check in the disassembler if the CClientState method + // you detour is shifting the 'this' pointer with 16 bytes forward, if + // so, you need to call this and use this pointer instead! The shifting + // happens as part of a compiler optimization that truncated the vtable + // pointers off so the 'this' pointer points directly to the class data + char* const pShifted = reinterpret_cast(this) - 0x10; return reinterpret_cast(pShifted); } @@ -123,7 +131,7 @@ public: _QWORD m_pServerClasses; int m_nServerClasses; int m_nServerClassBits; - __int64 m_StringTableContainer; + CNetworkStringTableContainer* m_StringTableContainer; char m_PersistenceData[98304]; _BYTE m_bPersistenceBaselineRecvd; int m_nPersistenceBaselineEntries; @@ -215,8 +223,11 @@ inline void(*CClientState__RunFrame)(CClientState* thisptr); inline void(*CClientState__Connect)(CClientState* thisptr, connectparams_t* connectParams); inline void(*CClientState__Disconnect)(CClientState* thisptr, bool bSendTrackingContext); inline void(*CClientState__ConnectionClosing)(CClientState* thisptr, const char* szReason); +inline bool(*CClientState__HookClientStringTable)(CClientState* thisptr, const char* tableName); + inline bool(*CClientState__ProcessStringCmd)(CClientState* thisptr, NET_StringCmd* msg); inline bool(*CClientState__ProcessServerTick)(CClientState* thisptr, SVC_ServerTick* msg); +inline bool(*CClientState__ProcessCreateStringTable)(CClientState* thisptr, SVC_CreateStringTable* msg); /////////////////////////////////////////////////////////////////////////////// class VClientState : public IDetour @@ -227,8 +238,10 @@ class VClientState : public IDetour LogFunAdr("CClientState::Connect", CClientState__Connect); LogFunAdr("CClientState::Disconnect", CClientState__Disconnect); LogFunAdr("CClientState::ConnectionClosing", CClientState__ConnectionClosing); + LogFunAdr("CClientState::HookClientStringTable", CClientState__HookClientStringTable); LogFunAdr("CClientState::ProcessStringCmd", CClientState__ProcessStringCmd); LogFunAdr("CClientState::ProcessServerTick", CClientState__ProcessServerTick); + LogFunAdr("CClientState::ProcessCreateStringTable", CClientState__ProcessCreateStringTable); LogVarAdr("g_ClientState", g_pClientState); LogVarAdr("g_ClientState_Shifted", g_pClientState_Shifted); } @@ -238,8 +251,10 @@ class VClientState : public IDetour g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 81 EC ?? ?? ?? ?? 48 8B 32").GetPtr(CClientState__Connect); g_GameDll.FindPatternSIMD("40 56 57 41 54 41 55 41 57 48 83 EC 30 44 0F B6 FA").GetPtr(CClientState__Disconnect); g_GameDll.FindPatternSIMD("40 53 48 83 EC 20 83 B9 ?? ?? ?? ?? ?? 48 8B DA 0F 8E ?? ?? ?? ??").GetPtr(CClientState__ConnectionClosing); + g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 57 48 83 EC 20 48 8B D9 48 8B FA 48 8B 89 ?? ?? ?? ?? 48 85 C9 0F 84 ?? ?? ?? ??").GetPtr(CClientState__HookClientStringTable); g_GameDll.FindPatternSIMD("40 53 48 81 EC ?? ?? ?? ?? 80 B9 ?? ?? ?? ?? ?? 48 8B DA").GetPtr(CClientState__ProcessStringCmd); g_GameDll.FindPatternSIMD("40 57 48 83 EC 20 83 B9 ?? ?? ?? ?? ?? 48 8B F9 7C 66").GetPtr(CClientState__ProcessServerTick); + g_GameDll.FindPatternSIMD("48 89 4C 24 ?? 53 56 48 81 EC ?? ?? ?? ?? 83 B9 ?? ?? ?? ?? ??").GetPtr(CClientState__ProcessCreateStringTable); } virtual void GetVar(void) const { diff --git a/src/engine/net.cpp b/src/engine/net.cpp index 93614bb8..fe249ac9 100644 --- a/src/engine/net.cpp +++ b/src/engine/net.cpp @@ -57,6 +57,54 @@ int NET_SendDatagram(SOCKET s, void* pPayload, int iLenght, netadr_t* pAdr, bool return result; } +//----------------------------------------------------------------------------- +// 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; +} + //----------------------------------------------------------------------------- // Purpose: safely decompresses the input buffer into the output buffer // Input : *lzss - @@ -65,7 +113,7 @@ int NET_SendDatagram(SOCKET s, void* pPayload, int iLenght, netadr_t* pAdr, bool // unBufSize - // Output : total decompressed bytes //----------------------------------------------------------------------------- -unsigned int NET_Decompress(CLZSS* lzss, unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize) +unsigned int NET_BufferToBufferDecompress_LZSS(CLZSS* lzss, unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize) { return lzss->SafeUncompress(pInput, pOutput, unBufSize); } @@ -301,7 +349,9 @@ void VNet::Detour(const bool bAttach) const DetourSetup(&v_NET_Config, &NET_Config, bAttach); DetourSetup(&v_NET_ReceiveDatagram, &NET_ReceiveDatagram, bAttach); DetourSetup(&v_NET_SendDatagram, &NET_SendDatagram, bAttach); - DetourSetup(&v_NET_Decompress, &NET_Decompress, bAttach); + + DetourSetup(&v_NET_BufferToBufferCompress, &NET_BufferToBufferCompress, bAttach); + DetourSetup(&v_NET_BufferToBufferDecompress_LZSS, &NET_BufferToBufferDecompress_LZSS, bAttach); DetourSetup(&v_NET_PrintFunc, &NET_PrintFunc, bAttach); } diff --git a/src/engine/net.h b/src/engine/net.h index 34ec844c..2a0d8637 100644 --- a/src/engine/net.h +++ b/src/engine/net.h @@ -24,7 +24,8 @@ inline void(*v_NET_SetKey)(netkey_t* pKey, const char* szHash); inline void(*v_NET_Config)(void); inline bool(*v_NET_ReceiveDatagram)(int iSocket, netpacket_s* pInpacket, bool bRaw); inline int(*v_NET_SendDatagram)(SOCKET s, void* pPayload, int iLenght, netadr_t* pAdr, bool bEncrypted); -inline unsigned int(*v_NET_Decompress)(CLZSS* lzss, unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize); +inline bool(*v_NET_BufferToBufferCompress)(uint8_t* const dest, size_t* const destLen, uint8_t* const source, const size_t sourceLen); +inline unsigned int(*v_NET_BufferToBufferDecompress_LZSS)(CLZSS* lzss, unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize); inline void(*v_NET_PrintFunc)(const char* fmt, ...); /////////////////////////////////////////////////////////////////////////////// @@ -35,6 +36,11 @@ void NET_GenerateKey(); void NET_PrintFunc(const char* fmt, ...); void NET_RemoveChannel(CClient* pClient, int nIndex, const char* szReason, uint8_t bBadRep, bool bRemoveNow); +bool NET_BufferToBufferCompress(uint8_t* const dest, size_t* const destLen, uint8_t* const source, const size_t sourceLen); +unsigned int NET_BufferToBufferDecompress(uint8_t* pInput, size_t& coBufsize, uint8_t* pOutput, const size_t unBufSize); + +unsigned int NET_BufferToBufferDecompress_LZSS(CLZSS* lzss, unsigned char* pInput, unsigned char* pOutput, unsigned int unBufSize); + bool NET_ReadMessageType(int* outType, bf_read* buffer); /////////////////////////////////////////////////////////////////////////////// @@ -53,7 +59,8 @@ class VNet : public IDetour LogFunAdr("NET_SetKey", v_NET_SetKey); LogFunAdr("NET_ReceiveDatagram", v_NET_ReceiveDatagram); LogFunAdr("NET_SendDatagram", v_NET_SendDatagram); - LogFunAdr("NET_Decompress", v_NET_Decompress); + LogFunAdr("NET_BufferToBufferCompress", v_NET_BufferToBufferCompress); + LogFunAdr("NET_BufferToBufferDecompress_LZSS", v_NET_BufferToBufferDecompress_LZSS); LogFunAdr("NET_PrintFunc", v_NET_PrintFunc); LogVarAdr("g_NetAdr", g_pNetAdr); LogVarAdr("g_NetKey", g_pNetKey); @@ -66,7 +73,10 @@ class VNet : public IDetour g_GameDll.FindPatternSIMD("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 20 48 8B F9 41 B8").GetPtr(v_NET_SetKey); g_GameDll.FindPatternSIMD("48 89 74 24 18 48 89 7C 24 20 55 41 54 41 55 41 56 41 57 48 8D AC 24 50 EB").GetPtr(v_NET_ReceiveDatagram); g_GameDll.FindPatternSIMD("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 56 41 57 48 81 EC ?? 05 ?? ??").GetPtr(v_NET_SendDatagram); - g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 41 56 45 33 F6").GetPtr(v_NET_Decompress); + + g_GameDll.FindPatternSIMD("48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 50 48 8B 05 ?? ?? ?? ?? 49 8B E9").GetPtr(v_NET_BufferToBufferCompress); + g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 41 56 45 33 F6").GetPtr(v_NET_BufferToBufferDecompress_LZSS); + g_GameDll.FindPatternSIMD("48 89 54 24 10 4C 89 44 24 18 4C 89 4C 24 20 C3 48").GetPtr(v_NET_PrintFunc); } virtual void GetVar(void) const diff --git a/src/engine/networkstringtable.h b/src/engine/networkstringtable.h index 50416799..91c68993 100644 --- a/src/engine/networkstringtable.h +++ b/src/engine/networkstringtable.h @@ -9,19 +9,32 @@ #define NETWORKSTRINGTABLE_H #include "tier0/fasttimer.h" #include "tier1/utlvector.h" +#include "tier1/utlhashtable.h" #include "tier1/bitbuf.h" +#include "public/networkstringtabledefs.h" #include "client/client.h" -typedef int TABLEID; - -class INetworkStringTable -{ - INetworkStringTable* m_pVTable; -}; - class CNetworkStringTable : public INetworkStringTable { public: + // Updating/Writing + virtual bool WriteStringTable(bf_write& buf) = 0; + virtual bool ReadStringTable(bf_read& buf) = 0; + + virtual void ParseUpdate(bf_read& buf, int numStrings) = 0; + + virtual pfnStringChanged GetCallback(void) = 0; + + virtual int WriteUpdate(CClient* const client, bf_write& buf, int tickAck) = 0; + virtual bool WriteBaselines(SVC_CreateStringTable& msg, char* msgBuffer, int msgBufferSize) = 0; + + virtual void PurgeAllClientSide(void) = 0; + virtual void EnableRollback(bool bState) = 0; + + // TODO[ AMOS ]: there are a few more entries below in the vftable that + // need to be mapped out, most of them set/get bit fields; + // see [ r5apex_ds + 0x1329888 ] + TABLEID GetTableId(void) const; int GetMaxStrings(void) const; const char* GetTableName(void) const; @@ -41,16 +54,20 @@ private: // !TODO }; -class CNetworkStringTableContainer : public INetworkStringTable +class CNetworkStringTableContainer : public INetworkStringTableContainer { public: static void WriteUpdateMessage(CNetworkStringTableContainer* thisp, CClient* client, unsigned int tick_ack, bf_write* msg); + // Guards so game .dll can't create tables at the wrong time + inline void AllowCreation(bool state) { m_bAllowCreation = state; } + private: bool m_bAllowCreation; // create guard int m_nTickCount; // current tick bool m_bLocked; // currently locked? bool m_bEnableRollback; // enables rollback feature + CUtlVector < CNetworkStringTable* > m_Tables; // the string tables }; diff --git a/src/public/inetchannel.h b/src/public/inetchannel.h index bf308731..6878e004 100644 --- a/src/public/inetchannel.h +++ b/src/public/inetchannel.h @@ -51,6 +51,22 @@ public: struct CS_INetChannelHandler : INetChannelHandler {}; +enum netsocket_e +{ + NS_CLIENT = 0, // client socket + NS_SERVER, // server socket + + // unknown as this seems unused in R5, but if the socket equals to this in + // CServer::ConnectionlessPacketHandler() in case C2S_Challenge, the packet + // sent back won't be encrypted + NS_UNK0, + + // used for chat room, communities, discord presence, EA/Origin, etc + NS_PRESENCE, + + MAX_SOCKETS // 4 in R5 +}; + typedef struct netpacket_s { netadr_t from; diff --git a/src/public/networkstringtabledefs.h b/src/public/networkstringtabledefs.h new file mode 100644 index 00000000..afc0c432 --- /dev/null +++ b/src/public/networkstringtabledefs.h @@ -0,0 +1,76 @@ +//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef NETWORKSTRINGTABLEDEFS_H +#define NETWORKSTRINGTABLEDEFS_H + +typedef int TABLEID; + +#define INVALID_STRING_TABLE -1 +const unsigned short INVALID_STRING_INDEX = ( unsigned short )-1; + +// table index is sent in log2( MAX_TABLES ) bits +#define MAX_TABLES 32 // Table id is 4 bits + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +class INetworkStringTable; + +typedef void ( *pfnStringChanged )( void* object, INetworkStringTable* stringTable, int stringNumber, char const* newString, void const* newData ); + +//----------------------------------------------------------------------------- +// Purpose: Game .dll shared string table interfaces +//----------------------------------------------------------------------------- +class INetworkStringTable +{ +public: + virtual ~INetworkStringTable( void ) {}; + + // Table Info + virtual const char *GetTableName( void ) const = 0; + virtual TABLEID GetTableId( void ) const = 0; + virtual int GetNumStrings( void ) const = 0; + virtual int GetMaxStrings( void ) const = 0; + virtual int GetEntryBits( void ) const = 0; + + // Networking + virtual void SetTick( int tick ) = 0; + virtual bool ChangedSinceTick( int tick ) const = 0; + + // Accessors (length -1 means don't change user data if string already exits) + virtual int AddString( bool bIsServer, const char *value, int length = -1, const void *userdata = 0 ) = 0; + virtual int AddString( const char* value ) = 0; + + virtual const char* GetString( int stringNumber ) = 0; + + virtual int Unused0( void ) = 0; // returns -1 + virtual int Unused1( void ) const = 0; // returns -1 + virtual int Unused2( void ) const = 0; // returns 0 + + virtual void SetStringUserData( int stringNumber, int length, const void *userdata ) = 0; + virtual const void* GetStringUserData( int stringNumber, int* length ) = 0; +}; + +class INetworkStringTableContainer +{ +public: + virtual ~INetworkStringTableContainer( void ) {}; + + // table creation/destruction + virtual INetworkStringTable *CreateStringTable( bool createTableMemory, const char *tableName, int maxentries, int userdatafixedsize = 0, int userdatanetworkbits = 0, int dictFlags = 0 ) = 0; + virtual void RemoveAllTables( void ) = 0; + + // table infos + virtual INetworkStringTable *FindTable( const char *tableName ) const = 0; + virtual INetworkStringTable *GetTable( TABLEID stringTable ) const = 0; + virtual int GetNumTables( void ) const = 0; + + virtual void SetAllowClientSideAddString( INetworkStringTable *table, bool bAllowClientSideAddString ) = 0; +}; + +#endif // NETWORKSTRINGTABLEDEFS_H