mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-12-21 00:29:33 +00:00
Preserve the Gibbly-bootinfo complex
This commit is contained in:
parent
27d6e2d77c
commit
188dc429de
@ -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.
|
||||
|
||||
|
1
setup.py
1
setup.py
@ -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'])],
|
||||
)
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
60
tbxi/cfrg_rsrc.py
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user