326 lines
6.9 KiB
Python
Executable File
326 lines
6.9 KiB
Python
Executable File
#!/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()))
|