newworld-rom/scripts/prcltool.py

190 lines
6.6 KiB
Python

# Ripped from somewhere else.
# Can kind-of rip parcels.
# Needs neatening up.
import struct
from binascii import crc32
import lzss
import struct
class ParcelsArea:
MAGIC = b'prcl \x01\x00\x00\x00 \x00\x00\x00\x14 \x00\x00\x00\x14 \x00\x00\x00\x00'.replace(b' ', b'')
def __init__(self, bytes=None):
if bytes is not None:
if not bytes.startswith(self.MAGIC):
raise ValueError('Instead of a prcl magic number: %r' % bytes[:len(self.MAGIC)])
# Read all the structs in the prcl area!
prcls = ParcelStruct.scan(bytes=bytes, first_offset=len(self.MAGIC))
prcls = list(prcls)
self.prcls = prcls
for prcl in prcls:
for entry in prcl.entries:
tpl = (entry.load_offset, entry.load_len, entry.compress)
x = bytes[entry.load_offset : entry.load_offset+entry.load_len]
if entry.compress:
x = lzss.extract(x)
entry.bytes = x
else:
self.prcls = []
def __bytes__(self):
binaries_added = {} # (bytes, compress): (compressed_bytes, offset, cksum)
things = bytearray(self.MAGIC)
for prcl in self.prcls:
prcl_offset = len(things)
things.extend(bytes(len(prcl)))
for entry in prcl.entries:
dict_lookup = (entry.bytes, entry.compress)
try:
if not entry.backref_allowed: raise ValueError
final_bytes, bytes_offset, cksum = binaries_added[dict_lookup]
except:
# Need to use this binary!
final_bytes = entry.bytes
if entry.compress:
final_bytes = lzss.compress(final_bytes)
bytes_offset = len(things)
cksum = crc32(final_bytes)
things.extend(final_bytes)
binaries_added[dict_lookup] = final_bytes, bytes_offset, cksum
# Pad to 4-byte boundary. Apple's build tool left pad bytes uninitialised!
while len(things) % 4:
things.append(0x99)
# Tell this structure where it can find its binary data.
entry.uncompressed_len = len(entry.bytes)
entry.save_cksum = cksum if entry.should_checksum else 0
entry.save_offset = bytes_offset
entry.save_len = len(final_bytes)
# Then tell it where it can find the next one, if any
if prcl is self.prcls[-1]:
prcl.save_nextoffset = 0
else:
prcl.save_nextoffset = len(things)
things[prcl_offset:prcl_offset+len(prcl)] = bytes(prcl)
# So now all structs and all data are placed. Phew.
return bytes(things)
class ParcelStruct:
HEADER_FMT = '>I4s4I32s32s'
@classmethod
def scan(cls, bytes, first_offset):
offset = first_offset
while offset:
parcel = cls(bytes=bytes, offset=offset)
yield parcel
offset = parcel.load_nextoffset
def __init__(self, bytes=None, offset=None):
if bytes is not None:
load_nextoffset, fourcc, my_len, flags, entry_count, entry_len, name, compat = struct.unpack_from(self.HEADER_FMT, bytes, offset)
# Checks
# if entry_len != ParcelEntry.__len__():
# raise ValueError('Parcel entry of %d bytes, expected %d' % (entry_len, ParcelEntry.__len__()))
# if my_len != header_size + entry_count*entry_len:
# raise ValueError('Incorrect parcel header size field %d not %d' % (my_len, struct.calcsize(self.HEADER_FMT) + entry_count*entry_len))
# Semantics
self.fourcc = fourcc
self.name = name.rstrip(b'\0').decode('ascii')
self.compat = compat.rstrip(b'\0').decode('ascii')
self.flags = flags
len_of_entries = entry_count*entry_len
entry_offsets = range(offset + my_len - len_of_entries, offset + my_len, entry_len)
self.entries = [ParcelEntry(bytes[o : o+entry_len]) for o in entry_offsets]
# Ephemerides
self.load_nextoffset = load_nextoffset
else:
self.fourcc = b' '
self.name = ''
self.compat = ''
self.flags = 0
self.entries = []
def __len__(self):
return struct.calcsize(self.HEADER_FMT) + sum(len(e) for e in self.entries)
def __bytes__(self):
save_nextoffset = self.save_nextoffset
fourcc = self.fourcc
my_len = len(self)
flags = self.flags
entry_count = len(self.entries)
entry_len = ParcelEntry.__len__()
name = self.name.encode('ascii')
compat = self.compat.encode('ascii')
header = struct.pack(self.HEADER_FMT, save_nextoffset, fourcc, my_len, flags, entry_count, entry_len, name, compat)
entries = b''.join(bytes(e) for e in self.entries)
return header + entries
class ParcelEntry:
FMT = '>4sI4s4I32s'
def __init__(self, bytes=None):
if bytes is not None:
(fourcc, flags, compress, uncompressed_len, load_cksum, load_len, load_offset, name) = struct.unpack(self.FMT, bytes)
self.fourcc = fourcc
self.flags = flags
self.compress = (compress == b'lzss')
self.uncompressed_len = uncompressed_len
self.name = name.rstrip(b'\0').decode('ascii')
self.backref_allowed = False
self.should_checksum = True
# Ephemeral stuff
self.load_cksum = load_cksum
self.load_len = load_len
self.load_offset = load_offset
#else:
# self.fourcc = b' '
# self.flags = 0
# self.compress = False
# self.uncompressed_len = 0
# self.name = ''
def __bytes__(self):
fourcc = self.fourcc
flags = self.flags
compress = b'lzss' if self.compress else b'\0\0\0\0'
uncompressed_len = self.uncompressed_len
save_cksum = self.save_cksum
save_len = self.save_len
save_offset = self.save_offset
name = self.name.encode('ascii')
return struct.pack(self.FMT, fourcc, flags, compress, uncompressed_len, save_cksum, save_len, save_offset, name)
@classmethod
def __len__(cls):
return struct.calcsize(cls.FMT)