- FMOD: FSB audio bank format support - THQ: PAK archives, G4RC textures, RAD video, STR strings, GML scripts - Volition: VPP archives, PEG textures, ASM container format - Wii: BNR banner format with metadata extraction Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
250 lines
6.6 KiB
Plaintext
250 lines
6.6 KiB
Plaintext
// PAK Archive format - THQ Australia Engine
|
|
// Used by: SpongeBob, Avatar, Jimmy Neutron, Megamind, etc.
|
|
// Dispatcher that routes to platform-specific parser based on magic and version
|
|
//
|
|
// Big-endian "pack" magic: Xbox 360, Wii, PS2 (newer games like Avatar)
|
|
// Little-endian "kcap" magic: Original Xbox, GameCube, PS2 (older games like SpongeBob)
|
|
|
|
// Main dispatcher for big-endian PAK files
|
|
type thqa_pak [root, display="THQA PAK Archive"] byteorder BE
|
|
{
|
|
criteria {
|
|
require ascii(bytesat(0, 4)) == "pack";
|
|
}
|
|
|
|
original_name = _name;
|
|
pak_version = u32at(4);
|
|
|
|
set_display("THQA PAK Archive");
|
|
|
|
if (pak_version == 2) {
|
|
// Xbox 360 format (24-byte entries)
|
|
set_name(original_name + " (Xbox 360)");
|
|
platform = "Xbox 360";
|
|
|
|
ui("platform", "Platform");
|
|
parse_here("thqa_pak_xenon");
|
|
} else if (pak_version == 1) {
|
|
// Wii format (16-byte entries)
|
|
set_name(original_name + " (Wii)");
|
|
platform = "Wii";
|
|
|
|
ui("platform", "Platform");
|
|
parse_here("thqa_pak_wii");
|
|
} else {
|
|
// Unknown version
|
|
set_name(original_name + " (Unknown v" + pak_version + ")");
|
|
platform = "Unknown";
|
|
|
|
ui("platform", "Platform");
|
|
magic = ascii(read(4));
|
|
|
|
ui("magic", "Magic");
|
|
u32 version;
|
|
|
|
ui("version", "Unknown Version");
|
|
}
|
|
}
|
|
|
|
// Dispatcher for little-endian PAK files (original Xbox, GameCube)
|
|
type thqa_pak_le [root, display="THQA PAK Archive"] byteorder LE
|
|
{
|
|
criteria {
|
|
require ascii(bytesat(0, 4)) == "kcap";
|
|
}
|
|
|
|
original_name = _name;
|
|
|
|
set_display("THQA PAK Archive");
|
|
set_name(original_name + " (Xbox)");
|
|
platform = "Xbox";
|
|
|
|
ui("platform", "Platform");
|
|
|
|
parse_here("thqa_pak_xbox");
|
|
}
|
|
|
|
// PAK entry child for big-endian files (Xbox 360/Wii)
|
|
// Some file types (gml, mmp, ofx, rcb, rsc) are zlib compressed
|
|
type thqa_pak_entry_be [display="PAK Entry"] byteorder BE
|
|
{
|
|
entry_name = ctx_get("_thqa_pak_entry_name");
|
|
set_name(entry_name);
|
|
raw_size = size();
|
|
|
|
ui("raw_size", "Size");
|
|
|
|
// Check if this is a zlib-compressed file type
|
|
is_compressed = ends_with(entry_name, ".gml") ||
|
|
ends_with(entry_name, ".mmp") ||
|
|
ends_with(entry_name, ".ofx") ||
|
|
ends_with(entry_name, ".rcb") ||
|
|
ends_with(entry_name, ".rsc");
|
|
|
|
if (is_compressed) {
|
|
compressed_data = read(size());
|
|
decompressed_data = zlib(compressed_data);
|
|
decompressed_size = len(decompressed_data);
|
|
|
|
ui("decompressed_size", "Decompressed Size");
|
|
file_magic = ascii(bytesof(decompressed_data, 0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
|
|
if (file_magic == "0lmg") {
|
|
set_display("GML Script");
|
|
child = decompressed_data |> parse thqa_pak_gml_be;
|
|
push("content", child);
|
|
} else if (file_magic == "g4rc") {
|
|
if (ends_with(entry_name, ".rcb")) {
|
|
set_display("RCB Texture");
|
|
child = decompressed_data |> parse thqa_pak_rcb_be;
|
|
push("content", child);
|
|
} else {
|
|
set_display("G4RC Resource");
|
|
child = decompressed_data |> parse thqa_pak_g4rc_be;
|
|
push("content", child);
|
|
}
|
|
} else if (file_magic == "dsrc") {
|
|
set_display("RSC Model");
|
|
child = decompressed_data |> parse thqa_pak_rsc_be;
|
|
push("content", child);
|
|
} else {
|
|
set_display("Decompressed Data");
|
|
ctx_set("_thqa_decomp_name", entry_name);
|
|
child = decompressed_data |> parse thqa_pak_decomp_be;
|
|
push("content", child);
|
|
}
|
|
} else {
|
|
// Check extension-based types first (before reading magic)
|
|
if (ends_with(entry_name, ".sub")) {
|
|
set_display("Subtitle File");
|
|
parse_here("thqa_sub");
|
|
} else {
|
|
// Magic-based detection
|
|
file_magic = ascii(bytesat(0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
|
|
if (file_magic == "pack") {
|
|
set_display("Nested PAK");
|
|
parse_here("thqa_pak");
|
|
} else if (file_magic == "rad0") {
|
|
set_display("RAD Resource");
|
|
parse_here("thqa_rad");
|
|
} else if (file_magic == "FSB3" || file_magic == "FSB4" || file_magic == "FSB5") {
|
|
set_display("FSB Sound Bank");
|
|
parse_here("fmod_fsb_embedded");
|
|
} else if (file_magic == "bats") {
|
|
set_display("String Table");
|
|
parse_here("thqa_str");
|
|
} else {
|
|
data = read(size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// PAK entry child for little-endian files (original Xbox)
|
|
type thqa_pak_entry_le [display="PAK Entry"] byteorder LE
|
|
{
|
|
entry_name = ctx_get("_thqa_pak_entry_name");
|
|
set_name(entry_name);
|
|
raw_size = size();
|
|
|
|
ui("raw_size", "Size");
|
|
|
|
// Check extension-based types first (before reading magic)
|
|
if (ends_with(entry_name, ".sub")) {
|
|
set_display("Subtitle File");
|
|
parse_here("thqa_sub");
|
|
} else if (size() >= 4) {
|
|
// Magic-based detection
|
|
file_magic = ascii(bytesat(0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
|
|
if (file_magic == "kcap") {
|
|
set_display("Nested PAK");
|
|
parse_here("thqa_pak_le");
|
|
} else if (file_magic == "0dar") {
|
|
set_display("RAD Resource");
|
|
parse_here("thqa_rad_le");
|
|
} else if (file_magic == "0lmg") {
|
|
set_display("GML Script");
|
|
parse_here("thqa_gml_le");
|
|
} else if (file_magic == "bats") {
|
|
set_display("String Table");
|
|
parse_here("thqa_str_le");
|
|
} else if (file_magic == "FSB3" || file_magic == "FSB4" || file_magic == "FSB5") {
|
|
set_display("FSB Sound Bank");
|
|
parse_here("fmod_fsb_embedded");
|
|
} else {
|
|
data = read(size());
|
|
}
|
|
} else {
|
|
data = read(size());
|
|
}
|
|
}
|
|
|
|
// Child types for decompressed BE content
|
|
type thqa_pak_gml_be [display="GML Script"] byteorder BE
|
|
{
|
|
set_name("Decompressed GML");
|
|
file_magic = ascii(bytesat(0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
data_size = size();
|
|
|
|
ui("data_size", "Size");
|
|
parse_here("thqa_gml");
|
|
}
|
|
|
|
type thqa_pak_g4rc_be [display="G4RC Resource"] byteorder BE
|
|
{
|
|
set_name("Decompressed G4RC");
|
|
file_magic = ascii(bytesat(0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
data_size = size();
|
|
|
|
ui("data_size", "Size");
|
|
data = read(size());
|
|
}
|
|
|
|
type thqa_pak_rcb_be [display="RCB Texture"] byteorder BE
|
|
{
|
|
set_name("Decompressed RCB");
|
|
file_magic = ascii(bytesat(0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
data_size = size();
|
|
|
|
ui("data_size", "Size");
|
|
parse_here("thqa_rcb");
|
|
}
|
|
|
|
type thqa_pak_rsc_be [display="RSC Model"] byteorder BE
|
|
{
|
|
set_name("Decompressed RSC");
|
|
file_magic = ascii(bytesat(0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
data_size = size();
|
|
|
|
ui("data_size", "Size");
|
|
parse_here("thqa_rsc");
|
|
}
|
|
|
|
type thqa_pak_decomp_be [display="Decompressed Data"] byteorder BE
|
|
{
|
|
set_name(ctx_get("_thqa_decomp_name"));
|
|
file_magic = ascii(bytesat(0, 4));
|
|
|
|
ui("file_magic", "Magic");
|
|
data_size = size();
|
|
|
|
ui("data_size", "Size");
|
|
data = read(size());
|
|
}
|