mirror of
https://github.com/elliotnunn/newworld-rom.git
synced 2024-12-22 17:29:24 +00:00
190 lines
6.6 KiB
Python
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)
|