Preserve the Gibbly-bootinfo complex

This commit is contained in:
Elliot Nunn 2019-08-02 14:32:00 +08:00
parent 27d6e2d77c
commit 188dc429de
7 changed files with 159 additions and 11 deletions

View File

@ -54,12 +54,6 @@ original build process.
ROM images predating before the "SuperMario" ROM (Quadra 660AV/840AV)
are not supported, excluding most 68k Mac ROMs.
The resource fork of a NewWorld ROM image is ignored, despite
containting a System Enabler that is paired with the data fork contents.
Simply copying the resource fork back will cause a crash, because the
'cfrg' resource contains offests to some PowerPC binaries at the end of
the data fork.
The `tbxi dump` format is likely to change. If you keep a collection of
dumped ROM images to peruse, re-dump them regularly.

View File

@ -24,6 +24,7 @@ setup_args = dict(
],
zip_safe=True,
packages=['tbxi'],
install_requires=['macresources'],
entry_points=dict(console_scripts=['tbxi = tbxi.__main__:main']),
ext_modules=[Extension('tbxi.fast_lzss', ['speedups/fast_lzss.c'])],
)

View File

@ -6,6 +6,7 @@ import sys
import os
from os import path
import shutil
import macresources
from .slow_lzss import decompress
@ -56,7 +57,34 @@ def main(args=None):
except FileNotFoundError:
pass
dispatcher.dump(f.read(), args.output, toplevel=True)
base, ext = path.splitext(args.file)
if ext.lower() == '.hqx':
import binhex
hb = binhex.HexBin(f)
data = hb.read()
rsrc = list(macresources.parse_file(hb.read_rsrc()))
else:
data = f.read()
rsrc = []
if not rsrc:
try:
with open(args.file + '.rdump', 'rb') as f:
rsrc = list(macresources.parse_rez_code(f.read()))
except FileNotFoundError:
pass
if not rsrc:
try:
with open(args.file + '/..namedfork/rsrc', 'rb') as f:
rsrc = list(macresources.parse_file(f.read()))
except FileNotFoundError:
pass
tpl = (data, rsrc)
dispatcher.dump(tpl, args.output, toplevel=True)
elif command == 'build':
parser.add_argument('dir', metavar='<input-dir>', help='source directory')
@ -67,7 +95,8 @@ def main(args=None):
data = dispatcher.build(args.dir)
if data.startswith(b'<CHRP-BOOT>'):
if isinstance(data, tuple):
data, rsrc = data # unpack the resource list from the data fork
base, ext = path.splitext(args.output)
if ext.lower() == '.hqx':
import binhex
@ -77,14 +106,29 @@ def main(args=None):
finfo.Type = b'tbxi'
finfo.Flags = 0
bh = binhex.BinHex(('Mac OS ROM', finfo, len(data), 0), args.output)
# Special-casing for no-resource-fork
rsrc = macresources.make_file(rsrc) if rsrc else b''
bh = binhex.BinHex(('Mac OS ROM', finfo, len(data), len(rsrc)), args.output)
bh.write(data)
bh.write_rsrc(b'')
bh.write_rsrc(rsrc)
bh.close()
return # do not write the usual way
else:
rsrc = macresources.make_rez_code(rsrc, ascii_clean=True)
# Special-casing for no-resource-fork
if rsrc:
with open(args.output + '.rdump', 'wb') as f:
f.write(rsrc)
else:
try:
os.remove(args.output + '.rdump')
except FileNotFoundError:
pass
with open(args.output + '.idump', 'wb') as f:
f.write(b'tbxichrp')

View File

@ -2,6 +2,7 @@ from os import path
import re
import zlib
import sys
import macresources
try:
from .fast_lzss import compress
@ -9,6 +10,7 @@ except ImportError:
from .slow_lzss import compress
from . import dispatcher
from . import cfrg_rsrc
def append_checksum(binary):
@ -100,4 +102,22 @@ def build(src):
if has_checksum: append_checksum(booter)
return bytes(booter)
# Add a System Enabler (or even just 'vers' information)
rsrcfork = []
try:
datafork = open(path.join(src, 'SysEnabler'), 'rb').read()
rsrcfork = list(macresources.parse_rez_code(open(path.join(src, 'SysEnabler.rdump'), 'rb').read()))
while len(booter) % 16: booter.append(0)
delta = len(booter)
booter.extend(datafork)
if len(datafork) > 0 and has_checksum: append_checksum(booter)
for r in rsrcfork:
if r.type == b'cfrg':
r.data = cfrg_rsrc.adjust_dfrkoffset_fields(r.data, delta)
except FileNotFoundError:
pass
return bytes(booter), rsrcfork

View File

@ -1,13 +1,19 @@
import os
from os import path
import re
import sys
import macresources
from .slow_lzss import decompress
from . import dispatcher
from . import cfrg_rsrc
# Special case: expects a (data, resource_list) tuple
def dump(binary, dest_dir):
if not isinstance(binary, tuple): raise dispatcher.WrongFormat
binary, rsrc = binary
if not binary.startswith(b'<CHRP-BOOT>'): raise dispatcher.WrongFormat
os.makedirs(dest_dir, exist_ok=True)
@ -48,3 +54,24 @@ def dump(binary, dest_dir):
parcels = decompress(parcels)
dispatcher.dump(parcels, path.join(dest_dir, filename))
# Lastly, dump the System Enabler (if present and rsrc fork not stripped)
if rsrc:
cfrgs = [r for r in rsrc if r.type == b'cfrg']
start, stop = cfrg_rsrc.get_dfrk_range([c.data for c in cfrgs], len(binary))
for c in cfrgs:
c.data = cfrg_rsrc.adjust_dfrkoffset_fields(c.data, -start)
with open(path.join(dest_dir, 'SysEnabler'), 'wb') as f:
f.write(binary[start:stop])
with open(path.join(dest_dir, 'SysEnabler.rdump'), 'wb') as f:
f.write(macresources.make_rez_code(rsrc, ascii_clean=True))
with open(path.join(dest_dir, 'SysEnabler.idump'), 'wb') as f:
f.write(b'gblyMACS')
elif b'Joy!' in binary[other_offset+other_size:]:
print('Resource fork missing, ignoring orphaned data fork PEFs', file=sys.stderr)

60
tbxi/cfrg_rsrc.py Normal file
View File

@ -0,0 +1,60 @@
# Routines to fiddle with 'cfrg' (Code Fragment) metadata resources
# These are required only because the code fragments
# referenced by a cfrg are usually at specific offsets
# in a data fork, rather than in a resource. When we
# manipulate a data fork, any corresponding cfrg must be
# adjusted accordingly. This un-Mac-like scheme originated
# as a way to allow code fragments to be memory-mapped,
# which is impossible in a frequently-repacked resource fork.
import struct
# Internal: where are the fields that must be edited?
def get_dfrkoffset_field_positions(cfrg):
# old-style cfrg only, seems to work fine...
entry_cnt, = struct.unpack_from('>L', cfrg, 28)
ctr = 32
for i in range(entry_cnt):
if len(cfrg) < ctr + 43: break
if cfrg[ctr + 23] == 1: # kDataForkCFragLocator
yield ctr + 24
ctr += 42 + 1 + cfrg[ctr + 42]
while ctr % 4: ctr += 1
# Tell this resource that you moved the PEFs that it references in the data fork
def adjust_dfrkoffset_fields(cfrg, delta):
cfrg = bytearray(cfrg)
for field in get_dfrkoffset_field_positions(cfrg):
ofs, = struct.unpack_from('>L', cfrg, field)
ofs += delta
struct.pack_into('>L', cfrg, field, ofs)
return bytes(cfrg)
# Get the (start, stop) offset range of PEFs in the data fork
def get_dfrk_range(cfrg_list, dfrk_len):
left = dfrk_len
right = 0
for cfrg in cfrg_list:
for field in get_dfrkoffset_field_positions(cfrg):
my_left, = struct.unpack_from('>L', cfrg, field)
left = min(my_left, left)
my_right, = struct.unpack_from('>L', cfrg, field + 4)
if my_right == 0:
right = dfrk_len
else:
right = max(right, my_left + my_right)
return left, right

View File

@ -60,6 +60,8 @@ def dump(binary, dest_path, toplevel=False):
for fmt in FORMATS:
mod = importlib.import_module('..%s_dump' % fmt, __name__)
try:
arg = binary
if isinstance(arg, tuple) and fmt != 'bootinfo': arg = arg[0] # strip found resource fork
mod.dump(binary, dest_path)
print(fmt)
break