mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Engine: partial rebuild for data block sender/receiver
This patch partially rebuilds the data block sender/receiver. The receiver leaks memory if the sender sends a bogus LZ4 packet, it would allocate memory to copy the encoded data into, from which it would decode it to the scratch buffer, but it would never deallocate this temporary buffer is the LZ4 decoder failed. This has been fixed. The second reason to rebuild these was to look into potential compression optimization. The data block rebuild now also features the latest LZ4 codec.
This commit is contained in:
parent
976f1ab5ae
commit
802e1f42eb
@ -280,7 +280,10 @@ ConVar* net_useRandomKey = nullptr;
|
||||
ConVar* net_usesocketsforloopback = nullptr;
|
||||
ConVar* net_processTimeBudget = nullptr;
|
||||
|
||||
ConVar* net_data_block_enabled = nullptr;
|
||||
ConVar* net_datablock_networkLossForSlowSpeed = nullptr;
|
||||
ConVar* net_compressDataBlock = nullptr;
|
||||
ConVar* net_compressDataBlockLzAcceleration = nullptr;
|
||||
|
||||
ConVar* pylon_matchmaking_hostname = nullptr;
|
||||
ConVar* pylon_host_update_interval = nullptr;
|
||||
@ -514,6 +517,8 @@ void ConVar_StaticInit(void)
|
||||
net_encryptionEnable = ConVar::StaticCreate("net_encryptionEnable" , "1", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED , "Use AES encryption on game packets.", false, 0.f, false, 0.f, nullptr, nullptr);
|
||||
net_useRandomKey = ConVar::StaticCreate("net_useRandomKey" , "1" , FCVAR_RELEASE , "Use random AES encryption key for game packets.", false, 0.f, false, 0.f, &NET_UseRandomKeyChanged_f, nullptr);
|
||||
net_processTimeBudget = ConVar::StaticCreate("net_processTimeBudget" ,"200" , FCVAR_RELEASE , "Net message process time budget in milliseconds (removing netchannel if exceeded).", true, 0.f, false, 0.f, nullptr, "0 = disabled");
|
||||
|
||||
net_compressDataBlockLzAcceleration = ConVar::StaticCreate("net_compressDataBlockLzAcceleration", "1", FCVAR_DEVELOPMENTONLY, "The acceleration value for LZ4 data block compression.", false, 0.f, false, 0.f, nullptr, nullptr);
|
||||
//-------------------------------------------------------------------------
|
||||
// NETWORKSYSTEM |
|
||||
pylon_matchmaking_hostname = ConVar::StaticCreate("pylon_matchmaking_hostname", "ms.r5reloaded.com", FCVAR_RELEASE | FCVAR_MATERIAL_SYSTEM_THREAD, "Holds the pylon matchmaking hostname.", false, 0.f, false, 0.f, &MP_HostName_Changed_f, nullptr);
|
||||
@ -593,7 +598,11 @@ void ConVar_InitShipped(void)
|
||||
hostport = g_pCVar->FindVar("hostport");
|
||||
host_hasIrreversibleShutdown = g_pCVar->FindVar("host_hasIrreversibleShutdown");
|
||||
host_timescale = g_pCVar->FindVar("host_timescale");
|
||||
|
||||
net_data_block_enabled = g_pCVar->FindVar("net_data_block_enabled");
|
||||
net_compressDataBlock = g_pCVar->FindVar("net_compressDataBlock");
|
||||
net_datablock_networkLossForSlowSpeed = g_pCVar->FindVar("net_datablock_networkLossForSlowSpeed");
|
||||
|
||||
net_usesocketsforloopback = g_pCVar->FindVar("net_usesocketsforloopback");
|
||||
#ifndef CLIENT_DLL
|
||||
sv_stats = g_pCVar->FindVar("sv_stats");
|
||||
|
@ -267,7 +267,10 @@ extern ConVar* net_useRandomKey;
|
||||
extern ConVar* net_usesocketsforloopback;
|
||||
extern ConVar* net_processTimeBudget;
|
||||
|
||||
extern ConVar* net_data_block_enabled;
|
||||
extern ConVar* net_datablock_networkLossForSlowSpeed;
|
||||
extern ConVar* net_compressDataBlock;
|
||||
extern ConVar* net_compressDataBlockLzAcceleration;
|
||||
|
||||
extern ConVar* pylon_matchmaking_hostname;
|
||||
extern ConVar* pylon_host_update_interval;
|
||||
|
@ -64,9 +64,11 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE
|
||||
"playlists"
|
||||
"stryder"
|
||||
|
||||
"libdetours"
|
||||
"liblzham"
|
||||
"libzstd"
|
||||
"liblz4"
|
||||
|
||||
"libdetours"
|
||||
"libcurl"
|
||||
"libprotobuf"
|
||||
"libspdlog"
|
||||
|
@ -18,6 +18,11 @@
|
||||
#include "thirdparty/lzham/include/lzham_types.h"
|
||||
#include "thirdparty/lzham/include/lzham.h"
|
||||
|
||||
#include "thirdparty/zstd/zstd.h"
|
||||
#include "thirdparty/zstd/decompress/zstd_decompress_internal.h"
|
||||
|
||||
#include "thirdparty/lz4/lz4.h"
|
||||
|
||||
#include "thirdparty/curl/include/curl/curl.h"
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
|
@ -340,6 +340,35 @@ bool CClient::VSendNetMsgEx(CClient* pClient, CNetMessage* pMsg, bool bLocal, bo
|
||||
return pClient->SendNetMsgEx(pMsg, bLocal, bForceReliable, bVoice);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
// Purpose: write data into data blocks to send to the client
|
||||
// Input : &buf
|
||||
//---------------------------------------------------------------------------------
|
||||
void CClient::WriteDataBlock(CClient* pClient, bf_write& buf)
|
||||
{
|
||||
#ifndef CLIENT_DLL
|
||||
if (net_data_block_enabled->GetBool())
|
||||
{
|
||||
buf.WriteUBitLong(net_NOP, NETMSG_TYPE_BITS);
|
||||
|
||||
const int remainingBits = buf.GetNumBitsWritten() % 8;
|
||||
|
||||
if (remainingBits && (8 - remainingBits) > 0)
|
||||
{
|
||||
// fill the last bits in the last byte with NOP
|
||||
buf.WriteUBitLong(net_NOP, 8 - remainingBits);
|
||||
}
|
||||
|
||||
const bool isMultiplayer = g_ServerGlobalVariables->m_nGameMode < GameMode_t::PVE_MODE;
|
||||
pClient->m_DataBlock.sender.WriteDataBlock(buf.GetData(), buf.GetNumBytesWritten(), isMultiplayer, buf.GetDebugName());
|
||||
}
|
||||
else
|
||||
{
|
||||
pClient->m_NetChannel->SendData(buf, true);
|
||||
}
|
||||
#endif // !CLIENT_DLL
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
// Purpose: some versions of the binary have an optimization that shifts the 'this'
|
||||
// pointer of the CClient structure by 8 bytes to avoid having to cache the vftable
|
||||
@ -492,6 +521,7 @@ void VClient::Detour(const bool bAttach) const
|
||||
DetourSetup(&CClient__ActivatePlayer, &CClient::VActivatePlayer, bAttach);
|
||||
DetourSetup(&CClient__SendNetMsgEx, &CClient::VSendNetMsgEx, bAttach);
|
||||
//DetourSetup(&CClient__SendSnapshot, &CClient::VSendSnapshot, bAttach);
|
||||
DetourSetup(&CClient__WriteDataBlock, &CClient::WriteDataBlock, bAttach);
|
||||
|
||||
DetourSetup(&CClient__ProcessStringCmd, &CClient::VProcessStringCmd, bAttach);
|
||||
DetourSetup(&CClient__ProcessSetConVar, &CClient::VProcessSetConVar, bAttach);
|
||||
|
@ -116,6 +116,8 @@ public: // Hook statics:
|
||||
static void* VSendSnapshot(CClient* pClient, CClientFrame* pFrame, int nTick, int nTickAck);
|
||||
static bool VSendNetMsgEx(CClient* pClient, CNetMessage* pMsg, bool bLocal, bool bForceReliable, bool bVoice);
|
||||
|
||||
static void WriteDataBlock(CClient* pClient, bf_write& buf);
|
||||
|
||||
static bool VProcessStringCmd(CClient* pClient, NET_StringCmd* pMsg);
|
||||
static bool VProcessSetConVar(CClient* pClient, NET_SetConVar* pMsg);
|
||||
|
||||
@ -265,6 +267,7 @@ inline void(*CClient__ActivatePlayer)(CClient* pClient);
|
||||
inline bool(*CClient__SetSignonState)(CClient* pClient, SIGNONSTATE signon);
|
||||
inline bool(*CClient__SendNetMsgEx)(CClient* pClient, CNetMessage* pMsg, bool bLocal, bool bForceReliable, bool bVoice);
|
||||
inline void*(*CClient__SendSnapshot)(CClient* pClient, CClientFrame* pFrame, int nTick, int nTickAck);
|
||||
inline void(*CClient__WriteDataBlock)(CClient* pClient, bf_write& buf);
|
||||
inline bool(*CClient__ProcessStringCmd)(CClient* pClient, NET_StringCmd* pMsg);
|
||||
inline bool(*CClient__ProcessSetConVar)(CClient* pClient, NET_SetConVar* pMsg);
|
||||
|
||||
@ -280,6 +283,7 @@ class VClient : public IDetour
|
||||
LogFunAdr("CClient::SetSignonState", CClient__SetSignonState);
|
||||
LogFunAdr("CClient::SendNetMsgEx", CClient__SendNetMsgEx);
|
||||
LogFunAdr("CClient::SendSnapshot", CClient__SendSnapshot);
|
||||
LogFunAdr("CClient::WriteDataBlock", CClient__WriteDataBlock);
|
||||
LogFunAdr("CClient::ProcessStringCmd", CClient__ProcessStringCmd);
|
||||
LogFunAdr("CClient::ProcessSetConVar", CClient__ProcessSetConVar);
|
||||
}
|
||||
@ -291,6 +295,7 @@ class VClient : public IDetour
|
||||
g_GameDll.FindPatternSIMD("40 53 48 83 EC 20 8B 81 B0 03 ?? ?? 48 8B D9 C6").GetPtr(CClient__ActivatePlayer);
|
||||
g_GameDll.FindPatternSIMD("40 53 55 56 57 41 56 48 83 EC 40 48 8B 05 ?? ?? ?? ??").GetPtr(CClient__SendNetMsgEx);
|
||||
g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 55 56 41 55 41 56 41 57 48 8D 6C 24 ??").GetPtr(CClient__SendSnapshot);
|
||||
g_GameDll.FindPatternSIMD("40 53 57 48 83 EC 38 48 8B 05 ?? ?? ?? ??").GetPtr(CClient__WriteDataBlock);
|
||||
g_GameDll.FindPatternSIMD("48 89 6C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 7A 20").GetPtr(CClient__ProcessStringCmd);
|
||||
|
||||
g_GameDll.FindPatternSIMD("48 83 EC 28 48 83 C2 20").GetPtr(CClient__ProcessSetConVar);
|
||||
|
@ -5,6 +5,8 @@
|
||||
//===========================================================================//
|
||||
#include "engine/client/clientstate.h"
|
||||
#include "datablock_receiver.h"
|
||||
#include "engine/common.h"
|
||||
#include "engine/host_cmd.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
@ -22,3 +24,137 @@ void ClientDataBlockReceiver::AcknowledgeTransmission()
|
||||
{
|
||||
ClientDataBlockReceiver__AcknowledgeTransmission(this);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: initialize the data block receiver context
|
||||
//-----------------------------------------------------------------------------
|
||||
void ClientDataBlockReceiver::StartBlockReceiver(const int transferSize, const double startTime)
|
||||
{
|
||||
m_bStartedRecv = true;
|
||||
m_nTransferSize = transferSize;
|
||||
m_nTotalBlocks = transferSize / MAX_DATABLOCK_FRAGMENT_SIZE + (transferSize % MAX_DATABLOCK_FRAGMENT_SIZE != 0);
|
||||
m_nBlockAckTick = 0;
|
||||
m_flStartTime = startTime;
|
||||
|
||||
memset(m_BlockStatus, 0, sizeof(m_BlockStatus));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: reset the data block receiver context
|
||||
//-----------------------------------------------------------------------------
|
||||
void ClientDataBlockReceiver::ResetBlockReceiver(const short transferNr)
|
||||
{
|
||||
m_nTransferNr = transferNr;
|
||||
|
||||
m_bStartedRecv = false;
|
||||
m_bCompletedRecv = false;
|
||||
|
||||
m_TransferId = 0;
|
||||
m_nTotalBlocks = 0;
|
||||
m_nBlockAckTick = 0;
|
||||
m_flStartTime = 0.0;
|
||||
|
||||
memset(m_BlockStatus, 0, sizeof(m_BlockStatus));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: process the recv'd data block and reconstruct the fragmented data
|
||||
//-----------------------------------------------------------------------------
|
||||
bool ClientDataBlockReceiver::ProcessDataBlock(const double startTime, const short transferId, const int transferSize,
|
||||
const short transferNr, const short currentBlockId, const void* const blockBuffer, const int blockBufferBytes)
|
||||
{
|
||||
// should we process a new transfer?
|
||||
if (transferNr != m_nTransferNr)
|
||||
ResetBlockReceiver(transferNr);
|
||||
|
||||
m_bInitialized = true;
|
||||
|
||||
// make sure we always receive fragments in order
|
||||
if (transferId != m_TransferId || m_bCompletedRecv)
|
||||
return false;
|
||||
|
||||
// initialize the receiver if this is the firs fragment
|
||||
if (!m_bStartedRecv)
|
||||
StartBlockReceiver(transferSize, startTime);
|
||||
|
||||
// received more blocks than the total # expected?
|
||||
if (currentBlockId >= m_nTotalBlocks)
|
||||
return false;
|
||||
|
||||
// check if we have already copied the data block
|
||||
if (!m_BlockStatus[currentBlockId])
|
||||
{
|
||||
const int scratchBufferOffset = currentBlockId * MAX_DATABLOCK_FRAGMENT_SIZE;
|
||||
|
||||
if (blockBufferBytes + scratchBufferOffset <= m_nTransferSize)
|
||||
memcpy(m_pScratchBuffer + scratchBufferOffset + (sizeof(ClientDataBlockHeader_s) -1), blockBuffer, blockBufferBytes);
|
||||
|
||||
++m_nBlockAckTick;
|
||||
m_BlockStatus[currentBlockId] = true;
|
||||
}
|
||||
|
||||
// check if we have recv'd enough fragments to decode the data
|
||||
if (m_nBlockAckTick != m_nTotalBlocks)
|
||||
return true;
|
||||
|
||||
AcknowledgeTransmission();
|
||||
m_bCompletedRecv = true;
|
||||
|
||||
const ClientDataBlockHeader_s* const pHeader = reinterpret_cast<ClientDataBlockHeader_s*>(m_pScratchBuffer);
|
||||
|
||||
if (pHeader->isCompressed)
|
||||
{
|
||||
// NOTE: the engine's implementation of this function does NOT free
|
||||
// this buffer when a malformed/corrupt LZ4 packet is sent to the
|
||||
// receiver; wrapped buffer in unique_ptr to make sure it never leaks!
|
||||
std::unique_ptr<char> encodedDataBuf(new char[SNAPSHOT_SCRATCH_BUFFER_SIZE]);
|
||||
|
||||
char* const pEncodedDataBuf = encodedDataBuf.get();
|
||||
char* const dataLocation = m_pScratchBuffer + sizeof(ClientDataBlockHeader_s);
|
||||
|
||||
// copy the encoded data in the newly allocated buffer so we can decode back
|
||||
// into the data block buffer we copied the encoded data from
|
||||
const int compressedSize = m_nTransferSize -1;
|
||||
|
||||
memcpy(pEncodedDataBuf, dataLocation, compressedSize);
|
||||
const int numDecode = LZ4_decompress_safe(pEncodedDataBuf, dataLocation, compressedSize, SNAPSHOT_SCRATCH_BUFFER_SIZE);
|
||||
|
||||
if (numDecode < 0)
|
||||
{
|
||||
Assert(0);
|
||||
|
||||
COM_ExplainDisconnection(true, "LZ4 error decompressing data block from server.\n");
|
||||
v_Host_Disconnect(true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_nTransferSize = numDecode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// truncate the byte that determines whether the data was compressed
|
||||
m_nTransferSize--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// NOTE: detoured for 2 reasons:
|
||||
// 1: when a corrupt or malformed compress packet is sent, the code never freed
|
||||
// the temporary copy buffer it made to decode the data into the scratch buf
|
||||
// 2: exploring other compression algorithms for potential optimizations
|
||||
//-----------------------------------------------------------------------------
|
||||
static bool HK_ProcessDataBlock(ClientDataBlockReceiver* receiver, const double startTime,
|
||||
const short transferId, const int transferSize, const short transferNr,
|
||||
const short currentBlockId, const void* const blockBuffer, const int blockBufferBytes)
|
||||
{
|
||||
return receiver->ProcessDataBlock(startTime, transferId, transferSize, transferNr,
|
||||
currentBlockId, blockBuffer, blockBufferBytes);
|
||||
}
|
||||
|
||||
void VClientDataBlockReceiver::Detour(const bool bAttach) const
|
||||
{
|
||||
DetourAttach(&ClientDataBlockReceiver__ProcessDataBlock, HK_ProcessDataBlock);
|
||||
}
|
||||
|
@ -11,25 +11,44 @@ public:
|
||||
virtual ~ClientDataBlockReceiver();
|
||||
virtual void AcknowledgeTransmission() override;
|
||||
|
||||
void StartBlockReceiver(const int transferSize, const double startTime);
|
||||
void ResetBlockReceiver(const short transferNr);
|
||||
|
||||
bool ProcessDataBlock(const double startTime, const short transferId, const int transferSize,
|
||||
const short counter, const short currentBlockId, const void* const blockBuffer, const int blockBufferBytes);
|
||||
|
||||
protected:
|
||||
CClientState* m_pClientState;
|
||||
bool m_bStartedRecv;
|
||||
bool m_bCompletedRecv;
|
||||
bool byte12;
|
||||
short m_TransferId;
|
||||
short m_Counter;
|
||||
short m_nTransferNr;
|
||||
bool m_bInitialized;
|
||||
int m_nTransferSize;
|
||||
int m_nTotalBlocks;
|
||||
int m_nBlockAckTick;
|
||||
double m_flStartTime;
|
||||
bool m_BlockStatus[DATABLOCK_STATUS_SIZE];
|
||||
void* m_pBigBuffer;
|
||||
|
||||
bool m_BlockStatus[MAX_DATABLOCK_FRAGMENTS];
|
||||
char* m_pScratchBuffer;
|
||||
};
|
||||
|
||||
struct ClientDataBlockHeader_s
|
||||
{
|
||||
char reserved[3]; // unused in retail
|
||||
bool isCompressed;
|
||||
};
|
||||
|
||||
// virtual methods
|
||||
inline void*(*ClientDataBlockReceiver__Destructor)(ClientDataBlockReceiver* thisptr);
|
||||
inline void*(*ClientDataBlockReceiver__AcknowledgeTransmission)(ClientDataBlockReceiver* thisptr);
|
||||
|
||||
// non-virtual methods
|
||||
inline bool (*ClientDataBlockReceiver__ProcessDataBlock)(ClientDataBlockReceiver* thisptr, const double time,
|
||||
const short transferId, const int transferSize, const short counter, const short currentBlockId,
|
||||
const void* const blockBuffer, const int blockBufferBytes);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class VClientDataBlockReceiver : public IDetour
|
||||
{
|
||||
@ -37,6 +56,7 @@ class VClientDataBlockReceiver : public IDetour
|
||||
{
|
||||
LogFunAdr("ClientDataBlockReceiver::~ClientDataBlockReceiver", ClientDataBlockReceiver__Destructor);
|
||||
LogFunAdr("ClientDataBlockReceiver::AcknowledgeTransmission", ClientDataBlockReceiver__AcknowledgeTransmission);
|
||||
LogFunAdr("ClientDataBlockReceiver::ProcessDataBlock", ClientDataBlockReceiver__ProcessDataBlock);
|
||||
}
|
||||
virtual void GetFun(void) const
|
||||
{
|
||||
@ -45,10 +65,13 @@ class VClientDataBlockReceiver : public IDetour
|
||||
" ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 05 ?? ?? ?? ?? C6 41 12 00").GetPtr(ClientDataBlockReceiver__Destructor);
|
||||
|
||||
g_GameDll.FindPatternSIMD("40 53 48 81 EC ?? ?? ?? ?? 4C 8B 51 08").GetPtr(ClientDataBlockReceiver__AcknowledgeTransmission);
|
||||
|
||||
g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 30 0F B7 44 24 ??")
|
||||
.GetPtr(ClientDataBlockReceiver__ProcessDataBlock);
|
||||
}
|
||||
virtual void GetVar(void) const { }
|
||||
virtual void GetCon(void) const { }
|
||||
virtual void Detour(const bool bAttach) const { }
|
||||
virtual void Detour(const bool bAttach) const;
|
||||
};
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -17,11 +17,11 @@ ServerDataBlockSender::~ServerDataBlockSender()
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: sends the datablock
|
||||
//-----------------------------------------------------------------------------
|
||||
void ServerDataBlockSender::SendDataBlock(short unk0, int unk1,
|
||||
short unk2, short unk3, const void* buffer, int length)
|
||||
void ServerDataBlockSender::SendDataBlock(const short transferId, const int transferSize,
|
||||
const short transferNr, const short blockNr, const uint8_t* const blockData, const int blockSize)
|
||||
{
|
||||
ServerDataBlockSender__SendDataBlock(this, unk0, unk1,
|
||||
unk2, unk3, buffer, length);
|
||||
ServerDataBlockSender__SendDataBlock(this, transferId, transferSize,
|
||||
transferNr, blockNr, blockData, blockSize);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -29,26 +29,27 @@ void ServerDataBlockSender::SendDataBlock(short unk0, int unk1,
|
||||
//-----------------------------------------------------------------------------
|
||||
float ServerDataBlockSender::GetResendRate() const
|
||||
{
|
||||
float flRet = 0.0f;
|
||||
const CClient* const pClient = m_pClient;
|
||||
|
||||
if (!m_pClient)
|
||||
return flRet;
|
||||
if (!pClient)
|
||||
return 0.0f;
|
||||
|
||||
const CNetChan* const pChan = pClient->GetNetChan();
|
||||
|
||||
CNetChan* pChan = m_pClient->GetNetChan();
|
||||
if (!pChan)
|
||||
return flRet;
|
||||
return 0.0f;
|
||||
|
||||
if (!m_bStartedTransfer)
|
||||
if (m_bStartedTransfer)
|
||||
return 0.0f;
|
||||
|
||||
const float netResendRate = pChan->GetResendRate();
|
||||
|
||||
if (netResendRate < net_datablock_networkLossForSlowSpeed->GetFloat())
|
||||
{
|
||||
flRet = pChan->GetNetworkLoss();
|
||||
|
||||
if (flRet < net_datablock_networkLossForSlowSpeed->GetFloat())
|
||||
{
|
||||
return m_flResendRate;
|
||||
}
|
||||
return m_flResendRate;
|
||||
}
|
||||
|
||||
return flRet;
|
||||
return netResendRate;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -58,3 +59,121 @@ const char* ServerDataBlockSender::GetReceiverName() const
|
||||
{
|
||||
return m_pClient->m_szServerName;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: initialize the data block sender context
|
||||
//-----------------------------------------------------------------------------
|
||||
void ServerDataBlockSender::StartBlockSender(const int transferSize, const bool isMultiplayer, const char* const debugName)
|
||||
{
|
||||
m_bMultiplayer = isMultiplayer;
|
||||
m_nBlockAckTick = 0;
|
||||
m_nTransferSize = transferSize + sizeof(ServerDataBlockHeader_s);
|
||||
|
||||
// calculate the number of data blocks we have, which get sent individually
|
||||
// to the receiver
|
||||
m_nTotalBlocks = m_nTransferSize / MAX_DATABLOCK_FRAGMENT_SIZE + (m_nTransferSize % MAX_DATABLOCK_FRAGMENT_SIZE != 0);
|
||||
|
||||
strncpy(m_szDebugName, debugName, sizeof(m_szDebugName));
|
||||
m_szDebugName[sizeof(m_szDebugName) - 1] = '\0';
|
||||
|
||||
// null status memory
|
||||
memset(m_bBlockAckStatus, 0, sizeof(m_bBlockAckStatus));
|
||||
memset(m_flBlockSendTimes, 0, sizeof(m_flBlockSendTimes));
|
||||
|
||||
m_bInitialized = true;
|
||||
m_bStartedTransfer = false;
|
||||
|
||||
const double currentTime = Plat_FloatTime();
|
||||
|
||||
m_TimeLastSend = currentTime;
|
||||
m_TimeCurrentSend = currentTime;
|
||||
m_TimeFirstSend = currentTime;
|
||||
|
||||
m_nTotalSizeRemaining = 4096;
|
||||
m_nBlockSendsAttempted = 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: reset the data block sender context
|
||||
//-----------------------------------------------------------------------------
|
||||
void ServerDataBlockSender::ResetBlockSender(void)
|
||||
{
|
||||
if (!m_bInitialized)
|
||||
return;
|
||||
|
||||
m_bInitialized = false;
|
||||
m_bStartedTransfer = false;
|
||||
|
||||
m_nTransferId = 0;
|
||||
m_nTransferSize = 0;
|
||||
m_nTotalBlocks = 0;
|
||||
m_nBlockAckTick = 0;
|
||||
|
||||
m_TimeCurrentSend = 0.0;
|
||||
m_TimeFirstSend = 0.0;
|
||||
|
||||
m_nTotalSizeRemaining = 0;
|
||||
|
||||
m_TimeLastSend = 0.0;
|
||||
m_szDebugName[0] = '\0';
|
||||
m_bDumbDataBlockInfo = false;
|
||||
m_nCurrentBlock = -1;
|
||||
m_nBlockSendsAttempted = 0;
|
||||
|
||||
memset(m_bBlockAckStatus, 0, sizeof(m_bBlockAckStatus));
|
||||
memset(m_flBlockSendTimes, 0, sizeof(m_flBlockSendTimes));
|
||||
|
||||
m_nTransferNr++;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: write the whole data in the data block scratch buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
void ServerDataBlockSender::WriteDataBlock(const uint8_t* const sourceData, const int dataSize,
|
||||
const bool isMultiplayer, const char* const debugName)
|
||||
{
|
||||
AcquireSRWLockExclusive(&m_Lock);
|
||||
|
||||
ServerDataBlockHeader_s* const pHeader = reinterpret_cast<ServerDataBlockHeader_s*>(m_pScratchBuffer);
|
||||
bool copyRaw = true;
|
||||
|
||||
int actualDataSize = dataSize;
|
||||
|
||||
if (net_compressDataBlock->GetBool())
|
||||
{
|
||||
const int encodedSize = LZ4_compress_fast((const char*)sourceData, (char*)m_pScratchBuffer + sizeof(ServerDataBlockHeader_s),
|
||||
dataSize, SNAPSHOT_SCRATCH_BUFFER_SIZE, net_compressDataBlockLzAcceleration->GetInt());
|
||||
|
||||
// this shouldn't happen at all
|
||||
if (!encodedSize)
|
||||
{
|
||||
Assert(0);
|
||||
Error(eDLL_T::SERVER, 0, "LZ4 error compressing data block for client.\n");
|
||||
}
|
||||
|
||||
// make sure the encoded data is smaller than the raw data, in some cases
|
||||
// this might turn larger which means we should just send raw data
|
||||
else if (encodedSize < dataSize)
|
||||
{
|
||||
actualDataSize = encodedSize;
|
||||
|
||||
pHeader->isCompressed = true;
|
||||
copyRaw = false;
|
||||
}
|
||||
}
|
||||
|
||||
// in case no compression was performed, we send the raw data
|
||||
if (copyRaw)
|
||||
{
|
||||
// this should equal the dataSize at this point, even if compression failed
|
||||
Assert(actualDataSize == dataSize);
|
||||
|
||||
pHeader->isCompressed = false;
|
||||
memcpy(m_pScratchBuffer + sizeof(ServerDataBlockHeader_s), sourceData, actualDataSize);
|
||||
}
|
||||
|
||||
// create the context
|
||||
StartBlockSender(actualDataSize, isMultiplayer, debugName);
|
||||
|
||||
ReleaseSRWLockExclusive(&m_Lock);
|
||||
}
|
||||
|
@ -9,39 +9,75 @@ class ServerDataBlockSender : public NetDataBlockSender
|
||||
friend class CClient;
|
||||
public:
|
||||
virtual ~ServerDataBlockSender() override;
|
||||
virtual void SendDataBlock(short unk0, int unk1,
|
||||
short unk2, short unk3, const void* buffer, int length) override;
|
||||
virtual void SendDataBlock(const short transferId, const int transferSize, const short transferNr,
|
||||
const short blockNr, const uint8_t* const blockData, const int blockSize) override;
|
||||
|
||||
virtual float GetResendRate() const override;
|
||||
virtual const char* GetReceiverName() const override;
|
||||
|
||||
void StartBlockSender(const int transferSize, const bool isMultiplayer, const char* const debugName);
|
||||
void ResetBlockSender();
|
||||
|
||||
void WriteDataBlock(const uint8_t* const sourceData, const int dataSize, const bool isMultiplayer, const char* const debugName);
|
||||
|
||||
protected:
|
||||
char pad_0008[56];
|
||||
RTL_SRWLOCK LOCK;
|
||||
RTL_SRWLOCK m_Lock;
|
||||
char pad_0048[56];
|
||||
|
||||
// the server side client handle that is our 'receiving' end
|
||||
CClient* m_pClient;
|
||||
|
||||
char m_bInitialized;
|
||||
char m_bStartedTransfer;
|
||||
|
||||
char m_bMultiplayer;
|
||||
char field_8B;
|
||||
|
||||
// the current transfer id, and the global transfer count for this
|
||||
// particular client. the transfer nr keeps getting incremented on
|
||||
// each new context setup
|
||||
short m_nTransferId;
|
||||
short m_nCounter;
|
||||
short m_nTransferNr;
|
||||
|
||||
// the total transfer size for the data, and the number of blocks this data
|
||||
// has been carved up to
|
||||
int m_nTransferSize;
|
||||
int m_nTotalBlocks;
|
||||
|
||||
// last block that has been ack'd, and the current block that is pending to
|
||||
// be sent to the receiver
|
||||
int m_nBlockAckTick;
|
||||
int m_nCurrentBlock;
|
||||
int m_nUnkA0;
|
||||
|
||||
// the total number of bytes remaining to be sent, and the number of times
|
||||
// we attempted to send data blocks
|
||||
int m_nTotalSizeRemaining;
|
||||
int m_nBlockSendsAttempted;
|
||||
|
||||
// the resend rate for this connection, which depends of the stability/loss
|
||||
// and other factors computed from the netchan
|
||||
float m_flResendRate;
|
||||
char pad_00AC[4];
|
||||
char pad_00AC[4]; // padding, in case we want to stuff our own vars in here
|
||||
|
||||
// times used to determine when a data block has been sent, and how long it
|
||||
// took to get this out and acknowledged
|
||||
double m_TimeCurrentSend;
|
||||
double m_TimeFirstSend;
|
||||
double m_TimeLastSend;
|
||||
double m_flBlockTimesArray[DATABLOCK_STATUS_SIZE];
|
||||
char m_szDebugName[64];
|
||||
bool m_bBlockStatusArray[DATABLOCK_STATUS_SIZE];
|
||||
void* m_pData;
|
||||
bool m_bAbnormalSending_Maybe;
|
||||
|
||||
// the last time we attempted to send this block, this gets updated when
|
||||
// a data block hasn't been acknowledged in time and is being resent
|
||||
double m_flBlockSendTimes[MAX_DATABLOCK_FRAGMENTS];
|
||||
|
||||
// the debug name used when details get dumped to the console
|
||||
char m_szDebugName[MAX_DATABLOCK_DEBUG_NAME];
|
||||
|
||||
// if a data block has been acknowledged by the receiver, we mark that
|
||||
// particular block as acknowledged
|
||||
bool m_bBlockAckStatus[MAX_DATABLOCK_FRAGMENTS];
|
||||
uint8_t* m_pScratchBuffer;
|
||||
bool m_bDumbDataBlockInfo;
|
||||
};
|
||||
|
||||
struct ServerDataBlock
|
||||
@ -52,9 +88,15 @@ struct ServerDataBlock
|
||||
ServerDataBlockSender sender;
|
||||
};
|
||||
|
||||
struct ServerDataBlockHeader_s
|
||||
{
|
||||
bool isCompressed;
|
||||
};
|
||||
|
||||
inline void*(*ServerDataBlockSender__Destructor)(ServerDataBlockSender* thisptr);
|
||||
inline void* (*ServerDataBlockSender__SendDataBlock)(ServerDataBlockSender* thisptr,
|
||||
short unk0, int unk1, short unk2, short unk3, const void* buffer, int length);
|
||||
const short transferId, const int transferSize, const short transferNr,
|
||||
const short blockNr, const uint8_t* const blockData, const int blockSize);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class VServerDataBlockSender : public IDetour
|
||||
|
@ -1,14 +1,20 @@
|
||||
#ifndef IDATABLOCK_H
|
||||
#define IDATABLOCK_H
|
||||
|
||||
#define DATABLOCK_STATUS_SIZE 0x300
|
||||
// the maximum size of each data block fragment
|
||||
#define MAX_DATABLOCK_FRAGMENT_SIZE 1024
|
||||
|
||||
// the maximum amount of fragments per data block
|
||||
#define MAX_DATABLOCK_FRAGMENTS 768
|
||||
|
||||
#define MAX_DATABLOCK_DEBUG_NAME 64
|
||||
|
||||
abstract_class NetDataBlockSender
|
||||
{
|
||||
public:
|
||||
virtual ~NetDataBlockSender() {};
|
||||
virtual void SendDataBlock(short unk0, int unk1,
|
||||
short unk2, short unk3, const void* buffer, int length) = 0;
|
||||
virtual void SendDataBlock(const short transferId, const int transferSize,
|
||||
const short transferNr, const short blockNr, const uint8_t* const blockData, const int blockSize) = 0;
|
||||
virtual float GetResendRate() const = 0;
|
||||
virtual const char* GetReceiverName() const = 0;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user