2023-06-22 00:33:00 +02:00
|
|
|
//===========================================================================//
|
|
|
|
//
|
2024-04-05 18:06:36 +02:00
|
|
|
// Purpose: client side data block receiver
|
2023-06-22 00:33:00 +02:00
|
|
|
//
|
|
|
|
//===========================================================================//
|
|
|
|
#include "engine/client/clientstate.h"
|
|
|
|
#include "datablock_receiver.h"
|
2024-04-05 18:06:36 +02:00
|
|
|
#include "common/proto_oob.h"
|
2024-02-19 19:29:47 +01:00
|
|
|
#include "engine/common.h"
|
|
|
|
#include "engine/host_cmd.h"
|
2023-06-22 00:33:00 +02:00
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: send an ack back to the server to let them know
|
2024-04-05 18:06:36 +02:00
|
|
|
// we received the data block
|
2023-06-22 00:33:00 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ClientDataBlockReceiver::AcknowledgeTransmission()
|
|
|
|
{
|
2024-04-05 18:06:36 +02:00
|
|
|
const CClientState* const cl = m_pClientState;
|
2024-02-19 19:29:47 +01:00
|
|
|
|
2024-04-05 18:06:36 +02:00
|
|
|
if (!cl)
|
|
|
|
{
|
|
|
|
Assert(0, "ClientDataBlockReceiver::AcknowledgeTransmission() called without a valid client handle!");
|
|
|
|
return;
|
|
|
|
}
|
2024-02-19 19:29:47 +01:00
|
|
|
|
2024-04-05 18:06:36 +02:00
|
|
|
const CNetChan* const chan = cl->m_NetChannel;
|
2024-02-19 19:29:47 +01:00
|
|
|
|
2024-04-05 18:06:36 +02:00
|
|
|
if (!chan)
|
|
|
|
{
|
|
|
|
Assert(0, "ClientDataBlockReceiver::AcknowledgeTransmission() called without a net channel!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
char dataBuf[DATABLOCK_FRAGMENT_PACKET_SIZE];
|
|
|
|
bf_write buf(&dataBuf, sizeof(dataBuf));
|
|
|
|
|
|
|
|
buf.WriteLong(CONNECTIONLESS_HEADER);
|
|
|
|
buf.WriteByte(C2S_DATABLOCK_ACK);
|
2024-02-19 19:29:47 +01:00
|
|
|
|
2024-04-05 18:06:36 +02:00
|
|
|
buf.WriteShort(m_TransferId);
|
|
|
|
buf.WriteShort(m_nTransferNr);
|
2024-02-19 19:29:47 +01:00
|
|
|
|
2024-04-05 18:06:36 +02:00
|
|
|
for (int i = m_nTotalBlocks; (i--) > 0;)
|
|
|
|
{
|
|
|
|
if (m_BlockStatus[i])
|
|
|
|
{
|
|
|
|
// ack the last blockNr we recv'd and processed
|
|
|
|
buf.WriteShort(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-02-19 19:29:47 +01:00
|
|
|
|
2024-04-05 18:06:36 +02:00
|
|
|
// send the data block ack packet
|
|
|
|
v_NET_SendPacket(NULL,
|
|
|
|
chan->GetSocket(),
|
|
|
|
chan->GetRemoteAddress(),
|
|
|
|
buf.GetData(),
|
|
|
|
buf.GetNumBytesWritten(),
|
|
|
|
NULL, false, NULL, true);
|
2024-02-19 19:29:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// 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);
|
|
|
|
}
|