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