#!/usr/bin/env python3 from os import path, listdir, environ, getcwd, chdir from sys import argv, stderr import datetime from subprocess import run, PIPE, DEVNULL import tempfile from shutil import copyfile BASILISK_PREFS = """ extfs screen win/640/480 seriala serialb /dev/null udptunnel false udpport 6066 bootdrive 0 bootdriver 0 ramsize 33554432 frameskip 0 modelid 5 cpu 3 fpu true nocdrom false nosound false noclipconversion false nogui false jit true jitfpu true jitdebug false jitcachesize 8192 jitlazyflush true jitinline true keyboardtype 5 keycodes false mousewheelmode 1 mousewheellines 3 dsp /dev/dsp mixer /dev/mixer ignoresegv false idlewait true """ SHEEPSHAVER_PREFS = """ extfs screen win/640/480 windowmodes 0 screenmodes 0 seriala serialb /dev/null bootdrive 0 bootdriver 0 ramsize 67108864 frameskip 0 gfxaccel true nocdrom false nonet false nosound false nogui false noclipconversion false ignoresegv false jit true jit68k false keyboardtype 5 ether keycodes false keycodefile mousewheelmode 1 mousewheellines 3 dsp /dev/dsp mixer /dev/mixer ignoresegv false idlewait true """ def h_topdir(): rc = 0 while rc == 0: rc = run(['hcd','::'], stderr=DEVNULL).returncode def climb(frm): yield frm l = frm while 1: t = path.dirname(l) if t == l: break l = t yield l def rel_path_to_hfs(i): if '/' not in i: return i if ':' in i: return i if i in '..': i = i + '/' if i.startswith('./'): i = ':' + i[2:] i = i.replace('../', ':') i = i.replace('/', ':') if not i.startswith(':'): i = ':' + i def rel_path_to_hfs(i): if not i.startswith('/') and ('/' in i or i in '..'): # do all this stuff: pass else: return i oi = i if i in '..': i = i + '/' if '/' not in i: return i if ':' in i: return i if i.startswith('./'): i = ':' + i[2:] i = i.replace('../', '::') i = i.replace('/', ':') if not i.startswith(':'): i = ':' + i try: if path.isdir(oi) and not i.endswith(':'): i = i + ':' except FileNotFoundError: pass return i my_name, *cmds = argv # Interpret leading '-options': which_emulator = 'vmac' while cmds and cmds[0].startswith('-'): if cmds[0] == '-v': which_emulator = 'vmac' elif cmds[0] == '-b': which_emulator = 'basilisk' elif cmds[0] == '-s': which_emulator = 'sheepshaver' else: raise ValueError('Unknown option: %s' % cmds[0]) cmds = cmds[1:] # Convert paths of things that actually exist: cmds = [rel_path_to_hfs(x) for x in cmds] # Choose our working directory... srcimage = 'SourceForEmulator.dmg' mpw_cd_cmd = 'Src:' try: newcwd = next(d for d in climb(getcwd()) if path.exists(path.join(d, srcimage))) except StopIteration: newcwd = getcwd() if newcwd != getcwd(): rel = path.relpath(getcwd(), newcwd) for c in rel.split('/'): mpw_cd_cmd += c + ':' chdir(newcwd) cmdline = ' '.join(cmds) scriptfolder = path.dirname(path.abspath(__file__)) vmac_app = path.join(scriptfolder, 'Mini vMac 9590.app') # requested for later: -t mc64 -m II -sound 0 -speed a -bg 1 -as 0 vmac_exec = path.join(vmac_app, 'Contents', 'MacOS') vmac_exec = path.join(vmac_exec, next(l for l in listdir(vmac_exec) if not l.startswith('.'))) basilisk_app = path.join(scriptfolder, 'BasiliskII.app') basilisk_exec = path.join(basilisk_app, 'Contents', 'MacOS') basilisk_exec = path.join(basilisk_exec, next(l for l in listdir(basilisk_exec) if not l.startswith('.'))) sheepshaver_app = path.join(scriptfolder, 'SheepShaver.app') sheepshaver_exec = path.join(sheepshaver_app, 'Contents', 'MacOS') sheepshaver_exec = path.join(sheepshaver_exec, next(l for l in listdir(sheepshaver_exec) if not l.startswith('.'))) bootimg = path.join(scriptfolder, 'MPW-VM.dmg') backupimg = path.join(scriptfolder, 'MPW-VM-KnownGood.dmg') hsync = path.join(scriptfolder, 'hsync') hsyncback = path.join(scriptfolder, 'hsyncback') if not path.exists(bootimg): print('Creating new scratch boot disk.') copyfile(backupimg, bootimg) run(['xattr', '-w', 'com.apple.metadata:com_apple_backup_excludeItem', 'com.apple.backupd', bootimg]) if cmds: mpw_cmd = u"""# This is an auto-generated MPW script! SetDirectory '%s' Set EmpwReturned 0 "{Boot}AutoGenInner" > "{Boot}StdOut" \u2265 "{Boot}StdErr" || Set EmpwReturned {Status} Echo {EmpwReturned} > "{Boot}Return" Move -y "{Boot}AutoGenInner" "{Boot}Trash:" Move -y "{__Startup__i}" "{Boot}Trash:" ShutDown -y """ % (mpw_cd_cmd) else: mpw_cmd = """# This is an auto-generated MPW script! SetDirectory '%s' Move -y "{__Startup__i}" "{Boot}Trash:" """ % mpw_cd_cmd mpw_cmd = mpw_cmd.replace('\n','\r') with open('/tmp/AutoGen', 'wb') as f: f.write(mpw_cmd.encode('mac_roman')) tmp_path = f.name if cmds: with open('/tmp/AutoGenInner', 'wb') as f: f.write(cmdline.encode('mac_roman')) tmp_path_inner = f.name run([hsync], check=True) run(['SetFile', '-t', 'TEXT', '-c', 'MPS ', tmp_path], check=True) run(['macbinary', 'encode', '--overwrite', '-o', tmp_path+'.bin', tmp_path], check=True) if cmds: run(['SetFile', '-t', 'TEXT', '-c', 'MPS ', tmp_path_inner], check=True) run(['macbinary', 'encode', '--overwrite', '-o', tmp_path_inner+'.bin', tmp_path_inner], check=True) run(['hmount', bootimg], check=True, stdout=DEVNULL) h_topdir() run(['hcopy', '-m', tmp_path+'.bin', ':MPW:Startup Items:'], check=True) if cmds: run(['hcopy', '-m', tmp_path_inner+'.bin', ':AutoGenInner'], check=True) run(['humount'], check=True) if which_emulator == 'vmac': # http://www.gryphel.com/c/minivmac/osx_note.html # Disable Path Randomization run(['xattr', '-cr', vmac_app], check=True) run([vmac_exec, bootimg, srcimage], check=True) elif which_emulator == 'basilisk': with tempfile.TemporaryDirectory(prefix='/tmp/') as d: with open(path.join(d, '.basilisk_ii_prefs'), 'w') as f: f.write(BASILISK_PREFS) print('disk', bootimg, file=f) print('disk', srcimage, file=f) print('rom', path.join(scriptfolder, 'MacIIci.ROM'), file=f) newenviron = dict(environ, HOME=d.rstrip('/')) run([basilisk_exec], env=newenviron, stdout=DEVNULL, stderr=DEVNULL, check=True) elif which_emulator == 'sheepshaver': with tempfile.TemporaryDirectory(prefix='/tmp/') as d: with open(path.join(d, '.sheepshaver_prefs'), 'w') as f: f.write(SHEEPSHAVER_PREFS) print('disk', bootimg, file=f) print('disk', srcimage, file=f) print('rom', path.join(scriptfolder, 'PowerMac.ROM'), file=f) newenviron = dict(environ, HOME=d.rstrip('/')) run(['arch', '-32', sheepshaver_exec], env=newenviron, stdout=DEVNULL, stderr=DEVNULL, check=True) run([hsyncback], check=True) if cmds: run(['hmount', bootimg], check=True, stdout=DEVNULL) run(['hcopy', '-t', ':StdOut', '/tmp/StdOut'], check=True) run(['hcopy', '-t', ':StdErr', '/tmp/StdErr'], check=True) run(['hcopy', '-t', ':Return', '/tmp/Return'], check=True) run(['humount'], check=True) with open('/tmp/StdOut', 'rb') as f: print(f.read().decode('mac_roman'), end='') with open('/tmp/StdErr', 'rb') as f: print(f.read().decode('mac_roman'), end='', file=stderr) with open('/tmp/Return', 'rb') as f: exit(int(f.read()))