Replay Format
The basic structure of a replay file consists of a Typemap, Headers and Snapshots:

Typemap & Headers
The very first section of the replay file contains the Typemap and Header information. These are preceded by a 4 byte integer which represents the combined byte size of the Typemap and Header together.
Typemap
The first section of the file is the Typemap
which maps named and versioned data types to an unsigned short
identifier. This is important as knowing the type and version allows for backwards compatibility in the parser.
All datatypes are then referenced via the identifier as opposed to its full name and version.
This mapping is required as the unsigned short
type identifier is assigned at runtime and there is no guarantee that the given identifier will be the same for a given type between subsequent replays as the identifiers are assigned in order that each plugin is loaded by bepinex.

Headers
The header section stores a list of data that remains unchanged throughout the replay. Its often used to store metadata, map geometry and static item locations such as terminals / power generators.

Each data entry starts with a 2 byte unsigned short
which represents the type identifier for the data structure. This identifier can then be looked up in the Typemap to get the typename and version to parse the structure.
The list of entries is terminated by an empty data entry marked with the type identifier for the type ReplayRecorder.EndOfHeader
.
A list of header data types and how they should be parsed can be found here.
Snapshots
Every tick the recorder takes a snapshot of the current game state. A snapshot typically consists of 2 parts:
Events - Events are used to store discrete moments such as damage / gunshot events. These have accurate timing information allowing them to be accurate beyond the tick rate.
Dynamics - Dynamics represent continuous structures such as moving entities. These are polled each tick and require interpolation between ticks.

Each snapshot is given an unsigned integer
millisecond timestamp. Currently the recorder does not handle overflow as its unlikely a game lasts long enough for this to be an issue.
Events
Each event entry has its Type ID
which represents the type identifier for the data structure. This identifier can then be looked up in the Typemap to get the typename and version to parse the structure.
It also has an additional Offset
which represents the exact timing for when the event was triggered with respect to the snapshots timestamp. For example, if the snapshot occurred at t = 1000ms
and the event has an offset of 10ms
, then the event was triggered at t = 990ms
.
A list of dynamic data types and how they should be parsed can be found here.
Dynamics
Each dynamic collection has its Type ID
which represents the type identifier for the data structure. This identifier can then be looked up in the Typemap to get the typename and version to parse each entry of the collection.
It should be noted that the list of dynamics presented in each snapshot only represent the dynamics which have changed since the last snapshot as opposed to representing all the dynamics in the current game state. It would be more appropriate to describe them as representing a "Delta Snapshot".
Whether the underlying data structure for a given dynamic stores the delta or actual information is up to the type definition.
A list of dynamic data types and how they should be parsed can be found here.
Within a collection, each dynamic instance will only exist given that it was previously spawned. Similarly a dynamic instance is only considered to no longer exist once it has despawned.
Last updated