feat(formats,cli): recover entry TOC paths from IDXD identity

Add hash::recover_toc_name + TOC_NAME_SCHEMES (confirmed path schemes:
unit\<ID>.tbl, weapon\<ID>.tbl, message\<ID>.tbl, effect\<ID>.tbl,
<name>.tbl) that reproduce an entry's original backslash path from its
internal identity string via the recovered name-hash.

`pak list` now extracts identifier candidates from each IDXD entry
(ID/Name/Model + pool tokens) and prints the resolved path when a scheme
matches — e.g. unit\UN_f001_TCAF_DeltaSaber_T.tbl,
weapon\Weapon_…_Missile.tbl, message\CharacterCARL.tbl — plus a
name-resolved count. 308/1004 resolved on GP_MAIN_GAME_E; the remainder
use deeper cross-referenced paths (deferred).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-09 21:56:42 +02:00
parent ac340b9219
commit 5dd526c1de
2 changed files with 90 additions and 7 deletions

View File

@@ -73,10 +73,61 @@ pub fn name_hash(name: &str) -> u32 {
name_hash_raw(&bytes)
}
/// Confirmed TOC key path schemes, as `(prefix, suffix)` around an entry's
/// internal identity string. The key is `name_hash("<prefix><id><suffix>")`
/// (backslash separators). Recovered by RE — see `xenia-rs/RE_SYMBOLS.md`.
///
/// - `("", ".tbl")` — root manifests / DefTables resource entries (`files.tbl`).
/// - `("unit\\", ".tbl")` — craft/ship definitions (`unit\UN_f001_…_EX5.tbl`).
/// - `("weapon\\", ".tbl")` — weapon definitions (`weapon\Weapon_…_Missile.tbl`).
/// - `("message\\", ".tbl")` — character/dialog entries (`message\CharacterCARL.tbl`).
/// - `("effect\\", ".tbl")` — effect definitions.
///
/// Not every entry type is covered yet (some use deeper, cross-referenced
/// directory paths, e.g. per-mission dialog); those return `None`.
pub const TOC_NAME_SCHEMES: &[(&str, &str)] = &[
("", ".tbl"),
("unit\\", ".tbl"),
("weapon\\", ".tbl"),
("message\\", ".tbl"),
("effect\\", ".tbl"),
];
/// Try to recover an entry's original path from its TOC `name_hash` and a set of
/// candidate identity strings (e.g. identifier-like tokens from an IDXD string
/// pool). Returns the first `<scheme>(id)` whose hash matches, or `None`.
pub fn recover_toc_name<S: AsRef<str>>(entry_hash: u32, id_candidates: &[S]) -> Option<String> {
for id in id_candidates {
let id = id.as_ref();
for (prefix, suffix) in TOC_NAME_SCHEMES {
let name = format!("{prefix}{id}{suffix}");
if name_hash(&name) == entry_hash {
return Some(name);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recovers_names_via_schemes() {
// Craft entry (unit\<ID>.tbl) and weapon entry, verified on the real disc.
assert_eq!(
recover_toc_name(0x7C96_296C, &["UN_f001_TCAF_DeltaSaber_T_EX5"]),
Some("unit\\UN_f001_TCAF_DeltaSaber_T_EX5.tbl".to_string())
);
// First matching candidate wins; non-matching candidates are skipped.
assert_eq!(
recover_toc_name(0x7C96_296C, &["nope", "UN_f001_TCAF_DeltaSaber_T_EX5"]),
Some("unit\\UN_f001_TCAF_DeltaSaber_T_EX5.tbl".to_string())
);
assert_eq!(recover_toc_name(0x7C96_296C, &["unrelated"]), None);
}
#[test]
fn matches_known_disc_hashes() {
// Confirmed present in retail TOCs (see module docs).