~aleteoryx/mtg-pac

1803f3a710790afa1caeea4296e0328656270059 — Aleteoryx 3 months ago 0fd560c master
kewl
3 files changed, 97 insertions(+), 7 deletions(-)

A README.md
M pvrt.tcl
R {extract => unpac}.tcl
A README.md => README.md +83 -0
@@ 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

M pvrt.tcl => pvrt.tcl +6 -3
@@ 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


R extract.tcl => unpac.tcl +8 -4
@@ 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 {