mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-06-06 11:29:27 +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)
|
ROM images predating before the "SuperMario" ROM (Quadra 660AV/840AV)
|
||||||
are not supported, excluding most 68k Mac ROMs.
|
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
|
The `tbxi dump` format is likely to change. If you keep a collection of
|
||||||
dumped ROM images to peruse, re-dump them regularly.
|
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,
|
zip_safe=True,
|
||||||
packages=['tbxi'],
|
packages=['tbxi'],
|
||||||
|
install_requires=['macresources'],
|
||||||
entry_points=dict(console_scripts=['tbxi = tbxi.__main__:main']),
|
entry_points=dict(console_scripts=['tbxi = tbxi.__main__:main']),
|
||||||
ext_modules=[Extension('tbxi.fast_lzss', ['speedups/fast_lzss.c'])],
|
ext_modules=[Extension('tbxi.fast_lzss', ['speedups/fast_lzss.c'])],
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import sys
|
||||||
import os
|
import os
|
||||||
from os import path
|
from os import path
|
||||||
import shutil
|
import shutil
|
||||||
|
import macresources
|
||||||
|
|
||||||
from .slow_lzss import decompress
|
from .slow_lzss import decompress
|
||||||
|
|
||||||
|
@ -56,7 +57,34 @@ def main(args=None):
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
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':
|
elif command == 'build':
|
||||||
parser.add_argument('dir', metavar='<input-dir>', help='source directory')
|
parser.add_argument('dir', metavar='<input-dir>', help='source directory')
|
||||||
|
@ -67,7 +95,8 @@ def main(args=None):
|
||||||
|
|
||||||
data = dispatcher.build(args.dir)
|
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)
|
base, ext = path.splitext(args.output)
|
||||||
if ext.lower() == '.hqx':
|
if ext.lower() == '.hqx':
|
||||||
import binhex
|
import binhex
|
||||||
|
@ -77,14 +106,29 @@ def main(args=None):
|
||||||
finfo.Type = b'tbxi'
|
finfo.Type = b'tbxi'
|
||||||
finfo.Flags = 0
|
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(data)
|
||||||
bh.write_rsrc(b'')
|
bh.write_rsrc(rsrc)
|
||||||
bh.close()
|
bh.close()
|
||||||
|
|
||||||
return # do not write the usual way
|
return # do not write the usual way
|
||||||
|
|
||||||
else:
|
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:
|
with open(args.output + '.idump', 'wb') as f:
|
||||||
f.write(b'tbxichrp')
|
f.write(b'tbxichrp')
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from os import path
|
||||||
import re
|
import re
|
||||||
import zlib
|
import zlib
|
||||||
import sys
|
import sys
|
||||||
|
import macresources
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .fast_lzss import compress
|
from .fast_lzss import compress
|
||||||
|
@ -9,6 +10,7 @@ except ImportError:
|
||||||
from .slow_lzss import compress
|
from .slow_lzss import compress
|
||||||
|
|
||||||
from . import dispatcher
|
from . import dispatcher
|
||||||
|
from . import cfrg_rsrc
|
||||||
|
|
||||||
|
|
||||||
def append_checksum(binary):
|
def append_checksum(binary):
|
||||||
|
@ -100,4 +102,22 @@ def build(src):
|
||||||
|
|
||||||
if has_checksum: append_checksum(booter)
|
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
|
import os
|
||||||
from os import path
|
from os import path
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
import macresources
|
||||||
|
|
||||||
from .slow_lzss import decompress
|
from .slow_lzss import decompress
|
||||||
|
|
||||||
from . import dispatcher
|
from . import dispatcher
|
||||||
|
from . import cfrg_rsrc
|
||||||
|
|
||||||
|
|
||||||
|
# Special case: expects a (data, resource_list) tuple
|
||||||
def dump(binary, dest_dir):
|
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
|
if not binary.startswith(b'<CHRP-BOOT>'): raise dispatcher.WrongFormat
|
||||||
|
|
||||||
os.makedirs(dest_dir, exist_ok=True)
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
|
@ -48,3 +54,24 @@ def dump(binary, dest_dir):
|
||||||
parcels = decompress(parcels)
|
parcels = decompress(parcels)
|
||||||
|
|
||||||
dispatcher.dump(parcels, path.join(dest_dir, filename))
|
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:
|
for fmt in FORMATS:
|
||||||
mod = importlib.import_module('..%s_dump' % fmt, __name__)
|
mod = importlib.import_module('..%s_dump' % fmt, __name__)
|
||||||
try:
|
try:
|
||||||
|
arg = binary
|
||||||
|
if isinstance(arg, tuple) and fmt != 'bootinfo': arg = arg[0] # strip found resource fork
|
||||||
mod.dump(binary, dest_path)
|
mod.dump(binary, dest_path)
|
||||||
print(fmt)
|
print(fmt)
|
||||||
break
|
break
|
||||||
|
|
Loading…
Reference in New Issue
Block a user