How to read C/C++ data structures

Since a lot of people modding Outbreak have next to zero experience of writing or reading C code, I feel like this small article might be a bit useful.

Most file formats in this info dump will be described as snippets of C structure declarations, which I personally find very easy and understandable to both read and write, as long as you're not using C's crazy declaration rules to its full extent (which is the entire reason this website exists.)

C structures usually follow this, ehm, structure:

struct <name> {
    <first member>;
    <second member>;
    ...
    <N-th member>;
};

While not always being the case in actual C, in this info dump you can assume that members have zero padding in between. That is, bytes of the next member start immediately after the previous one.

In this info dump, each member in a structure will more or less follow this template:

<datatype> <name> [array-size]

C uses char, short, int, long, float basic data types, however, their size is usually ambigious. So, for the sake of clarity, I'll be using these data type names instead:

Name Description Size (in bytes)
u8 unsigned 8-bit integer, also known as a byte 1
s8 signed 8-bit integer 1
u16 unsigned 16-bit integer 2
s16 signed 16-bit integer 2
u32 unsigned 32-bit integer 4
s32 signed 32-bit integer 4
f32 32-bit floating point value 4

One more important thing to keep in mind is that PS2 is a little-endian system, which means that the multi-byte values are encoded sort of backwards. For example, if you'd take an integer 12345 and convert it to hexidecimal, it would become 0x3039, but when writing it as bytes, you'll have to write bytes 39 30.

Example

If we have the following structures:

struct Foo {
    f32 val;
    s8  arr[4];
};

struct Bar {
    s16 a;
    s16 b;
    u32 c;
    Foo foo;
};

and, say, we want to convert the structure into bytes with the data set to these values:

Bar.a = -2;
Bar.b = -3;
Bar.c = 514;
Bar.foo.val = 1.0;
Bar.foo.arr[0] = 1;
Bar.foo.arr[1] = 2;
Bar.foo.arr[2] = 3;
Bar.foo.arr[3] = 4;

In this case, the bytes for the Bar structure will be as follows:

FE FF        // Bar.a
FD FF        // Bar.b
02 02 00 00  // Bar.c
00 00 80 3F  // Bar.foo.val
01           // Bar.foo.arr[0]
02           // Bar.foo.arr[1]
03           // Bar.foo.arr[2]
04           // Bar.foo.arr[3]

Without any comments and formatting (so, like in a hex editor), it would be

FE FF FD FF 02 02 00 00 00 00 80 3F 01 02 03 04

Last updated on 2024-10-18.