Pseudo-code conventions
All data types are little endian, unless noted otherwise. All strings are 8-bit US-ASCII, unless noted otherwise (i.e. a character occupies a byte, but only the lower 7 bits are used, the most significant bit is always zero).
All data types and structures are specified in pseudo-Rust code. If you do not know Rust, it should still be familiar.
Unsigned types are designated with u<bits>:
- u8is- uint8_tor- unsigned char/- bytein C
- u16is- uint16_t
- u32is- uint32_t
- u64is- uint64_t
Signed types are designated with i<bits>:
- i8is- int8_tor- signed char/- charin C
- i16is- int16_t
- i32is- int32_t
- i64is- int64_t
Floating point types are designated with f<bits>:
- f32is a single-precision IEEE 754 floating point number,- floatin C
- f64is a double-precision IEEE 754 floating point number,- doublein C
Note that for many integer data types, we don't know the exact bit size, or even if they are signed or unsigned, unless e.g. an obviously signed value was observed.
Fixed-length and variable length arrays are designated with [<type>; <length>], where the length may not be a valid Rust definition (for example, if it depends on another field).
Constants are aliases for a certain value that makes it convenient to reference by name. Constants will always have a data type specified.
#![allow(unused)] fn main() { const EXAMPLE: u32 = 1; }
Structures are basically memory views/instructions on how to interpret a block of memory. Assume a C-compatible layout and 32 bit alignment (discussed more shortly). They have a name, and then list fields by name followed by a value. An example:
#![allow(unused)] fn main() { struct Example { foo: u32, bar: [f32; foo - 4], } }
This means read an unsigned integer of 32 bits/4 bytes, and then read foo - 4 32 bits/4 bytes floating point numbers.
For structures where the use of a field isn't known, they will be designated with "unk" and the offset of the field in the structure, e.g. unk08. Because MechWarrior 3 is a 32-bit executable and most likely written in C++ (based on the dependencies), the structures the game actually uses will follow those padding rules. The structures provided will either be already 32-bit aligned, or will have explict padding fields, designated with "pad" and the offset.
Tuples are sequences of types/elements. This is similar to structures, except that the fields aren't named:
#![allow(unused)] fn main() { tuple Example(f32, f32, f32); }
This means a structure of 3 floating point values where the field names/usages aren't considered important. I will try to avoid tuples, but they are occasionally useful. You may always translate tuples into structures by naming the fields.
Enumerations are exclusive values, so only a single value is valid. The enumeration will have a integer type that indicates it's size when read. Zero (0) is not generally a valid value unless explicitly named:
#![allow(unused)] fn main() { enum Example: u16 { A = 1, B = 2, } }
Bitflags are similar to enumerations, but can have multiple values set or unset:
#![allow(unused)] fn main() { bitflags Example: u16 { A = 1 << 0, // 0x1 B = 1 << 1, // 0x2 } }
This means that zero (0) is generally valid (this means all unset). For the example, valid values are:
- 0
- 1 = A
- 2 = B
- 3 = A | B
Bitflags may also contain aliases of common flag combinations.