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