1#include <base/detect.h>
2#include <base/io.h>
3#include <base/time.h>
4
5#include <engine/external/json-parser/json.h>
6#include <engine/server.h>
7#include <engine/shared/config.h>
8
9#include <game/gamecore.h>
10#include <game/server/teehistorian.h>
11
12#include <gtest/gtest.h>
13
14#include <vector>
15
16void RegisterGameUuids(CUuidManager *pManager);
17
18class TeeHistorian : public ::testing::Test
19{
20protected:
21 CTeeHistorian m_TH;
22 CConfig m_Config;
23 CTuningParams m_Tuning;
24 CUuidManager m_UuidManager;
25 CTeeHistorian::CGameInfo m_GameInfo;
26
27 std::vector<unsigned char> m_vBuffer;
28
29 enum
30 {
31 STATE_NONE,
32 STATE_PLAYERS,
33 STATE_INPUTS,
34 };
35
36 int m_State;
37
38 TeeHistorian()
39 {
40 mem_zero(block: &m_Config, size: sizeof(m_Config));
41#define MACRO_CONFIG_INT(Name, ScriptName, Def, Min, Max, Save, Desc) \
42 m_Config.m_##Name = (Def);
43#define MACRO_CONFIG_COL(Name, ScriptName, Def, Save, Desc) MACRO_CONFIG_INT(Name, ScriptName, Def, 0, 0, Save, Desc)
44#define MACRO_CONFIG_STR(Name, ScriptName, Len, Def, Save, Desc) \
45 str_copy(m_Config.m_##Name, (Def), sizeof(m_Config.m_##Name));
46#include <engine/shared/config_variables.h>
47#undef MACRO_CONFIG_STR
48#undef MACRO_CONFIG_COL
49#undef MACRO_CONFIG_INT
50
51 RegisterUuids(pManager: &m_UuidManager);
52 RegisterTeehistorianUuids(pManager: &m_UuidManager);
53 RegisterGameUuids(pManager: &m_UuidManager);
54
55 SHA256_DIGEST Sha256 = {.data: {0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
56 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
57 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
58 0x01, 0x23}};
59
60 mem_zero(block: &m_GameInfo, size: sizeof(m_GameInfo));
61
62 m_GameInfo.m_GameUuid = CalculateUuid(pName: "test@ddnet.tw");
63 m_GameInfo.m_pServerVersion = "DDNet test";
64 m_GameInfo.m_StartTime = time(timer: nullptr);
65 m_GameInfo.m_pPrngDescription = "test-prng:02468ace";
66
67 m_GameInfo.m_pServerName = "server name";
68 m_GameInfo.m_ServerPort = 8303;
69 m_GameInfo.m_pGameType = "game type";
70
71 m_GameInfo.m_pMapName = "Kobra 3 Solo";
72 m_GameInfo.m_MapSize = 903514;
73 m_GameInfo.m_MapSha256 = Sha256;
74 m_GameInfo.m_MapCrc = 0xeceaf25c;
75
76 m_GameInfo.m_HavePrevGameUuid = false;
77 mem_zero(block: &m_GameInfo.m_PrevGameUuid, size: sizeof(m_GameInfo.m_PrevGameUuid));
78
79 m_GameInfo.m_pConfig = &m_Config;
80 m_GameInfo.m_pTuning = &m_Tuning;
81 m_GameInfo.m_pUuids = &m_UuidManager;
82
83 Reset(pGameInfo: &m_GameInfo);
84 }
85
86 static void WriteBuffer(std::vector<unsigned char> &vBuffer, const void *pData, size_t DataSize)
87 {
88 if(DataSize <= 0)
89 return;
90
91 const size_t OldSize = vBuffer.size();
92 vBuffer.resize(new_size: OldSize + DataSize);
93 mem_copy(dest: &vBuffer[OldSize], source: pData, size: DataSize);
94 }
95
96 static void Write(const void *pData, int DataSize, void *pUser)
97 {
98 TeeHistorian *pThis = (TeeHistorian *)pUser;
99 WriteBuffer(vBuffer&: pThis->m_vBuffer, pData, DataSize);
100 }
101
102 void Reset(const CTeeHistorian::CGameInfo *pGameInfo)
103 {
104 m_vBuffer.clear();
105 m_TH.Reset(pGameInfo, pfnWriteCallback: Write, pUser: this);
106 m_State = STATE_NONE;
107 }
108
109 void Expect(const unsigned char *pOutput, size_t OutputSize)
110 {
111 static CUuid TEEHISTORIAN_UUID = CalculateUuid(pName: "teehistorian@ddnet.tw");
112 static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"version_minor\":\"19\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\"";
113 static const char PREFIX2[] = "\",\"server_name\":\"server name\",\"server_port\":\"8303\",\"game_type\":\"game type\",\"map_name\":\"Kobra 3 Solo\",\"map_size\":\"903514\",\"map_sha256\":\"0123456789012345678901234567890123456789012345678901234567890123\",\"map_crc\":\"eceaf25c\",\"prng_description\":\"test-prng:02468ace\",\"config\":{},\"tuning\":{},\"uuids\":[";
114 static const char PREFIX3[] = "]}";
115
116 char aTimeBuf[64];
117 str_timestamp_ex(time: m_GameInfo.m_StartTime, buffer: aTimeBuf, buffer_size: sizeof(aTimeBuf), format: "%Y-%m-%dT%H:%M:%S%z");
118
119 std::vector<unsigned char> vBuffer;
120 WriteBuffer(vBuffer, pData: &TEEHISTORIAN_UUID, DataSize: sizeof(TEEHISTORIAN_UUID));
121 WriteBuffer(vBuffer, pData: PREFIX1, DataSize: str_length(str: PREFIX1));
122 WriteBuffer(vBuffer, pData: aTimeBuf, DataSize: str_length(str: aTimeBuf));
123 WriteBuffer(vBuffer, pData: PREFIX2, DataSize: str_length(str: PREFIX2));
124 for(int i = 0; i < m_UuidManager.NumUuids(); i++)
125 {
126 char aBuf[64];
127 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s\"%s\"",
128 i == 0 ? "" : ",",
129 m_UuidManager.GetName(Id: OFFSET_UUID + i));
130 WriteBuffer(vBuffer, pData: aBuf, DataSize: str_length(str: aBuf));
131 }
132 WriteBuffer(vBuffer, pData: PREFIX3, DataSize: str_length(str: PREFIX3));
133 WriteBuffer(vBuffer, pData: "", DataSize: 1);
134 WriteBuffer(vBuffer, pData: pOutput, DataSize: OutputSize);
135
136 ExpectFull(pOutput: vBuffer.data(), OutputSize: vBuffer.size());
137 }
138
139 void ExpectFull(const unsigned char *pOutput, size_t OutputSize)
140 {
141 const ::testing::TestInfo *pTestInfo =
142 ::testing::UnitTest::GetInstance()->current_test_info();
143 const char *pTestName = pTestInfo->name();
144
145 if(m_vBuffer.size() != OutputSize || mem_comp(a: m_vBuffer.data(), b: pOutput, size: OutputSize) != 0)
146 {
147 char aFilename[IO_MAX_PATH_LENGTH];
148 IOHANDLE File;
149
150 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%sGot.teehistorian", pTestName);
151 File = io_open(filename: aFilename, flags: IOFLAG_WRITE);
152 ASSERT_TRUE(File);
153 io_write(io: File, buffer: m_vBuffer.data(), size: m_vBuffer.size());
154 io_close(io: File);
155
156 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%sExpected.teehistorian", pTestName);
157 File = io_open(filename: aFilename, flags: IOFLAG_WRITE);
158 ASSERT_TRUE(File);
159 io_write(io: File, buffer: pOutput, size: OutputSize);
160 io_close(io: File);
161 }
162
163 printf(format: "pOutput = {");
164 size_t Start = 0; // skip over header;
165 for(size_t i = 0; i < m_vBuffer.size(); i++)
166 {
167 if(Start == 0)
168 {
169 if(m_vBuffer[i] == 0)
170 Start = i + 1;
171 continue;
172 }
173 if((i - Start) % 10 == 0)
174 printf(format: "\n\t");
175 else
176 printf(format: ", ");
177 printf(format: "0x%.2x", m_vBuffer[i]);
178 }
179 printf(format: "\n}\n");
180 ASSERT_EQ(m_vBuffer.size(), OutputSize);
181 ASSERT_TRUE(mem_comp(m_vBuffer.data(), pOutput, OutputSize) == 0);
182 }
183
184 void Tick(int Tick)
185 {
186 if(m_State == STATE_PLAYERS)
187 {
188 Inputs();
189 }
190 if(m_State == STATE_INPUTS)
191 {
192 m_TH.EndInputs();
193 m_TH.EndTick();
194 }
195 m_TH.BeginTick(Tick);
196 m_TH.BeginPlayers();
197 m_State = STATE_PLAYERS;
198 }
199 void Inputs()
200 {
201 m_TH.EndPlayers();
202 m_TH.BeginInputs();
203 m_State = STATE_INPUTS;
204 }
205 void Finish()
206 {
207 if(m_State == STATE_PLAYERS)
208 {
209 Inputs();
210 }
211 if(m_State == STATE_INPUTS)
212 {
213 m_TH.EndInputs();
214 m_TH.EndTick();
215 }
216 m_TH.Finish();
217 }
218 void DeadPlayer(int ClientId)
219 {
220 m_TH.RecordDeadPlayer(ClientId);
221 }
222 void Player(int ClientId, int x, int y)
223 {
224 CNetObj_CharacterCore Char;
225 mem_zero(block: &Char, size: sizeof(Char));
226 Char.m_X = x;
227 Char.m_Y = y;
228 m_TH.RecordPlayer(ClientId, pChar: &Char);
229 }
230};
231
232TEST_F(TeeHistorian, Empty)
233{
234 Expect(pOutput: (const unsigned char *)"", OutputSize: 0);
235}
236
237TEST_F(TeeHistorian, Finished)
238{
239 const unsigned char EXPECTED[] = {0x40}; // FINISH
240 m_TH.Finish();
241 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
242}
243
244TEST_F(TeeHistorian, TickImplicitOneTick)
245{
246 const unsigned char EXPECTED[] = {
247 0x42, 0x00, 0x01, 0x02, // PLAYERNEW cid=0 x=1 y=2
248 0x40, // FINISH
249 };
250 Tick(Tick: 1);
251 Player(ClientId: 0, x: 1, y: 2);
252 Finish();
253 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
254}
255
256TEST_F(TeeHistorian, TickImplicitTwoTicks)
257{
258 const unsigned char EXPECTED[] = {
259 0x42, 0x00, 0x01, 0x02, // PLAYER_NEW cid=0 x=1 y=2
260 0x00, 0x01, 0x40, // PLAYER cid=0 dx=1 dy=-1
261 0x40, // FINISH
262 };
263 Tick(Tick: 1);
264 Player(ClientId: 0, x: 1, y: 2);
265 Tick(Tick: 2);
266 Player(ClientId: 0, x: 2, y: 1);
267 Finish();
268 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
269}
270
271TEST_F(TeeHistorian, TickImplicitDescendingClientId)
272{
273 const unsigned char EXPECTED[] = {
274 0x42, 0x01, 0x02, 0x03, // PLAYER_NEW cid=1 x=2 y=3
275 0x42, 0x00, 0x04, 0x05, // PLAYER_NEW cid=0 x=4 y=5
276 0x40, // FINISH
277 };
278 Tick(Tick: 1);
279 DeadPlayer(ClientId: 0);
280 Player(ClientId: 1, x: 2, y: 3);
281 Tick(Tick: 2);
282 Player(ClientId: 0, x: 4, y: 5);
283 Player(ClientId: 1, x: 2, y: 3);
284 Finish();
285 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
286}
287
288TEST_F(TeeHistorian, TickExplicitAscendingClientId)
289{
290 const unsigned char EXPECTED[] = {
291 0x42, 0x00, 0x04, 0x05, // PLAYER_NEW cid=0 x=4 y=5
292 0x41, 0x00, // TICK_SKIP dt=0
293 0x42, 0x01, 0x02, 0x03, // PLAYER_NEW cid=1 x=2 y=3
294 0x40, // FINISH
295 };
296 Tick(Tick: 1);
297 Player(ClientId: 0, x: 4, y: 5);
298 DeadPlayer(ClientId: 1);
299 Tick(Tick: 2);
300 Player(ClientId: 0, x: 4, y: 5);
301 Player(ClientId: 1, x: 2, y: 3);
302 Finish();
303 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
304}
305
306TEST_F(TeeHistorian, TickImplicitEmpty)
307{
308 const unsigned char EXPECTED[] = {
309 0x40, // FINISH
310 };
311 for(int i = 1; i < 500; i++)
312 {
313 Tick(Tick: i);
314 }
315 for(int i = 1000; i < 100000; i += 1000)
316 {
317 Tick(Tick: i);
318 }
319 Finish();
320 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
321}
322
323TEST_F(TeeHistorian, TickExplicitStart)
324{
325 const unsigned char EXPECTED[] = {
326 0x41, 0xb3, 0x07, // TICK_SKIP dt=499
327 0x42, 0x00, 0x40, 0x40, // PLAYER_NEW cid=0 x=-1 y=-1
328 0x40, // FINISH
329 };
330 Tick(Tick: 500);
331 Player(ClientId: 0, x: -1, y: -1);
332 Finish();
333 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
334}
335
336TEST_F(TeeHistorian, TickExplicitPlayerMessage)
337{
338 const unsigned char EXPECTED[] = {
339 0x41, 0x00, // TICK_SKIP dt=0
340 0x46, 0x3f, 0x01, 0x00, // MESSAGE cid=63 msg="\0"
341 0x40, // FINISH
342 };
343 Tick(Tick: 1);
344 Inputs();
345 m_TH.RecordPlayerMessage(ClientId: 63, pMsg: "", MsgSize: 1);
346 Finish();
347 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
348}
349
350TEST_F(TeeHistorian, ExtraMessage)
351{
352 const unsigned char EXPECTED[] = {
353 0x41, 0x00, // TICK_SKIP dt=0
354 // EX uuid=6bb8ba88-0f0b-382e-8dae-dbf4052b8b7d data_len=0
355 0x4a,
356 0x6b, 0xb8, 0xba, 0x88, 0x0f, 0x0b, 0x38, 0x2e,
357 0x8d, 0xae, 0xdb, 0xf4, 0x05, 0x2b, 0x8b, 0x7d,
358 0x00,
359 0x40, // FINISH
360 };
361 Tick(Tick: 1);
362 Inputs();
363 m_TH.RecordTestExtra();
364 Finish();
365 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
366}
367
368TEST_F(TeeHistorian, DDNetVersion)
369{
370 const unsigned char EXPECTED[] = {
371 // EX uuid=60daba5c-52c4-3aeb-b8ba-b2953fb55a17 data_len=50
372 0x4a,
373 0x13, 0x97, 0xb6, 0x3e, 0xee, 0x4e, 0x39, 0x19,
374 0xb8, 0x6a, 0xb0, 0x58, 0x88, 0x7f, 0xca, 0xf5,
375 0x32,
376 // (DDNETVER) cid=0 connection_id=fb13a576-d35f-4893-b815-eedc6d98015b
377 // ddnet_version=13010 ddnet_version_str=DDNet 13.1 (3623f5e4cd184556)
378 0x00,
379 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
380 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b,
381 0x92, 0xcb, 0x01, 'D', 'D', 'N', 'e', 't',
382 ' ', '1', '3', '.', '1', ' ', '(', '3',
383 '6', '2', '3', 'f', '5', 'e', '4', 'c',
384 'd', '1', '8', '4', '5', '5', '6', ')',
385 0x00,
386 // EX uuid=41b49541-f26f-325d-8715-9baf4b544ef9 data_len=4
387 0x4a,
388 0x41, 0xb4, 0x95, 0x41, 0xf2, 0x6f, 0x32, 0x5d,
389 0x87, 0x15, 0x9b, 0xaf, 0x4b, 0x54, 0x4e, 0xf9,
390 0x04,
391 // (DDNETVER_OLD) cid=1 ddnet_version=13010
392 0x01, 0x92, 0xcb, 0x01,
393 0x40, // FINISH
394 };
395 CUuid ConnectionId = {
396 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
397 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b};
398 m_TH.RecordDDNetVersion(ClientId: 0, ConnectionId, DDNetVersion: 13010, pDDNetVersionStr: "DDNet 13.1 (3623f5e4cd184556)");
399 m_TH.RecordDDNetVersionOld(ClientId: 1, DDNetVersion: 13010);
400 Finish();
401 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
402}
403
404TEST_F(TeeHistorian, Auth)
405{
406 const unsigned char EXPECTED[] = {
407 // EX uuid=60daba5c-52c4-3aeb-b8ba-b2953fb55a17 data_len=16
408 0x4a,
409 0x60, 0xda, 0xba, 0x5c, 0x52, 0xc4, 0x3a, 0xeb,
410 0xb8, 0xba, 0xb2, 0x95, 0x3f, 0xb5, 0x5a, 0x17,
411 0x10,
412 // (AUTH_INIT) cid=0 level=3 auth_name="default_admin"
413 0x00, 0x03, 'd', 'e', 'f', 'a', 'u', 'l',
414 't', '_', 'a', 'd', 'm', 'i', 'n', 0x00,
415 // EX uuid=37ecd3b8-9218-3bb9-a71b-a935b86f6a81 data_len=9
416 0x4a,
417 0x37, 0xec, 0xd3, 0xb8, 0x92, 0x18, 0x3b, 0xb9,
418 0xa7, 0x1b, 0xa9, 0x35, 0xb8, 0x6f, 0x6a, 0x81,
419 0x09,
420 // (AUTH_LOGIN) cid=1 level=2 auth_name="foobar"
421 0x01, 0x02, 'f', 'o', 'o', 'b', 'a', 'r',
422 0x00,
423 // EX uuid=37ecd3b8-9218-3bb9-a71b-a935b86f6a81 data_len=7
424 0x4a,
425 0x37, 0xec, 0xd3, 0xb8, 0x92, 0x18, 0x3b, 0xb9,
426 0xa7, 0x1b, 0xa9, 0x35, 0xb8, 0x6f, 0x6a, 0x81,
427 0x07,
428 // (AUTH_LOGIN) cid=1 level=2 auth_name="help"
429 0x02, 0x01, 'h', 'e', 'l', 'p', 0x00,
430 // EX uuid=d4f5abe8-edd2-3fb9-abd8-1c8bb84f4a63 data_len=7
431 0x4a,
432 0xd4, 0xf5, 0xab, 0xe8, 0xed, 0xd2, 0x3f, 0xb9,
433 0xab, 0xd8, 0x1c, 0x8b, 0xb8, 0x4f, 0x4a, 0x63,
434 0x01,
435 // (AUTH_LOGOUT) cid=1
436 0x01,
437 0x40, // FINISH
438 };
439 m_TH.RecordAuthInitial(ClientId: 0, Level: AUTHED_ADMIN, pAuthName: "default_admin");
440 m_TH.RecordAuthLogin(ClientId: 1, Level: AUTHED_MOD, pAuthName: "foobar");
441 m_TH.RecordAuthLogin(ClientId: 2, Level: AUTHED_HELPER, pAuthName: "help");
442 m_TH.RecordAuthLogout(ClientId: 1);
443 Finish();
444 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
445}
446
447TEST_F(TeeHistorian, JoinLeave)
448{
449 const unsigned char EXPECTED[] = {
450 // EX uuid=1899a382-71e3-36da-937d-c9de6bb95b1d data_len=1
451 0x4a,
452 0x18, 0x99, 0xa3, 0x82, 0x71, 0xe3, 0x36, 0xda,
453 0x93, 0x7d, 0xc9, 0xde, 0x6b, 0xb9, 0x5b, 0x1d,
454 0x01,
455 // (JOINVER6) cid=6
456 0x06,
457 // JOIN cid=7
458 0x47, 0x06,
459 // EX uuid=59239b05-0540-318d-bea4-9aa1e80e7d2b data_len=1
460 0x4a,
461 0x59, 0x23, 0x9b, 0x05, 0x05, 0x40, 0x31, 0x8d,
462 0xbe, 0xa4, 0x9a, 0xa1, 0xe8, 0x0e, 0x7d, 0x2b,
463 0x01,
464 // (JOINVER7) cid=7
465 0x07,
466 // JOIN cid=7
467 0x47, 0x07,
468 // LEAVE cid=6 reason="too many pancakes"
469 0x48, 0x06, 't', 'o', 'o', ' ', 'm', 'a',
470 'n', 'y', ' ', 'p', 'a', 'n', 'c', 'a',
471 'k', 'e', 's', 0x00,
472 0x40, // FINISH
473 };
474 m_TH.RecordPlayerJoin(ClientId: 6, Protocol: CTeeHistorian::PROTOCOL_6);
475 m_TH.RecordPlayerJoin(ClientId: 7, Protocol: CTeeHistorian::PROTOCOL_7);
476 m_TH.RecordPlayerDrop(ClientId: 6, pReason: "too many pancakes");
477 Finish();
478 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
479}
480
481TEST_F(TeeHistorian, Input)
482{
483 CNetObj_PlayerInput Input = {.m_Direction: 1, .m_TargetX: 2, .m_TargetY: 3, .m_Jump: 4, .m_Fire: 5, .m_Hook: 6, .m_PlayerFlags: 7, .m_WantedWeapon: 8, .m_NextWeapon: 9, .m_PrevWeapon: 10};
484 const unsigned char EXPECTED[] = {
485 // TICK_SKIP dt=0
486 0x41, 0x00,
487 // new player -> InputNew
488 0x45,
489 0x00, // ClientId 0
490 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
491 // same unique id, same input -> nothing
492 // same unique id, different input -> InputDiff
493 0x44,
494 0x00, // ClientId 0
495 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
496 // different unique id, same input -> InputNew
497 0x45,
498 0x00, // ClientId 0
499 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
500 // FINISH
501 0x40};
502
503 Tick(Tick: 1);
504
505 // new player -> InputNew
506 m_TH.RecordPlayerInput(ClientId: 0, UniqueClientId: 1, pInput: &Input);
507 // same unique id, same input -> nothing
508 m_TH.RecordPlayerInput(ClientId: 0, UniqueClientId: 1, pInput: &Input);
509
510 Input.m_Direction = 0;
511
512 // same unique id, different input -> InputDiff
513 m_TH.RecordPlayerInput(ClientId: 0, UniqueClientId: 1, pInput: &Input);
514
515 // different unique id, same input -> InputNew
516 m_TH.RecordPlayerInput(ClientId: 0, UniqueClientId: 2, pInput: &Input);
517
518 Finish();
519 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
520}
521
522TEST_F(TeeHistorian, SaveSuccess)
523{
524 const unsigned char EXPECTED[] = {
525 // EX uuid=4560c756-da29-3036-81d4-90a50f0182cd datalen=42
526 0x4a,
527 0x45, 0x60, 0xc7, 0x56, 0xda, 0x29, 0x30, 0x36,
528 0x81, 0xd4, 0x90, 0xa5, 0x0f, 0x01, 0x82, 0xcd,
529 0x1a,
530 // team=21
531 0x15,
532 // save_id
533 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
534 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b,
535 // team_save
536 '2', '\t', 'H', '.', '\n', 'l', 'l', '0', 0x00,
537 // FINISH
538 0x40};
539
540 CUuid SaveId = {
541 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
542 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b};
543 const char *pTeamSave = "2\tH.\nll0";
544 m_TH.RecordTeamSaveSuccess(Team: 21, SaveId, pTeamSave);
545 Finish();
546 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
547}
548
549TEST_F(TeeHistorian, SaveFailed)
550{
551 const unsigned char EXPECTED[] = {
552 // EX uuid=b29901d5-1244-3bd0-bbde-23d04b1f7ba9 datalen=42
553 0x4a,
554 0xb2, 0x99, 0x01, 0xd5, 0x12, 0x44, 0x3b, 0xd0,
555 0xbb, 0xde, 0x23, 0xd0, 0x4b, 0x1f, 0x7b, 0xa9,
556 0x01,
557 // team=12
558 0x0c,
559 0x40};
560
561 m_TH.RecordTeamSaveFailure(Team: 12);
562 Finish();
563 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
564}
565
566TEST_F(TeeHistorian, LoadSuccess)
567{
568 const unsigned char EXPECTED[] = {
569 // EX uuid=e05408d3-a313-33df-9eb3-ddb990ab954a datalen=42
570 0x4a,
571 0xe0, 0x54, 0x08, 0xd3, 0xa3, 0x13, 0x33, 0xdf,
572 0x9e, 0xb3, 0xdd, 0xb9, 0x90, 0xab, 0x95, 0x4a,
573 0x1a,
574 // team=21
575 0x15,
576 // save_id
577 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
578 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b,
579 // team_save
580 '2', '\t', 'H', '.', '\n', 'l', 'l', '0', 0x00,
581 // FINISH
582 0x40};
583
584 CUuid SaveId = {
585 0xfb, 0x13, 0xa5, 0x76, 0xd3, 0x5f, 0x48, 0x93,
586 0xb8, 0x15, 0xee, 0xdc, 0x6d, 0x98, 0x01, 0x5b};
587 const char *pTeamSave = "2\tH.\nll0";
588 m_TH.RecordTeamLoadSuccess(Team: 21, SaveId, pTeamSave);
589 Finish();
590 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
591}
592
593TEST_F(TeeHistorian, LoadFailed)
594{
595 const unsigned char EXPECTED[] = {
596 // EX uuid=ef8905a2-c695-3591-a1cd-53d2015992dd datalen=42
597 0x4a,
598 0xef, 0x89, 0x05, 0xa2, 0xc6, 0x95, 0x35, 0x91,
599 0xa1, 0xcd, 0x53, 0xd2, 0x01, 0x59, 0x92, 0xdd,
600 0x01,
601 // team=12
602 0x0c,
603 0x40};
604
605 m_TH.RecordTeamLoadFailure(Team: 12);
606 Finish();
607 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
608}
609
610TEST_F(TeeHistorian, PlayerSwap)
611{
612 const unsigned char EXPECTED[] = {
613 // TICK_SKIP dt=0
614 0x41, 0x00,
615 // EX uuid=5de9b633-49cf-3e99-9a25-d4a78e9717d7 datalen=2
616 0x4a,
617 0x5d, 0xe9, 0xb6, 0x33, 0x49, 0xcf, 0x3e, 0x99,
618 0x9a, 0x25, 0xd4, 0xa7, 0x8e, 0x97, 0x17, 0xd7,
619 0x02,
620 // playerId1=11
621 0x0b,
622 // playerId2=21
623 0x15,
624 // FINISH
625 0x40};
626 Tick(Tick: 1);
627 m_TH.RecordPlayerSwap(ClientId1: 11, ClientId2: 21);
628 Finish();
629
630 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
631}
632
633TEST_F(TeeHistorian, PlayerTeam)
634{
635 const unsigned char EXPECTED[] = {
636 // TICK_SKIP dt=0
637 0x41, 0x00,
638 // EX uuid=a111c04e-1ea8-38e0-90b1-d7f993ca0da9 datalen=2
639 0x4a,
640 0xa1, 0x11, 0xc0, 0x4e, 0x1e, 0xa8, 0x38, 0xe0,
641 0x90, 0xb1, 0xd7, 0xf9, 0x93, 0xca, 0x0d, 0xa9,
642 0x02,
643 // (PLAYER_TEAM) cid=33 team=54
644 0x21, 0x36,
645 // TICK_SKIP dt=0
646 0x41, 0x00,
647 // EX uuid=a111c04e-1ea8-38e0-90b1-d7f993ca0da9 datalen=2
648 0x4a,
649 0xa1, 0x11, 0xc0, 0x4e, 0x1e, 0xa8, 0x38, 0xe0,
650 0x90, 0xb1, 0xd7, 0xf9, 0x93, 0xca, 0x0d, 0xa9,
651 0x02,
652 // (PLAYER_TEAM) cid=3 team=12
653 0x03, 0x0c,
654 // EX uuid=a111c04e-1ea8-38e0-90b1-d7f993ca0da9 datalen=2
655 0x4a,
656 0xa1, 0x11, 0xc0, 0x4e, 0x1e, 0xa8, 0x38, 0xe0,
657 0x90, 0xb1, 0xd7, 0xf9, 0x93, 0xca, 0x0d, 0xa9,
658 0x02,
659 // (PLAYER_TEAM) cid=33 team=0
660 0x21, 0x00,
661 // FINISH
662 0x40};
663
664 Tick(Tick: 1);
665 m_TH.RecordPlayerTeam(ClientId: 3, Team: 0);
666 m_TH.RecordPlayerTeam(ClientId: 33, Team: 54);
667 m_TH.RecordPlayerTeam(ClientId: 45, Team: 0);
668 Tick(Tick: 2);
669 m_TH.RecordPlayerTeam(ClientId: 3, Team: 12);
670 m_TH.RecordPlayerTeam(ClientId: 33, Team: 0);
671 m_TH.RecordPlayerTeam(ClientId: 45, Team: 0);
672 Finish();
673 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
674}
675
676TEST_F(TeeHistorian, TeamPractice)
677{
678 const unsigned char EXPECTED[] = {
679 // TICK_SKIP dt=0
680 0x41, 0x00,
681 // EX uuid=5792834e-81d1-34c9-a29b-b5ff25dac3bc datalen=2
682 0x4a,
683 0x57, 0x92, 0x83, 0x4e, 0x81, 0xd1, 0x34, 0xc9,
684 0xa2, 0x9b, 0xb5, 0xff, 0x25, 0xda, 0xc3, 0xbc,
685 0x02,
686 // (TEAM_PRACTICE) team=23 practice=1
687 0x17, 0x01,
688 // TICK_SKIP dt=0
689 0x41, 0x00,
690 // EX uuid=5792834e-81d1-34c9-a29b-b5ff25dac3bc datalen=2
691 0x4a,
692 0x57, 0x92, 0x83, 0x4e, 0x81, 0xd1, 0x34, 0xc9,
693 0xa2, 0x9b, 0xb5, 0xff, 0x25, 0xda, 0xc3, 0xbc,
694 0x02,
695 // (TEAM_PRACTICE) team=1 practice=1
696 0x01, 0x01,
697 // EX uuid=5792834e-81d1-34c9-a29b-b5ff25dac3bc datalen=2
698 0x4a,
699 0x57, 0x92, 0x83, 0x4e, 0x81, 0xd1, 0x34, 0xc9,
700 0xa2, 0x9b, 0xb5, 0xff, 0x25, 0xda, 0xc3, 0xbc,
701 0x02,
702 // (TEAM_PRACTICE) team=23 practice=0
703 0x17, 0x00,
704 // FINISH
705 0x40};
706
707 Tick(Tick: 1);
708 m_TH.RecordTeamPractice(Team: 1, Practice: false);
709 m_TH.RecordTeamPractice(Team: 16, Practice: false);
710 m_TH.RecordTeamPractice(Team: 23, Practice: true);
711 Tick(Tick: 2);
712 m_TH.RecordTeamPractice(Team: 1, Practice: true);
713 m_TH.RecordTeamPractice(Team: 16, Practice: false);
714 m_TH.RecordTeamPractice(Team: 23, Practice: false);
715 Finish();
716 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
717}
718
719TEST_F(TeeHistorian, PlayerRejoinVer6)
720{
721 const unsigned char EXPECTED[] = {
722 // EX uuid=c1e921d5-96f5-37bb-8a45-7a06f163d27e datalen=1
723 0x4a,
724 0xc1, 0xe9, 0x21, 0xd5, 0x96, 0xf5, 0x37, 0xbb,
725 0x8a, 0x45, 0x7a, 0x06, 0xf1, 0x63, 0xd2, 0x7e,
726 0x01,
727 // (PLAYER_REJOIN) cid=2
728 0x02,
729 // FINISH
730 0x40};
731
732 m_TH.RecordPlayerRejoin(ClientId: 2);
733 Finish();
734 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
735}
736
737TEST_F(TeeHistorian, PlayerReady)
738{
739 const unsigned char EXPECTED[] = {
740 // EX uuid=638587c9-3f75-3887-918e-a3c2614ffaa0 datalen=1
741 0x4a,
742 0x63, 0x85, 0x87, 0xc9, 0x3f, 0x75, 0x38, 0x87,
743 0x91, 0x8e, 0xa3, 0xc2, 0x61, 0x4f, 0xfa, 0xa0,
744 0x01,
745 // (PLAYER_READY) cid=63
746 0x3f,
747 // FINISH
748 0x40};
749
750 m_TH.RecordPlayerReady(ClientId: 63);
751 Finish();
752 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
753}
754
755TEST_F(TeeHistorian, PlayerReadyMultiple)
756{
757 const unsigned char EXPECTED[] = {
758 // TICK_SKIP dt=0
759 0x41, 0x00,
760 // EX uuid=638587c9-3f75-3887-918e-a3c2614ffaa0 datalen=1
761 0x4a,
762 0x63, 0x85, 0x87, 0xc9, 0x3f, 0x75, 0x38, 0x87,
763 0x91, 0x8e, 0xa3, 0xc2, 0x61, 0x4f, 0xfa, 0xa0,
764 0x01,
765 // (PLAYER_READY) cid=0
766 0x00,
767 // EX uuid=638587c9-3f75-3887-918e-a3c2614ffaa0 datalen=1
768 0x4a,
769 0x63, 0x85, 0x87, 0xc9, 0x3f, 0x75, 0x38, 0x87,
770 0x91, 0x8e, 0xa3, 0xc2, 0x61, 0x4f, 0xfa, 0xa0,
771 0x01,
772 // (PLAYER_READY) cid=11
773 0x0b,
774 // TICK_SKIP dt=0
775 0x41, 0x00,
776 // EX uuid=638587c9-3f75-3887-918e-a3c2614ffaa0 datalen=1
777 0x4a,
778 0x63, 0x85, 0x87, 0xc9, 0x3f, 0x75, 0x38, 0x87,
779 0x91, 0x8e, 0xa3, 0xc2, 0x61, 0x4f, 0xfa, 0xa0,
780 0x01,
781 // (PLAYER_READY) cid=63
782 0x3f,
783 0x40};
784
785 Tick(Tick: 1);
786 m_TH.RecordPlayerReady(ClientId: 0);
787 m_TH.RecordPlayerReady(ClientId: 11);
788 Tick(Tick: 2);
789 m_TH.RecordPlayerReady(ClientId: 63);
790 Finish();
791 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
792}
793
794TEST_F(TeeHistorian, AntibotEmpty)
795{
796 const unsigned char EXPECTED[] = {
797 // EX uuid=866bfdac-fb49-3c0b-a887-5fe1f3ea00b8 datalen=0
798 0x4a,
799 0x86, 0x6b, 0xfd, 0xac, 0xfb, 0x49, 0x3c, 0x0b,
800 0xa8, 0x87, 0x5f, 0xe1, 0xf3, 0xea, 0x00, 0xb8,
801 0x00,
802 // (ANTIBOT) antibot_data
803 };
804
805 m_TH.RecordAntibot(pData: "", DataSize: 0);
806 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
807}
808
809TEST_F(TeeHistorian, AntibotEmptyNulBytes)
810{
811 const unsigned char EXPECTED[] = {
812 // EX uuid=866bfdac-fb49-3c0b-a887-5fe1f3ea00b8 datalen=4
813 0x4a,
814 0x86, 0x6b, 0xfd, 0xac, 0xfb, 0x49, 0x3c, 0x0b,
815 0xa8, 0x87, 0x5f, 0xe1, 0xf3, 0xea, 0x00, 0xb8,
816 0x04,
817 // (ANTIBOT) antibot_data
818 0x00,
819 0x00,
820 0x00,
821 0x00};
822
823 m_TH.RecordAntibot(pData: "\0\0\0\0", DataSize: 4);
824 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
825}
826
827TEST_F(TeeHistorian, AntibotEmptyMessage)
828{
829 const unsigned char EXPECTED[] = {
830 // EX uuid=866bfdac-fb49-3c0b-a887-5fe1f3ea00b8 datalen=4
831 0x4a,
832 0x86, 0x6b, 0xfd, 0xac, 0xfb, 0x49, 0x3c, 0x0b,
833 0xa8, 0x87, 0x5f, 0xe1, 0xf3, 0xea, 0x00, 0xb8,
834 0x04,
835 // (ANTIBOT) antibot_data
836 0xf0,
837 0x9f,
838 0xa4,
839 0x96};
840
841 m_TH.RecordAntibot(pData: "🤖", DataSize: 4);
842 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
843}
844
845TEST_F(TeeHistorian, PlayerName)
846{
847 const unsigned char EXPECTED[] = {
848 // EX uuid=d016f9b9-4151-3b87-87e5-3a6087eb5f26 datalen=14
849 0x4a,
850 0xd0, 0x16, 0xf9, 0xb9, 0x41, 0x51, 0x3b, 0x87,
851 0x87, 0xe5, 0x3a, 0x60, 0x87, 0xeb, 0x5f, 0x26,
852 0x0e,
853 // (PLAYER_NAME) id=21 name="nameless tee"
854 0x15,
855 0x6e, 0x61, 0x6d, 0x65, 0x6c, 0x65, 0x73, 0x73,
856 0x20, 0x74, 0x65, 0x65, 0x00};
857
858 m_TH.RecordPlayerName(ClientId: 21, pName: "nameless tee");
859 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
860}
861
862TEST_F(TeeHistorian, PlayerFinish)
863{
864 const unsigned char EXPECTED[] = {
865 // EX uuid=68943c01-2348-3e01-9490-3f27f8269d94 datalen=4
866 0x4a,
867 0x68, 0x94, 0x3c, 0x01, 0x23, 0x48, 0x3e, 0x01,
868 0x94, 0x90, 0x3f, 0x27, 0xf8, 0x26, 0x9d, 0x94,
869 0x04,
870 // (PLAYER_FINISH) id=1 time=1000000
871 0x01, 0x80, 0x89, 0x7a};
872
873 m_TH.RecordPlayerFinish(ClientId: 1, TimeTicks: 1000000);
874 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
875}
876
877TEST_F(TeeHistorian, TeamFinish)
878{
879 const unsigned char EXPECTED[] = {
880 // EX uuid=9588b9af-3fdc-3760-8043-82deeee317a5 datalen=3
881 0x4a,
882 0x95, 0x88, 0xb9, 0xaf, 0x3f, 0xdc, 0x37, 0x60,
883 0x80, 0x43, 0x82, 0xde, 0xee, 0xe3, 0x17, 0xa5,
884 0x03,
885 // (TEAM_FINISH) team=63 Time=1000
886 0x3f, 0xa8, 0x0f};
887
888 m_TH.RecordTeamFinish(TeamId: 63, TimeTicks: 1000);
889 Expect(pOutput: EXPECTED, OutputSize: sizeof(EXPECTED));
890}
891
892TEST_F(TeeHistorian, PrevGameUuid)
893{
894 m_GameInfo.m_HavePrevGameUuid = true;
895 CUuid PrevGameUuid = {.m_aData: {
896 // fe19c218-f555-4002-a273-126c59ccc17a
897 0xfe, 0x19, 0xc2, 0x18, 0xf5, 0x55, 0x40, 0x02,
898 0xa2, 0x73, 0x12, 0x6c, 0x59, 0xcc, 0xc1, 0x7a,
899 //
900 }};
901 m_GameInfo.m_PrevGameUuid = PrevGameUuid;
902 Reset(pGameInfo: &m_GameInfo);
903 Finish();
904 json_value *pJson = json_parse(json: (const char *)m_vBuffer.data() + 16, length: -1);
905 ASSERT_TRUE(pJson);
906 const json_value &JsonPrevGameUuid = (*pJson)["prev_game_uuid"];
907 ASSERT_EQ(JsonPrevGameUuid.type, json_string);
908 EXPECT_STREQ(JsonPrevGameUuid, "fe19c218-f555-4002-a273-126c59ccc17a");
909 json_value_free(pJson);
910}
911