mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-06-28 09:30:13 +00:00
208 lines
5.8 KiB
Python
208 lines
5.8 KiB
Python
# Some scrounged code to give name/version suggestions for NDRVs
|
|
|
|
|
|
import struct
|
|
|
|
|
|
MAGIC = b'Joy!peff'
|
|
|
|
|
|
class PEF:
|
|
CONT_HEAD_FMT = '>4s4s4s5I2HI'
|
|
CONT_HEAD_LEN = struct.calcsize(CONT_HEAD_FMT)
|
|
|
|
SEC_HEAD_FMT = '>i5I4B'
|
|
SEC_HED_LEN = struct.calcsize(SEC_HEAD_FMT)
|
|
|
|
def __init__(self, data):
|
|
if not data.startswith(MAGIC): raise ValueError('not a pef')
|
|
|
|
(magic, fourcc, arch, ver,
|
|
timestamp, old_def_ver, old_imp_ver, cur_ver,
|
|
sec_count, inst_sec_count, reserv) = struct.unpack_from(self.CONT_HEAD_FMT, data)
|
|
|
|
sec_earliest = len(data)
|
|
sec_latest = 0
|
|
|
|
self.sections = []
|
|
self.sectypes = []
|
|
self.headeroffsets = []
|
|
|
|
self.code = None
|
|
|
|
for i in range(sec_count):
|
|
sh_offset = self.CONT_HEAD_LEN + self.SEC_HED_LEN*i
|
|
|
|
(sectionName, sectionAddress, execSize,
|
|
initSize, rawSize, containerOffset,
|
|
regionKind, shareKind, alignment, reserved) = struct.unpack_from(self.SEC_HEAD_FMT, data, sh_offset)
|
|
|
|
the_sec = data[containerOffset : containerOffset + rawSize]
|
|
|
|
if regionKind == 0 and execSize == initSize == rawSize:
|
|
the_sec = bytearray(the_sec)
|
|
self.code = the_sec
|
|
|
|
self.sections.append(the_sec)
|
|
self.sectypes.append(regionKind)
|
|
self.headeroffsets.append(sh_offset)
|
|
|
|
sec_earliest = min(sec_earliest, containerOffset)
|
|
sec_latest = max(sec_latest, containerOffset + rawSize)
|
|
|
|
if any(data[sec_latest:]):
|
|
print('nonzero trailing data from', hex(sec_latest), 'to', hex(len(data)), ' ... will cause incorrect output')
|
|
|
|
self.padmult = 1
|
|
while len(data) % (self.padmult * 2) == 0:
|
|
self.padmult *= 2
|
|
|
|
self.header = data[:sec_earliest]
|
|
|
|
def __bytes__(self):
|
|
accum = bytearray(self.header)
|
|
|
|
for i in range(len(self.sections)):
|
|
the_sec = self.sections[i]
|
|
hoff = self.headeroffsets[i]
|
|
|
|
while len(accum) % 16:
|
|
accum.append(0)
|
|
|
|
new_off = len(accum)
|
|
new_len = len(the_sec)
|
|
|
|
accum.extend(the_sec)
|
|
|
|
struct.pack_into('>I', accum, hoff + 20, new_off)
|
|
|
|
if the_sec is self.code:
|
|
for i in range(8, 20, 4):
|
|
struct.pack_into('>I', accum, hoff + i, new_len)
|
|
|
|
while len(accum) % self.padmult != 0:
|
|
accum.extend(b'\x00')
|
|
|
|
return bytes(accum)
|
|
|
|
|
|
def pidata(packed):
|
|
def pullarg(from_iter):
|
|
arg = 0
|
|
for i in range(4):
|
|
cont = next(from_iter)
|
|
arg <<= 7
|
|
arg |= cont & 0x7f
|
|
if not (cont & 0x80): break
|
|
else:
|
|
raise ValueError('arg spread over too many bytes')
|
|
return arg
|
|
|
|
packed = iter(packed)
|
|
unpacked = bytearray()
|
|
|
|
for b in packed:
|
|
opcode = b >> 5
|
|
arg = b & 0b11111 or pullarg(packed)
|
|
|
|
if opcode == 0b000: # zero
|
|
count = arg
|
|
unpacked.extend(b'\0' * count)
|
|
|
|
elif opcode == 0b001: # blockCopy
|
|
blockSize = arg
|
|
for i in range(blockSize):
|
|
unpacked.append(next(packed))
|
|
|
|
elif opcode == 0b010: # repeatedBlock
|
|
blockSize = arg
|
|
repeatCount = pullarg(packed) + 1
|
|
rawData = bytes(next(packed) for n in range(blockSize))
|
|
for n in range(repeatCount):
|
|
unpacked.extend(rawData)
|
|
|
|
elif opcode == 0b011 or opcode == 0b100: # interleaveRepeatBlockWithBlockCopy
|
|
commonSize = arg # or interleaveRepeatBlockWithZero
|
|
customSize = pullarg(packed)
|
|
repeatCount = pullarg(packed)
|
|
|
|
if opcode == 0b011:
|
|
commonData = bytes(next(packed) for n in range(commonSize))
|
|
else:
|
|
commonData = b'\0' * commonSize
|
|
|
|
for i in range(repeatCount):
|
|
unpacked.extend(commonData)
|
|
for j in range(customSize):
|
|
unpacked.append(next(packed))
|
|
unpacked.extend(commonData)
|
|
|
|
else:
|
|
raise ValueError('unknown pidata opcode/arg %s/%d' % (bin(opcode), arg))
|
|
return
|
|
|
|
return bytes(unpacked)
|
|
|
|
|
|
def parse_version(num):
|
|
maj, minbug, stage, unreleased = num.to_bytes(4, byteorder='big')
|
|
|
|
maj = '%x' % maj
|
|
minor, bugfix = '%02x' % minbug
|
|
|
|
if stage == 0x80:
|
|
stage = 'f'
|
|
elif stage == 0x60:
|
|
stage = 'b'
|
|
elif stage == 0x40:
|
|
stage = 'a'
|
|
elif stage == 0x20:
|
|
stage = 'd'
|
|
else:
|
|
stage = '?'
|
|
|
|
unreleased = '%0x' % unreleased
|
|
|
|
vers = maj + '.' + minor
|
|
|
|
if bugfix != '0':
|
|
vers += '.' + bugfix
|
|
|
|
if (stage, unreleased) != ('f', '0'):
|
|
vers += stage + unreleased
|
|
|
|
return vers
|
|
|
|
|
|
def pstring_or_cstring(s):
|
|
plen = s[0]
|
|
pstr = s[1:][:plen]
|
|
cstr = s.rstrip(b'\0')
|
|
if b'\0' in pstr or plen + 1 > len(s):
|
|
return cstr
|
|
else:
|
|
return pstr
|
|
|
|
|
|
def suggest_name(pef):
|
|
if not pef.startswith(b'Joy!peff'): return
|
|
|
|
try:
|
|
pef = PEF(pef)
|
|
|
|
for sectype, section in zip(pef.sectypes, pef.sections):
|
|
if sectype == 2: section = pidata(section)
|
|
|
|
if section and sectype in (1, 2):
|
|
hdr_ofs = section.find(b'mtej')
|
|
if hdr_ofs != -1:
|
|
sig, strvers, devnam, drvvers = struct.unpack_from('>4s L 32s L', section, hdr_ofs)
|
|
|
|
# devnam *should* be a 32-byte pascal string, but not if someone forgot the "\p"...
|
|
devnam = pstring_or_cstring(devnam)
|
|
|
|
sugg = devnam.decode('mac_roman') + '-' + parse_version(drvvers)
|
|
return sugg
|
|
except:
|
|
pass # do not complain about corrupt PEFs
|