enablifier/enablify

154 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
from zlib import adler32
from subprocess import run, DEVNULL, PIPE
from sys import argv
from os.path import exists
from shutil import copyfile
import tempfile
MAGIC = b'Joy!peffpwpc'
RFORK = '/..namedfork/rsrc'
# def copyfile(a, b):
# run(['cp', a, b], check=True, stderr=DEVNULL)
def _get_info_size(b):
a, b, c = b.partition(b'constant info-size')
a, b, c = a.rpartition(b'h#')
return int(c, 16)
def _get_cfrg_resources(path, *args):
cfrg_derez = run(['DeRez', '-only', 'cfrg', *args, path], check=True, stdout=PIPE).stdout
line_groups = []
for l in cfrg_derez.split(b'\n'):
if not l: continue
if l.startswith(b'data'):
line_groups.append([l])
else:
line_groups[-1].append(l)
cfrg_tuples = [] # list of (firstline, lastline, binary)
for l in line_groups:
if not l[-1].startswith(b'}'): raise ValueError
accum = bytearray()
for dl in l:
if not dl.lstrip().startswith(b'$'): continue
_, _, h = dl.partition(b'$"')
h, _, _ = h.rpartition(b'"')
h = h.decode('ascii')
h = bytes.fromhex(h)
accum.extend(h)
cfrg_tuples.append((l[0], l[-1], accum))
return cfrg_tuples
def _save_cfrg_resources(path, the_resources):
savestr = bytearray()
for firstline, lastline, data in the_resources:
savestr.extend(firstline)
savestr.extend(b'\n')
savestr.extend(b'$"' + data.hex().encode('ascii') + b'"\n')
# for b in data:
# savestr.extend((' $"%02x"\n' % b).encode('ascii'))
savestr.extend(lastline)
savestr.extend(b'\n')
# run(['cat'], check=True, input=savestr)
with tempfile.NamedTemporaryFile(mode='wb') as f:
f.write(savestr)
f.flush()
run(['Rez', '-a', '-o', path, f.name], check=True)
# return (start, stop)
def _get_code_fragment_range(b):
NASTY = b'\r\\ h#'
info_size = _get_info_size(b)
the_start = b.find(MAGIC, info_size)
the_end = len(b)
if NASTY in b[-100:]:
the_end = b.rfind(NASTY)
return the_start, the_end
def _find_all(a_str, sub):
start = 0
while True:
start = a_str.find(sub, start)
if start == -1: return
yield start
start += len(sub) # use start += 1 to find overlapping matches
def _replace_int32_in_bytearray(b, x, y):
x = x.to_bytes(length=4, byteorder='big', signed=False)
y = y.to_bytes(length=4, byteorder='big', signed=False)
b[:] = b.replace(x, y)
def enablify(orig, dest):
with open(orig, 'rb') as f:
orig_df = f.read()
with open(dest, 'rb') as f:
dest_df = bytearray(f.read())
# trim dest data fork if it already has enabler-related junk at the end
dest_info_size = _get_info_size(dest_df)
if len(dest_df) > dest_info_size + 20:
cut_at = dest_df.find(MAGIC, dest_info_size)
del dest_df[cut_at:]
dest_needs_checksum = b'\r\\ h#' in dest_df[-20:]
old_offset, old_stop = _get_code_fragment_range(orig_df)
while len(dest_df) % 16:
dest_df.append(0)
new_offset = len(dest_df)
every_sub_offset = list(_find_all(orig_df[old_offset:old_stop], MAGIC))
the_resources = list(_get_cfrg_resources(orig))
for _, _, rsrc_data in the_resources:
for this_sub_offset in every_sub_offset:
# print('Replacing', hex(old_offset + this_sub_offset), 'with', hex(new_offset + this_sub_offset))
_replace_int32_in_bytearray(rsrc_data, old_offset + this_sub_offset, new_offset + this_sub_offset)
dest_df.extend(orig_df[old_offset:old_stop])
# re-checksum?
if dest_needs_checksum:
cksum = adler32(dest_df)
cksum_str = ('\r\\ h# %08X' % cksum).encode('ascii')
dest_df.extend(cksum_str)
with open(dest, 'wb') as f:
f.write(dest_df)
if exists(orig + '.r'):
# print('doing the rez thing')
run(['Rez', '-o', dest, orig + '.r'], check=True)
else:
copyfile(orig + RFORK, dest + RFORK)
_save_cfrg_resources(dest, the_resources)
if __name__ == '__main__':
orig, dest = argv[1:]
enablify(orig, dest)