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:
@@ -16,10 +16,10 @@
|
||||
//! ```
|
||||
//!
|
||||
//! - `name_hash` — the TOC is **sorted ascending by `name_hash`** (binary-searchable).
|
||||
//! The hash is a **custom/unknown algorithm** (crc32/fnv/djb2/sdbm/elf/jenkins all
|
||||
//! ruled out), so entries cannot yet be looked up by their original string path.
|
||||
//! In practice most payloads are self-describing (see [`crate::idxd`]), so lookup
|
||||
//! by content is usually preferable anyway.
|
||||
//! The hash is a custom Barrett-reduced polynomial over the **lowercased** path,
|
||||
//! recovered from the retail title — see [`crate::hash`]. Look up entries by their
|
||||
//! original path with [`PakArchive::find_by_name`] / [`PakArchive::read_by_name`]
|
||||
//! (retail paths use backslash separators, e.g. `eng\weapon.tbl`).
|
||||
//! - `offset` — byte offset into the **concatenated** `.pNN` segments, `block_size`-aligned.
|
||||
//! - `comp_size` — stored (compressed) size of the entry.
|
||||
//!
|
||||
@@ -225,6 +225,18 @@ impl PakArchive {
|
||||
pub fn read_by_hash(&self, name_hash: u32) -> Option<Result<Vec<u8>, PakError>> {
|
||||
self.find(name_hash).map(|e| self.read(e))
|
||||
}
|
||||
|
||||
/// Look up an entry by its original path, hashing it the way the game does
|
||||
/// ([`crate::hash::name_hash`]). Names are case-insensitive; path separators
|
||||
/// must be **backslash** to match the on-disc form, e.g. `eng\weapon.tbl`.
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&PakEntry> {
|
||||
self.find(crate::hash::name_hash(name))
|
||||
}
|
||||
|
||||
/// Convenience: [`find_by_name`](Self::find_by_name) + [`read`](Self::read).
|
||||
pub fn read_by_name(&self, name: &str) -> Option<Result<Vec<u8>, PakError>> {
|
||||
self.find_by_name(name).map(|e| self.read(e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Decompress a single stored entry payload (the `"Z1"` container, or raw).
|
||||
|
||||
Reference in New Issue
Block a user