From 1803f3a710790afa1caeea4296e0328656270059 Mon Sep 17 00:00:00 2001 From: Aleteoryx Date: Tue, 16 Jun 2026 13:47:14 -0400 Subject: [PATCH] kewl --- README.md | 83 ++++++++++++++++++++++++++++++++++++++++ pvrt.tcl | 9 +++-- extract.tcl => unpac.tcl | 12 ++++-- 3 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 README.md rename extract.tcl => unpac.tcl (86%) diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..55ec6ea555f153d6c563fb12fe0c1b35b3037a1f --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +script to extract the .PAC asset bundle format, present in (at least) +magic the gathering for dreamcast. it also converts the PVRT textures +to PNGs + +# usage: + +install tcl and imagemagick. in particular, make sure `convert` is +present in $PATH. if you're on a unix, installing tcl should be +trivial. on windows, it is available in a package called "magicsplat". +YMMV. + +to run it, open a terminal and enter + +``` +$ ./unpac.tcl ./path/to/bundle.pac ./path/to/output/ +``` + +this may take a while for a PAC with large images, as the PVRT->PNG +decoder is largely implemented in tcl. optimization patches welcome. + +this will output a directory structure like: + +``` +./path/to/output/ + xxxx.pvr - decompressed powervr texture image + xxxx.png - for each .pvr, a matching .png is created. + + xxxx.njcm - ninja chunk model, raw 3d model data + + xxxx.njtl - ninja texlist, texture mapping info for a model + + xxxx.nmdm - ninja motion, animation + + xxxx.bin - unknown decompressed record type + xxxx.rec - unknown record type +``` + +the PVRT converter is incomplete, and only supports the pixel/data +modes present in magic the gathering. an unsupported PVRT will not be +converted, but you can apply an external tool to it if need be. + +# details: + +.PAC is a pretty simple bundle format. it consists of a header, and a +set of concatenated records, where each record begins with a 4-byte +prefix specifying its type. it is a little-endian format. + +``` +Header Format (all values are 4 bytes): + + +----------------+ + 0x00 | "PAC\0" magic | + +----------------+ + 0x04 | # of records | + +----------------+ + 0x08 | data start pos | + +----------------+ + 0x0c | record 1 end | + 0x10 | record 2 end | + | ... | + 0x?? | record # end | + +----------------+ +``` + +the first record is data start bytes into the file, from the absolute +beginning. it ends at record 1 end, and record 2 starts immediately +after. the last entry in the record end table is the length of the +file. + +4 record types are known: + +- `NZIP`, rest of record is deflate compressed data +- `NJCM`, ninja chunk model +- `NJTL`, ninja texlist +- `NMDM`, ninja motion + +NZIP appears to be for storing PVRT images. the other types are +well-known dreamcast sdk formats + +# license: + +if you pinky swear not to sue me for anything I pinky swear not to sue +you for anything. not like I have lawyer money, anyway diff --git a/pvrt.tcl b/pvrt.tcl index 46681a6c4048cb172f88b57949d15c4e76fbd23d..95ab6c74d86b64cd12ffa438f6c934851583a4cc 100644 --- a/pvrt.tcl +++ b/pvrt.tcl @@ -204,9 +204,8 @@ proc ::pvrt2tga::convert {src dst} { seek $sfp 16 } if {[read $sfp 4] != "PVRT"} { - puts "$src not PVRT" close $sfp - return + return -code error "missing PVRT magic" } binary scan [read $sfp 12] iccsss len pfmt dfmt _ w h @@ -216,7 +215,11 @@ proc ::pvrt2tga::convert {src dst} { set pdec [pfmt::lookup $pfmt] set ddec [dfmt::lookup $dfmt] -# puts "$src $dst $pdec $ddec $w $h" + if {$pdec == ""} { + return -code error "unsupported pixel format: $pfmt" + } elseif {$ddec == ""} { + return -code error "unsupported data format: $dfmt" + } $ddec $pdec srcbuf dstbuf $w $h diff --git a/extract.tcl b/unpac.tcl similarity index 86% rename from extract.tcl rename to unpac.tcl index 99335cf9c15a5506be8d214c27ffa10cad361179..5f9e36add326de54f75ae9d0f928da372d74322e 100755 --- a/extract.tcl +++ b/unpac.tcl @@ -67,10 +67,14 @@ foreach endpt $endtab { close $fp2 if {$ext == "pvr"} { - pvrt2tga::convert ${filebase}.${ext} ${filebase}.tga - exec -- convert ${filebase}.tga ${filebase}.png - file delete ${filebase}.tga - puts "${file}: record $id (decompressed): Converted to PNG" + if {[catch { + pvrt2tga::convert ${filebase}.${ext} ${filebase}.tga + exec -- convert ${filebase}.tga ${filebase}.png + file delete ${filebase}.tga + puts "${file}: record $id (decompressed): Converted to PNG" + } why]} { + puts "${file}: record $id (decompressed): PNG Conversion Failed: $why" + } } } NJCM {