Skip to content

Chunks

File extensions

.chunk_pc .g_chunk_pc

About

[V] Knobby said:

... The chunk_pc file is a loadable part of the world. It is what is needed for a physical chunk of the world. This includes the level geometry, level collision, sidewalk data, traffic splines, world objects, textures, etc. I say etc, but it really is probably just the tip of the iceberg. This is a massive monolithic file full of all kinds of things.

File contents

Things found in chunkfiles:

  • Materials
    Texture list, Shader params, Hashed shader names
  • GPU Models
    Every visible model is in the g_chunk file. The format for static models is figured out.
  • CPU Models
    The chunk_pc file itself contains models too, with no UV's or other extra data. Labeled as physmodels, since they're probably collision for loose objects.
  • Objects
    Static world models and loose objects.
  • Static world collision

  • Light sources

  • Mesh movers
    Used for doors and other simple animations.

Not yet found but expected:

  • Traffic paths, sidewalk data

Some of this stuff could lie in the parts where the layout is already figured out. Just have to mess around with these values.

The .chunk_pc file internally shares a lot of structures with other files.

.chunk_pc Layout

256B header

Header
Offset Type Name Comment
0x0 u32 magic
0x4 u32 version always 121
0x8
0xC u32 (unused) always 0
0x10
... ... ... ...
0x94 u32 cityobject_count
0x98 u32 unknown23_count
0x9C
0xA0
0xA4
0xA8
0xAC
0xB0
0xB4 u32 mesh_mover_count
0xB8 u32 unknown27_count
0xBC u32 unknown28_count
0xC0 u32 unknown29_count
0xC4 u32 unknown30_count
0xC8 u32 unknown31_count
0xCC
0xD0
0xD4 f32 These look like world coords.
0xD8 f32
0xDC f32
0xE0 f32
0xE4 f32
0xE8 f32
0xEC f32
0xF0
0xF4
0xF8
0xFC
chunk_pc header
// size: 0x100
struct chunk_header {
    int32_t     signature;      //
    int32_t     version;        // Version 121
    int32_t     unknown0x08;    // 
    int32_t     unused_0x0c;    // always 0
    // ... todo ... //
}

texture_list

Filename of each texture used in the chunk. Always starts at 0x100 as the header is fixed length. Size 16-byte aligned

chunk_pc texturelist
int32_t     num_textures;               //
uint32_t    padding[num_textures];      //
char        texture_names[num_textures];//
align 16                                //
The objects are split into two structs, located very far apart in the file. The second part is found at #objects. Some of these transforms are left unused by objects.

model_info

object_data_0_header    header
align 16
gpu_model_unknown       [header.num_gpu_models]
align 16
object_transform        [header.num_object_transforms]
align 16
object_unknown3         [header.num_unknown3s]
align 16
object_unknown4         [header.num_unknown4s]
align 16
structs
// size: 0x20
struct object_data_0_header {
    uint32_t    num_gpu_models;
    uint32_t    num_object_transforms;
    uint32_t    num_cpu_models;
    uint32_t    num_unknown3s;
    uint32_t    num_unknown4s;
};

// size: 0x18
struct gpu_model_unknown {
    int32_t unknown0x00;
    int32_t unknown0x04;
    int32_t unknown0x08;
    int32_t unknown0x0c;
    int32_t unknown0x10;
    int32_t unknown0x14;
};

// Every object has one of these, but there are always just a couple left unused??
// size: 0x50
struct object_transform {
    fl_vector   origin;         // XYZ 3x float
    fl_vector   xform_basis_x;  //
    fl_vector   xform_basis_y;  //
    fl_vector   xform_basis_z;  //
    // ... //
    float       unknown0x4C;    //
    uint32_t    unknown0x50;    // always 0?
    int32_t     unknown0x54;    //
    uint32_t    model_idx;      // gpu model index
    int32_t     unknown0x5C;    //
};

// size: 0x64
public struct object_unknown3 {
    float       unknown0x00;
    float       unknown0x04;
    float       unknown0x08;
    uint32_t    unknown0x0C;
    float       unknown0x10;
    uint32_t    unknown0x14;
    float       unknown0x18;
    float       unknown0x1C;
    float       unknown0x20;
    uint32_t    unknown0x24;
    float       unknown0x28;
    uint32_t    unknown0x2C;
    float       unknown0x30;
    float       unknown0x34;
    float       unknown0x38;
    float       unknown0x3C;
    float       unknown0x40;
    float       unknown0x44;
    float       unknown0x48;
    float       unknown0x4C;
    float       unknown0x50;
    float       unknown0x54;
    float       unknown0x58;
    float       unknown0x5C;
    uint32_t    unknown0x60;
};

// size: 0x34
public struct object_unknown4 {
    float   unknown0x00;
    float   unknown0x04;
    float   unknown0x08;
    float   unknown0x0C;
    float   unknown0x10;
    float   unknown0x14;
    float   unknown0x18;
    float   unknown0x1C;
    float   unknown0x20;
    float   unknown0x24;
    float   unknown0x28;
    float   unknown0x2C;
    float   unknown0x30;
};

static_collision

links

Some knowledge of havok collisions: https://niftools.sourceforge.net/wiki/Nif_Format/Mopp

Collision for all static objects in a chunk seem to be baked into one Havok collision blob. This is inherently unmoddable, unless some genius comes forward and figures out Havok collisions. Look up Havok MOPP for some info from other mod scenes, though there isn't much.

The vertices seem to match GPU models.

model_buffer_headers

Defines vertex and index buffers in both .chunk_pc and .g_chunk_pc.

    model_buffer_header     []
    model_v_buffer_header   [] // times num_v_buffers for each model_buffer_header

structs
// "CPU model" is stored in this file (.chunk_pc). Probably used for loose object collision.
// "GPU model" is stored in this .g_chunk

// One big shared one for g_chunk and many individual model buffers for cpu chunk
// size: 0x14
struct model_buffer_header {
    int16_t     type;           // Either 7 or 0? 7 is CPU, 0 GPU.
    int16_t     num_v_buffers;  // Always 1 for CPU
    int32_t     num_indices;    //
    int32_t     unknown0x08;    // always -1
    int32_t     unknown0x0c;    // always -1
    int32_t     unknown0x10;    // always 0
}

// Defines a vertex buffer.
// The model_buffer_header for g_chunk contains many v buffers, one for each combination of num_unk2b and num_uvs used in the chunk.
// size: 0x10
struct model_v_buffer_header {
    uint8_t     num_unk2b;      // Number of whatever this data is in a vertex. Adds 2B to vert size each. Always 0 on CPU
    uint8_t     num_uvs;        // Number of UV's in a vertex. Adds 4B to vert size each. Always 0 on CPU
    uint16_t    len_vertex;     // Size of one vert in bytes. Size is 12B + the above
    uint32_t    num_vertices;   //
    int32_t     unknown0x08;    // always -1
    int32_t     unknown0x0c;    // always 0
}

materials

Very similar to already documented SRTT / IV format.

g_model_headers

Defines offset and number for verts and indices for a mesh in the big shared .g_chunk_pc buffers

objects

  • objects
  • names
structs
struct object {
    fl_vector   bb_min;             // bbox used for culling
    uint32_t    unknown0x0c;        // always 0?
    fl_vector   bb_max;             // bbox used for culling
    float       render_distance;    //
    uint32_t    unknown0x20;        // always 0?
    uint32_t    unknown0x24;        //
    uint32_t    unknown0x28;        // always 0?
    int16_t     unknown0x2C;        //
    int16_t     unknown0x2E;        // zero pad for alignemt
    int32_t     flags;              //
    int32_t     unknown0x34;        //
    int32_t     unknown0x38;        // Almost always 0. Exceptions: sr2_skybox, sr2_intdkmissunkdk, sr2_intarcutlimo, sr2_intaicutjyucar
    int32_t     unknown0x3C;        //
    uint32_t    transform_idx;      // See object_transform 
    uint32_t    unknown0x44;        //
    uint32_t    unknown0x48;        //
    uint32_t    unknown0x4C;        //
};

unknown_names_12

Note

Are these the names of destroyable objects?

unknown13

unknown18

Note

These only appear in a couple chunkfiles around Saints HQ
See chunks numbered 93 and 106.

unknown19

unknown20

unknown21

unknown22

unknown23

unknown24

unknown25

mesh_movers

Count is found in the header.

Mesh movers define simple animations for objects. They're used at least for:

  • Doors
  • Rotating ultorball in front of their skyscraper
  • Skybox

Names for movers and starts can be found a bit further: #mesh_mover_names

KS
seq:
  - id: mesh_movers
    type: mesh_mover
    repeat: expr
    repeat-expr: mesh_mover_count

types:
  mesh_mover:
    seq:
      - id: unk0
        size: 14
      - id: start_count
        type: u2
      - id: unk1
        size: 12

unknown27

Count is found in the header.

unknown28

Count is found in the header.

unknown29

Count is found in the header.

unknown30

Count is found in the header.

unknown31

Count is found in the header.

unknown32

mesh_mover_names

#mesh_movers

Just a bunch of c-strings. Nothing else.

One entry contains:

  • Name of the mesh mover
  • Name of each "start" in the mover.
KS
seq:
  - id: mesh_mover_names
    type: mesh_mover_name(
      mesh_movers[_index].start_count
      )
    repeat: expr
    repeat-expr: mesh_mover_count
  - type: align(16)

types:
  mesh_mover_name:
    params:
      - id: start_count
        type: u2
    seq:
      - id: name
        type: strz
      - id: start_names
        type: strz
        repeat: expr
        repeat-expr: start_count

lights

Warning

The layout up to this point is only 99% figured out. Sometimes the lights aren't there.
If num_lights == 'MCHK', stop. This bubblegum fix will save you from parsing garbage on almost every chunk.

uint32_t    num_lights
uint32_t    unknown26b
light       lights[num_lights]
strz        light_names         //null terminated strings

align 16
float       light_unk2[]        // times num_unk_floats for every light
align 16
structs
struct light {
    int32_t     flags;
    uint32_t    unknown0x04;    // always 0?
    fl_vector   color;          // RGB 3x float
    uint32_t    unknown0x14;
    uint32_t    unknown0x18;
    uint32_t    unknown0x1c;
    uint32_t    num_unk_floats;
    int32_t     unknown0x24;    // always -1?
    int32_t     unknown0x28;    // Iggy's performance complaint
    int32_t     unknown0x2c;    // always -1?
    uint32_t    unknown0x30;
    float       unknown0x34;
    uint32_t    unknown0x38;
    fl_vector   origin;         // XYZ 3x float
    fl_vector   xform_basis_x;  // Basis X (probably)
    fl_vector   xform_basis_y;  // Basis Y (probably)
    fl_vector   xform_basis_z;  // Basis Z (probably)
    float       unknown0x6c;
    float       unknown0x70;
    float       radius_inner;   // Light has full intensity until this distance
    float       radius_outer;   // 
    float       render_dist;    //
    int32_t     unknown0x80;    // always -1?
    int32_t     parent_idx;     // -1 if none. Origin is relative to parent object, if set.
    uint32_t    unknown0x88;    // 
    uint32_t    unknown0x8c;    //
    uint32_t    type;           // ? maybe? Possible values: 0, 1, 2, 3
};
Flags
    // These flags are present at least sometimes.
    // TODO: verify that there aren't more of them
    // The names of shadow / light flags were found in the game exe, are they
    // exposed to lua?
    FLAG_UNKNOWN0 = 0x8;
    FLAG_UNKNOWN1 = 0x10;
    FLAG_UNKNOWN2 = 0x20;
    FLAG_UNKNOWN3 = 0x40;
    FLAG_UNKNOWN4 = 0x80;
    FLAG_LIGHT_LEVEL = 0x100;       // Cast light on static
    FLAG_LIGHT_CHARACTER = 0x200;   // Cast light on characters and props
    FLAG_SHADOW_LEVEL = 0x400;      // Static meshes create shadows
    FLAG_SHADOW_CHARACTER = 0x800;  // Characters and props create shadows
    FLAG_UNKNOWN9 = 0x2000;
    FLAG_UNKNOWN10 = 0x8000;
    FLAG_UNKNOWN11 = 0x20000;