feat(formats,viewer): decode T8aD textures, RATC bundles, LSTA sprites
Add three static PAK-format decoders in the Bevy-free formats crate and present them in the explorer: - t8ad.rs: linear 32bpp A8R8G8B8 2D texture (UI/HUD art). Dims at 0x14/0x18, header size keyed by the type field @0x1c (container-safe). Decodes the ~85% RGBA variants; defers the rest (likely DXT) rather than misdecoding. - ratc.rs: nested resource bundle — lists named children by magic scan. - lsta.rs: sprite list — walks the inline T8aD frames. Viewer: PakContent gains T8ad/Lsta/Ratc, decoded off-thread and bounded by an 8M-texel per-entry cap; detail views show the texture, a sprite grid, and a child list with thumbnails. Decoded images carry a "colours unverified" note — channel-order/sRGB stays on the dynamic-RE backlog. Tests: 12 new unit + 3 real-disc (T8aD >=70% decode over the hangar pack, LSTA frames, RATC named children incl. a decodable T8aD). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,81 @@ fn name_lookup_resolves_weapon_tbl() {
|
||||
assert!(obj.count > 0);
|
||||
}
|
||||
|
||||
/// Real T8aD entries decode to sane RGBA surfaces, and a good fraction of the
|
||||
/// pack's T8aD entries are decodable (the RGBA-variant rule holds broadly).
|
||||
#[test]
|
||||
fn hangar_t8ad_textures_decode() {
|
||||
skip_without_disc!(root);
|
||||
let arc = PakArchive::open(root.join("dat/GP_HANGAR_ARSENAL.pak")).unwrap();
|
||||
let (mut seen, mut decoded) = (0usize, 0usize);
|
||||
for e in arc.entries() {
|
||||
if e.comp_size > 4_000_000 {
|
||||
continue;
|
||||
}
|
||||
let Ok(p) = arc.read(e) else { continue };
|
||||
if !sylpheed_formats::t8ad::is_t8ad(&p) {
|
||||
continue;
|
||||
}
|
||||
seen += 1;
|
||||
if let Some(img) = sylpheed_formats::t8ad::parse(&p) {
|
||||
decoded += 1;
|
||||
assert!((1..=4096).contains(&img.width) && (1..=4096).contains(&img.height));
|
||||
assert_eq!(img.rgba.len(), (img.width * img.height * 4) as usize);
|
||||
}
|
||||
}
|
||||
assert!(seen > 100, "expected many T8aD, saw {seen}");
|
||||
// The RGBA rule should cover the large majority (≈85% disc-wide).
|
||||
assert!(decoded * 100 >= seen * 70, "decoded {decoded}/{seen}");
|
||||
}
|
||||
|
||||
/// A real LSTA sprite list yields inline T8aD frames.
|
||||
#[test]
|
||||
fn lsta_sprite_list_decodes() {
|
||||
skip_without_disc!(root);
|
||||
let arc = PakArchive::open(root.join("dat/GP_HANGAR_ARSENAL.pak")).unwrap();
|
||||
let mut found = false;
|
||||
for e in arc.entries() {
|
||||
let Ok(p) = arc.read(e) else { continue };
|
||||
if !sylpheed_formats::lsta::is_lsta(&p) {
|
||||
continue;
|
||||
}
|
||||
if let Some(frames) = sylpheed_formats::lsta::parse(&p) {
|
||||
if !frames.is_empty() {
|
||||
found = true;
|
||||
assert!(frames.iter().all(|f| !f.rgba.is_empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(found, "no decodable LSTA found");
|
||||
}
|
||||
|
||||
/// A real RATC bundle lists named children including a decodable T8aD.
|
||||
#[test]
|
||||
fn ratc_bundle_lists_children() {
|
||||
skip_without_disc!(root);
|
||||
let arc = PakArchive::open(root.join("dat/GP_HANGAR_ARSENAL.pak")).unwrap();
|
||||
let mut ok = false;
|
||||
for e in arc.entries() {
|
||||
if e.comp_size > 4_000_000 {
|
||||
continue;
|
||||
}
|
||||
let Ok(p) = arc.read(e) else { continue };
|
||||
if !sylpheed_formats::ratc::is_ratc(&p) {
|
||||
continue;
|
||||
}
|
||||
let Some(kids) = sylpheed_formats::ratc::parse(&p) else { continue };
|
||||
let has_named = kids.iter().any(|c| c.name.contains('.'));
|
||||
let has_t8ad = kids
|
||||
.iter()
|
||||
.any(|c| c.kind == "T8aD" && sylpheed_formats::t8ad::parse(&p[c.offset..c.offset + c.size]).is_some());
|
||||
if !kids.is_empty() && has_named && has_t8ad {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(ok, "no RATC with named children + a decodable T8aD");
|
||||
}
|
||||
|
||||
/// The English movie subtitle track (an `IXUD` entry) decodes to timed cues.
|
||||
#[test]
|
||||
fn eng_movie_subtitle_track() {
|
||||
|
||||
Reference in New Issue
Block a user