summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorheinrich5991 <heinrich5991@gmail.com>2018-10-06 12:43:51 +0200
committerheinrich5991 <heinrich5991@gmail.com>2018-10-12 22:09:24 +0200
commitaababc63eeeee1bc41672502ca6c7a1dd9f61d94 (patch)
treed098df2f76ea965eadf48de9dcba00e57766831c
parentee2afdac33f43d96a457bcf692a817489d6f896e (diff)
Add a backward compatible handshake
This work is basically on eeeee's handshake developed for DDRace. It works by realizing that the client will send back tick numbers sent in snapshots. The client puts these into a field named "last acked snapshot", aka the last snapshot the client saw (used for delta compression). This can be abused for a challenge-response handshake. For legacy clients (detected by a short `CTRLMSG_CONNECT` message), the idea is, upon reception of the `CTRLMSG_CONNECT` packet, to send a `CTRLMSG_CONNECTACCEPT` to fake accepting the connection, then send a packet containing all of the following: the rest of the initial connection build up (`MAP_CHANGE` to the standard dm1 map, `CON_READY`) and three empty snapshots (`SNAPEMPTY`) with the desired challenge. Due to client-side constraints, the token must be between 2 and `MAX_INT`. This lowers the security by roughly one bit to around 31 bits. If the `CTRLMSG_CONNECTACCEPT` message gets through to the client, but the other packet does not, the client is stuck. It won't receive any more packets from the server. If the client does not have the standard dm1 map, it will crash, since it accepts the `CON_READY` message from the server despite not having any map data. No data is saved until this point. When one receives an `INPUT` packet by a previously unknown client, the server checks whether it contains a correct token, and if it does, accept the new client. The client has received two vital messages from the server so far, so it expects the next sequence number to be 3. The client has sent an unknown amount of vital messages (might be a custom client) so we don't know what ack numbers it wants to see. We just treat the first vital chunk we receive as the new ack number. If we miss a packet due to that, the handshake will be broken and the client will be stuck. We send a `MAP_CHANGE` to the current map of the server and continue normally. Due to the large difference between packet sizes sent by the client to packets sent by the server, this legacy handshake is prone to reflection attacks due to IP spoofing. Rate limiting should be added.
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/engine/server/server.cpp9
-rw-r--r--src/engine/server/server.h2
-rw-r--r--src/engine/shared/config_variables.h2
-rw-r--r--src/engine/shared/network.cpp10
-rw-r--r--src/engine/shared/network.h22
-rw-r--r--src/engine/shared/network_client.cpp2
-rw-r--r--src/engine/shared/network_conn.cpp26
-rw-r--r--src/engine/shared/network_console.cpp2
-rw-r--r--src/engine/shared/network_server.cpp127
-rw-r--r--src/engine/shared/network_server_hack.cpp89
11 files changed, 267 insertions, 25 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7a8fb99e0..45db40b8d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -507,6 +507,7 @@ set_glob(ENGINE_SHARED GLOB src/engine/shared
network_console.cpp
network_console_conn.cpp
network_server.cpp
+ network_server_hack.cpp
packer.cpp
packer.h
protocol.h
diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp
index 14778e8f5..bd03a08ee 100644
--- a/src/engine/server/server.cpp
+++ b/src/engine/server/server.cpp
@@ -686,10 +686,10 @@ void CServer::DoSnapshot()
}
-int CServer::NewClientCallback(int ClientID, void *pUser)
+int CServer::NewClientCallback(int ClientID, bool Legacy, void *pUser)
{
CServer *pThis = (CServer *)pUser;
- pThis->m_aClients[ClientID].m_State = CClient::STATE_AUTH;
+ pThis->m_aClients[ClientID].m_State = !Legacy ? CClient::STATE_AUTH : CClient::STATE_CONNECTING;
pThis->m_aClients[ClientID].m_aName[0] = 0;
pThis->m_aClients[ClientID].m_aClan[0] = 0;
pThis->m_aClients[ClientID].m_Country = -1;
@@ -697,6 +697,11 @@ int CServer::NewClientCallback(int ClientID, void *pUser)
pThis->m_aClients[ClientID].m_AuthTries = 0;
pThis->m_aClients[ClientID].m_pRconCmdToSend = 0;
pThis->m_aClients[ClientID].Reset();
+
+ if(Legacy)
+ {
+ pThis->SendMap(ClientID);
+ }
return 0;
}
diff --git a/src/engine/server/server.h b/src/engine/server/server.h
index c3c1794dc..355882ad2 100644
--- a/src/engine/server/server.h
+++ b/src/engine/server/server.h
@@ -193,7 +193,7 @@ public:
void DoSnapshot();
- static int NewClientCallback(int ClientID, void *pUser);
+ static int NewClientCallback(int ClientID, bool Legacy, void *pUser);
static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
void SendMap(int ClientID);
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index e62ddf37b..3da55d41b 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -83,7 +83,7 @@ MACRO_CONFIG_STR(SvName, sv_name, 128, "unnamed server", CFGFLAG_SERVER, "Server
MACRO_CONFIG_STR(Bindaddr, bindaddr, 128, "", CFGFLAG_CLIENT|CFGFLAG_SERVER|CFGFLAG_MASTER, "Address to bind the client/server to")
MACRO_CONFIG_INT(SvPort, sv_port, 8303, 0, 0, CFGFLAG_SERVER, "Port to use for the server")
MACRO_CONFIG_INT(SvExternalPort, sv_external_port, 0, 0, 0, CFGFLAG_SERVER, "External port to report to the master servers")
-MACRO_CONFIG_INT(SvAllowOldClients, sv_allow_old_clients, 1, 0, 1, CFGFLAG_SERVER, "Allow clients to connect that do not support the anti-spoof protocol")
+MACRO_CONFIG_INT(SvAllowOldClients, sv_allow_old_clients, 1, 0, 1, CFGFLAG_SERVER, "Allow clients to connect that do not support the anti-spoof protocol (this presents a DoS risk)")
MACRO_CONFIG_STR(SvMap, sv_map, 128, "dm1", CFGFLAG_SERVER, "Map to use on the server")
MACRO_CONFIG_INT(SvMaxClients, sv_max_clients, 8, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients that are allowed on a server")
MACRO_CONFIG_INT(SvMaxClientsPerIP, sv_max_clients_per_ip, 4, 1, MAX_CLIENTS, CFGFLAG_SERVER, "Maximum number of clients with the same IP that can connect to the server")
diff --git a/src/engine/shared/network.cpp b/src/engine/shared/network.cpp
index 6a2eb921c..54d8add15 100644
--- a/src/engine/shared/network.cpp
+++ b/src/engine/shared/network.cpp
@@ -58,10 +58,16 @@ int CNetRecvUnpacker::FetchChunk(CNetChunk *pChunk)
// handle sequence stuff
if(m_pConnection && (Header.m_Flags&NET_CHUNKFLAG_VITAL))
{
- if(Header.m_Sequence == (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE)
+ if(m_pConnection->m_UnknownAck || Header.m_Sequence == (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE)
{
+ // in case we're in the backward compatibility
+ // path, we don't know the client's sequence
+ // number, so we can't decide whether this one
+ // is correct. but now we know.
+ m_pConnection->m_UnknownAck = false;
+
// in sequence
- m_pConnection->m_Ack = (m_pConnection->m_Ack+1)%NET_MAX_SEQUENCE;
+ m_pConnection->m_Ack = Header.m_Sequence;
}
else
{
diff --git a/src/engine/shared/network.h b/src/engine/shared/network.h
index 90cc1076b..c265c1ab8 100644
--- a/src/engine/shared/network.h
+++ b/src/engine/shared/network.h
@@ -3,6 +3,8 @@
#ifndef ENGINE_SHARED_NETWORK_H
#define ENGINE_SHARED_NETWORK_H
+#include <base/system.h>
+
#include "ringbuffer.h"
#include "huffman.h"
@@ -79,12 +81,15 @@ enum
NET_CONN_BUFFERSIZE=1024*32,
+ NET_COMPATIBILITY_SEQ=2,
+
NET_ENUM_TERMINATOR
};
typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser);
-typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser);
+typedef int (*NETFUNC_NEWCLIENT)(int ClientID, bool Legacy, void *pUser);
+typedef int (*NETFUNC_NEWCLIENT_CON)(int ClientID, void *pUser);
struct CNetChunk
{
@@ -140,6 +145,7 @@ class CNetConnection
friend class CNetRecvUnpacker;
private:
unsigned short m_Sequence;
+ bool m_UnknownAck; // ack not known due to the backward compatibility hack
unsigned short m_Ack;
unsigned short m_PeerAck;
unsigned m_State;
@@ -179,6 +185,7 @@ public:
void Init(NETSOCKET Socket, bool BlockCloseMsg);
int Connect(NETADDR *pAddr);
int Accept(NETADDR *pAddr, unsigned Token);
+ int AcceptLegacy(NETADDR *pAddr);
void Disconnect(const char *pReason);
int Update();
@@ -279,6 +286,10 @@ class CNetServer
unsigned GetToken(const NETADDR &Addr, int SaltIndex) const;
bool IsCorrectToken(const NETADDR &Addr, unsigned Token) const;
+ unsigned GetLegacyToken(const NETADDR &Addr) const;
+ unsigned GetLegacyToken(const NETADDR &Addr, int SaltIndex) const;
+ bool IsCorrectLegacyToken(const NETADDR &Addr, unsigned LegacyToken) const;
+
public:
int SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
@@ -316,14 +327,14 @@ class CNetConsole
class CNetBan *m_pNetBan;
CSlot m_aSlots[NET_MAX_CONSOLE_CLIENTS];
- NETFUNC_NEWCLIENT m_pfnNewClient;
+ NETFUNC_NEWCLIENT_CON m_pfnNewClient;
NETFUNC_DELCLIENT m_pfnDelClient;
void *m_UserPtr;
CNetRecvUnpacker m_RecvUnpacker;
public:
- void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
+ void SetCallbacks(NETFUNC_NEWCLIENT_CON pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
//
bool Open(NETADDR BindAddr, class CNetBan *pNetBan, int Flags);
@@ -378,6 +389,11 @@ public:
const char *ErrorString();
};
+// backward compatibility hack
+unsigned DeriveLegacyToken(unsigned Token);
+void ConstructLegacyHandshake(CNetPacketConstruct *pPacket1, CNetPacketConstruct *pPacket2, unsigned LegacyToken);
+bool DecodeLegacyHandshake(const void *pData, int DataSize, unsigned *pLegacyToken);
+
// TODO: both, fix these. This feels like a junk class for stuff that doesn't fit anywere
diff --git a/src/engine/shared/network_client.cpp b/src/engine/shared/network_client.cpp
index eea63d28f..d7ca73d22 100644
--- a/src/engine/shared/network_client.cpp
+++ b/src/engine/shared/network_client.cpp
@@ -85,7 +85,9 @@ int CNetClient::Recv(CNetChunk *pChunk)
{
if(m_Connection.State() != NET_CONNSTATE_OFFLINE && m_Connection.State() != NET_CONNSTATE_ERROR && net_addr_comp(m_Connection.PeerAddress(), &Addr) == 0
&& m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
+ {
m_RecvUnpacker.Start(&Addr, &m_Connection, 0);
+ }
}
}
}
diff --git a/src/engine/shared/network_conn.cpp b/src/engine/shared/network_conn.cpp
index 0af9d48d5..1c38c7001 100644
--- a/src/engine/shared/network_conn.cpp
+++ b/src/engine/shared/network_conn.cpp
@@ -12,6 +12,7 @@ void CNetConnection::ResetStats()
void CNetConnection::Reset()
{
m_Sequence = 0;
+ m_UnknownAck = false;
m_Ack = 0;
m_PeerAck = 0;
m_RemoteClosed = 0;
@@ -200,6 +201,7 @@ int CNetConnection::Accept(NETADDR *pAddr, unsigned Token)
m_PeerAddr = *pAddr;
mem_zero(m_ErrorString, sizeof(m_ErrorString));
m_State = NET_CONNSTATE_ONLINE;
+ m_LastRecvTime = time_get();
m_Token = Token;
if(g_Config.m_Debug)
{
@@ -208,6 +210,30 @@ int CNetConnection::Accept(NETADDR *pAddr, unsigned Token)
return 0;
}
+int CNetConnection::AcceptLegacy(NETADDR *pAddr)
+{
+ if(State() != NET_CONNSTATE_OFFLINE)
+ return -1;
+
+ // init connection
+ Reset();
+ m_PeerAddr = *pAddr;
+ mem_zero(m_ErrorString, sizeof(m_ErrorString));
+ m_State = NET_CONNSTATE_ONLINE;
+ m_LastRecvTime = time_get();
+
+ m_Token = 0;
+ m_UseToken = false;
+ m_UnknownAck = true;
+ m_Sequence = NET_COMPATIBILITY_SEQ;
+
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("connection", "legacy connecting online");
+ }
+ return 0;
+}
+
void CNetConnection::Disconnect(const char *pReason)
{
if(State() == NET_CONNSTATE_OFFLINE)
diff --git a/src/engine/shared/network_console.cpp b/src/engine/shared/network_console.cpp
index ded83f683..db8104b52 100644
--- a/src/engine/shared/network_console.cpp
+++ b/src/engine/shared/network_console.cpp
@@ -31,7 +31,7 @@ bool CNetConsole::Open(NETADDR BindAddr, CNetBan *pNetBan, int Flags)
return true;
}
-void CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
+void CNetConsole::SetCallbacks(NETFUNC_NEWCLIENT_CON pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
{
m_pfnNewClient = pfnNewClient;
m_pfnDelClient = pfnDelClient;
diff --git a/src/engine/shared/network_server.cpp b/src/engine/shared/network_server.cpp
index e12de47ad..877a25a07 100644
--- a/src/engine/shared/network_server.cpp
+++ b/src/engine/shared/network_server.cpp
@@ -128,6 +128,30 @@ bool CNetServer::IsCorrectToken(const NETADDR &Addr, unsigned Token) const
return false;
}
+unsigned CNetServer::GetLegacyToken(const NETADDR &Addr) const
+{
+ return GetLegacyToken(Addr, m_CurrentSalt);
+}
+
+unsigned CNetServer::GetLegacyToken(const NETADDR &Addr, int SaltIndex) const
+{
+ return DeriveLegacyToken(GetToken(Addr, SaltIndex));
+}
+
+bool CNetServer::IsCorrectLegacyToken(const NETADDR &Addr, unsigned LegacyToken) const
+{
+ for(unsigned i = 0; i < sizeof(m_aaSalts) / sizeof(m_aaSalts[0]); i++)
+ {
+ if(GetLegacyToken(Addr, i) == LegacyToken)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
/*
TODO: chopp up this function into smaller working parts
*/
@@ -201,19 +225,44 @@ int CNetServer::Recv(CNetChunk *pChunk)
{
continue; // silent ignore.. we got this client already
}
- if(m_RecvUnpacker.m_Data.m_DataSize < 1+512)
+ if(m_RecvUnpacker.m_Data.m_DataSize >= 1+512)
{
+ unsigned MyToken = GetToken(Addr);
+ unsigned char aConnectAccept[4];
+ uint32_to_be(&aConnectAccept[0], MyToken);
+ CNetBase::SendControlMsg(m_Socket, &Addr, 0, true, Token, NET_CTRLMSG_CONNECTACCEPT, aConnectAccept, sizeof(aConnectAccept));
if(g_Config.m_Debug)
{
- dbg_msg("netserver", "dropping short connect packet, size=%d", m_RecvUnpacker.m_Data.m_DataSize);
+ dbg_msg("netserver", "got connect, sending connect+accept challenge");
}
- continue;
}
+ // the legacy handshake doesn't support
+ // passwords, allowing the legacy
+ // handshake to function while a
+ // password is set would let these
+ // clients bypass the password check.
+ else if(g_Config.m_SvAllowOldClients && !g_Config.m_Password[0])
+ {
+ CNetPacketConstruct aPackets[2];
- unsigned MyToken = GetToken(Addr);
- unsigned char aConnectAccept[4];
- uint32_to_be(&aConnectAccept[0], MyToken);
- CNetBase::SendControlMsg(m_Socket, &Addr, 0, true, Token, NET_CTRLMSG_CONNECTACCEPT, aConnectAccept, sizeof(aConnectAccept));
+ unsigned LegacyToken = GetLegacyToken(Addr);
+ ConstructLegacyHandshake(&aPackets[0], &aPackets[1], LegacyToken);
+ for(int i = 0; i < 2; i++)
+ {
+ CNetBase::SendPacket(m_Socket, &Addr, &aPackets[i]);
+ }
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("netserver", "got legacy connect, sending legacy challenge");
+ }
+ }
+ else
+ {
+ if(g_Config.m_Debug)
+ {
+ dbg_msg("netserver", "dropping short connect packet, size=%d", m_RecvUnpacker.m_Data.m_DataSize);
+ }
+ }
}
else
{
@@ -222,11 +271,46 @@ int CNetServer::Recv(CNetChunk *pChunk)
{
if(!UseToken || !IsCorrectToken(Addr, Token))
{
- if(g_Config.m_Debug)
+ if(!UseToken && g_Config.m_SvAllowOldClients)
{
- dbg_msg("netserver", "dropping packet with missing/invalid token, present=%d token=%08x", (int)UseToken, Token);
+ m_RecvUnpacker.Start(&Addr, 0, -1);
+ CNetChunk Chunk;
+ unsigned LegacyToken;
+ bool Correct = false;
+ while(m_RecvUnpacker.FetchChunk(&Chunk))
+ {
+ if(DecodeLegacyHandshake(Chunk.m_pData, Chunk.m_DataSize, &LegacyToken))
+ {
+ if(IsCorrectLegacyToken(Addr, LegacyToken))
+ {
+ Correct = true;
+ break;
+ }
+ }
+ }
+ m_RecvUnpacker.Clear();
+ if(!Correct)
+ {
+ continue;
+ }
+ // if we find a correct token, fall through to
+ // the other connection handling below.
+ }
+ else
+ {
+ if(g_Config.m_Debug)
+ {
+ if(!UseToken)
+ {
+ dbg_msg("netserver", "dropping packet with missing token");
+ }
+ else
+ {
+ dbg_msg("netserver", "dropping packet with invalid token, token=%08x", (int)UseToken, Token);
+ }
+ }
+ continue;
}
- continue;
}
// only allow a specific number of players with the same ip
NETADDR ThisAddr = Addr, OtherAddr;
@@ -265,16 +349,29 @@ int CNetServer::Recv(CNetChunk *pChunk)
{
const char aFullMsg[] = "This server is full";
CNetBase::SendControlMsg(m_Socket, &Addr, 0, UseToken, Token, NET_CTRLMSG_CLOSE, aFullMsg, sizeof(aFullMsg));
+ continue;
+ }
+ ClientID = EmptySlot;
+ if(UseToken)
+ {
+ m_aSlots[ClientID].m_Connection.Accept(&Addr, Token);
}
else
{
- m_aSlots[EmptySlot].m_Connection.Accept(&Addr, Token);
- m_aSlots[EmptySlot].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
- if(m_pfnNewClient)
- m_pfnNewClient(EmptySlot, m_UserPtr);
+ m_aSlots[ClientID].m_Connection.AcceptLegacy(&Addr);
}
+ if(m_pfnNewClient)
+ {
+ m_pfnNewClient(ClientID, !UseToken, m_UserPtr);
+ }
+ if(!UseToken)
+ {
+ // Do not process the packet furtherly if it comes from a legacy handshake.
+ continue;
+ }
+ m_aSlots[ClientID].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr);
}
- else
+ if(m_aSlots[ClientID].m_Connection.Feed(&m_RecvUnpacker.m_Data, &Addr))
{
// normal packet
if(m_RecvUnpacker.m_Data.m_DataSize)
diff --git a/src/engine/shared/network_server_hack.cpp b/src/engine/shared/network_server_hack.cpp
new file mode 100644
index 000000000..01c137d0a
--- /dev/null
+++ b/src/engine/shared/network_server_hack.cpp
@@ -0,0 +1,89 @@
+#include "network.h"
+
+#include "packer.h"
+#include "protocol.h"
+
+
+unsigned DeriveLegacyToken(unsigned Token)
+{
+ // Clear the highest bit to get rid of negative numbers.
+ Token &= ~0x80000000;
+ if(Token < 2)
+ {
+ Token += 2;
+ }
+ return Token;
+}
+
+static void AddChunk(CNetPacketConstruct *pPacket, int Sequence, const void *pData, int DataSize)
+{
+ dbg_assert(pPacket->m_DataSize + NET_MAX_CHUNKHEADERSIZE + DataSize <= (int)sizeof(pPacket->m_aChunkData), "too much data");
+
+ CNetChunkHeader Header;
+ Header.m_Flags = Sequence >= 0 ? NET_CHUNKFLAG_VITAL : 0;
+ Header.m_Size = DataSize;
+ Header.m_Sequence = Sequence >= 0 ? Sequence : 0;
+
+ unsigned char *pPacketData = &pPacket->m_aChunkData[pPacket->m_DataSize];
+ const unsigned char *pChunkStart = pPacketData;
+ pPacketData = Header.Pack(pPacketData);
+ pPacket->m_DataSize += pPacketData - pChunkStart;
+ mem_copy(pPacketData, pData, DataSize);
+ pPacket->m_DataSize += DataSize;
+ pPacket->m_NumChunks++;
+}
+
+static int System(int MsgID)
+{
+ return (MsgID << 1) | 1;
+}
+
+void ConstructLegacyHandshake(CNetPacketConstruct *pPacket1, CNetPacketConstruct *pPacket2, unsigned LegacyToken)
+{
+ pPacket1->m_Flags = NET_PACKETFLAG_CONTROL;
+ pPacket1->m_Ack = 0;
+ pPacket1->m_NumChunks = 0;
+ pPacket1->m_DataSize = 1;
+ pPacket1->m_aChunkData[0] = NET_CTRLMSG_CONNECTACCEPT;
+
+ pPacket2->m_Flags = 0;
+ pPacket2->m_Ack = 0;
+ pPacket2->m_NumChunks = 0;
+ pPacket2->m_DataSize = 0;
+
+ CPacker Packer;
+
+ Packer.Reset();
+ Packer.AddInt(System(NETMSG_MAP_CHANGE));
+ Packer.AddString("dm1", 0);
+ Packer.AddInt(0xf2159e6e);
+ Packer.AddInt(5805);
+ AddChunk(pPacket2, 1, Packer.Data(), Packer.Size());
+
+ Packer.Reset();
+ Packer.AddInt(System(NETMSG_CON_READY));
+ AddChunk(pPacket2, 2, Packer.Data(), Packer.Size());
+
+ for(int i = -2; i <= 0; i++)
+ {
+ Packer.Reset();
+ Packer.AddInt(System(NETMSG_SNAPEMPTY));
+ Packer.AddInt(LegacyToken + i);
+ Packer.AddInt(i == -2 ? LegacyToken + i + 1 : 1);
+ AddChunk(pPacket2, -1, Packer.Data(), Packer.Size());
+ }
+}
+
+bool DecodeLegacyHandshake(const void *pData, int DataSize, unsigned *pLegacyToken)
+{
+ CUnpacker Unpacker;
+ Unpacker.Reset(pData, DataSize);
+ int MsgID = Unpacker.GetInt();
+ int Token = Unpacker.GetInt();
+ if(Unpacker.Error() || MsgID != System(NETMSG_INPUT))
+ {
+ return false;
+ }
+ *pLegacyToken = Token;
+ return true;
+}