feat(formats): recover IPFB TOC name-hash; look up entries by path
The pak TOC key was previously an unknown hash (crc32/fnv/djb2/… ruled
out), forcing content-only lookup. Recovered it by static RE of the
retail title:
sub_824609C8 (pak lookup-by-name) dups the requested path, lowercases
it (sub_825F4F90), hashes with sub_82455C78, then binary-searches the
sorted TOC. sub_82455C78 is a per-byte Barrett-reduced polynomial hash
(modulus 0x00FFF9D7, reciprocal 0x80031493): the low 24 bits are the
modular hash, the top byte an 8-bit additive checksum of the bytes.
New `hash` module reproduces it exactly (faithful op sequence, no
textbook %). Verified against the real disc: name_hash("files.tbl") ==
0x83421153 and name_hash("eng\\weapon.tbl") == 0x900C8DCD, both present
in retail TOCs. Add PakArchive::find_by_name / read_by_name and a disc
integration test resolving eng\weapon.tbl by path (→ valid IDXD).
Note: retail paths use backslash separators (eng\weapon.tbl).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -97,3 +97,28 @@ fn dsaber_missile_weapon_stats() {
|
||||
assert_eq!(obj.get_i64("TriggerShotCount"), Some(12));
|
||||
assert_eq!(obj.get_f32("MaximumRange"), Some(10000.0));
|
||||
}
|
||||
|
||||
/// The recovered TOC name-hash resolves real entries by their original path.
|
||||
#[test]
|
||||
fn name_lookup_resolves_weapon_tbl() {
|
||||
skip_without_disc!(root);
|
||||
let arc = PakArchive::open(root.join("dat/GP_HANGAR_ARSENAL.pak")).unwrap();
|
||||
|
||||
// eng\weapon.tbl is present and hashes to the retail TOC key.
|
||||
let entry = arc
|
||||
.find_by_name("eng\\weapon.tbl")
|
||||
.expect("eng\\weapon.tbl must resolve by name");
|
||||
assert_eq!(entry.name_hash, 0x900C_8DCD);
|
||||
|
||||
// Case-insensitive, matching the game's lowercasing.
|
||||
assert_eq!(
|
||||
arc.find_by_name("ENG\\Weapon.TBL").map(|e| e.name_hash),
|
||||
Some(0x900C_8DCD)
|
||||
);
|
||||
|
||||
// The payload decompresses to a self-describing IDXD table.
|
||||
let bytes = arc.read_by_name("eng\\weapon.tbl").unwrap().unwrap();
|
||||
assert_eq!(&bytes[..4], b"IDXD");
|
||||
let obj = IdxdObject::parse(&bytes).expect("weapon.tbl parses as IDXD");
|
||||
assert!(obj.count > 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user