diff --git a/kauai-ata.pef b/kauai-ata.pef new file mode 100644 index 0000000..1b95c24 Binary files /dev/null and b/kauai-ata.pef differ diff --git a/macmini.py b/macmini.py old mode 100644 new mode 100755 index 63f77b6..bcba6be --- a/macmini.py +++ b/macmini.py @@ -1,2 +1,219 @@ #!/usr/bin/env python3 +import patch_common + +from os import path +import shutil +import os +import struct + + +src, cleanup = patch_common.get_src(desc=''' +Boot the Mac mini. Works on Mac OS ROM v6.7 and later (Mac OS 9.1, late '01). +First, patches the boot script to (a) add PowerMac10,1/2 to the COMPATIBLE tag, +(b) pretend that the machine is a Cube and (c) add a prim-info property to the +PMU device. Second, patches the NanoKernel to prevent the CPU Plugin from +hanging when a THRM register access silently fails. Third, if necessary, patches +the Kauai ATA driver into old ROMs (pre-9.1). +''') + + +def sign_extend(value, bits): + sign_bit = 1 << (bits - 1) + return (value & (sign_bit - 1)) - (value & sign_bit) + + +def patch_nk_proc_table(orig): + try: + code = bytearray(orig) + + # find the processor flags table, and make our glorious G4 known... + # our reference point is the ConfigInfo-tyle 601 info, which comes right after + tbl = code.index(b'\x00\x00\x10\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x20\x00\x20\x00\x01\x00\x40\x00\x40\x00\x20\x00\x20\x00\x20\x00\x08\x00\x08\x01\x00\x00\x02') + num_entries = 0 + for i in reversed(range(0, tbl, 4)): + if code[i] and code[i+1]: + break + num_entries += 1 + + assert num_entries == 32 + tbl -= (1+1+4) * num_entries + print('NK PowerCall table patch: location @', hex(tbl)) + + pvr = 16 + 3 # 0x8003 + + code[tbl + pvr] = 0x23 # upper nib means use NAP bit, lower nib I can't remember + tbl += num_entries + code[tbl + pvr] = 2 # HID0_NHR_and_sleep + tbl += num_entries + struct.pack_into('>L', code, tbl + 4*pvr, 0x1F) # hasL2CR hasPLRUL1 hasTAU hasVMX hasMSSregs + + return bytes(code) + + except: + print('NK PowerCall table patch: patch failed') + return orig + + +# On the 7447a (or something), accesses to the THRM registers fail silently, +# causing the CPU Plugin to hang when asked to get the temp. Patch the NK to +# test for a non-THRM CPU, and return an error in this case. (The NK is involved +# because it invokes the CPUP fragment in supervisor mode in response to an +# MPCpuPlugin call.) + +def patch_nk_mpcpuplugin(orig): + try: + # For picking apart PPC stuff, turn it into a list of longs... + while len(orig) % 4: orig.append(b'\0') # first ensure 4-aligned + code = [x for (x,) in struct.iter_unpack('>L', orig)] + + # find syscall dispatch routine + for i in range(3, len(code)): + if code[i] == 0x92E80020: # stw r23, 0x20(r8) + if code[i-1] == 0x92E90020: # stw r23, 0x20(r9) + i -= 1 # ignore this annoying instruction + + if code[i-3] >> 16 == 0x3EE0 and code[i-2] >> 16 == 0x62F7 and code[i-1] == 0x7EF7CA14: + # lis r23, HI; ori r23, r23, LO; add r23, r23, r25 + mpdisp = ((code[i-3] & 0xFFFF) << 16) + (code[i-2] & 0xFFFF) + if code[mpdisp//4] == 0x7C3042A6: continue # FP hander has same offset in diff table + print('NK MPCpuPlugin patch: MPDispatch @', hex(mpdisp)) + break + elif code[i-1] >> 16 == 0x3AF9: # addi r23, r25, LO + mpdisp = code[i-1] & 0xFFFF + if code[mpdisp//4] == 0x7C3042A6: continue # FP hander has same offset in diff table + print('NK MPCpuPlugin patch: MPDispatch @', hex(mpdisp)) + break + + # find routine's table + for i in range(mpdisp//4, len(code)): + if code[i] & 0xFC000003 == 0x48000000: # short unconditional non-link jump to end of table + mpcnt = (code[i] & 0x3FFFFFF) // 4 - 1 + mptab = (i + 1) * 4 + print('NK MPCpuPlugin patch: MPDispatchTable @', hex(mptab)) + break + + # MP calls invoked via "li r0, NUMBER; sc", and this is that number: + kMPCPUPlugin = 46 + + if mpcnt > kMPCPUPlugin: + mpcpuplugin = code[mptab//4 + kMPCPUPlugin] + 4*kMPCPUPlugin # weird table + print('NK MPCpuPlugin patch: MPCpuPlugin @', hex(mpcpuplugin)) + + for i in range(mpcpuplugin//4, len(code)): + if code[i] & 0xFC000003 == 0x48000000: # short unconditional non-link jump to call return routine + returnproc = sign_extend(code[i] & 0x3FFFFFF, 26) + i*4 + print('NK MPCpuPlugin patch: return proc @', hex(returnproc)) + break + + # insert our new implementation into the table + code[mptab//4 + kMPCPUPlugin] = 4*len(code) - 4*kMPCPUPlugin + + print('NK MPCpuPlugin patch: new MPCpuPlugin @', hex(len(code)*4)) + + # place our new implementation at the end of the NK + code.extend([ + 0x2C03000C, # cmpwi r3, kGetProcessorTemp + 0x40820020, # bne passthru + 0x7D1F42A6, # mfpvr r8 + 0x5508001E, # rlwinm r8, r8, 0, 0xFFFF0000 + 0x3D208003, # lis r9, 0x8003 + 0x7C084800, # cmpw r8, r9 + 0x4082000C, # bne passthru + 0x3860CD2B, # li r3, kCantReportProcessorTemperatureErr + # place a return path branch here + # place a passthru branch here + ]) + + # add those two unconditional branches + for targ in [returnproc, mpcpuplugin]: + inst = targ - len(code)*4 + inst = inst & 0x3FFFFFF + inst |= 0x48000000 + code.append(inst) + + return b''.join(struct.pack('>L', x) for x in code) + + except: + print('NK MPCpuPlugin patch: patch failed') + return orig + + +FORTH = r''' +\ Hacks for Mac mini, should not affect other machines +" /" select-dev " model" active-package get-package-property 0= if + decode-string 2swap 2drop 2dup " PowerMac10,1" $= -rot " PowerMac10,2" $= or if + + \ Pretend to be a Power Mac G4 Cube + " /" select-dev + " PowerMac5,1" encode-string 2dup + " model" property + " MacRISC" encode-string encode+ + " MacRISC2" encode-string encode+ + " Power Macintosh" encode-string encode+ + " compatible" property + device-end + + \ Pretend to have a PowerPC 7445/55, actual PVR unaffected + " /cpus/PowerPC,G4@0" select-dev + 80010201 encode-int " cpu-version" property + device-end + + \ Set prim-info (for PwrMgr v2 in NativePowerMgrLib) + " via-pmu/power-mgt" select-dev + 000000ff encode-int + 0000002c encode-int encode+ + 00030d40 encode-int encode+ + 0001e705 encode-int encode+ + 00001400 encode-int encode+ + 00000000 encode-int encode+ + 0000260d encode-int encode+ + 46000270 encode-int encode+ + " prim-info" property + device-end + + then +then \ End of mini hacks +'''.strip() + +def patch_booter(text): + text = text.replace('\n', '\nPowerMac10,1 PowerMac10,2 ', 1) + text = text.replace('', '\n'+FORTH, 1) + return text + + +for (parent, folders, files) in os.walk(src): + folders.sort(); files.sort() # make it kinda deterministic + for filename in files: + full = path.join(src, parent, filename) + + if filename.startswith('NanoKernel'): + code = open(full, 'rb').read() + code = patch_nk_proc_table(code) + code = patch_nk_mpcpuplugin(code) + open(full, 'wb').write(code) + + elif filename == 'Bootscript': + text = open(full).read() + text = patch_booter(text) + open(full, 'w').write(text) + + elif filename == 'Parcelfile': + if not path.exists(path.join(src, parent, 'kauai-ata.pef')): + print('ROM lacks Kauai ATA driver (< ROM 9.1), patching it in') # the only known version + shutil.copy(path.join(path.dirname(__file__), 'kauai-ata.pef'), path.join(src, parent)) + + with open(full, 'a') as f: + f.write('prop flags=0x0000c a=kauai-ata b=ata\n') + f.write('\tndrv flags=0x00006 name=driver,AAPL,MacOS,PowerPC src=kauai-ata.pef.lzss\n\n') + + if path.exists(path.join(src, parent, 'MotherBoardHAL.pef')): + print('ROM has MotherBoardHAL (< ROM 6.7), therefore unlikely to work') + + elif filename == 'cicn_-20020': + print('Patching Happy Mac as tradition dictates. Credit to MacTron:') + print('http://macos9lives.com/smforum/index.php/topic,4354.msg30328.html#msg30328') + shutil.copyfile(path.join(path.dirname(__file__), 'mactron.cicn'), full) + + +cleanup() diff --git a/mactron.cicn b/mactron.cicn new file mode 100644 index 0000000..9dd013f Binary files /dev/null and b/mactron.cicn differ diff --git a/patch_common.py b/patch_common.py new file mode 100644 index 0000000..7525751 --- /dev/null +++ b/patch_common.py @@ -0,0 +1,46 @@ +from os import path +from subprocess import run, DEVNULL +import argparse +import shutil +import sys +import tempfile + +def get_src(desc=None): + parser = argparse.ArgumentParser(description=desc) + + parser.add_argument('src', action='store', help='Original (ROM or dumped dir)') + parser.add_argument('-o', action='store', help='New') + + args = parser.parse_args() + + if not path.exists(args.src): + print('File not found', file=sys.stderr); sys.exit(1) + + if not path.isdir(args.src) and args.o is None: + print('Cannot edit a ROM in place, use -o', file=sys.stderr); sys.exit(1) + + if path.isdir(args.src): + def cleanup(): + pass + + if args.o: + try: + shutil.rmtree(args.o) + except FileNotFoundError: + pass + shutil.copytree(args.src, args.o) + src = args.o + else: + src = args.src + + else: + tmp = tempfile.mkdtemp() + src = path.join(tmp, 'editrom') + + run(['python3', '-m', 'tbxi', 'dump', '-o', src, args.src], check=True, stdout=DEVNULL) + + def cleanup(): + run(['python3', '-m', 'tbxi', 'build', '-o', args.o, src], check=True, stdout=DEVNULL) + shutil.rmtree(tmp) + + return src, cleanup