Skip to content

Instantly share code, notes, and snippets.

@sevonj
Last active May 15, 2023 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sevonj/250006853487afada89aff6b6ae574a3 to your computer and use it in GitHub Desktop.
Save sevonj/250006853487afada89aff6b6ae574a3 to your computer and use it in GitHub Desktop.
Saints Row 2 .chunk_pc Kaitai Struct specification
meta:
id: sr2_chunk_pc
file-extension: chunk_pc
license: MIT
encoding: utf-8
endian: le
doc: |
MIT License
Copyright (c) 2022 Möyh Mäyhem
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The chunk parser is incomplete, but it gets pretty far.
seq:
- id: magic
contents: [0x12, 0xca, 0xca, 0xbb]
- id: version
contents: [0x79, 0x0, 0x0, 0x0] # Version 121. Same for every chunk in sr2.
- contents: [0x0e, 0x0, 0x0, 0x0] # Unknown. Same for every chunk in sr2.
- id: meshlibrary # 0 on every single chunkfile except for sr2_meshlibrary
size: 4
- id: header_0x10
type: u4
- id: header_0x14
size: 24
- contents: [0, 0, 0, 0, 0, 0, 0, 0]
- id: header_xx
size: 40
- contents: [0, 0, 0, 0]
- id: header_xxx
size: 20
- contents: [0, 0, 0, 0]
- id: header_xxxx
size: 20
- id: len_g_chunk_1
type: u4
- id: len_g_chunk_2
type: u4
- id: num_cityobjects
type: u4
- id: num_unknown23
type: u4
- id: header_0x9c
type: u4
- id: header_0xa0
type: u4
- contents: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 16B null
- id: num_mesh_movers
type: u4
- id: num_unknown27
type: u4
- id: num_unknown28
type: u4
- id: num_unknown29
type: u4
- id: num_unknown30
type: u4
- id: num_unknown31
type: u4
- id: header_0xcc
type: u4
- id: num_unknown26 # almost always 0, exceptions: sr2_skybox, sr2_intdkmissunkdk, sr2_intarcutlimo, sr2_intaicutjyucar
type: u4
- id: world_min_x
type: f4
- id: world_min_y
type: f4
- id: world_min_z
type: f4
- id: world_max_x
type: f4
- id: world_max_y
type: f4
- id: world_max_z
type: f4
- id: header_0xec
type: f4
- id: num_unknown32
type: u4
- contents: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 12B null
# Textures
- id: num_texture_names
type: u4
- size: num_texture_names * 4
- id: texture_names
type: strz
repeat: expr
repeat-expr: num_texture_names
- type: align(16)
# Model info
- id: num_rendermodels
type: u4
- id: num_cityobject_parts
type: u4
- id: model_count
type: u4
- id: num_unknown3s
type: u4
- id: num_unknown4s
type: u4
- type: align(16)
- id: rendermodel_unk0s
type: rendermodel_unk0
repeat: expr
repeat-expr: num_rendermodels
- type: align(16)
- id: cityobject_parts_offset
type: offset(_io.pos)
- id: cityobject_parts
type: cityobject_part
repeat: expr
repeat-expr: num_cityobject_parts
- type: align(16)
- id: unknown3s
type: unknown3
repeat: expr
repeat-expr: num_unknown3s
- type: align(16)
- id: unknown4s
type: unknown4
repeat: expr
repeat-expr: num_unknown4s
- type: align(16)
- id: num_unk_worldpositions
type: u4
# Assumption: The data from here to mopp is some
# complicated Havok collision model.
- id: unk_worldpositions # is this vertices of a baked model?
type: vec3
repeat: expr
repeat-expr: num_unk_worldpositions
- id: num_unknown6s # seems incomprehensible.
type: u4
- id: unknown6s
size: 3
repeat: expr
repeat-expr: num_unknown6s
- id: num_unknown7s
type: u4
- id: unknown7s
type: u4
repeat: expr
repeat-expr: num_unknown7s
- id: num_unknown8s
type: u4
- id: unknown8s
size: 12
repeat: expr
repeat-expr: num_unknown8s
- type: align(16)
# https://niftools.sourceforge.net/wiki/Nif_Format/Mopp
- id: len_havok_mopp
type: u4
- type: align(16)
- id: havok_mopp
size: len_havok_mopp
- type: align(4)
# is this a bounding box?
- id: unknown10min
type: vec3
- id: unknown10max
type: vec3
- type: align(16)
# Model header
- id: model_headers
type: model_header(_index)
repeat: expr
repeat-expr: model_count
- id: vert_headers
type: vert_header_cont(model_headers[_index].vert_header_count)
repeat: expr
repeat-expr: model_count
- type: align(16)
- id: phys_models
type: phys_model_buffer(
model_headers[_index].type == 7,
model_headers[_index].num_indices,
vert_headers[_index].vert_header[0].num_vertices)
repeat: expr
repeat-expr: model_count
# Materials
- id: num_materials
type: u4
- type: align(16)
- id: num_mat_shader_params
type: u4
- id: pad_mat
size: 8
- id: num_mat_unknown3s
type: u4
- id: mat_unknown1
type: u4
- id: materials
type: material
repeat: expr
repeat-expr: num_materials
- id: mat_flags
type: mat_bitflag(materials[_index].bitflag_count)
repeat: expr
repeat-expr: num_materials
# Messing with these break shaders.
- id: mat_unknown2
size: 16
repeat: expr
repeat-expr: num_materials
- type: align(16)
# Mostly colors, sometimes affects scrolling textures.
- id: mat_shader_params
type: f4
repeat: expr
repeat-expr: num_mat_shader_params
- id: mat_textures
type: mat_tex_cont
repeat: expr
repeat-expr: num_materials
- id: mat_unknown3s
type: mat_unknown3
repeat: expr
repeat-expr: num_mat_unknown3s
# uints up to thousands?
- id: mat_unknown3_unk2
size: mat_unknown3s[_index].unk2_count * 4
repeat: expr
repeat-expr: num_mat_unknown3s
- id: rendermodels
type: rendermodel
repeat: expr
repeat-expr: num_rendermodels
- id: cityobjects
type: cityobject
repeat: expr
repeat-expr: num_cityobjects
- id: cityobject_names
type: strz
repeat: expr
repeat-expr: num_cityobjects
- type: align(16)
# are these names of destroyable objects?
- id: len_unknown_names
type: u4
- id: unknown_names
size: len_unknown_names
- type: align(16)
- id: num_unknown13
type: u4
- id: unknown13
type: u4
repeat: expr
repeat-expr: num_unknown13
- type: align(16)
- id: len_pad17
type: u4
- size: len_pad17
- type: align(16)
# Only appears in crib chunks and chunk093, which is where Saints HQ is
- id: num_unknown18s
type: u4
- id: unknown18s
type: unknown18
repeat: expr
repeat-expr: num_unknown18s
- type: align(16)
- id: num_unknown19
type: u4
- size: num_unknown19 * 28
- id: unknown19
type: f4
repeat: expr
repeat-expr: num_unknown19 * 7
- id: num_unknown20
type: u4
- size: num_unknown20 * 12
- id: unknown20
size: 12
repeat: expr
repeat-expr: num_unknown20
- type: align(16)
# TODO: find a chunk that uses this one and get it's length right
- id: num_unknown21
type: u4
- id: unknown21_pad
size: num_unknown21 * 8
- id: unknown21
type: u4
repeat: expr
repeat-expr: num_unknown21 * 2
- type: align(16)
- id: num_unknown22
type: u4
- id: unknown22
type: u4
repeat: expr
repeat-expr: num_unknown22
- type: align(16)
# floats, some world coord
- id: unknown23
type: f4
repeat: expr
repeat-expr: num_unknown23 * 12
- id: num_unknown24s
type: u4
- contents: [0, 0, 0, 0]
- id: num_unknown25
type: u4
- contents: [0, 0, 0, 0]
- id: unknown24s
type: unknown24
repeat: expr
repeat-expr: num_unknown24s
- id: unknown25
size: 2
repeat: expr
repeat-expr: num_unknown25
- type: align(16)
- id: unknown26
size: 2
repeat: expr
repeat-expr: num_unknown26
- type: align(16)
- id: mesh_movers
type: mesh_mover
repeat: expr
repeat-expr: num_mesh_movers
- id: unknown27
size: 24
repeat: expr
repeat-expr: num_unknown27
- id: unknown28
size: 36
repeat: expr
repeat-expr: num_unknown28
- id: unknown29
size: 4
repeat: expr
repeat-expr: num_unknown29
- id: unknown30
size: 12
repeat: expr
repeat-expr: num_unknown30
- id: unknown31
size: 8
repeat: expr
repeat-expr: num_unknown31
# Floats: 0's and 1's
- id: unknown32
size: 8
repeat: expr
repeat-expr: num_unknown32
- id: mesh_mover_names
type: mesh_mover_name(
mesh_movers[_index].start_count
)
repeat: expr
repeat-expr: num_mesh_movers
- type: align(16)
# This is a duct tape solution to ignore lights
# when they don't exist. 1212891981 == "MCKH"
- id: num_lights
type: num_lights
- id: lights_offset
type: offset(_io.pos)
- id: light_unknown
type: u4
if: num_lights.result != 0
- id: light_data
type: light_data
repeat: expr
repeat-expr: num_lights.result
- id: light_names
type: strz
repeat: expr
repeat-expr: num_lights.result
- type: align(16)
- id: light_unkfloats
type: light_unkfloats(light_data[_index].num_light_unkfloats)
repeat: expr
repeat-expr: num_lights.result
- type: align(16)
#- id: num_unknown33s
# type: u4
#- size: 4 #contents: [0,0,0,0]
#- id: num_unknown33b
# type: u4
#- size: 4 #contents: [0,0,0,0]
#- id: unknown33s
# type: unknown33
# repeat: expr
# repeat-expr: num_unknown33s
#- id: unknown33b
# type: u2
# repeat: expr
# repeat-expr: num_unknown33b
#- type: align(16)
#- size: 96
# if: num_lights != 1212891981 # MCHK
#
#- id: num_unknown33
# type: u4
# if: num_lights != 1212891981 # MCHK
#
#- contents: [0,0,0,0]
# if: num_lights != 1212891981 # MCHK
#
#- id: unknown33
# size: 200
# repeat: expr
# repeat-expr: num_unknown33
# if: num_lights != 1212891981 # MCHK
#- size: 88
#- contents: [77, 67, 75, 72] # MCHK
#- id: mckh_unk0
# type: u4
#- id: len_mckh
# type: u4
#- id: mckh
# size: len_mckh
#- size: 68
#- id: mckhs
# type: mckh
# repeat: expr
# repeat-expr: 132
#- id: test
# size: 1000
types:
align:
params:
- id: size
type: u4
seq:
- size: (size - _io.pos) % size
vec3:
seq:
- id: x
type: f4
- id: y
type: f4
- id: z
type: f4
offset:
params:
- id: off
type: s8
rendermodel_unk0:
seq:
- id: unk0
type: u4
- id: unk1
type: u4
- id: unk2
type: u4
- id: unk3
type: u4
- id: unk4
type: u4
- id: unk5
type: u4
cityobject_part:
seq:
- id: pos
type: vec3
- id: basis_x
type: vec3
- id: basis_y
type: vec3
- id: basis_z
type: vec3
- id: rest_of_the_transform
size: 36
- id: unk0
type: u4
- id: rendermodel_id
type: u4
- id: unk1
type: u4
unknown3:
seq:
- id: float0
type: f4
- id: float1
type: f4
- id: float2
type: f4
- id: float3
type: f4
- id: float4
type: f4
- id: float5
type: f4
- id: float6
type: f4
- id: float7
type: f4
- id: float8
type: f4
- id: float9
type: f4
- id: floata
type: f4
- id: floatb
type: f4
- id: floatc
type: f4
- id: floatd
type: f4
- id: floate
type: f4
- id: floatf
type: f4
- id: float10
type: f4
- id: float11
type: f4
- id: float12
type: f4
- id: float13
type: f4
- id: float14
type: f4
- id: float15
type: f4
- id: float16
type: f4
- id: float17
type: f4
- id: float18
type: f4
unknown4:
seq:
- id: float0
type: f4
- id: float1
type: f4
- id: float2
type: f4
- id: float3
type: f4
- id: float4
type: f4
- id: float5
type: f4
- id: float6
type: f4
- id: float7
type: f4
- id: float8
type: f4
- id: float9
type: f4
- id: floata
type: f4
- id: floatb
type: f4
- id: floatc
type: f4
model_header:
doc: |
A model header will talk about one of these two of things:
- A physmodel (type 7)
Each physmodel has a separate buffer and therefore each has its own
model_header. The buffers are stored in this file right after vert
headers.
- Rendermodels (type 0)
All buffers used for rendermodels are under this header. There is
exactly one rendermodel header per chunk (or none?).
Contains multiple vert_headers: one for each rendermodel vert buffer.
Buffers are stored in g_chunk file.
This does not tell how to get individual rendermodels. See rendermodel
type for that.
TODO: organize vert_headers in here somehow and get rid of vert_header_cont.
params:
- id: i
type: s4
seq: # 20B
- id: type # Either 7 or 0? 7 is physmodel, 0 rendermodel.
type: u2
- id: vert_header_count # here
type: u2
- id: num_indices
type: u4
- contents: [-1, -1, -1, -1]
- contents: [-1, -1, -1, -1]
- contents: [0, 0, 0, 0]
vert_header_cont:
doc: |
TODO: See todo in model_header.
params:
- id: num_vert_header
type: u2
seq:
- id: vert_header
type: vert_header
repeat: expr
repeat-expr: num_vert_header
vert_header:
doc: |
Describes a vertex buffer.
unk2b_count:
Probs vertex normals or something
Each adds 2B to vert_size.
uv_count:
How many uv sets are used.
Each UV set adds 4B to vert_size
vert_size:
Size is 12B + above.
Physmodels don't use the above so size is always 12B.
unk:
There's probably some meaning to this but it's
_always_ the same. 0xffffffff00000000
seq: # 16B
- id: unk2b_count
type: u1
- id: uv_count
type: u1
- id: vert_size
type: u2
- id: num_vertices
type: u4
- contents: [-1, -1, -1, -1]
- contents: [0, 0, 0, 0]
phys_model_buffer:
params:
- id: is_physmodel
type: bool
- id: num_indices
type: u4
- id: num_vertices
type: u4
seq:
- id: vertices
type: vec3
repeat: expr
repeat-expr: num_vertices
if: is_physmodel
- type: align(16)
- id: indices
type: u2
repeat: expr
repeat-expr: num_indices
if: is_physmodel
- type: align(16)
material:
doc: |
TODO: Move mat_textures, mat_bitflags, etc. inside material.
seq:
- id: unk
size: 12
- id: bitflag_count
type: u2
- id: tex_count
type: u2
- contents: [0, 0]
- id: unk1
type: u2
- id: unk2
type: s4
mat_bitflag:
params:
- id: size
type: u2
seq:
- id: data
size: size * 6
- size: (4 - size * 6) % 4
mat_tex_cont:
seq:
- id: tex_data
type: mat_tex
repeat: expr
repeat-expr: 16
mat_tex:
seq:
- id: tex_id
type: u2
- id: tex_flag
type: u2
mat_unknown3:
seq:
- id: unk
size: 8
- id: unk2_count
type: u2
- id: unk3
type: u2
- contents: [-1, -1, -1, -1]
rendermodel:
doc: |
Describes vertex/index offsets and counts in buffers.
Rendermodels are divided to submeshes per material.
Some models use what I named submesh2. This format is unknown
and in such cases the normal submeshes are garbage too.
To be confirmed: bbox is a bounding box with min and max vert coords.
Bbox usage is unknown.
seq:
- id: bbox_min
type: vec3
- id: bbox_max
type: vec3
- id: unk0
type: u4
- id: unk1
type: u4
- id: check_submeshes
type: u4
- id: check_submesh2
type: u4
- type: align(16)
- id: submesh_unk0
type: u2
if: check_submeshes != 0
- id: num_submeshes
type: u2
if: check_submeshes != 0
- id: submesh_unk1
size: 4
if: check_submeshes != 0
- id: submesh_unk2
type: u4
if: check_submeshes != 0
- id: submesh_pad1
contents: [0, 0, 0, 0]
if: check_submeshes != 0
- id: submesh2_unk0
type: u2
if: check_submesh2 != 0
- id: num_submesh2s
type: u2
if: check_submesh2 != 0
- id: submesh2_unk1
size: 4
if: check_submesh2 != 0
- id: submesh2_unk2
type: u4
if: check_submesh2 != 0
- id: submesh2_pad1
contents: [0, 0, 0, 0]
if: check_submesh2 != 0
- id: submeshes
type: submesh
repeat: expr
repeat-expr: num_submeshes
- id: submesh2s
type: submesh2
if: check_submesh2 != 0
repeat: expr
repeat-expr: num_submesh2s
types:
submesh:
seq:
- id: vert_buffer_id
type: u4
- id: off_indices
type: u4
- id: off_vertices
type: u4
- id: num_indices
type: u2
- id: material_id
type: u2
submesh2:
seq:
- id: unused
contents: [0, 0, 0, 0]
- id: unknown
size: 4
- id: unused2
contents: [0, 0, 0, 0]
- id: num_indices # is it?
type: u2
- id: material_id
type: u2
cityobject:
doc: |
bbox is used for culling.
seq:
- id: bbox_min
type: vec3
- contents: [0, 0, 0, 0]
- id: bbox_max
type: vec3
- id: cull_distance
type: f4
- contents: [0, 0, 0, 0]
- id: unk0
size: 4
- contents: [0, 0, 0, 0]
- id: unk1
type: s2
- contents: [0, 0]
- id: flags
size: 4
- id: unk3
size: 4
- id: unk8 # Almost always 0. Exceptions: sr2_skybox, sr2_intdkmissunkdk, sr2_intarcutlimo, sr2_intaicutjyucar
size: 4
- id: unk4
type: s4
- id: cityobject_part_id
type: u4
- id: unk5
type: u4
- id: unk6
type: s4
- id: unk7
type: s4
unknown18:
seq:
- id: unk0
type: u4
- id: num_data0
type: u4
- size: num_data0 * 12
- id: data0
type: u4
repeat: expr
repeat-expr: num_data0
- id: num_data1
type: u4
- size: num_data1 * 12
- id: data1
type: u4
repeat: expr
repeat-expr: num_data1
- id: num_data2
type: u4
- size: num_data2 * 12
- id: data2
type: u4
repeat: expr
repeat-expr: num_data2
# World coord box
unknown24:
seq:
- id: box_min
type: vec3
- id: unk0
type: u4
- id: box_max
type: vec3
- id: unk1
type: u4
- id: unk2
type: u4
- contents: [0, 0, 0, 0]
- id: unk4
type: u4
- id: unk5
type: u4
mesh_mover:
seq:
- id: unk0
size: 14
- id: start_count
type: u2
- id: unk1
size: 12
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
num_lights:
seq:
- id: input
type: u4
instances:
result:
value: "input != 1212891981 ? input : 0"
light_data:
seq:
- id: bitmask
type: b1
repeat: expr
repeat-expr: 32
- contents: [0, 0, 0, 0]
- id: r
type: f4
- id: g
type: f4
- id: b
type: f4
- id: unk5
type: u4
doc: bitmask?
- id: unk6
type: u4
doc: bitmask?
- id: unk7
type: u4
doc: bitmask?
- id: num_light_unkfloats
type: u4
- contents: [-1, -1, -1, -1]
- id: unk10
type: u4
doc: bitmask?
- contents: [-1, -1, -1, -1]
- id: unk12
type: u4
doc: bitmask?
- id: unkf13
type: f4
- id: unkf14
type: f4
- id: position
type: vec3
- id: unkf18
type: f4
- id: unkf19
type: f4
- id: unkf20
type: f4
- id: unkf21
type: f4
- id: unkf22
type: f4
- id: unkf23
type: f4
- id: unkf24
type: f4
- id: unkf25
type: f4
- id: unkf26
type: f4
- id: unkf27
type: f4
- id: unkf28
type: f4
- id: radius_inner
type: f4
- id: radius_outer
type: f4
- id: render_dist
type: f4
- contents: [-1, -1, -1, -1]
- id: parent_cityobject
type: s4
- id: unk34
type: u4
doc: bitmask?
- id: unk35
type: u4
doc: often 0, otherwise <250
- id: type # 0 == omni, 2 == circular spotlight
type: u4
instances:
bitflag0:
value: bitmask[0x00]
bitflag1:
value: bitmask[0x01]
bitflag2:
value: bitmask[0x02]
bitflag3:
value: bitmask[0x03]
bitflag4:
value: bitmask[0x04]
bitflag8:
value: bitmask[0x09]
bitflag10:
value: bitmask[0x0a]
shadow_character:
value: bitmask[0x0c]
shadow_level:
value: bitmask[0x0d]
light_character:
value: bitmask[0x0e]
light_level:
value: bitmask[0x0f]
bitflag22:
value: bitmask[0x16]
light_unkfloats:
params:
- id: num
type: u4
seq:
- id: unk
type: f4
repeat: expr
repeat-expr: num
unknown33:
seq:
- id: box_min
type: vec3
- size: 4
- id: box_max
type: vec3
- size: 4
- id: unk
size: 16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment