Key Data Structures

Outlines common data structures and how they are stored in the replay file.

Note that the coordinate system will follow Unity's convention so all positions and rotations use the left handed coordinate system (x: right, y: up, z: forward).

Vectors

Vectors are simply 3 floating point values packed together:

Quaternions

Quaternions consist of 4 floating point values packed together:

However, to save space, quaternions are packed into a smaller datastructure by utilising the fact that normalized quaternions always have a magnitude of 1. This way the largest component can be omitted and recalculated by subtracting all the existing components from 1. We store which component of the quaternion is missing by using a single byte:

Here are how the quarternions can be unpacked when parsing:

Half Precision

To reduce size of replays, many values are stored in half precision. This is simply a 16-bit floating point value instead of 32-bit. I perform this conversion by doing the following:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint AsUInt(float x) {
    return *(uint*)&x;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe float AsFloat(uint x) {
    return *(float*)&x;
}

// NOTE:: These Half <-> Float conversions do not account for Infinity or NaN!

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float HalfToFloat(ushort x) {
    // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
    int e = (x & 0x7C00) >> 10; // exponent
    int m = (x & 0x03FF) << 13; // mantissa
    int v = (int)(AsUInt((float)m) >> 23); // evil log2 bit hack to count leading zeros in denormalized format
    return AsFloat((uint)((x & 0x8000) << 16 | Convert.ToInt32(e != 0) * ((e + 112) << 23 | m) | (Convert.ToInt32(e == 0) & Convert.ToInt32(m != 0)) * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000)))); // sign : normalized : denormalized
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort FloatToHalf(float x) {
    // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits
    uint b = AsUInt(x) + 0x00001000; // round-to-nearest-even: add last bit after truncated mantissa
    uint e = (b & 0x7F800000) >> 23; // exponent
    uint m = b & 0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding
    return (ushort)((b & 0x80000000) >> 16 | Convert.ToInt32(e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) | (Convert.ToInt32(e < 113) & Convert.ToInt32(e > 101)) * ((((0x007FF000 + m) >> (int)(125 - e)) + 1) >> 1) | Convert.ToUInt32(e > 143) * 0x7FFF); // sign : normalized : denormalized : saturate
}

Half precision Vectors or Quaternions utilise 16-bit floats instead of 32-bit floats.

Boolean

Booleans are stored as a single byte where 1 is True and 0 is False.

Strings

Strings are stored in UTF-8 byte format where the first 2 bytes is an unsigned short that represents the length of the string in bytes:

Identifier

Identifier is a custom data structure used to represent various types in GTFO such as enemies, gear, items and vanity.

The first component of the identifier is a single byte that represents the enum:

IdentifierType {
  Unknown = 0
  Gear = 1
  Alias_Gear = 2
  Item = 3
  Enemy = 4
  Vanity = 5
}

The rest of the identifier is parsed as according to its type.

Unknown

This type does not have any additional data to be parsed. It represents an unknown object.

Gear

This type represents any in game gear such as guns, melees and tools. The game constructs these using a gear builder system which is expanded upon here.

The gear builder uses a JSON string to construct each weapon, this string is what is what is stored in the replay along side an addition shorthand alias:

Alias_Gear

An extension to the Gear type which does not include the entire gear string but just its alias:

This type is always used once the alias for a given gear is defined. This is because storing the full gear JSON string is expensive in terms of space.

Item

This type has an additional unsigned short value that is the persistentID of the item.

Enemy

This type has an additional unsigned short value that is the persistentID of the enemy type.

Vanity

This type has an additional unsigned short value that is the persistentID of the vanity item.

Last updated