From 0d7872c79eaeb19b3fd08c39c013a1043db1fd9b Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Fri, 22 May 2020 17:58:41 +0200 Subject: [PATCH] Send DDNet version early in the connection process This gets rid of the problem that we don't know whether we should send full snapshots to clients because they haven't told us about them being DDNet yet. --- src/engine/client.h | 2 + src/engine/client/client.cpp | 13 ++ src/engine/client/client.h | 2 + src/engine/server.h | 12 +- src/engine/server/server.cpp | 66 +++++-- src/engine/server/server.h | 7 + src/engine/shared/protocol.h | 1 + src/engine/shared/protocol_ex_msgs.h | 1 + src/engine/shared/teehistorian_ex_chunks.h | 2 + src/game/client/gameclient.cpp | 11 ++ src/game/client/gameclient.h | 4 + src/game/server/ddracechat.cpp | 6 +- src/game/server/entities/character.cpp | 6 +- src/game/server/entities/projectile.cpp | 2 +- src/game/server/gamecontext.cpp | 192 ++++++++++++--------- src/game/server/gamecontext.h | 2 +- src/game/server/gamecontroller.cpp | 2 +- src/game/server/gameworld.cpp | 4 +- src/game/server/player.cpp | 20 ++- src/game/server/player.h | 2 +- src/game/server/teams.cpp | 10 +- src/game/server/teehistorian.cpp | 34 ++++ src/game/server/teehistorian.h | 3 + src/game/version.h | 1 + src/test/teehistorian.cpp | 37 ++++ 25 files changed, 319 insertions(+), 123 deletions(-) diff --git a/src/engine/client.h b/src/engine/client.h index b6ed31a03..e8b3af4be 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -240,6 +240,8 @@ public: virtual const char *GetItemName(int Type) = 0; virtual const char *Version() = 0; virtual const char *NetVersion() = 0; + virtual int DDNetVersion() = 0; + virtual const char *DDNetVersionStr() = 0; virtual void OnDummyDisconnect() = 0; virtual void Echo(const char *pString) = 0; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index cd194b64d..a5b75d377 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -401,6 +401,12 @@ int CClient::SendMsg(CMsgPacker *pMsg, int Flags) void CClient::SendInfo() { + CMsgPacker MsgVer(NETMSG_CLIENTVER, true); + MsgVer.AddRaw(&m_ConnectionID, sizeof(m_ConnectionID)); + MsgVer.AddInt(GameClient()->DDNetVersion()); + MsgVer.AddString(GameClient()->DDNetVersionStr(), 0); + SendMsg(&MsgVer, MSGFLAG_VITAL); + CMsgPacker Msg(NETMSG_INFO, true); Msg.AddString(GameClient()->NetVersion(), 128); Msg.AddString(m_Password, 128); @@ -673,6 +679,7 @@ void CClient::Connect(const char *pAddress, const char *pPassword) Disconnect(); + m_ConnectionID = RandomUuid(); str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr)); str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr); @@ -2965,6 +2972,12 @@ void CClient::Run() m_DummySendConnInfo = false; // send client info + CMsgPacker MsgVer(NETMSG_CLIENTVER, true); + MsgVer.AddRaw(&m_ConnectionID, sizeof(m_ConnectionID)); + MsgVer.AddInt(GameClient()->DDNetVersion()); + MsgVer.AddString(GameClient()->DDNetVersionStr(), 0); + SendMsg(&MsgVer, MSGFLAG_VITAL); + CMsgPacker MsgInfo(NETMSG_INFO, true); MsgInfo.AddString(GameClient()->NetVersion(), 128); MsgInfo.AddString(m_Password, 128); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index a2f4c72f0..1741e42aa 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -103,6 +103,8 @@ class CClient : public IClient, public CDemoPlayer::IListener char m_aServerAddressStr[256]; + CUuid m_ConnectionID; + unsigned m_SnapshotParts[2]; int64 m_LocalStartTime; diff --git a/src/engine/server.h b/src/engine/server.h index 63dba0f0e..4dd110edc 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -28,7 +28,10 @@ public: { const char *m_pName; int m_Latency; - int m_ClientVersion; + bool m_GotDDNetVersion; + int m_DDNetVersion; + const char *m_pDDNetVersionStr; + const CUuid *m_pConnectionID; }; int Tick() const { return m_CurrentGameTick; } @@ -43,6 +46,7 @@ public: virtual bool ClientIngame(int ClientID) = 0; virtual bool ClientAuthed(int ClientID) = 0; virtual int GetClientInfo(int ClientID, CClientInfo *pInfo) = 0; + virtual void SetClientDDNetVersion(int ClientID, int DDNetVersion) = 0; virtual void GetClientAddr(int ClientID, char *pAddrStr, int Size) = 0; virtual void RestrictRconOutput(int ClientID) = 0; @@ -112,7 +116,7 @@ public: { CClientInfo Info; GetClientInfo(Client, &Info); - if (Info.m_ClientVersion >= VERSION_DDNET_OLD) + if (Info.m_DDNetVersion >= VERSION_DDNET_OLD) return true; int *pMap = GetIdMap(Client); bool Found = false; @@ -132,7 +136,7 @@ public: { CClientInfo Info; GetClientInfo(Client, &Info); - if (Info.m_ClientVersion >= VERSION_DDNET_OLD) + if (Info.m_DDNetVersion >= VERSION_DDNET_OLD) return true; Target = clamp(Target, 0, VANILLA_MAX_CLIENTS-1); int *pMap = GetIdMap(Client); @@ -234,8 +238,6 @@ public: // DDRace virtual void OnSetAuthed(int ClientID, int Level) = 0; - virtual int GetClientVersion(int ClientID) = 0; - virtual void SetClientVersion(int ClientID, int Version) = 0; virtual bool PlayerExists(int ClientID) = 0; virtual void OnClientEngineJoin(int ClientID) = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index dfca811da..467673c22 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -256,6 +256,9 @@ void CServer::CClient::Reset() m_Score = 0; m_NextMapChunk = 0; m_Flags = 0; + m_DDNetVersion = VERSION_NONE; + m_GotDDNetVersionPacket = false; + m_DDNetVersionSettled = false; } CServer::CServer() @@ -516,12 +519,34 @@ int CServer::GetClientInfo(int ClientID, CClientInfo *pInfo) { pInfo->m_pName = m_aClients[ClientID].m_aName; pInfo->m_Latency = m_aClients[ClientID].m_Latency; - pInfo->m_ClientVersion = GameServer()->GetClientVersion(ClientID); + pInfo->m_GotDDNetVersion = m_aClients[ClientID].m_DDNetVersionSettled; + pInfo->m_DDNetVersion = m_aClients[ClientID].m_DDNetVersion >= 0 ? m_aClients[ClientID].m_DDNetVersion : VERSION_VANILLA; + if(m_aClients[ClientID].m_GotDDNetVersionPacket) + { + pInfo->m_pConnectionID = &m_aClients[ClientID].m_ConnectionID; + pInfo->m_pDDNetVersionStr = m_aClients[ClientID].m_aDDNetVersionStr; + } + else + { + pInfo->m_pConnectionID = 0; + pInfo->m_pDDNetVersionStr = 0; + } return 1; } return 0; } +void CServer::SetClientDDNetVersion(int ClientID, int DDNetVersion) +{ + dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid"); + + if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) + { + m_aClients[ClientID].m_DDNetVersion = DDNetVersion; + m_aClients[ClientID].m_DDNetVersionSettled = true; + } +} + void CServer::GetClientAddr(int ClientID, char *pAddrStr, int Size) { if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) @@ -873,7 +898,7 @@ int CServer::NewClientNoAuthCallback(int ClientID, void *pUser) int CServer::NewClientCallback(int ClientID, void *pUser) { CServer *pThis = (CServer *)pUser; - pThis->m_aClients[ClientID].m_State = CClient::STATE_AUTH; + pThis->m_aClients[ClientID].m_State = CClient::STATE_PREAUTH; pThis->m_aClients[ClientID].m_SupportsMapSha256 = false; pThis->m_aClients[ClientID].m_DnsblState = CClient::DNSBL_STATE_NONE; pThis->m_aClients[ClientID].m_aName[0] = 0; @@ -1195,9 +1220,28 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) if(Sys) { // system message - if(Msg == NETMSG_INFO) + if(Msg == NETMSG_CLIENTVER) { - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_AUTH) + if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_PREAUTH) + { + CUuid *pConnectionID = (CUuid *)Unpacker.GetRaw(sizeof(*pConnectionID)); + int DDNetVersion = Unpacker.GetInt(); + const char *pDDNetVersionStr = Unpacker.GetString(CUnpacker::SANITIZE_CC); + if(Unpacker.Error() || !str_utf8_check(pDDNetVersionStr) || DDNetVersion < 0) + { + return; + } + m_aClients[ClientID].m_ConnectionID = *pConnectionID; + m_aClients[ClientID].m_DDNetVersion = DDNetVersion; + str_copy(m_aClients[ClientID].m_aDDNetVersionStr, pDDNetVersionStr, sizeof(m_aClients[ClientID].m_aDDNetVersionStr)); + m_aClients[ClientID].m_DDNetVersionSettled = true; + m_aClients[ClientID].m_GotDDNetVersionPacket = true; + m_aClients[ClientID].m_State = CClient::STATE_AUTH; + } + } + else if(Msg == NETMSG_INFO) + { + if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && (m_aClients[ClientID].m_State == CClient::STATE_PREAUTH || m_aClients[ClientID].m_State == CClient::STATE_AUTH)) { const char *pVersion = Unpacker.GetString(CUnpacker::SANITIZE_CC); if(!str_utf8_check(pVersion)) @@ -1351,11 +1395,13 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } if(Unpacker.Error() == 0 && !str_comp(pCmd, "crashmeplx")) { - int version = GameServer()->GetClientVersion(ClientID); - if (GameServer()->PlayerExists(ClientID) && version < VERSION_DDNET_OLD) - GameServer()->SetClientVersion(ClientID, VERSION_DDNET_OLD); - } else - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0 && m_aClients[ClientID].m_Authed) + int Version = m_aClients[ClientID].m_DDNetVersion; + if (GameServer()->PlayerExists(ClientID) && Version < VERSION_DDNET_OLD) + { + m_aClients[ClientID].m_DDNetVersion = VERSION_DDNET_OLD; + } + } + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Error() == 0 && m_aClients[ClientID].m_Authed) { if (GameServer()->PlayerExists(ClientID)) { @@ -2332,7 +2378,7 @@ void CServer::ConStatus(IConsole::IResult *pResult, void *pUser) } str_format(aBuf, sizeof(aBuf), "id=%d addr=<{%s}> name='%s' client=%d secure=%s flags=%d%s%s", - i, aAddrStr, pThis->m_aClients[i].m_aName, pThis->GameServer()->GetClientVersion(i), + i, aAddrStr, pThis->m_aClients[i].m_aName, pThis->m_aClients[i].m_DDNetVersion, pThis->m_NetServer.HasSecurityToken(i) ? "yes" : "no", pThis->m_aClients[i].m_Flags, aDnsblStr, aAuthStr); } else diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 03e785191..04baed2d8 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -125,6 +125,7 @@ public: enum { STATE_EMPTY = 0, + STATE_PREAUTH, STATE_AUTH, STATE_CONNECTING, STATE_READY, @@ -182,6 +183,11 @@ public: // DDRace NETADDR m_Addr; + bool m_GotDDNetVersionPacket; + bool m_DDNetVersionSettled; + int m_DDNetVersion; + char m_aDDNetVersionStr[64]; + CUuid m_ConnectionID; // DNSBL int m_DnsblState; @@ -261,6 +267,7 @@ public: const char *GetAuthName(int ClientID); void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, SHA256_DIGEST *pMapSha256, int *pMapCrc); int GetClientInfo(int ClientID, CClientInfo *pInfo); + void SetClientDDNetVersion(int ClientID, int DDNetVersion); void GetClientAddr(int ClientID, char *pAddrStr, int Size); const char *ClientName(int ClientID); const char *ClientClan(int ClientID); diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h index d1e522e70..045881074 100644 --- a/src/engine/shared/protocol.h +++ b/src/engine/shared/protocol.h @@ -99,6 +99,7 @@ enum enum { + VERSION_NONE = -1, VERSION_VANILLA = 0, VERSION_DDRACE = 1, VERSION_DDNET_OLD = 2, diff --git a/src/engine/shared/protocol_ex_msgs.h b/src/engine/shared/protocol_ex_msgs.h index 7d7b0e9da..cbd360ade 100644 --- a/src/engine/shared/protocol_ex_msgs.h +++ b/src/engine/shared/protocol_ex_msgs.h @@ -26,3 +26,4 @@ UUID(NETMSG_IDONTKNOW, "i-dont-know@ddnet.tw") UUID(NETMSG_RCONTYPE, "rcon-type@ddnet.tw") UUID(NETMSG_MAP_DETAILS, "map-details@ddnet.tw") UUID(NETMSG_CAPABILITIES, "capabilities@ddnet.tw") +UUID(NETMSG_CLIENTVER, "clientver@ddnet.tw") diff --git a/src/engine/shared/teehistorian_ex_chunks.h b/src/engine/shared/teehistorian_ex_chunks.h index 8d14ec77d..c2b34ffa9 100644 --- a/src/engine/shared/teehistorian_ex_chunks.h +++ b/src/engine/shared/teehistorian_ex_chunks.h @@ -1,6 +1,8 @@ // This file can be included several times. UUID(TEEHISTORIAN_TEST, "teehistorian-test@ddnet.tw") +UUID(TEEHISTORIAN_DDNETVER_OLD, "teehistorian-ddnetver-old@ddnet.tw") +UUID(TEEHISTORIAN_DDNETVER, "teehistorian-ddnetver@ddnet.tw") UUID(TEEHISTORIAN_AUTH_INIT, "teehistorian-auth-init@ddnet.tw") UUID(TEEHISTORIAN_AUTH_LOGIN, "teehistorian-auth-login@ddnet.tw") UUID(TEEHISTORIAN_AUTH_LOGOUT, "teehistorian-auth-logout@ddnet.tw") diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 1f43b865d..b5c114513 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -112,6 +112,8 @@ void CGameClient::CStack::Add(class CComponent *pComponent) { m_paComponents[m_N const char *CGameClient::Version() { return GAME_VERSION; } const char *CGameClient::NetVersion() { return GAME_NETVERSION; } +int CGameClient::DDNetVersion() { return CLIENT_VERSIONNR; } +const char *CGameClient::DDNetVersionStr() { return m_aDDNetVersionStr; } const char *CGameClient::GetItemName(int Type) { return m_NetObjHandler.GetObjName(Type); } void CGameClient::OnConsoleInit() @@ -287,6 +289,15 @@ void CGameClient::OnInit() int64 Start = time_get(); + if(GIT_SHORTREV_HASH) + { + str_format(m_aDDNetVersionStr, sizeof(m_aDDNetVersionStr), "%s %s (%s)", GAME_NAME, GAME_RELEASE_VERSION, GIT_SHORTREV_HASH); + } + else + { + str_format(m_aDDNetVersionStr, sizeof(m_aDDNetVersionStr), "%s %s", GAME_NAME, GAME_RELEASE_VERSION); + } + // set the language g_Localization.Load(g_Config.m_ClLanguagefile, Storage(), Console()); diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 93ad76d3a..4f5bfafc3 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -108,6 +108,8 @@ class CGameClient : public IGameClient int m_CheckInfo[2]; + char m_aDDNetVersionStr[64]; + static void ConTeam(IConsole::IResult *pResult, void *pUserData); static void ConKill(IConsole::IResult *pResult, void *pUserData); @@ -360,6 +362,8 @@ public: virtual const char *GetItemName(int Type); virtual const char *Version(); virtual const char *NetVersion(); + virtual int DDNetVersion(); + virtual const char *DDNetVersionStr(); // actions // TODO: move these diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 1e4fa84a8..c4f49cfe3 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -1310,7 +1310,7 @@ void CGameContext::ConSetTimerType(IConsole::IResult *pResult, void *pUserData) if(str_comp_nocase(pResult->GetString(0), "gametimer") == 0) { - if(pPlayer->m_ClientVersion >= VERSION_DDNET_GAMETICK) + if(pPlayer->GetClientVersion() >= VERSION_DDNET_GAMETICK) pPlayer->m_TimerType = CPlayer::TIMERTYPE_GAMETIMER; else pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "timer", "gametimer is not supported by your client."); @@ -1319,7 +1319,7 @@ void CGameContext::ConSetTimerType(IConsole::IResult *pResult, void *pUserData) pPlayer->m_TimerType = CPlayer::TIMERTYPE_BROADCAST; else if(str_comp_nocase(pResult->GetString(0), "both") == 0) { - if(pPlayer->m_ClientVersion >= VERSION_DDNET_GAMETICK) + if(pPlayer->GetClientVersion() >= VERSION_DDNET_GAMETICK) pPlayer->m_TimerType = CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST; else { @@ -1331,7 +1331,7 @@ void CGameContext::ConSetTimerType(IConsole::IResult *pResult, void *pUserData) pPlayer->m_TimerType = CPlayer::TIMERTYPE_NONE; else if(str_comp_nocase(pResult->GetString(0), "cycle") == 0) { - if(pPlayer->m_ClientVersion >= VERSION_DDNET_GAMETICK) + if(pPlayer->GetClientVersion() >= VERSION_DDNET_GAMETICK) { if(pPlayer->m_TimerType < CPlayer::TIMERTYPE_NONE) pPlayer->m_TimerType++; diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index cddb4d41e..e0e710db8 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -1306,7 +1306,7 @@ void CCharacter::HandleBroadcast() CPlayerData *pData = GameServer()->Score()->PlayerData(m_pPlayer->GetCID()); if(m_DDRaceState == DDRACE_STARTED && m_CpLastBroadcast != m_CpActive && - m_CpActive > -1 && m_CpTick > Server()->Tick() && m_pPlayer->m_ClientVersion == VERSION_VANILLA && + m_CpActive > -1 && m_CpTick > Server()->Tick() && m_pPlayer->GetClientVersion() == VERSION_VANILLA && pData->m_BestTime && pData->m_aBestCpTime[m_CpActive] != 0) { char aBroadcast[128]; @@ -1452,7 +1452,7 @@ void CCharacter::HandleTiles(int Index) m_CpActive = cp; m_CpCurrent[cp] = m_Time; m_CpTick = Server()->Tick() + Server()->TickSpeed() * 2; - if(m_pPlayer->m_ClientVersion >= VERSION_DDRACE) { + if(m_pPlayer->GetClientVersion() >= VERSION_DDRACE) { CPlayerData *pData = GameServer()->Score()->PlayerData(m_pPlayer->GetCID()); CNetMsg_Sv_DDRaceTime Msg; Msg.m_Time = (int)m_Time; @@ -1477,7 +1477,7 @@ void CCharacter::HandleTiles(int Index) m_CpActive = cpf; m_CpCurrent[cpf] = m_Time; m_CpTick = Server()->Tick() + Server()->TickSpeed()*2; - if(m_pPlayer->m_ClientVersion >= VERSION_DDRACE) { + if(m_pPlayer->GetClientVersion() >= VERSION_DDRACE) { CPlayerData *pData = GameServer()->Score()->PlayerData(m_pPlayer->GetCID()); CNetMsg_Sv_DDRaceTime Msg; Msg.m_Time = (int)m_Time; diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp index f989fc3bf..63d9ff8f0 100644 --- a/src/game/server/entities/projectile.cpp +++ b/src/game/server/entities/projectile.cpp @@ -325,7 +325,7 @@ void CProjectile::Snap(int SnappingClient) CNetObj_Projectile *pProj = static_cast(Server()->SnapNewItem(NETOBJTYPE_PROJECTILE, m_ID, sizeof(CNetObj_Projectile))); if(pProj) { - if(SnappingClient > -1 && GameServer()->m_apPlayers[SnappingClient] && GameServer()->m_apPlayers[SnappingClient]->m_ClientVersion >= VERSION_DDNET_ANTIPING_PROJECTILE) + if(SnappingClient > -1 && GameServer()->m_apPlayers[SnappingClient] && GameServer()->m_apPlayers[SnappingClient]->GetClientVersion() >= VERSION_DDNET_ANTIPING_PROJECTILE) FillExtraInfo(pProj); else FillInfo(pProj); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 2830b3647..cae296036 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -517,7 +517,7 @@ void CGameContext::SendVoteSet(int ClientID) void CGameContext::SendVoteStatus(int ClientID, int Total, int Yes, int No) { - if (Total > VANILLA_MAX_CLIENTS && m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion <= VERSION_DDRACE) + if (Total > VANILLA_MAX_CLIENTS && m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetClientVersion() <= VERSION_DDRACE) { Yes = float(Yes) * VANILLA_MAX_CLIENTS / float(Total); No = float(No) * VANILLA_MAX_CLIENTS / float(Total); @@ -592,15 +592,19 @@ void CGameContext::SendTuningParams(int ClientID, int Zone) else pParams = (int *)&(m_aTuningList[Zone]); - unsigned int last = sizeof(m_Tuning)/sizeof(int); - if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion < VERSION_DDNET_EXTRATUNES) - last = 33; - else if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion < VERSION_DDNET_HOOKDURATION_TUNE) - last = 37; - else if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion < VERSION_DDNET_FIREDELAY_TUNE) - last = 38; + unsigned int Last = sizeof(m_Tuning)/sizeof(int); + if(m_apPlayers[ClientID]) + { + int ClientVersion = m_apPlayers[ClientID]->GetClientVersion(); + if(ClientVersion < VERSION_DDNET_EXTRATUNES) + Last = 33; + else if(ClientVersion < VERSION_DDNET_HOOKDURATION_TUNE) + Last = 37; + else if(ClientVersion < VERSION_DDNET_FIREDELAY_TUNE) + Last = 38; + } - for(unsigned i = 0; i < last; i++) + for(unsigned i = 0; i < Last; i++) { if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetCharacter()) { @@ -1095,6 +1099,13 @@ void CGameContext::OnClientEnter(int ClientID) if(!Server()->ClientPrevIngame(ClientID)) { + IServer::CClientInfo Info; + Server()->GetClientInfo(ClientID, &Info); + if(Info.m_GotDDNetVersion) + { + OnClientDDNetVersionKnown(ClientID); + } + char aBuf[512]; str_format(aBuf, sizeof(aBuf), "'%s' entered and joined the %s", Server()->ClientName(ClientID), m_pController->GetTeamName(m_apPlayers[ClientID]->GetTeam())); SendChat(-1, CGameContext::CHAT_ALL, aBuf); @@ -1218,6 +1229,79 @@ void CGameContext::OnClientEngineDrop(int ClientID, const char *pReason) } } +void CGameContext::OnClientDDNetVersionKnown(int ClientID) +{ + IServer::CClientInfo Info; + Server()->GetClientInfo(ClientID, &Info); + int ClientVersion = Info.m_DDNetVersion; + dbg_msg("ddnet", "cid=%d version=%d", ClientID, ClientVersion); + + if(m_TeeHistorianActive) + { + if(Info.m_pConnectionID && Info.m_pDDNetVersionStr) + { + m_TeeHistorian.RecordDDNetVersion(ClientID, *Info.m_pConnectionID, ClientVersion, Info.m_pDDNetVersionStr); + } + else + { + m_TeeHistorian.RecordDDNetVersionOld(ClientID, ClientVersion); + } + } + + CPlayer *pPlayer = m_apPlayers[ClientID]; + if(ClientVersion >= VERSION_DDNET_GAMETICK) + pPlayer->m_TimerType = g_Config.m_SvDefaultTimerType; + + //first update his teams state + ((CGameControllerDDRace *)m_pController)->m_Teams.SendTeamsState(ClientID); + + //second give him records + SendRecord(ClientID); + + //third give him others current time for table score + if(g_Config.m_SvHideScore) + { + return; + } + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_apPlayers[i] && Score()->PlayerData(i)->m_CurrentTime > 0) + { + CNetMsg_Sv_PlayerTime Msg; + Msg.m_Time = Score()->PlayerData(i)->m_CurrentTime * 100; + Msg.m_ClientID = i; + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID); + //also send its time to others + + } + } + //also send its time to others + if(Score()->PlayerData(ClientID)->m_CurrentTime > 0) + { + //TODO: make function for this fucking steps + CNetMsg_Sv_PlayerTime Msg; + Msg.m_Time = Score()->PlayerData(ClientID)->m_CurrentTime * 100; + Msg.m_ClientID = ClientID; + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1); + } + + //and give him correct tunings + if (ClientVersion >= VERSION_DDNET_EXTRATUNES) + SendTuningParams(ClientID, pPlayer->m_TuneZone); + + //tell old clients to update + if (ClientVersion < VERSION_DDNET_UPDATER_FIXED && g_Config.m_SvClientSuggestionOld[0] != '\0') + SendBroadcast(g_Config.m_SvClientSuggestionOld, ClientID); + //tell known bot clients that they're botting and we know it + if (((ClientVersion >= 15 && ClientVersion < 100) || ClientVersion == 502) && g_Config.m_SvClientSuggestionBot[0] != '\0') + SendBroadcast(g_Config.m_SvClientSuggestionBot, ClientID); + //autoban known bot versions + if(g_Config.m_SvBannedVersions[0] != '\0' && IsVersionBanned(ClientVersion)) + { + Server()->Kick(ClientID, "unsupported client"); + } +} + void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) { void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgID, pUnpacker); @@ -1752,66 +1836,19 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) } else if (MsgID == NETMSGTYPE_CL_ISDDNET) { - int Version = pUnpacker->GetInt(); - - if (pUnpacker->Error()) + IServer::CClientInfo Info; + Server()->GetClientInfo(ClientID, &Info); + if(Info.m_GotDDNetVersion) { - if (pPlayer->m_ClientVersion < VERSION_DDRACE) - pPlayer->m_ClientVersion = VERSION_DDRACE; + return; } - else if(pPlayer->m_ClientVersion < Version) - pPlayer->m_ClientVersion = Version; - - if(pPlayer->m_ClientVersion >= VERSION_DDNET_GAMETICK) - pPlayer->m_TimerType = g_Config.m_SvDefaultTimerType; - - dbg_msg("ddnet", "%d using Custom Client %d", ClientID, pPlayer->m_ClientVersion); - - //first update his teams state - ((CGameControllerDDRace*)m_pController)->m_Teams.SendTeamsState(ClientID); - - //second give him records - SendRecord(ClientID); - - //third give him others current time for table score - if(g_Config.m_SvHideScore) return; - for(int i = 0; i < MAX_CLIENTS; i++) + int DDNetVersion = pUnpacker->GetInt(); + if(pUnpacker->Error() || DDNetVersion < 0) { - if(m_apPlayers[i] && Score()->PlayerData(i)->m_CurrentTime > 0) - { - CNetMsg_Sv_PlayerTime Msg; - Msg.m_Time = Score()->PlayerData(i)->m_CurrentTime * 100; - Msg.m_ClientID = i; - Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID); - //also send its time to others - - } - } - //also send its time to others - if(Score()->PlayerData(ClientID)->m_CurrentTime > 0) - { - //TODO: make function for this fucking steps - CNetMsg_Sv_PlayerTime Msg; - Msg.m_Time = Score()->PlayerData(ClientID)->m_CurrentTime * 100; - Msg.m_ClientID = ClientID; - Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1); - } - - //and give him correct tunings - if (Version >= VERSION_DDNET_EXTRATUNES) - SendTuningParams(ClientID, pPlayer->m_TuneZone); - - //tell old clients to update - if (Version < VERSION_DDNET_UPDATER_FIXED && g_Config.m_SvClientSuggestionOld[0] != '\0') - SendBroadcast(g_Config.m_SvClientSuggestionOld, ClientID); - //tell known bot clients that they're botting and we know it - if (((Version >= 15 && Version < 100) || Version == 502) && g_Config.m_SvClientSuggestionBot[0] != '\0') - SendBroadcast(g_Config.m_SvClientSuggestionBot, ClientID); - //autoban known bot versions - if(g_Config.m_SvBannedVersions[0] != '\0' && IsVersionBanned(Version)) - { - Server()->Kick(ClientID, "unsupported client"); + DDNetVersion = VERSION_DDRACE; } + Server()->SetClientDDNetVersion(ClientID, DDNetVersion); + OnClientDDNetVersionKnown(ClientID); } else if (MsgID == NETMSGTYPE_CL_SHOWOTHERS) { @@ -3430,18 +3467,18 @@ void CGameContext::Whisper(int ClientID, char *pStr) void CGameContext::WhisperID(int ClientID, int VictimID, char *pMessage) { - if (!CheckClientID2(ClientID)) + if(!CheckClientID2(ClientID)) return; - if (!CheckClientID2(VictimID)) + if(!CheckClientID2(VictimID)) return; - if (m_apPlayers[ClientID]) + if(m_apPlayers[ClientID]) m_apPlayers[ClientID]->m_LastWhisperTo = VictimID; char aBuf[256]; - if (m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_ClientVersion >= VERSION_DDNET_WHISPER) + if(GetClientVersion(ClientID) >= VERSION_DDNET_WHISPER) { CNetMsg_Sv_Chat Msg; Msg.m_Team = CHAT_WHISPER_SEND; @@ -3458,7 +3495,7 @@ void CGameContext::WhisperID(int ClientID, int VictimID, char *pMessage) SendChatTarget(ClientID, aBuf); } - if (m_apPlayers[VictimID] && m_apPlayers[VictimID]->m_ClientVersion >= VERSION_DDNET_WHISPER) + if(GetClientVersion(VictimID) >= VERSION_DDNET_WHISPER) { CNetMsg_Sv_Chat Msg2; Msg2.m_Team = CHAT_WHISPER_RECV; @@ -3544,18 +3581,9 @@ void CGameContext::List(int ClientID, const char *pFilter) int CGameContext::GetClientVersion(int ClientID) { - return m_apPlayers[ClientID] - ? m_apPlayers[ClientID]->m_ClientVersion - : 0; -} - -void CGameContext::SetClientVersion(int ClientID, int Version) -{ - if(!m_apPlayers[ClientID]) - { - return; - } - m_apPlayers[ClientID]->m_ClientVersion = Version; + IServer::CClientInfo Info = {0}; + Server()->GetClientInfo(ClientID, &Info); + return Info.m_DDNetVersion; } bool CGameContext::PlayerModerating() diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index b7ae07863..083dfc24c 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -250,6 +250,7 @@ public: virtual const char *NetVersion(); // DDRace + void OnClientDDNetVersionKnown(int ClientID); virtual void FillAntibot(CAntibotRoundData *pData); int ProcessSpamProtection(int ClientID); int GetDDRaceTeam(int ClientID); @@ -257,7 +258,6 @@ public: int64 m_NonEmptySince; int64 m_LastMapVote; int GetClientVersion(int ClientID); - void SetClientVersion(int ClientID, int Version); bool PlayerExists(int ClientID) { return m_apPlayers[ClientID]; }; // Returns true if someone is actively moderating. bool PlayerModerating(); diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 554d26696..b63a05849 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -516,7 +516,7 @@ void IGameController::Snap(int SnappingClient) CPlayer *pPlayer = SnappingClient > -1 ? GameServer()->m_apPlayers[SnappingClient] : 0; CPlayer *pPlayer2; - if(pPlayer && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && pPlayer->m_ClientVersion >= VERSION_DDNET_GAMETICK) + if(pPlayer && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && pPlayer->GetClientVersion() >= VERSION_DDNET_GAMETICK) { if((pPlayer->GetTeam() == -1 || pPlayer->IsPaused()) && pPlayer->m_SpectatorID != SPEC_FREEVIEW diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp index a7e5ca5ff..7edfb99a1 100644 --- a/src/game/server/gameworld.cpp +++ b/src/game/server/gameworld.cpp @@ -186,8 +186,8 @@ void CGameWorld::UpdatePlayerMaps() !GameServer()->m_apPlayers[i]->IsPaused() && GameServer()->m_apPlayers[i]->GetTeam() != -1 && !ch->CanCollide(i) && (!GameServer()->m_apPlayers[i] || - GameServer()->m_apPlayers[i]->m_ClientVersion == VERSION_VANILLA || - (GameServer()->m_apPlayers[i]->m_ClientVersion >= VERSION_DDRACE && + GameServer()->m_apPlayers[i]->GetClientVersion() == VERSION_VANILLA || + (GameServer()->m_apPlayers[i]->GetClientVersion() >= VERSION_DDRACE && !GameServer()->m_apPlayers[i]->m_ShowOthers ) ) diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 848514d2c..63d3ca156 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -103,7 +103,6 @@ void CPlayer::Reset() GameServer()->Score()->PlayerData(m_ClientID)->Reset(); - m_ClientVersion = VERSION_VANILLA; m_ShowOthers = g_Config.m_SvShowOthersDefault; m_ShowAll = g_Config.m_SvShowAllDefault; m_SpecTeam = 0; @@ -296,12 +295,13 @@ void CPlayer::Snap(int SnappingClient) if(!pPlayerInfo) return; + int ClientVersion = GetClientVersion(); pPlayerInfo->m_Latency = SnappingClient == -1 ? m_Latency.m_Min : GameServer()->m_apPlayers[SnappingClient]->m_aActLatency[m_ClientID]; - pPlayerInfo->m_Local = (int)(m_ClientID == SnappingClient && (m_Paused != PAUSE_PAUSED || m_ClientVersion >= VERSION_DDNET_OLD)); + pPlayerInfo->m_Local = (int)(m_ClientID == SnappingClient && (m_Paused != PAUSE_PAUSED || ClientVersion >= VERSION_DDNET_OLD)); pPlayerInfo->m_ClientID = id; - pPlayerInfo->m_Team = (m_ClientVersion < VERSION_DDNET_OLD || m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS; + pPlayerInfo->m_Team = (ClientVersion < VERSION_DDNET_OLD || m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS; - if(m_ClientID == SnappingClient && m_Paused == PAUSE_PAUSED && m_ClientVersion < VERSION_DDNET_OLD) + if(m_ClientID == SnappingClient && m_Paused == PAUSE_PAUSED && ClientVersion < VERSION_DDNET_OLD) pPlayerInfo->m_Team = TEAM_SPECTATORS; // send 0 if times of others are not shown @@ -337,10 +337,7 @@ void CPlayer::Snap(int SnappingClient) void CPlayer::FakeSnap() { - // This is problematic when it's sent before we know whether it's a non-64-player-client - // Then we can't spectate players at the start - - if(m_ClientVersion >= VERSION_DDNET_OLD) + if(GetClientVersion() >= VERSION_DDNET_OLD) return; int FakeID = VANILLA_MAX_CLIENTS - 1; @@ -421,7 +418,7 @@ void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput) // Magic number when we can hope that client has successfully identified itself if(m_NumInputs == 20) { - if(g_Config.m_SvClientSuggestion[0] != '\0' && m_ClientVersion <= VERSION_DDNET_OLD) + if(g_Config.m_SvClientSuggestion[0] != '\0' && GetClientVersion() <= VERSION_DDNET_OLD) GameServer()->SendBroadcast(g_Config.m_SvClientSuggestion, m_ClientID); } } @@ -481,6 +478,11 @@ void CPlayer::OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput) m_pCharacter->OnDirectInput(NewInput); } +int CPlayer::GetClientVersion() const +{ + return m_pGameServer->GetClientVersion(m_ClientID); +} + CCharacter *CPlayer::GetCharacter() { if(m_pCharacter && m_pCharacter->IsAlive()) diff --git a/src/game/server/player.h b/src/game/server/player.h index aee0a7b3a..0dd104df6 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -26,6 +26,7 @@ public: void SetTeam(int Team, bool DoChatMsg=true); int GetTeam() const { return m_Team; }; int GetCID() const { return m_ClientID; }; + int GetClientVersion() const; void Tick(); void PostTick(); @@ -162,7 +163,6 @@ public: bool IsPlaying(); int64 m_Last_KickVote; int64 m_Last_Team; - int m_ClientVersion; bool m_ShowOthers; bool m_ShowAll; bool m_SpecTeam; diff --git a/src/game/server/teams.cpp b/src/game/server/teams.cpp index 7eca61256..bdc09ee70 100644 --- a/src/game/server/teams.cpp +++ b/src/game/server/teams.cpp @@ -385,7 +385,7 @@ void CGameTeams::SendTeamsState(int ClientID) if (g_Config.m_SvTeam == 3) return; - if (!m_pGameContext->m_apPlayers[ClientID] || m_pGameContext->m_apPlayers[ClientID]->m_ClientVersion <= VERSION_DDRACE) + if (!m_pGameContext->m_apPlayers[ClientID] || m_pGameContext->m_apPlayers[ClientID]->GetClientVersion() <= VERSION_DDRACE) return; CMsgPacker Msg(NETMSGTYPE_SV_TEAMSSTATE); @@ -587,7 +587,7 @@ void CGameTeams::OnFinish(CPlayer* Player, float Time, const char *pTimestamp) NeedToSendNewRecord = true; for (int i = 0; i < MAX_CLIENTS; i++) { - if (GetPlayer(i) && GetPlayer(i)->m_ClientVersion >= VERSION_DDRACE) + if (GetPlayer(i) && GetPlayer(i)->GetClientVersion() >= VERSION_DDRACE) { if (!g_Config.m_SvHideScore || i == Player->GetCID()) { @@ -600,19 +600,19 @@ void CGameTeams::OnFinish(CPlayer* Player, float Time, const char *pTimestamp) } } - if (NeedToSendNewRecord && Player->m_ClientVersion >= VERSION_DDRACE) + if (NeedToSendNewRecord && Player->GetClientVersion() >= VERSION_DDRACE) { for (int i = 0; i < MAX_CLIENTS; i++) { if (GameServer()->m_apPlayers[i] - && GameServer()->m_apPlayers[i]->m_ClientVersion >= VERSION_DDRACE) + && GameServer()->m_apPlayers[i]->GetClientVersion() >= VERSION_DDRACE) { GameServer()->SendRecord(i); } } } - if (Player->m_ClientVersion >= VERSION_DDRACE) + if (Player->GetClientVersion() >= VERSION_DDRACE) { CNetMsg_Sv_DDRaceTime Msg; Msg.m_Time = (int)(Time * 100.0f); diff --git a/src/game/server/teehistorian.cpp b/src/game/server/teehistorian.cpp index 3bc442a00..f2b202cd0 100644 --- a/src/game/server/teehistorian.cpp +++ b/src/game/server/teehistorian.cpp @@ -488,6 +488,40 @@ void CTeeHistorian::EndTick() m_State = STATE_BEFORE_TICK; } +void CTeeHistorian::RecordDDNetVersionOld(int ClientID, int DDNetVersion) +{ + CPacker Buffer; + Buffer.Reset(); + Buffer.AddInt(ClientID); + Buffer.AddInt(DDNetVersion); + + if(m_Debug) + { + dbg_msg("teehistorian", "ddnetver_old cid=%d ddnet_version=%d", ClientID, DDNetVersion); + } + + WriteExtra(UUID_TEEHISTORIAN_DDNETVER_OLD, Buffer.Data(), Buffer.Size()); +} + +void CTeeHistorian::RecordDDNetVersion(int ClientID, CUuid ConnectionID, int DDNetVersion, const char *pDDNetVersionStr) +{ + CPacker Buffer; + Buffer.Reset(); + Buffer.AddInt(ClientID); + Buffer.AddRaw(&ConnectionID, sizeof(ConnectionID)); + Buffer.AddInt(DDNetVersion); + Buffer.AddString(pDDNetVersionStr, 0); + + if(m_Debug) + { + char aConnnectionID[UUID_MAXSTRSIZE]; + FormatUuid(ConnectionID, aConnnectionID, sizeof(aConnnectionID)); + dbg_msg("teehistorian", "ddnetver cid=%d connection_id=%s ddnet_version=%d ddnet_version_str=%s", ClientID, aConnnectionID, DDNetVersion, pDDNetVersionStr); + } + + WriteExtra(UUID_TEEHISTORIAN_DDNETVER, Buffer.Data(), Buffer.Size()); +} + void CTeeHistorian::RecordAuthInitial(int ClientID, int Level, const char *pAuthName) { CPacker Buffer; diff --git a/src/game/server/teehistorian.h b/src/game/server/teehistorian.h index 9c4107f23..bfa3232f1 100644 --- a/src/game/server/teehistorian.h +++ b/src/game/server/teehistorian.h @@ -63,6 +63,9 @@ public: void EndTick(); + void RecordDDNetVersionOld(int ClientID, int DDNetVersion); + void RecordDDNetVersion(int ClientID, CUuid ConnectionID, int DDNetVersion, const char *pDDNetVersionStr); + void RecordAuthInitial(int ClientID, int Level, const char *pAuthName); void RecordAuthLogin(int ClientID, int Level, const char *pAuthName); void RecordAuthLogout(int ClientID); diff --git a/src/game/version.h b/src/game/version.h index d9401ce1a..4a68f8714 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -4,6 +4,7 @@ #define GAME_VERSION_H #define GAME_VERSION "0.6.4, 13.1" #define GAME_NETVERSION "0.6 626fce9a778df4d4" +#define GAME_NAME "DDNet" #define GAME_RELEASE_VERSION "13.1" #define CLIENT_VERSIONNR 13010 extern const char *GIT_SHORTREV_HASH; diff --git a/src/test/teehistorian.cpp b/src/test/teehistorian.cpp index ef75cf69c..2ead81344 100644 --- a/src/test/teehistorian.cpp +++ b/src/test/teehistorian.cpp @@ -319,6 +319,43 @@ TEST_F(TeeHistorian, ExtraMessage) Expect(EXPECTED, sizeof(EXPECTED)); } +TEST_F(TeeHistorian, DDNetVersion) +{ + const unsigned char EXPECTED[] = { + // EX uuid=60daba5c-52c4-3aeb-b8ba-b2953fb55a17 data_len=50 + 0x4a, + 0x13, 0x97, 0xb6, 0x3e, 0xee, 0x4e, 0x39, 0x19, + 0xb8, 0x6a, 0xb0, 0x58, 0x88, 0x7f, 0xca, 0xf5, + 0x32, + // (DDNETVER) cid=0 connection_id=fb13a576-d35f-4893-b815-eedc6d98015b + // ddnet_version=13010 ddnet_version_str=DDNet 13.1 (3623f5e4cd184556) + 0x00, + 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93, + 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b, + 0x92, 0xcb, 0x01, 'D', 'D', 'N', 'e', 't', + ' ', '1', '3', '.', '1', ' ', '(', '3', + '6', '2', '3', 'f', '5', 'e', '4', 'c', + 'd', '1', '8', '4', '5', '5', '6', ')', + 0x00, + // EX uuid=1397b63e-ee4e-3919-b86a-b058887fcaf5 data_len=4 + 0x4a, + 0x41, 0xb4, 0x95, 0x41, 0xf2, 0x6f, 0x32, 0x5d, + 0x87, 0x15, 0x9b, 0xaf, 0x4b, 0x54, 0x4e, 0xf9, + 0x04, + // (DDNETVER_OLD) cid=1 ddnet_version=13010 + 0x01, 0x92, 0xcb, 0x01, + 0x40, // FINISH + }; + CUuid ConnectionID = { + 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93, + 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b, + }; + m_TH.RecordDDNetVersion(0, ConnectionID, 13010, "DDNet 13.1 (3623f5e4cd184556)"); + m_TH.RecordDDNetVersionOld(1, 13010); + Finish(); + Expect(EXPECTED, sizeof(EXPECTED)); +} + TEST_F(TeeHistorian, Auth) { const unsigned char EXPECTED[] = {