Update README.md
[sm64pc.git] / tools / unpak.py
blob7e515aba3e84d9b595fc5183d080f75c3719d3ae
1 #!/usr/bin/env python3
3 # requires Pillow and zstandard for Python
4 # on msys, install Pillow with
5 # pacman -S mingw-w64-x86_64-python-pillow
6 # zstd needs to be compiled, because the one in pip3 fails to do so:
7 # pacman -S mingw-w64-x86_64-python-setuptools mingw-w64-x86_64-zstd
8 # git clone https://github.com/indygreg/python-zstandard.git --recursive && cd python-zstandard
9 # python setup.py build_ext --external clean
10 # run like this:
11 # ./unpak.py pakfile.pak outdir tools/default_crcmap.txt
12 # any files not found in crcmap will go to the "nonmatching" folder
14 import os
15 import sys
16 import zstd
17 import struct
18 from PIL import Image
20 PAK_MAGIC = b'\x11\xde\x37\x10\x68\x75\xb6\xe8'
22 if len(sys.argv) < 3:
23 print('usage: unpak <input.pak> <outdir> [<crcmap>]')
24 sys.exit(1)
26 pakfname = sys.argv[1]
27 outpath = sys.argv[2]
28 mapfname = "crcmap.txt"
29 if len(sys.argv) > 3:
30 mapfname = sys.argv[3]
32 # load the CRC map
33 crcmap = dict()
34 try:
35 with open(mapfname, 'r') as f:
36 for line in f:
37 line = line.strip()
38 if line == '' or line[0] == '#':
39 continue
40 tok = line.split(',')
41 crcstr = tok[0].strip()
42 if crcstr.startswith('0x'):
43 crc = int(crcstr[2:], 16)
44 else:
45 crc = int(crcstr)
46 path = os.path.join(outpath, tok[1].strip())
47 if crc in crcmap:
48 crcmap[crc].append(path)
49 else:
50 crcmap[crc] = [path]
51 except OSError as e:
52 print('could not open {0}: {1}'.format(mapfname, e))
53 sys.exit(2)
54 except ValueError as e:
55 print('invalid integer in {0}: {1}'.format(mapfname, e))
56 sys.exit(2)
58 unmatchdir = os.path.join(outpath, "nonmatching")
59 if not os.path.exists(unmatchdir):
60 os.makedirs(unmatchdir)
62 # read the PAK
63 try:
64 texlist = []
65 with open(pakfname, "rb") as f:
66 magic = f.read(len(PAK_MAGIC))
67 if magic != PAK_MAGIC:
68 print('invalid magic in PAK ' + pakfname)
69 sys.exit(3)
70 texcount = int.from_bytes(f.read(8), byteorder='little')
71 print('reading {0} textures from {1}'.format(texcount, pakfname))
72 for i in range(texcount):
73 crc = int.from_bytes(f.read(4), byteorder='little')
74 size = int.from_bytes(f.read(4), byteorder='little')
75 offset = int.from_bytes(f.read(8), byteorder='little')
76 width = int.from_bytes(f.read(8), byteorder='little')
77 height = int.from_bytes(f.read(8), byteorder='little')
78 texlist.append((crc, size, offset, width, height))
79 for (crc, size, ofs, w, h) in texlist:
80 f.seek(ofs)
81 data = f.read(size)
82 img = Image.frombytes('RGBA', (w, h), zstd.decompress(data))
83 if crc in crcmap:
84 for path in crcmap[crc]:
85 [fpath, fname] = os.path.split(path)
86 if not os.path.exists(fpath):
87 os.makedirs(fpath)
88 img.save(path)
89 else:
90 print('unknown crc: {0:08x}'.format(crc))
91 path = os.path.join(unmatchdir, "{0:08x}.png".format(crc))
92 img.save(path)
93 except OSError as e:
94 print('could not open {0}: {1}'.format(pakfname, e))
95 sys.exit(3)