feat(viewer): data pack (.pak / IDXD) browser
Roadmap #3. The explorer can now open IPFB data packs — the game's ship/weapon/ mission definition tables. Selecting a `.pak` shows a master-detail browser: entry list (hash · inner-format · identity) on the left, the selected IDXD object's schema + explicit-field property table on the right. Load path: a `.pak` selection reads the index plus its sibling `.pNN` segments (ISO reader or extracted dir, probing p00.. until the first gap), assembles via PakArchive::from_parts, and builds owned PakRow/PakDetail entirely off-thread — so the UI holds only plain data (no borrow of a PakArchive, WASM-safe). Two decode caps (64 MiB cumulative, 16 MiB/entry) keep a huge sound.pak from hanging; over-budget entries show as "(not decoded)". Wiring mirrors the texture path: new IsoLoaderMsg::PakLoaded → PendingPak staging → apply_pak. A shared reset (free GPU handle + clear texture/text/pak previews) runs in both apply systems, so exactly one viewer is active per selection and the prior GPU texture is always freed when switching modes. Reuse over duplication: the CLI's inner_label / idxd_identity move into the formats crate as pak::inner_format_label and IdxdObject::identity (bodies verbatim → CLI output unchanged), now shared by CLI and GUI. PakView registered unconditionally (wasm-safe; population native-only). ViewMode unchanged — the central panel dispatches on the populated resource. Workspace builds; 22 formats tests + all crate tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -171,6 +171,18 @@ impl IdxdObject {
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// A short identity string for the object: the first of `ID` / `Name` /
|
||||
/// `Model` present, else the schema hash. Shared by the CLI `pak list` and
|
||||
/// the GUI pack browser.
|
||||
pub fn identity(&self) -> String {
|
||||
for key in ["ID", "Name", "Model"] {
|
||||
if let Some(v) = self.get_raw(key) {
|
||||
return format!("{key}={v}");
|
||||
}
|
||||
}
|
||||
format!("schema {:08x}", self.schema_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the trailing string pool. Finds the smallest offset whose suffix is
|
||||
|
||||
@@ -262,6 +262,26 @@ fn be32(b: &[u8], o: usize) -> u32 {
|
||||
u32::from_be_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]])
|
||||
}
|
||||
|
||||
/// Best-effort human label for a decompressed entry's inner format, from its
|
||||
/// first bytes: `IDXD`, `xml`, a printable 4-char tag (RATC, IXUD, LSTA, …), or
|
||||
/// a hex fallback. Shared by the CLI `pak list` and the GUI pack browser.
|
||||
pub fn inner_format_label(payload: &[u8]) -> String {
|
||||
if payload.len() < 4 {
|
||||
return "empty".into();
|
||||
}
|
||||
let m = &payload[0..4];
|
||||
if m == b"IDXD" {
|
||||
"IDXD".into()
|
||||
} else if payload.starts_with(b"<?xml") {
|
||||
"xml".into()
|
||||
} else if m.iter().all(|&b| b.is_ascii_graphic()) {
|
||||
// Many inner formats are 4-char tags (RATC, T8aD, IXUD, LSTA, ttcf, …).
|
||||
String::from_utf8_lossy(m).into_owned()
|
||||
} else {
|
||||
format!("{:02X}{:02X}{:02X}{:02X}", m[0], m[1], m[2], m[3])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user