Decode tfrag normals from the overlay's sine table

Normals are per-vertex, and the tfrag's light records store them as an
azimuth and elevation *index* into a 256-entry sine table rather than as
angles. This decodes that table and builds normals for the renderer.

The reference reads the table from an ELF at BOOT_NORMAL_TABLE_VADDR
(0x165500) and notes every level overlay carries a copy. The overlay has
no ELF header -- it is a raw loadable image starting with code -- so there
is no program header to map a virtual address through, and the offset is
not fixed either: level 0 has the table at 0xb650c, level 5 at 0xcca30,
level 26 at 0xb6284.

So it is located by content. Every table starts with exactly (1.0, 0.0),
angle zero, and the scan then requires all 256 entries to match
cos(i*pi/128), sin(i*pi/128). Validating the whole curve is what makes the
search safe: a lone (1.0, 0.0) pair of floats occurs by accident in a
2.7 MB executable, and a test plants one as a decoy. The values are read
from the disc rather than recomputed, since those are the numbers the game
indexes.

A normal is (cos(az)cos(el), sin(az)cos(el), sin(el)) *negated* -- the
stored normals point into the surface -- and then given the same Z-up to
Y-up swap as positions, or the lighting comes out sideways. The azimuth
and elevation fields are stored signed but index 0..=255, so entry 200
must be read as 200, not as -56; there is a test for that.

Also frames and parses the level light bank from the gameplay extent, and
the pipeline now writes overlay.bin. The per-vertex lighting evaluation
is not written yet, so the renderer stays unlit by default. GC_ENGINE_LIT=1
substitutes a host directional light for inspection -- that is a stand-in,
not the game's lighting, and it is deliberately not the default.

The hash table is regenerated for the 27 new overlay.bin files: now 5,213
files and 5,762,497,559 bytes.
This commit is contained in:
2026-10-10 03:09:07 +02:00
parent 17c7e8ccc9
commit 90f02890f8
9 changed files with 477 additions and 16 deletions
+102
View File
@@ -28,6 +28,7 @@ use gc_formats::tfrag::{
parse_tfrag, tfrag_lod_link, tfrag_triangles, TfragAdGifs, TfragDrawMode, TfragGeometry, parse_tfrag, tfrag_lod_link, tfrag_triangles, TfragAdGifs, TfragDrawMode, TfragGeometry,
TfragHeader, TfragsBlock, TfragHeader, TfragsBlock,
}; };
use gc_formats::tfrag_light::NormalTable;
/// Raw position units per world unit. /// Raw position units per world unit.
pub const UNITS_PER_UNIT: f32 = 1024.0; pub const UNITS_PER_UNIT: f32 = 1024.0;
@@ -67,6 +68,8 @@ pub struct TriangleBatch {
pub positions: Vec<[f32; 3]>, pub positions: Vec<[f32; 3]>,
pub uvs: Vec<[f32; 2]>, pub uvs: Vec<[f32; 2]>,
pub colors: Vec<[f32; 4]>, pub colors: Vec<[f32; 4]>,
/// Surface normals, from the patch's own light records.
pub normals: Vec<[f32; 3]>,
/// Three vertex indices per triangle. /// Three vertex indices per triangle.
pub indices: Vec<u32>, pub indices: Vec<u32>,
/// The ad-gif in force per triangle, parallel to `indices`. /// The ad-gif in force per triangle, parallel to `indices`.
@@ -78,6 +81,9 @@ pub struct LevelSource<'a> {
pub core_data: &'a [u8], pub core_data: &'a [u8],
pub core_index: &'a [u8], pub core_index: &'a [u8],
pub gs_ram: &'a [u8], pub gs_ram: &'a [u8],
/// The level overlay, for the normal table. Optional: without it the level
/// still builds, just without normals.
pub overlay: Option<&'a [u8]>,
} }
/// Where a level's terrain sits: the centre of its patch bounding spheres and /// Where a level's terrain sits: the centre of its patch bounding spheres and
@@ -180,6 +186,25 @@ pub struct ResolvedVertex {
pub color: [f32; 4], pub color: [f32; 4],
} }
/// The surface normal of a vertex's position, in Bevy space.
///
/// Taken from the patch's per-vertex light record, which stores an azimuth and
/// elevation index into the overlay's sine table rather than an angle. Morphing
/// does not rotate the normal -- the game lerps positions and leaves lighting
/// to its own pass -- so this is the vertex's own normal regardless of blend.
pub fn vertex_normal(g: &TfragGeometry, vinfo: usize, table: Option<&NormalTable>) -> [f32; 3] {
let Some(table) = table else {
return [0.0, 1.0, 0.0];
};
let i = g.position_index(vinfo);
let Some(light) = g.lights.get(i) else {
return [0.0, 1.0, 0.0];
};
let n = table.normal(light.azimuth_index(), light.elevation_index());
// Same Z-up to Y-up swap as positions, or the lighting comes out sideways.
[n[0], n[2], -n[1]]
}
/// The stored colour of a vertex's position, or opaque white if absent. /// The stored colour of a vertex's position, or opaque white if absent.
pub fn vertex_color(g: &TfragGeometry, vinfo: usize) -> [f32; 4] { pub fn vertex_color(g: &TfragGeometry, vinfo: usize) -> [f32; 4] {
let i = g.position_index(vinfo); let i = g.position_index(vinfo);
@@ -276,6 +301,7 @@ pub fn mode_collapses(mode: TfragDrawMode) -> bool {
/// Decode every patch in a level's tfrags block, as seen from `camera`. /// Decode every patch in a level's tfrags block, as seen from `camera`.
pub fn build_level(src: &LevelSource<'_>, camera: [f32; 3]) -> anyhow::Result<LevelGeometry> { pub fn build_level(src: &LevelSource<'_>, camera: [f32; 3]) -> anyhow::Result<LevelGeometry> {
let normals = src.overlay.and_then(|o| NormalTable::from_overlay(o).ok());
let ch = LevelCoreHeader::parse(src.core_index)?; let ch = LevelCoreHeader::parse(src.core_index)?;
let table = texture_table(src.core_index, ch.tfrag_textures)?; let table = texture_table(src.core_index, ch.tfrag_textures)?;
@@ -350,6 +376,7 @@ pub fn build_level(src: &LevelSource<'_>, camera: [f32; 3]) -> anyhow::Result<Le
positions: Vec::new(), positions: Vec::new(),
uvs: Vec::new(), uvs: Vec::new(),
colors: Vec::new(), colors: Vec::new(),
normals: Vec::new(),
indices: Vec::new(), indices: Vec::new(),
ad_gifs: Vec::new(), ad_gifs: Vec::new(),
}); });
@@ -359,6 +386,7 @@ pub fn build_level(src: &LevelSource<'_>, camera: [f32; 3]) -> anyhow::Result<Le
batch.positions.push(r.position); batch.positions.push(r.position);
batch.uvs.push(r.uv); batch.uvs.push(r.uv);
batch.colors.push(r.color); batch.colors.push(r.color);
batch.normals.push(vertex_normal(&g, v, normals.as_ref()));
} }
batch.indices.extend_from_slice(&[base, base + 1, base + 2]); batch.indices.extend_from_slice(&[base, base + 1, base + 2]);
batch.ad_gifs.push(*ad); batch.ad_gifs.push(*ad);
@@ -534,3 +562,77 @@ mod tests {
); );
} }
} }
#[cfg(test)]
mod normal_tests {
use super::*;
use gc_formats::tfrag::TfragLight;
use gc_formats::tfrag_light::NormalTable;
fn table() -> NormalTable {
// 256 entries of (cos, sin) at i * pi / 128, as the overlay holds them.
let mut cos_sin = Vec::with_capacity(256);
for i in 0..256usize {
let a = i as f64 * std::f64::consts::PI / 128.0;
cos_sin.push([(a.cos() as f32).to_bits(), (a.sin() as f32).to_bits()]);
}
NormalTable { cos_sin }
}
fn geometry_with_light(azimuth: i16, elevation: i16) -> TfragGeometry {
TfragGeometry {
positions: vec![[0, 0, 0]],
vertex_info: vec![gc_formats::tfrag::TfragVertexInfo {
s: 0,
t: 0,
parent: 0,
vertex: 0,
}],
lights: vec![TfragLight {
unknown_0: 0,
intensity: 0,
azimuth: azimuth as i8,
elevation: elevation as i8,
color: 0,
}],
..TfragGeometry::default()
}
}
#[test]
fn the_light_record_indexes_the_table() {
// Azimuth 0, elevation 0 is -X in the table, which becomes -X in Bevy.
let g = geometry_with_light(0, 0);
let n = vertex_normal(&g, 0, Some(&table()));
assert!((n[0] + 1.0).abs() < 1e-5, "{n:?}");
assert!(n[1].abs() < 1e-5 && n[2].abs() < 1e-5, "{n:?}");
}
#[test]
fn normals_get_the_same_axis_swap_as_positions() {
// Elevation 64 is pi/2, so the table gives a normal along Z, negated.
let g = geometry_with_light(0, 64);
let n = vertex_normal(&g, 0, Some(&table()));
// PS2 (0, 0, -1) must land on Bevy -Y.
assert!(n[0].abs() < 1e-5 && n[2].abs() < 1e-5, "{n:?}");
assert!((n[1] + 1.0).abs() < 1e-5, "{n:?}");
}
#[test]
fn an_unsigned_index_is_used_even_when_the_field_reads_negative() {
// 200 as an i8 is -56, but it indexes table entry 200, not 56.
let g = geometry_with_light(-56, 0);
let from_field = vertex_normal(&g, 0, Some(&table()));
let mut g2 = g.clone();
g2.lights[0].azimuth = -56;
assert_eq!(from_field, vertex_normal(&g2, 0, Some(&table())));
}
#[test]
fn missing_normals_fall_back_rather_than_panicking() {
let g = geometry_with_light(0, 0);
assert_eq!(vertex_normal(&g, 0, None), [0.0, 1.0, 0.0]);
let empty = TfragGeometry::default();
assert_eq!(vertex_normal(&empty, 7, Some(&table())), [0.0, 1.0, 0.0]);
}
}
+6
View File
@@ -244,10 +244,12 @@ fn load_source(dir: &std::path::Path, level: usize) -> Result<(LevelFiles, [f32;
let core_data = std::fs::read(level_dir.join("core_data.bin")).context("core_data.bin")?; let core_data = std::fs::read(level_dir.join("core_data.bin")).context("core_data.bin")?;
let core_index = std::fs::read(level_dir.join("core_index.bin")).context("core_index.bin")?; let core_index = std::fs::read(level_dir.join("core_index.bin")).context("core_index.bin")?;
let gs_ram = std::fs::read(level_dir.join("gs_ram.bin")).context("gs_ram.bin")?; let gs_ram = std::fs::read(level_dir.join("gs_ram.bin")).context("gs_ram.bin")?;
let overlay = std::fs::read(level_dir.join("overlay.bin")).ok();
let src = LevelSource { let src = LevelSource {
core_data: &core_data, core_data: &core_data,
core_index: &core_index, core_index: &core_index,
gs_ram: &gs_ram, gs_ram: &gs_ram,
overlay: overlay.as_deref(),
}; };
let (centre, _radius) = level_bounds(&src)?; let (centre, _radius) = level_bounds(&src)?;
Ok(( Ok((
@@ -255,6 +257,7 @@ fn load_source(dir: &std::path::Path, level: usize) -> Result<(LevelFiles, [f32;
core_data, core_data,
core_index, core_index,
gs_ram, gs_ram,
overlay,
}, },
centre, centre,
)) ))
@@ -265,6 +268,7 @@ struct LevelFiles {
core_data: Vec<u8>, core_data: Vec<u8>,
core_index: Vec<u8>, core_index: Vec<u8>,
gs_ram: Vec<u8>, gs_ram: Vec<u8>,
overlay: Option<Vec<u8>>,
} }
impl LevelFiles { impl LevelFiles {
@@ -273,6 +277,7 @@ impl LevelFiles {
core_data: &self.core_data, core_data: &self.core_data,
core_index: &self.core_index, core_index: &self.core_index,
gs_ram: &self.gs_ram, gs_ram: &self.gs_ram,
overlay: self.overlay.as_deref(),
} }
} }
} }
@@ -389,6 +394,7 @@ fn mesh_of(b: &TriangleBatch) -> Option<Mesh> {
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, b.positions.clone()); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, b.positions.clone());
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, b.uvs.clone()); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, b.uvs.clone());
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, b.colors.clone()); mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, b.colors.clone());
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, b.normals.clone());
// No normals and no colours: lighting is the game's own per-vertex light // No normals and no colours: lighting is the game's own per-vertex light
// array, which is parsed but not yet reproduced. Inventing normals would // array, which is parsed but not yet reproduced. Inventing normals would
// make unlit terrain look lit and hide that gap. // make unlit terrain look lit and hide that gap.
+27
View File
@@ -2494,6 +2494,7 @@ e1baca5f8178da95e7dc496305a36f984be542aa 15 hud/0780.bin
4a8f9d6608b776a86fb15420acadea4c2b749a9f 13279232 level0/data.bin 4a8f9d6608b776a86fb15420acadea4c2b749a9f 13279232 level0/data.bin
133206d28cd25c39f1b6a760e345c0f0f409b7bf 659456 level0/gameplay.bin 133206d28cd25c39f1b6a760e345c0f0f409b7bf 659456 level0/gameplay.bin
acef5e387c8051229eecf70a64fd6daa077b34b3 518656 level0/gs_ram.bin acef5e387c8051229eecf70a64fd6daa077b34b3 518656 level0/gs_ram.bin
d7045d6c1256a9ffa241acca76e3761c34da3341 2751268 level0/overlay.bin
7ec9566aeae513fe272bc97899a4dbe15a2a74ff 1503232 level0/sound_bank.bin 7ec9566aeae513fe272bc97899a4dbe15a2a74ff 1503232 level0/sound_bank.bin
6b1a358c6eb6039d0df51fce061b58c274e8c99c 19023392 level1/core_data.bin 6b1a358c6eb6039d0df51fce061b58c274e8c99c 19023392 level1/core_data.bin
96b33cef1ac3c9bab5283ccf11bd86b0aa20ebcb 12165629 level1/core_data.wad 96b33cef1ac3c9bab5283ccf11bd86b0aa20ebcb 12165629 level1/core_data.wad
@@ -2502,12 +2503,14 @@ acef5e387c8051229eecf70a64fd6daa077b34b3 518656 level0/gs_ram.bin
a07364ccac5fb6dc3d1fb1c859fca13b205e6a1f 811008 level1/gameplay.bin a07364ccac5fb6dc3d1fb1c859fca13b205e6a1f 811008 level1/gameplay.bin
afc39fac19d1a66aba56c4509c55977229dcb3cf 569344 level1/gs_ram.bin afc39fac19d1a66aba56c4509c55977229dcb3cf 569344 level1/gs_ram.bin
1318041a43acca46fb4f2348b2f4fa81bd810948 18432 level1/occlusion.bin 1318041a43acca46fb4f2348b2f4fa81bd810948 18432 level1/occlusion.bin
54c0247eb3b0a08da2c6c87c5a23fc06bbe3fa50 2763668 level1/overlay.bin
5d6ead57dd2adf83e75b53ef5d026682676d5010 1300480 level1/sound_bank.bin 5d6ead57dd2adf83e75b53ef5d026682676d5010 1300480 level1/sound_bank.bin
c7bbd091e77719876e53fe38cfb66ad68d0952d3 14427312 level10/core_data.bin c7bbd091e77719876e53fe38cfb66ad68d0952d3 14427312 level10/core_data.bin
1c56f35c21dd429f52cdc701de3d3ab846f57918 8999864 level10/core_data.wad 1c56f35c21dd429f52cdc701de3d3ab846f57918 8999864 level10/core_data.wad
2c0073d2e78ff61beb69050087ff953ac1ccc65c 19696 level10/core_index.bin 2c0073d2e78ff61beb69050087ff953ac1ccc65c 19696 level10/core_index.bin
fe9e5169820bc9272aa5fcfbbabfc74e7b12d1ce 12498944 level10/data.bin fe9e5169820bc9272aa5fcfbbabfc74e7b12d1ce 12498944 level10/data.bin
4036bc105fa429b97a725ad40bd4051168c886ad 367872 level10/gs_ram.bin 4036bc105fa429b97a725ad40bd4051168c886ad 367872 level10/gs_ram.bin
76fa348a8764cbe97c8caf5a892105007c2bf4a2 2870708 level10/overlay.bin
d3e48237b03649578f6962e36f1bc197c3a8af9f 1306624 level10/sound_bank.bin d3e48237b03649578f6962e36f1bc197c3a8af9f 1306624 level10/sound_bank.bin
da253d8b4dc4f8fc482880c54cd832c53bfd4aa0 20717584 level11/core_data.bin da253d8b4dc4f8fc482880c54cd832c53bfd4aa0 20717584 level11/core_data.bin
a0173c7577180d32e9bfed67464eecf3bc12c033 11414905 level11/core_data.wad a0173c7577180d32e9bfed67464eecf3bc12c033 11414905 level11/core_data.wad
@@ -2516,6 +2519,7 @@ a0173c7577180d32e9bfed67464eecf3bc12c033 11414905 level11/core_data.wad
ff6811c08f3fe034bae54ab066e3051d4cbb39cc 1034240 level11/gameplay.bin ff6811c08f3fe034bae54ab066e3051d4cbb39cc 1034240 level11/gameplay.bin
472e916feae6b88e5028e48e9d2e4d1a433a8ed7 538624 level11/gs_ram.bin 472e916feae6b88e5028e48e9d2e4d1a433a8ed7 538624 level11/gs_ram.bin
e91c93e8af747ce8c0802c3c96cd88acc126a5c3 51200 level11/occlusion.bin e91c93e8af747ce8c0802c3c96cd88acc126a5c3 51200 level11/occlusion.bin
4502a46e00bbbb3a0a59aa4d5899fd3b3ca44948 3151128 level11/overlay.bin
850f01ef9678ed0b0b6242c410ad7e57b935079f 991232 level11/sound_bank.bin 850f01ef9678ed0b0b6242c410ad7e57b935079f 991232 level11/sound_bank.bin
d13c8eb4f2ab552e330a119bf1e14d55f9921220 20995456 level12/core_data.bin d13c8eb4f2ab552e330a119bf1e14d55f9921220 20995456 level12/core_data.bin
9d8355548c0d5c911e18124e83a04f3c58d604de 11510373 level12/core_data.wad 9d8355548c0d5c911e18124e83a04f3c58d604de 11510373 level12/core_data.wad
@@ -2523,6 +2527,7 @@ d13c8eb4f2ab552e330a119bf1e14d55f9921220 20995456 level12/core_data.bin
a134bad90883ad15ff7b52d909da461a491d253b 15136768 level12/data.bin a134bad90883ad15ff7b52d909da461a491d253b 15136768 level12/data.bin
b058a1f7858198be55a90bf70d81db1f8039e132 813056 level12/gameplay.bin b058a1f7858198be55a90bf70d81db1f8039e132 813056 level12/gameplay.bin
cbccba141d2efe7b4165dd81a265532465106def 510464 level12/gs_ram.bin cbccba141d2efe7b4165dd81a265532465106def 510464 level12/gs_ram.bin
c47b9490cf2a0145331d2b2229234fd1e1b2e172 2843328 level12/overlay.bin
709e185927a11b846e6b1d66c2fa770cf284ea65 1568768 level12/sound_bank.bin 709e185927a11b846e6b1d66c2fa770cf284ea65 1568768 level12/sound_bank.bin
74fd46bfcfcb97132c509d2147070327976faab4 21301168 level13/core_data.bin 74fd46bfcfcb97132c509d2147070327976faab4 21301168 level13/core_data.bin
7c4b8c7fd086de66e19bd440a5e63fea4729e0bb 12037495 level13/core_data.wad 7c4b8c7fd086de66e19bd440a5e63fea4729e0bb 12037495 level13/core_data.wad
@@ -2530,6 +2535,7 @@ c7a99c3252427f11a202a907df605b3d43baaae0 35744 level13/core_index.bin
29c09e0877c62ba4dee1325cf00c944f9ad78ceb 15702016 level13/data.bin 29c09e0877c62ba4dee1325cf00c944f9ad78ceb 15702016 level13/data.bin
ab5e7bd921f3973e3d15052838ec87676d35928a 854016 level13/gameplay.bin ab5e7bd921f3973e3d15052838ec87676d35928a 854016 level13/gameplay.bin
0554fcecd5cbe57f3b90a97bd75edba4ea438752 555520 level13/gs_ram.bin 0554fcecd5cbe57f3b90a97bd75edba4ea438752 555520 level13/gs_ram.bin
3fbaf06d37e5ffc64dd7fa3f37e3fe20d3ea8037 2833444 level13/overlay.bin
2793ed3153f9f8ace889b855dbfc53f29c94c0bc 1406976 level13/sound_bank.bin 2793ed3153f9f8ace889b855dbfc53f29c94c0bc 1406976 level13/sound_bank.bin
386e9a7cef58f23f66fbc21de42be234c25be0b4 19738512 level14/core_data.bin 386e9a7cef58f23f66fbc21de42be234c25be0b4 19738512 level14/core_data.bin
b34bf0528e2c753ce9fd65791e89ba6ec8491358 11217244 level14/core_data.wad b34bf0528e2c753ce9fd65791e89ba6ec8491358 11217244 level14/core_data.wad
@@ -2537,6 +2543,7 @@ cc03cc08cc55f27d57bcdc6c9c435e28479a65e3 34848 level14/core_index.bin
fb30c8c10944377af4a76e2804209be630dba993 14948352 level14/data.bin fb30c8c10944377af4a76e2804209be630dba993 14948352 level14/data.bin
95157d87f0c5e76a38daead9e07d01281b65ebf0 692224 level14/gameplay.bin 95157d87f0c5e76a38daead9e07d01281b65ebf0 692224 level14/gameplay.bin
0d8c49b2b405df331f859a077a6dfbb6aa922b3d 582656 level14/gs_ram.bin 0d8c49b2b405df331f859a077a6dfbb6aa922b3d 582656 level14/gs_ram.bin
bdcd73fb41d07da3ce35f60a2fd48af3d8ffe96b 2874964 level14/overlay.bin
28a70e7880794f94a76e2b340373d1dc40484fee 1689600 level14/sound_bank.bin 28a70e7880794f94a76e2b340373d1dc40484fee 1689600 level14/sound_bank.bin
9e1a9269a59741f15608ee773192a4c4e3adadbb 14566720 level15/core_data.bin 9e1a9269a59741f15608ee773192a4c4e3adadbb 14566720 level15/core_data.bin
3c7f4291ff09cdfb0c1cd2e435fb4c039faf7572 8538458 level15/core_data.wad 3c7f4291ff09cdfb0c1cd2e435fb4c039faf7572 8538458 level15/core_data.wad
@@ -2544,6 +2551,7 @@ cda9e44cee8b5ac58f9abd97dfcdbbe14e76d490 21792 level15/core_index.bin
39e2c8e15375dae4c73f61a7be980dccf4a85aa9 12122112 level15/data.bin 39e2c8e15375dae4c73f61a7be980dccf4a85aa9 12122112 level15/data.bin
9b13d67b1276d25937cab21344fa8a6687cc0882 491520 level15/gameplay.bin 9b13d67b1276d25937cab21344fa8a6687cc0882 491520 level15/gameplay.bin
d1437c7d2106f6e9cdb54f66cea4f1a714dc8867 407552 level15/gs_ram.bin d1437c7d2106f6e9cdb54f66cea4f1a714dc8867 407552 level15/gs_ram.bin
76b9c880945039ece6e51762b8dd76ffc680da7c 2915968 level15/overlay.bin
f89c334b887f377f341fc9232d474affa89fc544 1476608 level15/sound_bank.bin f89c334b887f377f341fc9232d474affa89fc544 1476608 level15/sound_bank.bin
49c2efb647efc0baaf919aaaa140f0a660725b3d 21374192 level16/core_data.bin 49c2efb647efc0baaf919aaaa140f0a660725b3d 21374192 level16/core_data.bin
9c9291257d21ac62dff3ac4090f9d2f9b6c5c3e8 12442996 level16/core_data.wad 9c9291257d21ac62dff3ac4090f9d2f9b6c5c3e8 12442996 level16/core_data.wad
@@ -2551,6 +2559,7 @@ f89c334b887f377f341fc9232d474affa89fc544 1476608 level15/sound_bank.bin
8e58f79a35fbe78382065bdce0f53d2cc9f1ccf1 16091136 level16/data.bin 8e58f79a35fbe78382065bdce0f53d2cc9f1ccf1 16091136 level16/data.bin
af60ee961ab3c4508f6fc7f7e5db32d1bbe08769 595968 level16/gameplay.bin af60ee961ab3c4508f6fc7f7e5db32d1bbe08769 595968 level16/gameplay.bin
9b85cc208f66ffbbdff60d979597e1e2c47bc593 577024 level16/gs_ram.bin 9b85cc208f66ffbbdff60d979597e1e2c47bc593 577024 level16/gs_ram.bin
2bdb86a7a4c69af80eb5f30d62ed58f337961f3f 2798992 level16/overlay.bin
84723c80beedd7e55143a424a1b2f6255a200ab0 1654784 level16/sound_bank.bin 84723c80beedd7e55143a424a1b2f6255a200ab0 1654784 level16/sound_bank.bin
485e5343a6f146179b319fb7d76388eee6e21276 20920320 level17/core_data.bin 485e5343a6f146179b319fb7d76388eee6e21276 20920320 level17/core_data.bin
fc90583b7560be17115f57c8ecf6fc14ec2e59d8 10922651 level17/core_data.wad fc90583b7560be17115f57c8ecf6fc14ec2e59d8 10922651 level17/core_data.wad
@@ -2558,6 +2567,7 @@ fc90583b7560be17115f57c8ecf6fc14ec2e59d8 10922651 level17/core_data.wad
d7669eec9eb3cf36bf4f649333ae79864c1a7043 14575616 level17/data.bin d7669eec9eb3cf36bf4f649333ae79864c1a7043 14575616 level17/data.bin
10a5f5e2862f7364a9bbe886dadb9ebca8ab0381 796672 level17/gameplay.bin 10a5f5e2862f7364a9bbe886dadb9ebca8ab0381 796672 level17/gameplay.bin
899d4e61f032af4c66419d3a286a25d1e97e5644 574464 level17/gs_ram.bin 899d4e61f032af4c66419d3a286a25d1e97e5644 574464 level17/gs_ram.bin
f76912e7914400cc5ceeca59f6780bc5143997c8 2803324 level17/overlay.bin
37c76a37d9e3cbbed6dfc324b0b90d3498ecb0e7 1347584 level17/sound_bank.bin 37c76a37d9e3cbbed6dfc324b0b90d3498ecb0e7 1347584 level17/sound_bank.bin
f9240e2bd6406f206e155b09cc5c914b6574e41e 20268640 level18/core_data.bin f9240e2bd6406f206e155b09cc5c914b6574e41e 20268640 level18/core_data.bin
f44add8374224a84070995ecb4b1be8cfb58aa9f 11721139 level18/core_data.wad f44add8374224a84070995ecb4b1be8cfb58aa9f 11721139 level18/core_data.wad
@@ -2565,6 +2575,7 @@ f44add8374224a84070995ecb4b1be8cfb58aa9f 11721139 level18/core_data.wad
d0f931677cb6589a564937c3d15c13344b923969 15501312 level18/data.bin d0f931677cb6589a564937c3d15c13344b923969 15501312 level18/data.bin
2adc53f721fca95dd1012994d243e0704edd034c 675840 level18/gameplay.bin 2adc53f721fca95dd1012994d243e0704edd034c 675840 level18/gameplay.bin
7f6a823c4b47fdfb58e29237b680d03e946329db 571136 level18/gs_ram.bin 7f6a823c4b47fdfb58e29237b680d03e946329db 571136 level18/gs_ram.bin
e5370c97b9a2ccdb00678e57f9e18042eab6735d 2937576 level18/overlay.bin
b26483cc29ecb8b6d2c71919ff7cd56b09522bc1 1492992 level18/sound_bank.bin b26483cc29ecb8b6d2c71919ff7cd56b09522bc1 1492992 level18/sound_bank.bin
0918d824b72b892a24f46108bbc60f6e875236f6 20233504 level19/core_data.bin 0918d824b72b892a24f46108bbc60f6e875236f6 20233504 level19/core_data.bin
b79df8e074db7d3c75bc2b3b9c2a4de61e5dd9cd 11797811 level19/core_data.wad b79df8e074db7d3c75bc2b3b9c2a4de61e5dd9cd 11797811 level19/core_data.wad
@@ -2573,6 +2584,7 @@ b0ea377a39a616dbd2b15224a9996925c7b28f4d 31536 level19/core_index.bin
8700a93086450328f2693b553e1df447a79fcd83 825344 level19/gameplay.bin 8700a93086450328f2693b553e1df447a79fcd83 825344 level19/gameplay.bin
d5061a551eb354ddf7952c4bfd62767c6e074c3f 497920 level19/gs_ram.bin d5061a551eb354ddf7952c4bfd62767c6e074c3f 497920 level19/gs_ram.bin
877ca7fb8e80d810d6c37fb993795aa10b5b1033 47104 level19/occlusion.bin 877ca7fb8e80d810d6c37fb993795aa10b5b1033 47104 level19/occlusion.bin
428ea51a157b35cdf831f2a6908647d8ffe6a91b 2821856 level19/overlay.bin
8298f799c394542e912e41ff3321ee55c70e0c8f 1570816 level19/sound_bank.bin 8298f799c394542e912e41ff3321ee55c70e0c8f 1570816 level19/sound_bank.bin
6d56189ad74f04d366116b736ab12888154de833 20209408 level2/core_data.bin 6d56189ad74f04d366116b736ab12888154de833 20209408 level2/core_data.bin
f0eb5c033e84092748cf3457818b0d4c85da7a0a 11372886 level2/core_data.wad f0eb5c033e84092748cf3457818b0d4c85da7a0a 11372886 level2/core_data.wad
@@ -2581,6 +2593,7 @@ f0eb5c033e84092748cf3457818b0d4c85da7a0a 11372886 level2/core_data.wad
bfc9f6bc73edfa7deb46505f187c274dcf7b863a 790528 level2/gameplay.bin bfc9f6bc73edfa7deb46505f187c274dcf7b863a 790528 level2/gameplay.bin
164f775e33d9325b3e4c5ce4cbf8c515ed311fd2 513024 level2/gs_ram.bin 164f775e33d9325b3e4c5ce4cbf8c515ed311fd2 513024 level2/gs_ram.bin
01c7d4c7b0ecb44a91694ffde796ff941057191a 28672 level2/occlusion.bin 01c7d4c7b0ecb44a91694ffde796ff941057191a 28672 level2/occlusion.bin
d7182afdf4a986c9c781a9658373281bfc37df22 2928424 level2/overlay.bin
bce56b3b899d72c5d89f612bd70447f066c10757 1118208 level2/sound_bank.bin bce56b3b899d72c5d89f612bd70447f066c10757 1118208 level2/sound_bank.bin
daf162b58756e2661f3ec891c8736a1611b4dc52 19637472 level20/core_data.bin daf162b58756e2661f3ec891c8736a1611b4dc52 19637472 level20/core_data.bin
7e7203ac3a2dbf578546573a34fa6dce4c336d35 10449039 level20/core_data.wad 7e7203ac3a2dbf578546573a34fa6dce4c336d35 10449039 level20/core_data.wad
@@ -2589,6 +2602,7 @@ b12e8599d211addc85cbe138e58157ade380a575 37360 level20/core_index.bin
7368af71bc6c070fc6422fd69a354b2f3518ed2b 833536 level20/gameplay.bin 7368af71bc6c070fc6422fd69a354b2f3518ed2b 833536 level20/gameplay.bin
4c7325cfdde68fc9160ec9a2cd18da3d327dcd9a 534272 level20/gs_ram.bin 4c7325cfdde68fc9160ec9a2cd18da3d327dcd9a 534272 level20/gs_ram.bin
61ae37031d1df746bffb4a0bddeaee85558c894e 36864 level20/occlusion.bin 61ae37031d1df746bffb4a0bddeaee85558c894e 36864 level20/occlusion.bin
a5235e8ce98aa3cfbb499ae988c419a05a0bcf55 2976888 level20/overlay.bin
abce77e8841c0ed0fa006db81840b35ea4c75afd 1335296 level20/sound_bank.bin abce77e8841c0ed0fa006db81840b35ea4c75afd 1335296 level20/sound_bank.bin
5e850ab42d2dc3e8fa1f9ae74d401be1658c9ba7 14483120 level21/core_data.bin 5e850ab42d2dc3e8fa1f9ae74d401be1658c9ba7 14483120 level21/core_data.bin
f10321014e7ccd61a65f294baefaf35875d6294b 8178784 level21/core_data.wad f10321014e7ccd61a65f294baefaf35875d6294b 8178784 level21/core_data.wad
@@ -2596,6 +2610,7 @@ f10321014e7ccd61a65f294baefaf35875d6294b 8178784 level21/core_data.wad
4a3ebd058fe62df17ed4d74fe190442ff2993474 11722752 level21/data.bin 4a3ebd058fe62df17ed4d74fe190442ff2993474 11722752 level21/data.bin
6c2b9faaef23c142a61316190852251404734c39 448512 level21/gameplay.bin 6c2b9faaef23c142a61316190852251404734c39 448512 level21/gameplay.bin
43a953d770cd4c5833eda5c1ecae26d7ce127be1 512256 level21/gs_ram.bin 43a953d770cd4c5833eda5c1ecae26d7ce127be1 512256 level21/gs_ram.bin
384f39176ccbd107924eacc05d6330dda4be523f 2766612 level21/overlay.bin
8fbcb2360d5244e92bfd7c2ccd453eec44cd042b 1167360 level21/sound_bank.bin 8fbcb2360d5244e92bfd7c2ccd453eec44cd042b 1167360 level21/sound_bank.bin
7b0733a07f28e7d7cf5d8a19b1058a98a5563c9f 14288048 level22/core_data.bin 7b0733a07f28e7d7cf5d8a19b1058a98a5563c9f 14288048 level22/core_data.bin
34a2d1ac57f13cacdf46cd7d73f356951f87dd43 8186976 level22/core_data.wad 34a2d1ac57f13cacdf46cd7d73f356951f87dd43 8186976 level22/core_data.wad
@@ -2603,6 +2618,7 @@ edb2fd7c72d0a517613fd813429aa9036e2eb509 25152 level22/core_index.bin
3519cac29e6371802b40e3d8234ef7064abee336 11683840 level22/data.bin 3519cac29e6371802b40e3d8234ef7064abee336 11683840 level22/data.bin
a8760bb193082c5e85faeb5f799ab8585f366805 557056 level22/gameplay.bin a8760bb193082c5e85faeb5f799ab8585f366805 557056 level22/gameplay.bin
9c3af51d0b3ad2b786a428a49a8ee9d30886d636 482048 level22/gs_ram.bin 9c3af51d0b3ad2b786a428a49a8ee9d30886d636 482048 level22/gs_ram.bin
ae40d45a6605d5929f54e63b531919cb6ab093a9 2753704 level22/overlay.bin
dba3b2b5856efbd28a8aeaa15fbee6f4fb479895 1363968 level22/sound_bank.bin dba3b2b5856efbd28a8aeaa15fbee6f4fb479895 1363968 level22/sound_bank.bin
c6b6f4087f4c9cd374a587b13777f9575953838a 12647664 level23/core_data.bin c6b6f4087f4c9cd374a587b13777f9575953838a 12647664 level23/core_data.bin
dbdca886def506bd2e6fd189c0fc4ca8d2834b97 6900832 level23/core_data.wad dbdca886def506bd2e6fd189c0fc4ca8d2834b97 6900832 level23/core_data.wad
@@ -2610,18 +2626,21 @@ ed196a1541e056da4433d366c1a096143ea87ca0 23024 level23/core_index.bin
9a26194ba7d5930279a62113b1377ffeb1294269 10414080 level23/data.bin 9a26194ba7d5930279a62113b1377ffeb1294269 10414080 level23/data.bin
336b3c864d1aa317003499e7d1d2d5a8cee13d58 505856 level23/gameplay.bin 336b3c864d1aa317003499e7d1d2d5a8cee13d58 505856 level23/gameplay.bin
bcc4a8508f14af07ee85dd5f06dff7a910d73b6a 411136 level23/gs_ram.bin bcc4a8508f14af07ee85dd5f06dff7a910d73b6a 411136 level23/gs_ram.bin
38f7544efa497b26e494e31169fa057ce2a1b62c 2841396 level23/overlay.bin
e254322f1d87d2650c21ec2c118546df1a56d9e8 1675264 level23/sound_bank.bin e254322f1d87d2650c21ec2c118546df1a56d9e8 1675264 level23/sound_bank.bin
508808939296de54fee8928824bdcc6d37204d01 6386608 level24/core_data.bin 508808939296de54fee8928824bdcc6d37204d01 6386608 level24/core_data.bin
c44701fc0a229dc8071a30594231d5edfb6b4119 3805312 level24/core_data.wad c44701fc0a229dc8071a30594231d5edfb6b4119 3805312 level24/core_data.wad
c526f84012360c24a07e4bbd6823dd18ce8b3181 16608 level24/core_index.bin c526f84012360c24a07e4bbd6823dd18ce8b3181 16608 level24/core_index.bin
f38883faa65e74f67a44da4a55b9b476848d7cae 6981632 level24/data.bin f38883faa65e74f67a44da4a55b9b476848d7cae 6981632 level24/data.bin
adf90875335b529183111edaeaa90138ba111dbe 308480 level24/gs_ram.bin adf90875335b529183111edaeaa90138ba111dbe 308480 level24/gs_ram.bin
2b5f97e7aa6a6bf01e5ed2c918ca19b7eafb9593 2615776 level24/overlay.bin
3bf88b1d1c455ab90bbacc3270a3f54c94b900ef 937984 level24/sound_bank.bin 3bf88b1d1c455ab90bbacc3270a3f54c94b900ef 937984 level24/sound_bank.bin
c81ea8c485748fccec6619bacc75afe8ce2f7e20 5898736 level25/core_data.bin c81ea8c485748fccec6619bacc75afe8ce2f7e20 5898736 level25/core_data.bin
80d763434b9dda1224c446de67e4907cfbdcfbe7 3513706 level25/core_data.wad 80d763434b9dda1224c446de67e4907cfbdcfbe7 3513706 level25/core_data.wad
c493407fb493ff359e90d1bdfc1b379a2a6c540a 16592 level25/core_index.bin c493407fb493ff359e90d1bdfc1b379a2a6c540a 16592 level25/core_index.bin
e57a615ccce4afd0a3de13f638ecaa19f88d0789 6852608 level25/data.bin e57a615ccce4afd0a3de13f638ecaa19f88d0789 6852608 level25/data.bin
b54226627562788f722c2fdc314d2117ca6ed592 287488 level25/gs_ram.bin b54226627562788f722c2fdc314d2117ca6ed592 287488 level25/gs_ram.bin
eee845138d0c88116743bb36929a6dc894b62e14 2801992 level25/overlay.bin
e9d246c13a7198b42a71f081d767b9e9598d01a1 1370112 level25/sound_bank.bin e9d246c13a7198b42a71f081d767b9e9598d01a1 1370112 level25/sound_bank.bin
948974c4fd10e7ca7ad1a362af9cb46aa6c81773 9530880 level26/core_data.bin 948974c4fd10e7ca7ad1a362af9cb46aa6c81773 9530880 level26/core_data.bin
6140efd9a7ebbcd81bfddf1ce0a5395ffb136d2e 5894651 level26/core_data.wad 6140efd9a7ebbcd81bfddf1ce0a5395ffb136d2e 5894651 level26/core_data.wad
@@ -2629,6 +2648,7 @@ d71c13701f26cd0a4bbcb9508fb0387ba54d2d62 20704 level26/core_index.bin
4fdd7d88a5d3db63418c968f8a5f53ac370b86be 9224192 level26/data.bin 4fdd7d88a5d3db63418c968f8a5f53ac370b86be 9224192 level26/data.bin
0d63ad0bc2edcd958b1c7dbec303c256ed8850e1 679936 level26/gameplay.bin 0d63ad0bc2edcd958b1c7dbec303c256ed8850e1 679936 level26/gameplay.bin
abb3fb6a4175cf6b41eff1d7178d59ce7affb84f 378880 level26/gs_ram.bin abb3fb6a4175cf6b41eff1d7178d59ce7affb84f 378880 level26/gs_ram.bin
518bb5c68f2d028c7c5934f878abbd3ba9d6c63f 2695436 level26/overlay.bin
8f6a74b8008a9ebc9e92853d0b339a8e725c9992 1073152 level26/sound_bank.bin 8f6a74b8008a9ebc9e92853d0b339a8e725c9992 1073152 level26/sound_bank.bin
f5d3cd4480fe6a52a019d04401a360018b279b81 21148480 level3/core_data.bin f5d3cd4480fe6a52a019d04401a360018b279b81 21148480 level3/core_data.bin
690209848c64647194548d5e81f329d471392a8e 11383592 level3/core_data.wad 690209848c64647194548d5e81f329d471392a8e 11383592 level3/core_data.wad
@@ -2636,6 +2656,7 @@ f5d3cd4480fe6a52a019d04401a360018b279b81 21148480 level3/core_data.bin
eb8366046b5643cc54d42d800c06bf66ccd4ef36 15157248 level3/data.bin eb8366046b5643cc54d42d800c06bf66ccd4ef36 15157248 level3/data.bin
55cc4acecac6ba19232e6505fdc06a22b0633a45 749568 level3/gameplay.bin 55cc4acecac6ba19232e6505fdc06a22b0633a45 749568 level3/gameplay.bin
ed1f77cd35e2572adc51f4f43de030f31b8c6ed9 675840 level3/gs_ram.bin ed1f77cd35e2572adc51f4f43de030f31b8c6ed9 675840 level3/gs_ram.bin
6fb6a7e7a64afd2f1e897f23640866f28154aa1d 2820776 level3/overlay.bin
d986825dfc2bed713eb9699504536346658aaa3f 1705984 level3/sound_bank.bin d986825dfc2bed713eb9699504536346658aaa3f 1705984 level3/sound_bank.bin
f8c0aa3778b5bcb1995e516045b28ca6ac35502f 21107088 level4/core_data.bin f8c0aa3778b5bcb1995e516045b28ca6ac35502f 21107088 level4/core_data.bin
ec75f324670188af581c4fceaa4cb285353f95b8 12171908 level4/core_data.wad ec75f324670188af581c4fceaa4cb285353f95b8 12171908 level4/core_data.wad
@@ -2644,6 +2665,7 @@ cc4997acbdc0a6ac85d427227c04e6af6e34cd46 38080 level4/core_index.bin
eeee7ebf8d9980f9afc6d9c2bc2156bcca015ddd 960512 level4/gameplay.bin eeee7ebf8d9980f9afc6d9c2bc2156bcca015ddd 960512 level4/gameplay.bin
5a9a603e46e2008df02e60dce2c66ae9368d9993 522496 level4/gs_ram.bin 5a9a603e46e2008df02e60dce2c66ae9368d9993 522496 level4/gs_ram.bin
fed269c2d706a2ba11cab965e090fd41d434055c 49152 level4/occlusion.bin fed269c2d706a2ba11cab965e090fd41d434055c 49152 level4/occlusion.bin
6941b8cb87a7ff14f53b2a2511bfa2d43f25ba47 3027588 level4/overlay.bin
bdd446767b1a84043d65ff968278cd2d5808705a 1236992 level4/sound_bank.bin bdd446767b1a84043d65ff968278cd2d5808705a 1236992 level4/sound_bank.bin
cb07a102a92b887d06d9314c07ce1db50e14c1fb 16715440 level5/core_data.bin cb07a102a92b887d06d9314c07ce1db50e14c1fb 16715440 level5/core_data.bin
e5cc08e39cb6883b391605bdcff8ba3bb8423699 11205994 level5/core_data.wad e5cc08e39cb6883b391605bdcff8ba3bb8423699 11205994 level5/core_data.wad
@@ -2651,6 +2673,7 @@ d41e06bc7086ed6876bef602420cca8d52492b1e 23536 level5/core_index.bin
b241ea9d8fe18dd5bdbc8a4255ba8450a66b3c6a 14772224 level5/data.bin b241ea9d8fe18dd5bdbc8a4255ba8450a66b3c6a 14772224 level5/data.bin
c71539e0f8b0e2d666d9382475539c7a590fee04 514048 level5/gameplay.bin c71539e0f8b0e2d666d9382475539c7a590fee04 514048 level5/gameplay.bin
ad4318ac2d601a2b1b0b9b444325082e0fc50ffc 434176 level5/gs_ram.bin ad4318ac2d601a2b1b0b9b444325082e0fc50ffc 434176 level5/gs_ram.bin
c5f6b22d88d8b9eab711872337fc358bcd4051a8 2863676 level5/overlay.bin
5c2fd4470dd8c892af7686770af8cf3e2396373c 1390592 level5/sound_bank.bin 5c2fd4470dd8c892af7686770af8cf3e2396373c 1390592 level5/sound_bank.bin
8ea18c5a51e4ae07ca56d92d65ffde60f4bcce12 20935696 level6/core_data.bin 8ea18c5a51e4ae07ca56d92d65ffde60f4bcce12 20935696 level6/core_data.bin
f2962a73cfb15ab1bef23253d863d2433b264383 12810673 level6/core_data.wad f2962a73cfb15ab1bef23253d863d2433b264383 12810673 level6/core_data.wad
@@ -2658,6 +2681,7 @@ f2962a73cfb15ab1bef23253d863d2433b264383 12810673 level6/core_data.wad
ccb0a4e8c51be81fbcd8ad325ab8d521bbdb7604 16777216 level6/data.bin ccb0a4e8c51be81fbcd8ad325ab8d521bbdb7604 16777216 level6/data.bin
b9ff3b4b8fb6308aac7f5ddba1c4b1099d484dd5 929792 level6/gameplay.bin b9ff3b4b8fb6308aac7f5ddba1c4b1099d484dd5 929792 level6/gameplay.bin
c404dfa08cab9603e5deeeed9778e4d29b755d88 604160 level6/gs_ram.bin c404dfa08cab9603e5deeeed9778e4d29b755d88 604160 level6/gs_ram.bin
4d981fd750196894f96f0a24437bc76210a1663a 3086372 level6/overlay.bin
82fcce964396eb508768933d092a557bc1b05c89 1669120 level6/sound_bank.bin 82fcce964396eb508768933d092a557bc1b05c89 1669120 level6/sound_bank.bin
6692290d78706a2b92cba541264ddea9a764e2f2 18209392 level7/core_data.bin 6692290d78706a2b92cba541264ddea9a764e2f2 18209392 level7/core_data.bin
f1cae801f1050c57258f93a7035148b3094ece03 10780010 level7/core_data.wad f1cae801f1050c57258f93a7035148b3094ece03 10780010 level7/core_data.wad
@@ -2666,6 +2690,7 @@ f1cae801f1050c57258f93a7035148b3094ece03 10780010 level7/core_data.wad
bc8f542a56d4795c77774201903e25b01962c039 636928 level7/gameplay.bin bc8f542a56d4795c77774201903e25b01962c039 636928 level7/gameplay.bin
9671dd66d0a20ef778bbd76f0e8517792646467c 580352 level7/gs_ram.bin 9671dd66d0a20ef778bbd76f0e8517792646467c 580352 level7/gs_ram.bin
e02d1b0d2339d72ed5d07abfe105f41e2288f59f 16384 level7/occlusion.bin e02d1b0d2339d72ed5d07abfe105f41e2288f59f 16384 level7/occlusion.bin
82f26617c99f502324fb556006f8f859fda1b5f1 2733840 level7/overlay.bin
8e07c803b709100023c3b44c8842eec9b8f49898 1556480 level7/sound_bank.bin 8e07c803b709100023c3b44c8842eec9b8f49898 1556480 level7/sound_bank.bin
264a306c3a83b9137d9753b282e9c773cd386713 21086224 level8/core_data.bin 264a306c3a83b9137d9753b282e9c773cd386713 21086224 level8/core_data.bin
0a96094edd63dc4dd940894b007f85b21874cfeb 9700660 level8/core_data.wad 0a96094edd63dc4dd940894b007f85b21874cfeb 9700660 level8/core_data.wad
@@ -2674,6 +2699,7 @@ ee6f0ca2c15e1e0828d63547a8c19d2c45b8c0e2 13375488 level8/data.bin
ff4c7dd1924165ddc7dbb9494a166eb3e140c95c 899072 level8/gameplay.bin ff4c7dd1924165ddc7dbb9494a166eb3e140c95c 899072 level8/gameplay.bin
2d0d9a2d6092d0172bf50a3716d03b3631580e8b 541184 level8/gs_ram.bin 2d0d9a2d6092d0172bf50a3716d03b3631580e8b 541184 level8/gs_ram.bin
afeb717c151befdabfcf6297b0dbb1772ac13fdb 22528 level8/occlusion.bin afeb717c151befdabfcf6297b0dbb1772ac13fdb 22528 level8/occlusion.bin
3dc31bcd468b9e2eae2325a64d8da466f4de228b 2865764 level8/overlay.bin
1f65f6b44e4e4a146aaac3d63b7f459657de0010 1593344 level8/sound_bank.bin 1f65f6b44e4e4a146aaac3d63b7f459657de0010 1593344 level8/sound_bank.bin
fd39207fd776cb3ba6b8d5f6d3b10d61610c5e49 21592016 level9/core_data.bin fd39207fd776cb3ba6b8d5f6d3b10d61610c5e49 21592016 level9/core_data.bin
314f79d0a1d9ae630d67da9e3259a658c3c5642b 12578171 level9/core_data.wad 314f79d0a1d9ae630d67da9e3259a658c3c5642b 12578171 level9/core_data.wad
@@ -2681,6 +2707,7 @@ ffed974844aa0bef984bc33c86ff6757e0f9c5bc 34976 level9/core_index.bin
db530147f5d900e5a85294070a0a5e707c086213 16232448 level9/data.bin db530147f5d900e5a85294070a0a5e707c086213 16232448 level9/data.bin
3839c295b0408460f50032ff166cac27c0bdf411 864256 level9/gameplay.bin 3839c295b0408460f50032ff166cac27c0bdf411 864256 level9/gameplay.bin
e117d8deddac4a05f917a218e7583859397ad4d9 568320 level9/gs_ram.bin e117d8deddac4a05f917a218e7583859397ad4d9 568320 level9/gs_ram.bin
6756cf309ec79cc8de37a266f4c9a6d098a96ccf 2811948 level9/overlay.bin
bc38b5983d8717ff4c7a35fac3cd1042ef7bd675 1697792 level9/sound_bank.bin bc38b5983d8717ff4c7a35fac3cd1042ef7bd675 1697792 level9/sound_bank.bin
f666c702e75052034d72125db6bd5d6821ac8379 9 misc/0000.bin f666c702e75052034d72125db6bd5d6821ac8379 9 misc/0000.bin
2af38bb5b2fe77af4ed2a6079ebe5857247082a5 367 misc/0001.bin 2af38bb5b2fe77af4ed2a6079ebe5857247082a5 367 misc/0001.bin
1 3e93b99123a5611ed8cd197437a341daa7ad140c 264449 IOPRP255.IMG
2494 4a8f9d6608b776a86fb15420acadea4c2b749a9f 13279232 level0/data.bin
2495 133206d28cd25c39f1b6a760e345c0f0f409b7bf 659456 level0/gameplay.bin
2496 acef5e387c8051229eecf70a64fd6daa077b34b3 518656 level0/gs_ram.bin
2497 d7045d6c1256a9ffa241acca76e3761c34da3341 2751268 level0/overlay.bin
2498 7ec9566aeae513fe272bc97899a4dbe15a2a74ff 1503232 level0/sound_bank.bin
2499 6b1a358c6eb6039d0df51fce061b58c274e8c99c 19023392 level1/core_data.bin
2500 96b33cef1ac3c9bab5283ccf11bd86b0aa20ebcb 12165629 level1/core_data.wad
2503 a07364ccac5fb6dc3d1fb1c859fca13b205e6a1f 811008 level1/gameplay.bin
2504 afc39fac19d1a66aba56c4509c55977229dcb3cf 569344 level1/gs_ram.bin
2505 1318041a43acca46fb4f2348b2f4fa81bd810948 18432 level1/occlusion.bin
2506 54c0247eb3b0a08da2c6c87c5a23fc06bbe3fa50 2763668 level1/overlay.bin
2507 5d6ead57dd2adf83e75b53ef5d026682676d5010 1300480 level1/sound_bank.bin
2508 c7bbd091e77719876e53fe38cfb66ad68d0952d3 14427312 level10/core_data.bin
2509 1c56f35c21dd429f52cdc701de3d3ab846f57918 8999864 level10/core_data.wad
2510 2c0073d2e78ff61beb69050087ff953ac1ccc65c 19696 level10/core_index.bin
2511 fe9e5169820bc9272aa5fcfbbabfc74e7b12d1ce 12498944 level10/data.bin
2512 4036bc105fa429b97a725ad40bd4051168c886ad 367872 level10/gs_ram.bin
2513 76fa348a8764cbe97c8caf5a892105007c2bf4a2 2870708 level10/overlay.bin
2514 d3e48237b03649578f6962e36f1bc197c3a8af9f 1306624 level10/sound_bank.bin
2515 da253d8b4dc4f8fc482880c54cd832c53bfd4aa0 20717584 level11/core_data.bin
2516 a0173c7577180d32e9bfed67464eecf3bc12c033 11414905 level11/core_data.wad
2519 ff6811c08f3fe034bae54ab066e3051d4cbb39cc 1034240 level11/gameplay.bin
2520 472e916feae6b88e5028e48e9d2e4d1a433a8ed7 538624 level11/gs_ram.bin
2521 e91c93e8af747ce8c0802c3c96cd88acc126a5c3 51200 level11/occlusion.bin
2522 4502a46e00bbbb3a0a59aa4d5899fd3b3ca44948 3151128 level11/overlay.bin
2523 850f01ef9678ed0b0b6242c410ad7e57b935079f 991232 level11/sound_bank.bin
2524 d13c8eb4f2ab552e330a119bf1e14d55f9921220 20995456 level12/core_data.bin
2525 9d8355548c0d5c911e18124e83a04f3c58d604de 11510373 level12/core_data.wad
2527 a134bad90883ad15ff7b52d909da461a491d253b 15136768 level12/data.bin
2528 b058a1f7858198be55a90bf70d81db1f8039e132 813056 level12/gameplay.bin
2529 cbccba141d2efe7b4165dd81a265532465106def 510464 level12/gs_ram.bin
2530 c47b9490cf2a0145331d2b2229234fd1e1b2e172 2843328 level12/overlay.bin
2531 709e185927a11b846e6b1d66c2fa770cf284ea65 1568768 level12/sound_bank.bin
2532 74fd46bfcfcb97132c509d2147070327976faab4 21301168 level13/core_data.bin
2533 7c4b8c7fd086de66e19bd440a5e63fea4729e0bb 12037495 level13/core_data.wad
2535 29c09e0877c62ba4dee1325cf00c944f9ad78ceb 15702016 level13/data.bin
2536 ab5e7bd921f3973e3d15052838ec87676d35928a 854016 level13/gameplay.bin
2537 0554fcecd5cbe57f3b90a97bd75edba4ea438752 555520 level13/gs_ram.bin
2538 3fbaf06d37e5ffc64dd7fa3f37e3fe20d3ea8037 2833444 level13/overlay.bin
2539 2793ed3153f9f8ace889b855dbfc53f29c94c0bc 1406976 level13/sound_bank.bin
2540 386e9a7cef58f23f66fbc21de42be234c25be0b4 19738512 level14/core_data.bin
2541 b34bf0528e2c753ce9fd65791e89ba6ec8491358 11217244 level14/core_data.wad
2543 fb30c8c10944377af4a76e2804209be630dba993 14948352 level14/data.bin
2544 95157d87f0c5e76a38daead9e07d01281b65ebf0 692224 level14/gameplay.bin
2545 0d8c49b2b405df331f859a077a6dfbb6aa922b3d 582656 level14/gs_ram.bin
2546 bdcd73fb41d07da3ce35f60a2fd48af3d8ffe96b 2874964 level14/overlay.bin
2547 28a70e7880794f94a76e2b340373d1dc40484fee 1689600 level14/sound_bank.bin
2548 9e1a9269a59741f15608ee773192a4c4e3adadbb 14566720 level15/core_data.bin
2549 3c7f4291ff09cdfb0c1cd2e435fb4c039faf7572 8538458 level15/core_data.wad
2551 39e2c8e15375dae4c73f61a7be980dccf4a85aa9 12122112 level15/data.bin
2552 9b13d67b1276d25937cab21344fa8a6687cc0882 491520 level15/gameplay.bin
2553 d1437c7d2106f6e9cdb54f66cea4f1a714dc8867 407552 level15/gs_ram.bin
2554 76b9c880945039ece6e51762b8dd76ffc680da7c 2915968 level15/overlay.bin
2555 f89c334b887f377f341fc9232d474affa89fc544 1476608 level15/sound_bank.bin
2556 49c2efb647efc0baaf919aaaa140f0a660725b3d 21374192 level16/core_data.bin
2557 9c9291257d21ac62dff3ac4090f9d2f9b6c5c3e8 12442996 level16/core_data.wad
2559 8e58f79a35fbe78382065bdce0f53d2cc9f1ccf1 16091136 level16/data.bin
2560 af60ee961ab3c4508f6fc7f7e5db32d1bbe08769 595968 level16/gameplay.bin
2561 9b85cc208f66ffbbdff60d979597e1e2c47bc593 577024 level16/gs_ram.bin
2562 2bdb86a7a4c69af80eb5f30d62ed58f337961f3f 2798992 level16/overlay.bin
2563 84723c80beedd7e55143a424a1b2f6255a200ab0 1654784 level16/sound_bank.bin
2564 485e5343a6f146179b319fb7d76388eee6e21276 20920320 level17/core_data.bin
2565 fc90583b7560be17115f57c8ecf6fc14ec2e59d8 10922651 level17/core_data.wad
2567 d7669eec9eb3cf36bf4f649333ae79864c1a7043 14575616 level17/data.bin
2568 10a5f5e2862f7364a9bbe886dadb9ebca8ab0381 796672 level17/gameplay.bin
2569 899d4e61f032af4c66419d3a286a25d1e97e5644 574464 level17/gs_ram.bin
2570 f76912e7914400cc5ceeca59f6780bc5143997c8 2803324 level17/overlay.bin
2571 37c76a37d9e3cbbed6dfc324b0b90d3498ecb0e7 1347584 level17/sound_bank.bin
2572 f9240e2bd6406f206e155b09cc5c914b6574e41e 20268640 level18/core_data.bin
2573 f44add8374224a84070995ecb4b1be8cfb58aa9f 11721139 level18/core_data.wad
2575 d0f931677cb6589a564937c3d15c13344b923969 15501312 level18/data.bin
2576 2adc53f721fca95dd1012994d243e0704edd034c 675840 level18/gameplay.bin
2577 7f6a823c4b47fdfb58e29237b680d03e946329db 571136 level18/gs_ram.bin
2578 e5370c97b9a2ccdb00678e57f9e18042eab6735d 2937576 level18/overlay.bin
2579 b26483cc29ecb8b6d2c71919ff7cd56b09522bc1 1492992 level18/sound_bank.bin
2580 0918d824b72b892a24f46108bbc60f6e875236f6 20233504 level19/core_data.bin
2581 b79df8e074db7d3c75bc2b3b9c2a4de61e5dd9cd 11797811 level19/core_data.wad
2584 8700a93086450328f2693b553e1df447a79fcd83 825344 level19/gameplay.bin
2585 d5061a551eb354ddf7952c4bfd62767c6e074c3f 497920 level19/gs_ram.bin
2586 877ca7fb8e80d810d6c37fb993795aa10b5b1033 47104 level19/occlusion.bin
2587 428ea51a157b35cdf831f2a6908647d8ffe6a91b 2821856 level19/overlay.bin
2588 8298f799c394542e912e41ff3321ee55c70e0c8f 1570816 level19/sound_bank.bin
2589 6d56189ad74f04d366116b736ab12888154de833 20209408 level2/core_data.bin
2590 f0eb5c033e84092748cf3457818b0d4c85da7a0a 11372886 level2/core_data.wad
2593 bfc9f6bc73edfa7deb46505f187c274dcf7b863a 790528 level2/gameplay.bin
2594 164f775e33d9325b3e4c5ce4cbf8c515ed311fd2 513024 level2/gs_ram.bin
2595 01c7d4c7b0ecb44a91694ffde796ff941057191a 28672 level2/occlusion.bin
2596 d7182afdf4a986c9c781a9658373281bfc37df22 2928424 level2/overlay.bin
2597 bce56b3b899d72c5d89f612bd70447f066c10757 1118208 level2/sound_bank.bin
2598 daf162b58756e2661f3ec891c8736a1611b4dc52 19637472 level20/core_data.bin
2599 7e7203ac3a2dbf578546573a34fa6dce4c336d35 10449039 level20/core_data.wad
2602 7368af71bc6c070fc6422fd69a354b2f3518ed2b 833536 level20/gameplay.bin
2603 4c7325cfdde68fc9160ec9a2cd18da3d327dcd9a 534272 level20/gs_ram.bin
2604 61ae37031d1df746bffb4a0bddeaee85558c894e 36864 level20/occlusion.bin
2605 a5235e8ce98aa3cfbb499ae988c419a05a0bcf55 2976888 level20/overlay.bin
2606 abce77e8841c0ed0fa006db81840b35ea4c75afd 1335296 level20/sound_bank.bin
2607 5e850ab42d2dc3e8fa1f9ae74d401be1658c9ba7 14483120 level21/core_data.bin
2608 f10321014e7ccd61a65f294baefaf35875d6294b 8178784 level21/core_data.wad
2610 4a3ebd058fe62df17ed4d74fe190442ff2993474 11722752 level21/data.bin
2611 6c2b9faaef23c142a61316190852251404734c39 448512 level21/gameplay.bin
2612 43a953d770cd4c5833eda5c1ecae26d7ce127be1 512256 level21/gs_ram.bin
2613 384f39176ccbd107924eacc05d6330dda4be523f 2766612 level21/overlay.bin
2614 8fbcb2360d5244e92bfd7c2ccd453eec44cd042b 1167360 level21/sound_bank.bin
2615 7b0733a07f28e7d7cf5d8a19b1058a98a5563c9f 14288048 level22/core_data.bin
2616 34a2d1ac57f13cacdf46cd7d73f356951f87dd43 8186976 level22/core_data.wad
2618 3519cac29e6371802b40e3d8234ef7064abee336 11683840 level22/data.bin
2619 a8760bb193082c5e85faeb5f799ab8585f366805 557056 level22/gameplay.bin
2620 9c3af51d0b3ad2b786a428a49a8ee9d30886d636 482048 level22/gs_ram.bin
2621 ae40d45a6605d5929f54e63b531919cb6ab093a9 2753704 level22/overlay.bin
2622 dba3b2b5856efbd28a8aeaa15fbee6f4fb479895 1363968 level22/sound_bank.bin
2623 c6b6f4087f4c9cd374a587b13777f9575953838a 12647664 level23/core_data.bin
2624 dbdca886def506bd2e6fd189c0fc4ca8d2834b97 6900832 level23/core_data.wad
2626 9a26194ba7d5930279a62113b1377ffeb1294269 10414080 level23/data.bin
2627 336b3c864d1aa317003499e7d1d2d5a8cee13d58 505856 level23/gameplay.bin
2628 bcc4a8508f14af07ee85dd5f06dff7a910d73b6a 411136 level23/gs_ram.bin
2629 38f7544efa497b26e494e31169fa057ce2a1b62c 2841396 level23/overlay.bin
2630 e254322f1d87d2650c21ec2c118546df1a56d9e8 1675264 level23/sound_bank.bin
2631 508808939296de54fee8928824bdcc6d37204d01 6386608 level24/core_data.bin
2632 c44701fc0a229dc8071a30594231d5edfb6b4119 3805312 level24/core_data.wad
2633 c526f84012360c24a07e4bbd6823dd18ce8b3181 16608 level24/core_index.bin
2634 f38883faa65e74f67a44da4a55b9b476848d7cae 6981632 level24/data.bin
2635 adf90875335b529183111edaeaa90138ba111dbe 308480 level24/gs_ram.bin
2636 2b5f97e7aa6a6bf01e5ed2c918ca19b7eafb9593 2615776 level24/overlay.bin
2637 3bf88b1d1c455ab90bbacc3270a3f54c94b900ef 937984 level24/sound_bank.bin
2638 c81ea8c485748fccec6619bacc75afe8ce2f7e20 5898736 level25/core_data.bin
2639 80d763434b9dda1224c446de67e4907cfbdcfbe7 3513706 level25/core_data.wad
2640 c493407fb493ff359e90d1bdfc1b379a2a6c540a 16592 level25/core_index.bin
2641 e57a615ccce4afd0a3de13f638ecaa19f88d0789 6852608 level25/data.bin
2642 b54226627562788f722c2fdc314d2117ca6ed592 287488 level25/gs_ram.bin
2643 eee845138d0c88116743bb36929a6dc894b62e14 2801992 level25/overlay.bin
2644 e9d246c13a7198b42a71f081d767b9e9598d01a1 1370112 level25/sound_bank.bin
2645 948974c4fd10e7ca7ad1a362af9cb46aa6c81773 9530880 level26/core_data.bin
2646 6140efd9a7ebbcd81bfddf1ce0a5395ffb136d2e 5894651 level26/core_data.wad
2648 4fdd7d88a5d3db63418c968f8a5f53ac370b86be 9224192 level26/data.bin
2649 0d63ad0bc2edcd958b1c7dbec303c256ed8850e1 679936 level26/gameplay.bin
2650 abb3fb6a4175cf6b41eff1d7178d59ce7affb84f 378880 level26/gs_ram.bin
2651 518bb5c68f2d028c7c5934f878abbd3ba9d6c63f 2695436 level26/overlay.bin
2652 8f6a74b8008a9ebc9e92853d0b339a8e725c9992 1073152 level26/sound_bank.bin
2653 f5d3cd4480fe6a52a019d04401a360018b279b81 21148480 level3/core_data.bin
2654 690209848c64647194548d5e81f329d471392a8e 11383592 level3/core_data.wad
2656 eb8366046b5643cc54d42d800c06bf66ccd4ef36 15157248 level3/data.bin
2657 55cc4acecac6ba19232e6505fdc06a22b0633a45 749568 level3/gameplay.bin
2658 ed1f77cd35e2572adc51f4f43de030f31b8c6ed9 675840 level3/gs_ram.bin
2659 6fb6a7e7a64afd2f1e897f23640866f28154aa1d 2820776 level3/overlay.bin
2660 d986825dfc2bed713eb9699504536346658aaa3f 1705984 level3/sound_bank.bin
2661 f8c0aa3778b5bcb1995e516045b28ca6ac35502f 21107088 level4/core_data.bin
2662 ec75f324670188af581c4fceaa4cb285353f95b8 12171908 level4/core_data.wad
2665 eeee7ebf8d9980f9afc6d9c2bc2156bcca015ddd 960512 level4/gameplay.bin
2666 5a9a603e46e2008df02e60dce2c66ae9368d9993 522496 level4/gs_ram.bin
2667 fed269c2d706a2ba11cab965e090fd41d434055c 49152 level4/occlusion.bin
2668 6941b8cb87a7ff14f53b2a2511bfa2d43f25ba47 3027588 level4/overlay.bin
2669 bdd446767b1a84043d65ff968278cd2d5808705a 1236992 level4/sound_bank.bin
2670 cb07a102a92b887d06d9314c07ce1db50e14c1fb 16715440 level5/core_data.bin
2671 e5cc08e39cb6883b391605bdcff8ba3bb8423699 11205994 level5/core_data.wad
2673 b241ea9d8fe18dd5bdbc8a4255ba8450a66b3c6a 14772224 level5/data.bin
2674 c71539e0f8b0e2d666d9382475539c7a590fee04 514048 level5/gameplay.bin
2675 ad4318ac2d601a2b1b0b9b444325082e0fc50ffc 434176 level5/gs_ram.bin
2676 c5f6b22d88d8b9eab711872337fc358bcd4051a8 2863676 level5/overlay.bin
2677 5c2fd4470dd8c892af7686770af8cf3e2396373c 1390592 level5/sound_bank.bin
2678 8ea18c5a51e4ae07ca56d92d65ffde60f4bcce12 20935696 level6/core_data.bin
2679 f2962a73cfb15ab1bef23253d863d2433b264383 12810673 level6/core_data.wad
2681 ccb0a4e8c51be81fbcd8ad325ab8d521bbdb7604 16777216 level6/data.bin
2682 b9ff3b4b8fb6308aac7f5ddba1c4b1099d484dd5 929792 level6/gameplay.bin
2683 c404dfa08cab9603e5deeeed9778e4d29b755d88 604160 level6/gs_ram.bin
2684 4d981fd750196894f96f0a24437bc76210a1663a 3086372 level6/overlay.bin
2685 82fcce964396eb508768933d092a557bc1b05c89 1669120 level6/sound_bank.bin
2686 6692290d78706a2b92cba541264ddea9a764e2f2 18209392 level7/core_data.bin
2687 f1cae801f1050c57258f93a7035148b3094ece03 10780010 level7/core_data.wad
2690 bc8f542a56d4795c77774201903e25b01962c039 636928 level7/gameplay.bin
2691 9671dd66d0a20ef778bbd76f0e8517792646467c 580352 level7/gs_ram.bin
2692 e02d1b0d2339d72ed5d07abfe105f41e2288f59f 16384 level7/occlusion.bin
2693 82f26617c99f502324fb556006f8f859fda1b5f1 2733840 level7/overlay.bin
2694 8e07c803b709100023c3b44c8842eec9b8f49898 1556480 level7/sound_bank.bin
2695 264a306c3a83b9137d9753b282e9c773cd386713 21086224 level8/core_data.bin
2696 0a96094edd63dc4dd940894b007f85b21874cfeb 9700660 level8/core_data.wad
2699 ff4c7dd1924165ddc7dbb9494a166eb3e140c95c 899072 level8/gameplay.bin
2700 2d0d9a2d6092d0172bf50a3716d03b3631580e8b 541184 level8/gs_ram.bin
2701 afeb717c151befdabfcf6297b0dbb1772ac13fdb 22528 level8/occlusion.bin
2702 3dc31bcd468b9e2eae2325a64d8da466f4de228b 2865764 level8/overlay.bin
2703 1f65f6b44e4e4a146aaac3d63b7f459657de0010 1593344 level8/sound_bank.bin
2704 fd39207fd776cb3ba6b8d5f6d3b10d61610c5e49 21592016 level9/core_data.bin
2705 314f79d0a1d9ae630d67da9e3259a658c3c5642b 12578171 level9/core_data.wad
2707 db530147f5d900e5a85294070a0a5e707c086213 16232448 level9/data.bin
2708 3839c295b0408460f50032ff166cac27c0bdf411 864256 level9/gameplay.bin
2709 e117d8deddac4a05f917a218e7583859397ad4d9 568320 level9/gs_ram.bin
2710 6756cf309ec79cc8de37a266f4c9a6d098a96ccf 2811948 level9/overlay.bin
2711 bc38b5983d8717ff4c7a35fac3cd1042ef7bd675 1697792 level9/sound_bank.bin
2712 f666c702e75052034d72125db6bd5d6821ac8379 9 misc/0000.bin
2713 2af38bb5b2fe77af4ed2a6079ebe5857247082a5 367 misc/0001.bin
+13
View File
@@ -158,6 +158,19 @@ pub fn unpack(iso_path: &Path, out_dir: &Path) -> Result<Vec<Extracted>, String>
); );
parts += 1; parts += 1;
} }
// The level's executable. It carries the normal
// table the tfrag lighting microcode indexes, so
// it is not optional background.
let overlay = data_header.part(LevelDataPart::Overlay);
if let Ok(bytes) = overlay.slice(data_extent, data_extent.len()) {
write_file(
out_dir,
&format!("{stem}/overlay.bin"),
bytes,
&mut written,
);
parts += 1;
}
// Graphics Synthesizer RAM holds the CLUTs the tfrag // Graphics Synthesizer RAM holds the CLUTs the tfrag
// texture table's palette indices point into, so it // texture table's palette indices point into, so it
// is needed to decode any level texture. // is needed to decode any level texture.
+1
View File
@@ -6,4 +6,5 @@ pub mod level_data;
pub mod level_wad; pub mod level_wad;
pub mod texture; pub mod texture;
pub mod tfrag; pub mod tfrag;
pub mod tfrag_light;
pub mod wad; pub mod wad;
+15 -2
View File
@@ -611,13 +611,26 @@ pub struct TfragRgba {
pub struct TfragLight { pub struct TfragLight {
pub unknown_0: i8, pub unknown_0: i8,
pub intensity: i8, pub intensity: i8,
/// Normal azimuth, pi/128 radians per unit. /// Normal azimuth, as an *index* into the overlay's 256-entry sine table.
pub azimuth: i8, pub azimuth: i8,
/// Normal elevation, pi/128 radians per unit. /// Normal elevation, as an index into the same table.
pub elevation: i8, pub elevation: i8,
pub color: i16, pub color: i16,
} }
impl TfragLight {
/// Azimuth as the unsigned table index. The field is stored signed but
/// indexes 0..=255, so the byte is what matters, not its sign.
pub fn azimuth_index(&self) -> u8 {
self.azimuth as u8
}
/// Elevation as the unsigned table index.
pub fn elevation_index(&self) -> u8 {
self.elevation as u8
}
}
/// Everything one tfrag contributes to a draw call. /// Everything one tfrag contributes to a draw call.
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
pub struct TfragGeometry { pub struct TfragGeometry {
+271
View File
@@ -0,0 +1,271 @@
//! Tfrag lighting inputs: the normal table and the level light bank.
//!
//! Spec: `docs/formats/tfrag.md`, `docs/formats/tfrag-lighting.md`.
//!
//! A tfrag's own light records store an azimuth and an elevation per vertex,
//! as *indices* into a 256-entry sine table rather than as angles. The table
//! lives in the level's overlay executable, and it is not at a fixed offset:
//! level 0 has it at `0xb650c`, level 5 at `0xcca30`. The overlay is also a
//! raw loadable image with **no ELF header**, so there is no program header to
//! map a virtual address through. The table is therefore located by its own
//! contents and checked before use — see [`NormalTable::from_overlay`].
//!
//! Nothing here reproduces the lighting *arithmetic*. That runs in the PS2's
//! float model, which the engine owns; this module only decodes the inputs
//! exactly.
use crate::buf::{Buf, Error, ErrorKind, Result};
/// 256 pairs of `(cos, sin)` as raw PS2 float bits.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NormalTable {
/// `[angle][0]` is cos, `[angle][1]` is sin, for `angle * pi / 128`.
pub cos_sin: Vec<[u32; 2]>,
}
/// How far a candidate entry may differ from `cos(i * pi / 128)` and still be
/// accepted. The disc's values are f32 and the check is f64, so exact equality
/// is not available; the tolerance is far below anything the table could
/// legitimately differ by.
const TABLE_EPSILON: f64 = 1e-3;
impl NormalTable {
/// Find the sine table inside a level overlay.
///
/// The overlay carries no ELF header, so the table cannot be reached
/// through a program header the way the boot executable's copy can. Every
/// table begins with exactly `(1.0, 0.0)` — angle 0 — so the scan starts
/// there and then requires all 256 entries to match the expected sine
/// curve. Validating the whole table rather than just the first entry is
/// what makes the search safe: a lone `1.0, 0.0` pair occurs by accident.
pub fn from_overlay(overlay: &[u8]) -> Result<NormalTable> {
if overlay.len() < 256 * 8 {
return Err(Error {
offset: 0,
context: "overlay is too small to hold a normal table",
kind: ErrorKind::OutOfBounds {
needed: 256 * 8,
available: overlay.len(),
},
});
}
let mut expected = [0f64; 512];
for i in 0..256usize {
let a = i as f64 * std::f64::consts::PI / 128.0;
expected[i * 2] = a.cos();
expected[i * 2 + 1] = a.sin();
}
// The first entry must be exactly (1, 0) before the table is even a
// candidate; that is what makes the scan cheap.
let mut at = 0usize;
while at + 256 * 8 <= overlay.len() {
let (c, s) = read_pair(overlay, at);
if c == 1.0 && s == 0.0 && matches_curve(overlay, at, &expected) {
let mut cos_sin = Vec::with_capacity(256);
for i in 0..256usize {
let (c, s) = read_pair(overlay, at + i * 8);
cos_sin.push([c.to_bits(), s.to_bits()]);
}
return Ok(NormalTable { cos_sin });
}
at += 4;
}
Err(Error {
offset: 0,
context: "no normal table found in the overlay",
kind: ErrorKind::Invalid("normal table not found"),
})
}
/// Cosine and sine for an angle index, as the PS2 floats the game uses.
pub fn get(&self, index: u8) -> (f32, f32) {
let e = self.cos_sin[index as usize];
(f32::from_bits(e[0]), f32::from_bits(e[1]))
}
/// A surface normal from an azimuth and elevation index.
///
/// `decode_normal` in the reference builds `(cos(a)cos(e), sin(a)cos(e),
/// sin(e))` and then negates all three, so the stored normals point
/// *into* the surface. Preserved rather than corrected: fidelity beats
/// what looks right.
pub fn normal(&self, azimuth: u8, elevation: u8) -> [f32; 3] {
let (ca, sa) = self.get(azimuth);
let (ce, se) = self.get(elevation);
[-(ca * ce), -(sa * ce), -se]
}
}
fn read_pair(bytes: &[u8], at: usize) -> (f32, f32) {
let c = f32::from_le_bytes(bytes[at..at + 4].try_into().unwrap());
let s = f32::from_le_bytes(bytes[at + 4..at + 8].try_into().unwrap());
(c, s)
}
fn matches_curve(overlay: &[u8], at: usize, expected: &[f64; 512]) -> bool {
for i in 0..256usize {
let (c, s) = read_pair(overlay, at + i * 8);
if !c.is_finite() || !s.is_finite() {
return false;
}
if (c as f64 - expected[i * 2]).abs() > TABLE_EPSILON {
return false;
}
if (s as f64 - expected[i * 2 + 1]).abs() > TABLE_EPSILON {
return false;
}
}
true
}
/// The level's directional and point light sets, from the `gameplay` extent.
///
/// Only the framing is decoded here. The lighting pass itself needs the PS2
/// float model and lives in the engine.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LightBank {
/// Directional light sets, in the order the microcode indexes them.
pub sets: Vec<DirLightSet>,
pub point_lights: Vec<PointLight>,
}
/// One directional light pair: a colour and a direction for each of A and B.
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct DirLightSet {
pub color_a: [f32; 4],
pub color_b: [f32; 4],
pub dir_a: [f32; 4],
pub dir_b: [f32; 4],
}
/// A point light.
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct PointLight {
pub color: [f32; 4],
pub pos: [f32; 4],
}
/// Cap the light counts so a corrupt header cannot ask for a huge allocation.
const MAX_LIGHTS: usize = 64;
impl LightBank {
/// Parse the bank from a level's `gameplay` extent.
///
/// The header at +0x04 is an offset to a count followed by the sets; a
/// negative count means the level has no lights.
pub fn parse(gameplay: &[u8]) -> Result<LightBank> {
let mut b = Buf::new(gameplay);
let ofs = b.u32("light bank offset")? as usize;
let count = b.i32("directional light count")?;
if count < 0 {
return Ok(LightBank::default());
}
let n = (count as usize).min(MAX_LIGHTS);
let mut sets = Vec::with_capacity(n);
for _ in 0..n {
sets.push(read_set(gameplay, ofs + 0x10 + sets.len() * 0x40)?);
}
Ok(LightBank {
sets,
point_lights: Vec::new(),
})
}
}
/// One 0x40-byte directional light set: four V4 lanes each for colour A,
/// direction A, colour B and direction B.
fn read_set(bytes: &[u8], at: usize) -> Result<DirLightSet> {
at.checked_add(0x40)
.filter(|&e| e <= bytes.len())
.ok_or(Error {
offset: at,
context: "directional light set runs past the gameplay extent",
kind: ErrorKind::OutOfBounds {
needed: 0x40,
available: bytes.len().saturating_sub(at),
},
})?;
let v4 = |o: usize| -> [f32; 4] {
let mut v = [0f32; 4];
for (i, slot) in v.iter_mut().enumerate() {
*slot = f32::from_bits(u32::from_le_bytes(
bytes[o + i * 4..o + i * 4 + 4].try_into().unwrap(),
));
}
v
};
Ok(DirLightSet {
color_a: v4(at),
dir_a: v4(at + 0x10),
color_b: v4(at + 0x20),
dir_b: v4(at + 0x30),
})
}
#[cfg(test)]
mod tests {
use super::*;
/// A synthetic overlay holding one correct table plus noise around it.
fn overlay_with_table() -> Vec<u8> {
let mut v = vec![0u8; 0x40];
// A decoy that starts with (1, 0) but then leaves the curve.
v[0x10..0x14].copy_from_slice(&1.0f32.to_le_bytes());
v[0x14..0x18].copy_from_slice(&0.0f32.to_le_bytes());
v[0x18..0x1c].copy_from_slice(&9.0f32.to_le_bytes());
let at = 0x400usize;
v.resize(at + 256 * 8, 0);
for i in 0..256usize {
let a = i as f64 * std::f64::consts::PI / 128.0;
v[at + i * 8..at + i * 8 + 4].copy_from_slice(&(a.cos() as f32).to_le_bytes());
v[at + i * 8 + 4..at + i * 8 + 8].copy_from_slice(&(a.sin() as f32).to_le_bytes());
}
v
}
#[test]
fn finds_the_table_and_skips_decoys() {
let o = overlay_with_table();
let t = NormalTable::from_overlay(&o).expect("table should be found");
assert_eq!(t.cos_sin.len(), 256);
assert_eq!(t.get(0), (1.0, 0.0));
assert!(t.get(64).0.abs() < 1e-6);
assert!((t.get(64).1 - 1.0).abs() < 1e-5);
assert!((t.get(128).0 + 1.0).abs() < 1e-5);
}
#[test]
fn a_short_overlay_is_an_error_not_a_panic() {
assert!(NormalTable::from_overlay(&[0u8; 16]).is_err());
assert!(NormalTable::from_overlay(&[]).is_err());
assert!(NormalTable::from_overlay(&vec![0u8; 4096]).is_err());
}
#[test]
fn normals_point_into_the_surface() {
let t = NormalTable::from_overlay(&overlay_with_table()).unwrap();
// Azimuth 0, elevation 0 is (1, 0, 0) before negation.
assert_eq!(t.normal(0, 0), [-1.0, 0.0, 0.0]);
// Elevation 64 is pi/2, so z is +/-1.
let n = t.normal(0, 64);
assert!(n[0].abs() < 1e-5 && n[1].abs() < 1e-5);
assert!((n[2] + 1.0).abs() < 1e-5);
// Every normal is a unit vector.
for a in [0u8, 32, 96, 200] {
for e in [0u8, 32, 64, 96] {
let n = t.normal(a, e);
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
assert!((len - 1.0).abs() < 1e-4, "a={a} e={e} len={len}");
}
}
}
#[test]
fn a_level_with_no_lights_parses_as_empty() {
let mut g = vec![0u8; 32];
g[0x04..0x08].copy_from_slice(&(-1i32).to_le_bytes());
let b = LightBank::parse(&g).expect("should parse");
assert!(b.sets.is_empty());
}
}
+29 -9
View File
@@ -528,16 +528,36 @@ brightness changes did nothing at all. Faking it would also have hidden the
gap. The colour attribute is picked up from the mesh automatically, so it is gap. The colour attribute is picked up from the mesh automatically, so it is
the *colour* data that is missing lighting, not a flag that was left off. the *colour* data that is missing lighting, not a flag that was left off.
The real model is `LightTfrags`, which needs three things this project has not The real model is `LightTfrags`, which needs three things:
parsed yet:
* the **light bank** from the level's `gameplay` extent (directional and point * the **light records** -- already parsed: azimuth and elevation as *indices*
light sets, and the per-vertex light `select` field); into the overlay's sine table, plus the base colour;
* the **normal table**, 256 pairs of cos/sin read from an ELF at * the **normal table** -- now decoded (`gc_formats::tfrag_light`), and normals
`BOOT_NORMAL_TABLE_VADDR` (`0x165500`) -- every level overlay carries one, so are built and fed to the renderer;
it does not need the boot ELF; * the **light bank** from the level's `gameplay` extent -- framed and parsed,
* the **light records** themselves, which are already parsed: azimuth and but the per-vertex evaluation is not written yet.
elevation at pi/128 radians per unit, and the base colour.
### The normal table, and why it is found by searching
The reference reads the table from an ELF at `BOOT_NORMAL_TABLE_VADDR`
(`0x165500`) and notes every level overlay carries a copy. **The overlay has no
ELF header** -- it is a raw loadable image starting with code -- so there is no
program header to map a virtual address through, and the offset is not fixed
either: level 0 has the table at `0xb650c`, level 5 at `0xcca30`, level 26 at
`0xb6284`.
So it is located by content. Every table starts with exactly `(1.0, 0.0)` --
angle zero -- and the scan then requires all 256 entries to match
`cos(i·pi/128), sin(i·pi/128)`. Validating the whole curve is what makes the
search safe: a lone `(1.0, 0.0)` pair of floats occurs by accident in a 2.7 MB
executable, and a test plants one as a decoy. The values read from the disc are
used as-is rather than recomputed, since those are the numbers the game indexes.
A normal is `(cos(az)cos(el), sin(az)cos(el), sin(el))` **negated** -- the
stored normals point into the surface -- then given the same Z-up to Y-up swap
as positions, or the lighting comes out sideways. The azimuth and elevation
fields are stored signed but index 0..=255, so the byte is what matters, not its
sign: entry 200 is read as 200, not as -56.
Given the table, a normal is `(cos(az)cos(el), sin(az)cos(el), sin(el))`, Given the table, a normal is `(cos(az)cos(el), sin(az)cos(el), sin(el))`,
negated, exactly as `decode_normal` builds it. The lighting pass itself is negated, exactly as `decode_normal` builds it. The lighting pass itself is
+13 -5
View File
@@ -14,7 +14,7 @@ early, while they are still cheap to retire.
- [x] Repository hygiene in place (pre-commit guard, `.gitignore`, licence - [x] Repository hygiene in place (pre-commit guard, `.gitignore`, licence
record) record)
- [x] **Phase 1 complete.** The disc pipeline runs end to end and is verified - [x] **Phase 1 complete.** The disc pipeline runs end to end and is verified
against a committed hash table: 5,186 files, 5,685,541,143 bytes, every against a committed hash table: 5,213 files, 5,762,497,559 bytes, every
one of the 92 asset containers parsing. one of the 92 asset containers parsing.
- [x] **Phase 2 in progress.** Level containers are parsed through their typed - [x] **Phase 2 in progress.** Level containers are parsed through their typed
header, and the `data` extent is parsed as a container of byte ranges. The header, and the `data` extent is parsed as a container of byte ranges. The
@@ -35,11 +35,19 @@ early, while they are still cheap to retire.
drawn unlit so the frame shows real albedo rather than a faked lighting drawn unlit so the frame shows real albedo rather than a faked lighting
model. Verified against the stored values (level 0 patch 0 runs model. Verified against the stored values (level 0 patch 0 runs
`(25,26,26)` to `(108,144,141)`), which are genuinely dark. `(25,26,26)` to `(108,144,141)`), which are genuinely dark.
- [ ] **tfrag lighting.** Needs the level light bank from `gameplay`, the - [x] **Tfrag normals.** The 256-entry sine table is decoded from the level
256-entry normal table from the overlay ELF at `0x165500`, and the overlay and normals are built per vertex and handed to the renderer. The
already-parsed azimuth/elevation light records. The pass itself is overlay has **no ELF header**, so the table cannot be reached through a
program header, and its offset differs per level (level 0 `0xb650c`,
level 5 `0xcca30`); it is located by matching the whole 256-entry curve.
Normals are negated as stored and get the same axis swap as positions.
The pipeline now also writes `overlay.bin`.
- [ ] **tfrag lighting pass.** The light bank from `gameplay` is framed and
parsed; the per-vertex evaluation is not written yet. It is
`base + A*max(dA, dA*wA) + B*max(dB, dB*wB)` and has to run in the PS2 `base + A*max(dA, dA*wA) + B*max(dB, dB*wB)` and has to run in the PS2
float model. Detail in `plan/level-assets.md`. float model. Until then the renderer stays unlit by default;
`GC_ENGINE_LIT=1` substitutes a host directional light for inspection,
which is not the game's lighting.
- [ ] Then the GS register set and the guard-band clip test. - [ ] Then the GS register set and the guard-band clip test.
The disc being the exact build the reference projects pin is the single The disc being the exact build the reference projects pin is the single