-
Notifications
You must be signed in to change notification settings - Fork 5
/
eareplay.html
589 lines (493 loc) · 29.7 KB
/
eareplay.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>EA Replay File Structure</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="Content-Style-Type" content="text/css">
<meta name="ROBOTS" content="FOLLOW">
<style type="text/css">
html { margin: 0; padding: 0; border: none; font-family: Calibri; }
div.listing { white-space: pre; font-family: Consolas, "DejaVu Sans Mono", Courier, monospace; font-size: .8em; line-height: 1.5em;
border: 1px solid navy; margin: 0 2em; padding: 1em; background-color: #FFE; }
table { margin: 0 auto; border-collapse: collapse; border-top: 2px solid black; border-bottom: 2px solid black; }
thead tr { border-bottom: 1px solid black; }
td, th { text-align: left; margin: 0; padding: .5ex 1ex; }
</style>
</head>
<body>
<h1>EA Replay File Formats</h1>
<p>Each replay file consists of the following global structure:</p>
<ol>
<li>Header</li>
<li>Chunks (variable length, arbitrary in number)</li>
<li>End-of-chunks terminator</li>
<li>Footer</li>
</ol>
<p>We use the following data types.</p>
<ul>
<li><strong>char</strong>: one byte, for use in arrays for plain text</li>
<li><strong>byte</strong>: one byte, used for numeric values</li>
<li><strong>uint16_t</strong>: unsigned two-byte integer, stored in little-endian order</li>
<li><strong>uint32_t</strong>: unsigned four-byte integer, stored in little-endian order</li>
<li><strong>tb_ch</strong>: An unsigned two-byte value used to represent a BMP Unicode codepoint, stored in little-endian order
<li><strong>tb_str</strong>: null-terminated array of tb_ch's, to be interpreted as raw BMP Unicode codepoints (?)</li>
</ul>
<p>It is not clear whether two-byte strings are fixed-width strings of raw BMP Unicode codepoints or variable-width UTF-16LE strings.
In the absence of non-BMP characters, the two are essentially identical.</p>
<h2>Tiberium Wars / Kane’s Wrath / Red Alert 3</h2>
<h3>Differences</h3>
<p>In <em>Tiberium Wars</em> and <em>Kane’s Wrath</em>, MAGIC_SIZE is 18, U1_SIZE is 33 and U2_SIZE is 19,
in <em>Red Alert 3</em> MAGIC_SIZE is 17, U1_SIZE is 31 and U2_SIZE is 20.</p>
<h3>Header</h3>
<p>The global header structure is as follows. Note that there are small differences depending
on whether the replay is from a multiplayer or from a skirmish game, distinguished by the value
of <strong>hnumber1</strong>.</p>
<div class="listing">char str_magic[MAGIC_SIZE]; // == "C&C3 REPLAY HEADER" or "RA3 REPLAY HEADER"
byte hnumber1; // My guess: skirmish (0x04) vs. multiplayer (0x05)
uint32_t vermajor; // 0x01
uint32_t verminor; // 0x00,...,0x09
uint32_t buildmajor;
uint32_t buildminor;
byte hnumber2; // My guess: No commentary = 0x06, with commentary track = 0x1E
byte zero1; // == 0x00
tb_str match_title;
tb_str match_description;
tb_str match_map_name;
tb_str match_map_id;
byte number_of_players;
player_t player_data[number_of_players + 1]; // the additional record at the end might be related to commentating
uint32_t offset;
uint32_t str_repl_length; // always == 8
char str_repl_magic[str_repl_length]; // == "CNC3RPL\0"
IF game is Tiberium Wars or Red Alert 3
char mod_info[22];
ENDIF
uint32_t timestamp;
byte unknown1[U1_SIZE]; // usually all zero
uint32_t header_len;
char header[header_len]; // not null-terminated!
byte replay_saver;
uint32_t zero3; // == 0x000000
uint32_t zero4; // == 0x000000
uint32_t filename_length;
tb_ch filename[filename_length];
tb_ch date_time[8];
uint32_t vermagic_len;
char vermagic[vermagic_len];
uint32_t magic_hash;
byte zero4; // == 0x00
uint32_2 unknown2[U2_SIZE];</div>
<p>The header contains a variable number of player records, described by the following <strong>player_t</strong> pseudo-structure of variable length.</p>
<div class="listing">uint32_t player_id;
tb_str player_name;
IF hnumber1 == 0x05
byte team_number;
ENDIF</div>
<p>The values in the header are mostly self-explanatory, though some remarks are in order.</p>
<ul>
<li>In <em>Tiberium Wars</em> 1.07 and above, and in <em>Red Alert 3</em>, there is a 22-byte field
<strong>mod_info</strong>. This is tokenized into null-terminated strings with information about the
mod used for the replay. The default game mod names are "CNC3" and "RA3", respectively. The entire field
is absent in <em>Kane’s Wrath</em> replays and <em>Tiberium Wars</em> up to version 1.06.</li>
<li>The <strong>timestamp</strong> is a standard Unix timestamp for the date and time in GMT.</li>
<li><strong>replay_saver</strong> is the 0-based index of the player who saved the replay.</li>
<li>The values of <strong>date_time</strong> have the following meaning: 0: year, 1: month, 2: weekday (0-6 = Sun-Sat), 3: day, 4: hour, 5: minute, 6: second. The meaning of value 7 is unknown.</li>
<li>The string <strong>vermagic</strong> consists of a textual represention of the version and build as already contained
in the corresponding header fields.</li>
<li>The 32-bit <strong>magic_hash</strong> seems to be an invariant of the game version/mod, but it is not clear how it is derived.</li>
<li>The value of <strong>offset</strong> is the difference between the beginning of the first chunk and the beginning of the “CNC3RPL\0” magic
(<strong>str_repl_magic</strong>).</li>
<li>The meaning of the data in <strong>unknown1</strong> and <strong>unknown2</strong> is unknown.</li>
<li>The bulk of header information is contained in the plain-text string <strong>header</strong>. This is a semicolon-separated list of items of the form “key = value”. When the key is “S”, the value is a colon-separated list of items. Each item describes one player slot (including observers and commentators), and each item consists of a comma-separated list of tokens. The first token is of the form “H<em>name</em>”, where <em>name</em> is the player name, or “C[E|M|H|B]” for computer opponents; the second token is the IP address (ASCII represention of a hexadecimal number), the sixth token specifies the faction. See next section for details.</li>
</ul>
<h3>Plain-text game info</h3>
<p>Lauren wrote a very nice description of the <strong>header</strong> plain-text string:</p>
<div class="listing">GameInfo
M= [short; unknown][char[]; MapName];
MC= [int; Map CRC]
MS= [int; Map File Size]
SD= [int; Seed?]
GSID= [short; GameSpy (Match) ID]
GT= [int; Game...]
PC= [int; Post Commentator]
RU= [int; Initial Camera Player] [int; Game Speed] [int; Initial Resources] [bool; Broadcast Game] [bool; Allow Commentary] [int; Tape Delay] [bool; Random Crates] [bool; Enable VoIP] [int; ? -1] [int; ? -1] [int; ? -1] [int; ? -1] [int; ? -1]
S= [char[]; Player Name],[int; IP],[int; ?],[TT|FT],[int; ?],[Faction],[int; ?],[int; ?],[int; ?],[int; ?],[int; ?],[char[]; Clan Tag]
Annotations:
M=
unknown: hex, 12bit
MS=
File Size: 0 for custom maps
GSID=
GameSpy (Match) ID: hex, 5D91 for Skirmish
RU=
Initial Camera Player: 1 based
S=
Player Name: (H[uman]PlayerName)|(C[PU](E[asy]|M[edium]|H[ard]|B[rutal]))
TT|FT: maybe TrueTrue|FalseTrue, FT only when automatched, FT gets TT when commentated for the replay saver
Faction: 1 based, order corresponds to ini
Clan Tag: without brackets</div>
<h3>The replay body</h3>
<p>The main body of a replay file consists of “chunks”. Each chunk is of the following form.</p>
<div class="listing">uint32_t time_code;
byte chunk_type; // 1, 2, 3 or 4
uint32_t chunk_size;
byte[chunk_size] data;
uint32_t zero; // == 0x00000000</div>
<p>Chunks have to be read consecutively. The end of the chunks is signalled when <strong>time_code</strong> takes the value 0x7FFFFFFF, in which case
<em>there is no more data following the time code</em> and the footer follows.</p>
<p>One unit of time code corresponds to two frames, i.e. 1/15th of a second.</p>
<p>The meaning and structure of replay chunks is discussed further below.</p>
<h3>Footer</h3>
<p>The footer contains the time code of the final chunk. This makes it possible to determine the duration
of the replay without walking through the entire file. The size of the footer, <n>footer_length</n>, is
stored in the final four bytes of the replay. The footer takes the following form:</p>
<div class="listing">char footer_str[17/18]; // == "RA3 REPLAY FOOTER"/"C&C3 REPLAY FOOTER"
uint32_t final_time_code;
byte data[footer_length - 17/18 - 8];
uint32_t footer_length;</div>
<p>The <code>data</code> field is either of length 1 and takes the value 0x02, or otherwise <code>data</code>
is of the form <code>{ 0x01, 0x02, uint32_t n, byte footer_payload[n] }</code> (so necessarily
<code>n + 6 + 8 + 17/18 == footer_length</code>). The meaning of <code>footer_payload</code> is unknown,
although it appears that the final 18 bytes (when <code>n</code> is 32 or 36, in RA3 only) are
IEEE754 32-bit floats, perhaps the kill/death ratio of players 1 to 6.</p>
<p><strong>Warning:</strong> While it appears that the footer could in principle have any length, only certain
specific forms of footers may be recognized by the games.</p>
<h3>Replay chunks in Tiberium Wars / Kane’s Wrath / Red Alert 3</h3>
<p>Each replay chunk appears to come in one of four types, indicated by the value of <strong>chunk_type</strong>.</p>
<p>Chunk types 3 and 4 only appear to be present in replays with a commentary track, and it seems that type 3
contains the audio data and type 4 the telestrator data.</p>
<p>Let us focus on chunk types 1 and 2. We always have <code>data[0] ==
1</code>. For chunks of type 2 we also have <code>data[1] == 0</code>. Let us thus
define the <em>payload data</em> for both chunk types.</p>
<p><strong>Type 1:</strong> <code>data[chunk_size]</code> has the following structure.</p>
<div class="listing">byte; // == 1
uint32_t number_of_commands;
byte[chunk_size-5] payload;</div>
<p>The payload contains a number of separate commands, as indicated by <strong>number_of_commands</strong>.
Each command is terminated by 0xFF, but see below for a detailed discussion.</p>
<p><strong>Type 2:</strong> <code>data[chunk_size]</code> has the following structure.</p>
<div class="listing">byte; // == 1
byte; // == 0
uint32_t n; // player number, index in the player list in the plain-text game info
byte; // == 0x0E in TW/KW, 0x0F in RA3
uint32_t c; // == time_code;
byte[chunk_size-11] payload;</div>
<p>It appears that type-2 chunks only ever appear with chunk_size
24 or 40. The chunk data, starting at <code>payload + 12</code>, appears to
consist of 3 or 7 IEEE754 32-bit floating point values. The first
three appear to be map coordinates (<var>x</var>, <var>z</var>,
<var>y</var>) in units of feet, and the last four values
appear to determine the camera angle and zoom. Specifically,
the structure of <code>payload</code> appears to be as follows:</p>
<div class="listing">byte flags; // 0x01: position, 0x02: rotation
IF flags & 0x01
float32 position[3]; // (x, z, height) in units of feet
ENDIF
IF flags & 0x02
float32 rotation[4]; // quaternion components?
ENDIF</div>
<p>It is almost certain that chunks of type 2 contain camera
movement data, and <code>n</code> is the player number, a
zero-based index into the list of players that appears in the
plain-text header, as well as a “heartbeat”. Chunks of
type 1 contain user actions that actually affect the gameplay, as well
as another set of heartbeats.</p>
<h3>Heartbeats</h3>
<p>Several types of replay data get generated automatically and regularly without
any user action. Let us call these “heartbeats” collectively. The nature
of this data is not known, but it stands to reason that it serves to confirm the
integrity of the game state across the multiple players.</p>
<p>Each active player creates a type-2 heartbeat once every second, for every logical
frame that is a multiple of 15, as well as right at the very start, at timecode 1. This
chunk is always 40 bytes long and appears to contain the complete camera configuration
at that point.</p>
<p>There are also heartbeats in the type-1 chunks: In <em>Red
Alert 3</em>, every three seconds, when the timecode is of the
form 45<var>k</var> + 1, each player emits a heartbeat
command with ID 0x21. In <em>Tiberium Wars</em> and
<em>Kane’s Wrath</em>, a similar heartbeat occurs every 30
seconds at timecodes 450<var>k</var> + 4 (or sometimes
+ 3), and the command ID is respectively 0x57 and 0x61.</p>
<p>Further, there is another command, currently misdubbed
“scroll” (though it appears to have nothing to do with
the camera), which is emitted irregularly, yet for each player in
equal number and at a rate of roughly one every two seconds. Its
ID is 0x85, 0x8F and 0x37 respectively in TW/KW/RA3.</p>
<h3>Type-1 Command Chunks</h3>
<p>Chunks of type 1 contain a variable number of commands; the number of commands is stored in <strong>number_of_commands</strong> and
the actual command codes are contained in <strong>payload</strong>. Each command is of the following form:</p>
<div class="listing">byte command_id;
byte player_id;
byte code[command_size - 3];
byte terminator; // == 0xFF</div>
<p>The value of <strong>command_size</strong> depends on the command type, which is determined by <strong>command_id</strong>:</p>
<ul>
<li>Some commands are of fixed size.</li>
<li>Some few commands are of variable size that need special treatment. There may be a
either a set of fixed options, or there may be a size value somewhere in the command.
See the replay reader source code for those special cases.</li>
<li>The majority of commands are of variable size but of <strong>standard layout</strong>.
This layout depends on a single offset parameter, <strong>n</strong>, and is described
below.</li>
</ul>
<p><strong>Standard-layout variable-sized commands.</strong> A command of standard layout
with offset <code>n</code> has the following form:</p>
<ol>
<li><code>payload[0]</code>: Command ID</li>
<li><code>payload[1]</code>: Player ID</li>
<li>If <code>n</code> > 2, here come <code>n</code> − 2 bytes
of command header.</li>
<li>Here comes a loop: Let <code>x</code> be a byte, and set <code>x = payload[n];</code>.
<ul>
<li>If <code>x == 0xFF</code> stop, you have reached the end of the command.
<li>Let <code>c = (x >> 4) + 1;</code>, i.e. take the upper four bits of <code>x</code> and add one.</li>
<li>Read in <code>c</code> values that are 32-bit integers.
<li>Read in one more byte and assign it to <code>x</code>, and repeat the loop.
</ul>
</li>
<li>It is unknown whether the lower four bits of <code>x</code> have any
relevance. The only observed values are 0, 4 and 5.
</ol>
<p>The player IDs in type-1 chunk commands appear to relate to the player index in the plain-text header by
a simple formula: <code>index = player_id / 8 - k</code>, where <code>k</code> is 3 for <em>Tiberium
Wars</em> and <em>Kane’s Wrath</em> and 2 for <em>Red Alert 3</em>.</p>
<p>The creator of <a href="http://www.airlea.nl/kwrt/">TW/KW Replay Tool</a>, <em>Wmm</em>, has generously provided
his findings on the command types for <em>Red Alert 3</em>. The command-size column either contains a single
number for a fixed-size command, “std: <var>n</var>” for a command in standard layout with offset <var>n</var>,
or “special” for variable-length commands that require special treatment.</p>
<table>
<thead>
<tr><th>command_id</th><th>command_size</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>0x00</td><td>45</td><td>Harder secundary ability, like the bunker of Soviet Combat Engineer??</td></tr>
<tr><td>0x01</td><td>special</td><td>For example at the end of every replay; shows the creator of the replay; also observed in other places.</td></tr>
<tr><td>0x02</td><td>special</td><td>Set rally point.</td></tr>
<tr><td>0x03</td><td>17</td><td>Start/resume research upgrade.</td></tr>
<tr><td>0x04</td><td>17</td><td>Pause/cancel research upgrade.</td></tr>
<tr><td>0x05</td><td>20</td><td>Start/resume unit production.</td></tr>
<tr><td>0x06</td><td>20</td><td>Pause/cancel unit production.</td></tr>
<tr><td>0x07</td><td>17</td><td>Start/resume building construction. (Allies and Soviets only, Empire Cores are treated as units.)</td></tr>
<tr><td>0x08</td><td>17</td><td>Pause/cancel building construction.</td></tr>
<tr><td>0x09</td><td>35</td><td>Place building on map (Allies and Soviets only).</td></tr>
<tr><td>0x0A</td><td>std: 2</td><td>Sell building.</td></tr>
<tr><td>0x0C</td><td>special</td><td>Possibly ungarrison?</td></tr>
<tr><td>0x0D</td><td>std: 2</td><td>Attack.</td></tr>
<tr><td>0x0E</td><td>std: 2</td><td>Force-fire.</td></tr>
<tr><td>0x0F</td><td>16</td><td></td></tr>
<tr><td>0x10</td><td>special</td><td>Garrison a building.</td></tr>
<tr><td>0x12</td><td>std: 2</td><td></td></tr>
<tr><td>0x14</td><td>16</td><td>Move units.</td></tr>
<tr><td>0x15</td><td>16</td><td>Attack-move units.</td></tr>
<tr><td>0x16</td><td>16</td><td>Force-move units.</td></tr>
<tr><td>0x1A</td><td>std: 2</td><td>Stop command.</td></tr>
<tr><td>0x1B</td><td>std: 2</td><td></td></tr>
<tr><td>0x21</td><td>20</td><td>A heartbeat that every player generates at 45<code>n</code> + 1 frames (every 3 seconds).</td></tr>
<tr><td>0x28</td><td>std: 2</td><td>Start repair building.</td></tr>
<tr><td>0x29</td><td>std: 2</td><td>Stop repair building.</td></tr>
<tr><td>0x2A</td><td>std: 2</td><td>‘Q’-select.</td></tr>
<tr><td>0x2C</td><td>29</td><td>Formation-move preview.</td></tr>
<tr><td>0x2E</td><td>std: 2</td><td>Stance change.</td></tr>
<tr><td>0x2F</td><td>std: 2</td><td>Possibly related to waypoint/planning?</td></tr>
<tr><td>0x32</td><td>53</td><td>Harder Security Point usage like Surveillance Sweep.</td></tr>
<tr><td>0x33</td><td>special</td><td>Some UUID followed by an IP address plus port number.</td></tr>
<tr><td>0x34</td><td>45</td><td>Some UUID.</td></tr>
<tr><td>0x35</td><td>1049</td><td>Player info?</td></tr>
<tr><td>0x36</td><td>16</td><td></td></tr>
<tr><td>0x37</td><td>std: 2</td><td>“Scrolling”, an irregularly, automatically generated command.</td></tr>
<tr><td>0x47</td><td>std: 2</td><td>Unknown, always appears in logical frame 5, and than this logical frame contains this command equally as the number of players.</td></tr>
<tr><td>0x48</td><td>std: 2</td><td></td></tr>
<tr><td>0x4B</td><td>special</td><td>Place beacon.</td></tr>
<tr><td>0x4C</td><td>std: 2</td><td>Delete beacon (F9 has something to do with this??).</td></tr>
<tr><td>0x4D</td><td>???</td><td>Place text in beacon.</td></tr>
<tr><td>0x4E</td><td>std: 2</td><td>Player power (Secret Protocols).</td></tr>
<tr><td>0x52</td><td>std: 2</td><td></td></tr>
<tr><td>0x5F</td><td>11</td><td></td></tr>
<tr><td>0xF5</td><td>std: 5</td><td>Drag a selection box and/or select units.</td></tr>
<tr><td>0xF6</td><td>std: 5</td><td>Unknown. You get this command when building a Empire Dojo Core and deploying it. Than it should appear once, no idea what it does.</td></tr>
<tr><td>0xF8</td><td>std: 4</td><td>Left mouse button click.</td></tr>
<tr><td>0xF9</td><td>std: 2</td><td></td></tr>
<tr><td>0xFA</td><td>std: 7</td><td>Create group.</td></tr>
<tr><td>0xFB</td><td>std: 2</td><td>Select group.</td></tr>
<tr><td>0xFC</td><td>std: 2</td><td></td></tr>
<tr><td>0xFD</td><td>std: 7</td><td></td></tr>
<tr><td>0xFE</td><td>std: 15</td><td>Simple use of secundary ability, like those of War Bear, Conscript and Flaktrooper.</td></tr>
<tr><td>0xFF</td><td>std: 34</td><td>Simple select and klick Security Point usage like Sleeper Ambush.</td></tr>
</tbody>
</table>
<h2>Tiberian Twilight</h2>
<p>The replay format of <em>Tiberian Twilight</em> is a lot simpler than that of previous games. The header is less redundant, and header fields
start at fixed offsets, making direct parsing easier. The chunks are no longer separated by four zero bytes, and there are only two chunk types
(types 1 and 2), on account of the absence of a commentary/telestrator feature. Zero-terminated two-byte strings appear padded with zeros inside
regions of fixed size on multiple occasions.</p>
<h3>Header</h3>
<div class="listing">uint32_t file_version; // == 7
char str_magic[11]; // == "CnC4RPLCnC4"
byte zero[18]; // all zero
uint32_t timestamp; // Unix timestamp
byte zero[37]; // all zero
uint32_t header_len; // Always at offset 0x4A, max 1010 = 0x3F2
char header[header_len];
byte replay_saver; // Player who saved the replay
byte zero[8]; // all zero
uint32_t filename_len;
tb_ch filename[filename_len];
tb_ch date_time[8]; // As in TW/KW/RA3
uint32_t vermagic_len;
char vermagic[vermagic_len];
uint32_t magic_hash;
/* Lots of sparse data, not yet analysed */
/* advance to offset 0x440 */
char match_title[512]; // padded tb_str
char match_desc[1024]; // padded tb_str
char map_name[512]; // padded tb_str
player_data_t player_data[10]; // see below
char zero[144]; // all zero
uint32_t unknown[2]; // appears to be { 10, 0 }
</div>
<p>The <strong>player_data_t</strong> data contains the player ID, team number
(always 0 or 1) and the player's name, as a tb_str padded to 64 bytes:</p>
<div class="listing">uint32_t player_id;
uint32_t player_team; // either 0 or 1
char player_name[64]; // padded tb_str</div>
<h3>The replay body</h3>
<p>The replay body consists of a sequence of chunks. The first chunk starts at the fixed offset <strong>0xFA8</strong>.
Chunks are of this form:</p>
<div class="listing">uint32_t time_code;
uint16_t chunk_type; // either 1 or 2
uint16_t chunk_size;
byte data[chunk_size];</div>
<p><strong>Type 1:</strong> data[chunk_size] has the following structure.</p>
<div class="listing">uint16_t chunk_code; // ???
uint32_t number_of_commands;
byte payload[chunk_size - 6];</div>
<p>The payload contains a number of separate commands, as indicated by <strong>number_of_commands</strong>.
Each command is terminated by the three bytes {0x00, 0x00, 0xFF}.</p>
<p><strong>Type 2:</strong> data[chunk_size] has the following structure.</p>
<div class="listing">byte chunk_code; // ????
byte[2]; // == { 0x01, 0x00 }
uint32_t player_number; // 0 to 9 (?)
byte; // == 0x05;
uint32_t tc; // == time_code
byte payload[chunk_size - 12];</div>
<p>Like in the previous games, type-2 chunks contain a player number, followed by a fixed byte (here 0x05), followed by
the chunk timecode again.</p>
<p><strong>End of replay:</strong> The replay finishes when <strong>time_code == 0xFFFFFFFF</strong> and
<strong>chunk_type == 0xFFFF</strong>. In that event, <strong>chunk_size</strong> contains the length of
the footer, and the replay is concluded by as many bytes, which constitute the footer. (In that sense,
the footer is merely another chunk of type 0xFFFF.)</p>
<p>It appears that the footer chunk size is always 64, so the footer chunk is 72 bytes long. It is plausible
that part of the footer (the final 48 byte?) are 12 IEEE754 floats related to the kill/death ratio.</p>
<h2>Generals, Zero Hour, Battle for Middle Earth, Rise of the Witch King</h2>
<p>The replay format of the games <em>Generals</em>, <em>Zero
Hour</em>, <em>Battle for Middle Earth</em>, <em>Battle for Middle
Earth 2</em>, and its expansion <em>Rise of the Witch King</em>
is considerably simpler than that of the later games. Most
notably, the chunks do not have length information, so one must
know their sizes by other means. Also, there is no footer, just a
final chunk.</p>
<h3>Header</h3>
<p>The size <var>u1</var> is 12 for <em>Generals</em> and <em>Zero Hour</em>, and 21 in the BfME games; <var>u2</var> is 8 in CCG/ZH and 13 in the BfME games; <var>u3</var> is 4 except in BfME2, where it is 6.</p>
<div class="listing">char str_magic[6/8]; // == "GENREP", "BFMEREPL" or "BFME2RPL"
uint32_t timestamp_begin;
uint32_t timestamp_end;
byte unknown1[u1];
tb_str str_filename;
tb_ch date_time[8]; // as in TW/KW
tb_str str_version;
tb_str str_build_date;
uint16_t version_minor;
uint16_t version_major;
byte magic_hash[u2]
char header[]; // null-terminated!
uint16_t unknown2;
uint32_t unkonwn3[u3];</div>
<p><strong>Remarks.</strong> The two timestamps appear to indicate the real-world start and end time of the game. The <strong>header</strong> is of the same format as the later games.</p>
<h3>The replay body</h3>
<p>The replay body consists of a sequence of <em>chunks</em>. Each chunk is of the following form:</p>
<div class="listing">uint32_t time_code;
uint32_t chunk_code;
uint32_t number; // Player number?
byte number_of_commands;
struct { byte cmd, byte nargs } signature[number_of_commands];
byte arguments[]; // variable!</div>
<p>In the simplest case, <strong>number_of_commands</strong> is
zero and <strong>signature</strong> and <strong>arguments</strong>
are empty. Otherwise, there are <strong>number_of_commands</strong> many
pairs of bytes {<strong>cmd</strong>,<strong>nargs</strong>}, where <strong>cmd</strong>
refers to one of about ten commands and <strong>nargs</strong> is the
number of arguments. The size of an argument depends on the command type.
Finally, <strong>arguments</strong> consists of all the arguments of all
the commands, simply one after the other.</p>
<p>The meaning of the chunk codes is unknown in general. Only a handful of values
seem to occur, all in the range 0x300 – 0x500, and 0x1B, 0x1D. Many codes only
ever seem to appear with a fixed, specific signature of commands.</p>
<p>These are the known commands and their argument sizes.</p>
<table>
<thead>
<tr><th>command</th><th>argument size (bytes)</th><th>Description/Notes</th></tr>
</thead>
<tbody>
<tr><td>0x00</td><td>4</td><td>?</td></tr>
<tr><td>0x01</td><td>4</td><td>?</td></tr>
<tr><td>0x02</td><td>1</td><td>?</td></tr>
<tr><td>0x03</td><td>4</td><td>?</td></tr>
<tr><td>0x04</td><td>4</td><td>?</td></tr>
<tr><td>0x06</td><td>12</td><td>3 uint32_t's? 1 long double?</td></tr>
<tr><td>0x07</td><td>12</td><td>3 uint32_t's? 1 long double?</td></tr>
<tr><td>0x08</td><td>16</td><td>4 uint32_t's?</td></tr>
<tr><td>0x09</td><td>4/16</td><td>4 bytes in BFME2, 16 otherwise</td></tr>
<tr><td>0x0A</td><td>4</td><td>4 uint32_t's?</td></tr>
</tbody>
</table>
<p>There is no footer. The final chunk has <strong>number_of_commands</strong> set to zero, and it appears to have command code 0x1B or 0x1D.</p>
<h2>Utility functions</h2>
<p>Here is an example function for converting a raw Unicode codepoint into a UTF-8-encoded string
(restricted to at most 4 bytes, not the theoretical maxiumum of 6 bytes).</p>
<div class="listing">typedef union _codepoint_t
{
char c[4];
unsigned char u[4];
unsigned int i;
} codepoint_t;
void codepointToUTF8(unsigned int cp, codepoint_t * szOut)
{
size_t len = 0;
szOut->u[0] = szOut->u[1] = szOut->u[2] = szOut->u[3] = 0;
if (cp < 0x0080) len++;
else if (cp < 0x0800) len += 2;
else len += 3;
int i = 0;
if (cp < 0x0080)
szOut->u[i++] = (unsigned char) cp;
else if (cp < 0x0800)
{
szOut->u[i++] = 0xc0 | (( cp ) >> 6 );
szOut->u[i++] = 0x80 | (( cp ) & 0x3F );
}
else
{
szOut->u[i++] = 0xE0 | (( cp ) >> 12 );
szOut->u[i++] = 0x80 | (( ( cp ) >> 6 ) & 0x3F );
szOut->u[i++] = 0x80 | (( cp ) & 0x3F );
}
}
// Result can be retrieved e.g. via std::string(szOut->c)</div>
<p>The above function can be used to read a null-terminated string of type <strong>tb_str</strong> and
store the result as a UTF-8-encoded string:</p>
<div class="listing">std::string read2ByteString(std::istream & in)
{
codepoint_t ccp;
char cbuf[2];
std::string s;
while (true)
{
in.read(&cbuf, 2);
if (cbuf[0] == 0 && cbuf[1] == 0) break;
codepointToUTF8(((unsigned int)(cbuf[1]) << 8) | ((unsigned int)(cbuf[0])), &ccp);
s += std::string(ccp.c);
}
return s;
}</div>
</body>
</html>