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