// 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()); }