mirror of
https://github.com/elliotnunn/empw.git
synced 2025-02-17 14:30:54 +00:00
Initial commit
This commit is contained in:
commit
616e75d59a
BIN
MPW-VM-KnownGood.dmg
Normal file
BIN
MPW-VM-KnownGood.dmg
Normal file
Binary file not shown.
41
Mini vMac 9590.app/Contents/Info.plist
Normal file
41
Mini vMac 9590.app/Contents/Info.plist
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>****</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>mnvm9590</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>mnvm9590-3.5.8-mc64, Copyright 2017 maintained by Paul C. Pratt.</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>ICONAPPO.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.gryphel.minivmac</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Mini vMac</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.5.8</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3.5.8</string>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
BIN
Mini vMac 9590.app/Contents/MacOS/mnvm9590
Executable file
BIN
Mini vMac 9590.app/Contents/MacOS/mnvm9590
Executable file
Binary file not shown.
1
Mini vMac 9590.app/Contents/PkgInfo
Normal file
1
Mini vMac 9590.app/Contents/PkgInfo
Normal file
@ -0,0 +1 @@
|
||||
APPL????
|
@ -0,0 +1 @@
|
||||
dummy
|
BIN
Mini vMac 9590.app/Contents/Resources/ICONAPPO.icns
Normal file
BIN
Mini vMac 9590.app/Contents/Resources/ICONAPPO.icns
Normal file
Binary file not shown.
19
README
Normal file
19
README
Normal file
@ -0,0 +1,19 @@
|
||||
EMPW (Emulated MPW) = Mini vMac + Macintosh Programmer's Workshop + glue
|
||||
|
||||
|
||||
Requirements (install all with brew):
|
||||
- hfsutils
|
||||
- rsync (newer than the one shipping with macOS)
|
||||
- python3
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
# Enjoy an interactive MPW session in Mini vMac (kills your battery life!)
|
||||
$ cd my-fancy-source-tree && empw
|
||||
|
||||
# Run a non-interactive MPW command and capture its standard output.
|
||||
$ empw Help FileSystem
|
||||
|
||||
# Transfer files to the emulated MPW session.
|
||||
$ echo Hello, world! > TextFile && empw Catenate TextFile
|
104
empw
Executable file
104
empw
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from os import path, listdir
|
||||
from sys import argv
|
||||
import datetime
|
||||
from subprocess import run, PIPE, DEVNULL
|
||||
import tempfile
|
||||
from shutil import copyfile
|
||||
|
||||
|
||||
|
||||
def h_topdir():
|
||||
rc = 0
|
||||
while rc == 0:
|
||||
rc = run(['hcd','::'], stderr=DEVNULL).returncode
|
||||
|
||||
|
||||
|
||||
|
||||
my_name, *cmds = argv
|
||||
|
||||
srcimage = 'SourceForEmulator.dmg'
|
||||
|
||||
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('.')))
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
if cmds:
|
||||
mpw_cmd = """# This is an auto-generated MPW script!
|
||||
Echo '# %s'
|
||||
Echo '# %s'
|
||||
SetDirectory Src:
|
||||
Begin
|
||||
%s
|
||||
End > "{Boot}StdOut"
|
||||
Echo
|
||||
Move -y "{__Startup__i}" "{Boot}Trash:"
|
||||
ShutDown -y
|
||||
""" % (datetime.datetime.now(), cmdline, cmdline)
|
||||
else:
|
||||
mpw_cmd = """# This is an auto-generated MPW script!
|
||||
SetDirectory Src:
|
||||
Move -y "{__Startup__i}" "{Boot}Trash:"
|
||||
"""
|
||||
|
||||
mpw_cmd = mpw_cmd.replace('\n','\r')
|
||||
|
||||
with open('/tmp/AutoGen', 'w') as f:
|
||||
f.write(mpw_cmd)
|
||||
tmp_path = 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)
|
||||
|
||||
run(['hmount', bootimg], check=True, stdout=DEVNULL)
|
||||
h_topdir()
|
||||
run(['hcopy', '-m', tmp_path+'.bin', ':MPW:Startup Items:'], check=True)
|
||||
run(['humount'], check=True)
|
||||
|
||||
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
run([hsyncback], check=True)
|
||||
|
||||
|
||||
|
||||
|
||||
if cmds:
|
||||
run(['hmount', bootimg], check=True, stdout=DEVNULL)
|
||||
run(['hcopy', '-t', ':StdOut', '/tmp/StdOut'], check=True)
|
||||
run(['humount'], check=True)
|
||||
|
||||
with open('/tmp/StdOut') as f:
|
||||
print(f.read(), end='')
|
255
hsync
Executable file
255
hsync
Executable file
@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from subprocess import run, PIPE, DEVNULL
|
||||
from sys import argv
|
||||
from time import sleep
|
||||
import datetime
|
||||
from os import path
|
||||
|
||||
import tempfile
|
||||
|
||||
# oldrun = run
|
||||
# def run(*args, **kwargs):
|
||||
# print('CMD', *args)
|
||||
# return oldrun(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
# I am now modifying this to accept *no* arguments,
|
||||
# instead operating on the CWD.
|
||||
|
||||
|
||||
# A couple of useful HFSUTILS wrappers
|
||||
|
||||
def h_topdir():
|
||||
rc = 0
|
||||
while rc == 0:
|
||||
rc = run(['hcd','::'], stderr=DEVNULL).returncode
|
||||
|
||||
def h_reccd(path_comps):
|
||||
h_topdir()
|
||||
for p in path_comps:
|
||||
run(['hcd', p], check=True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# We might need to sneakily edit some metadata...
|
||||
|
||||
def getfours(path):
|
||||
cmds = ('-c','-t')
|
||||
cps = (run(['GetFileInfo', cmd, path], stdout=PIPE, check=True) for cmd in cmds)
|
||||
return tuple(eval('b' + cp.stdout.decode('ascii')) for cp in cps)
|
||||
|
||||
def gettype(path):
|
||||
cmds = ('-t',)
|
||||
cps = (run(['GetFileInfo', cmd, path], stdout=PIPE, check=True) for cmd in cmds)
|
||||
return next(eval('b' + cp.stdout.decode('ascii')) for cp in cps)
|
||||
|
||||
def setfours(path, cc, tc):
|
||||
cc, tc = (set_to.replace(b'\0',b'\\0').decode('ascii') for set_to in (cc, tc))
|
||||
run(['SetFile', '-c', cc, '-t', tc, path], check=True)
|
||||
|
||||
def ensure_fourccs(xpath):
|
||||
cc = gettype(xpath)
|
||||
|
||||
if cc == b'\0\0\0\0':
|
||||
cc = b'MPS '
|
||||
tc = b'TEXT'
|
||||
|
||||
if xpath.endswith('.x'):
|
||||
tc = b'XCOF'
|
||||
elif xpath.endswith('.o') or xpath.endswith('.lib'):
|
||||
tc = b'OBJ '
|
||||
elif xpath.endswith('.rsrc') or xpath.endswith('Resources'):
|
||||
tc = b'rsrc'
|
||||
cc = b'RSED'
|
||||
|
||||
setfours(xpath, cc, tc)
|
||||
|
||||
|
||||
|
||||
|
||||
# All relative to the CWD, of course:
|
||||
srcimg = 'SourceForEmulator.dmg'
|
||||
label = 'Src'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Read and sanitise the arguments. Outputs: "dirs" and "cmds" (both lists, may be empty)
|
||||
# Dirs are in /full/path/without/trailing/slash form, thanks to os.path.abspath
|
||||
|
||||
if not path.exists(srcimg):
|
||||
run(['dd', 'if=/dev/zero', 'of='+srcimg, 'bs=1048576', 'count='+str(128)], stdout=DEVNULL, stderr=DEVNULL, check=True)
|
||||
run(['hformat', '-l', label, srcimg], stdout=DEVNULL, check=True)
|
||||
run(['humount'], check=True)
|
||||
|
||||
|
||||
|
||||
|
||||
# Now run rsync and see what we can get out of it! (Should at least reconsider rsync policy!)
|
||||
# Rsync should update any changed file
|
||||
# Potential problem: need to remove ._ thingummyjigs!
|
||||
|
||||
rsync_opts = [
|
||||
'--dry-run', # Ask rsync what it *would* do if macOS
|
||||
'--itemize-changes', # hadn't dropped R/W HFS support.
|
||||
'--exclude', '.*', # Down with dotfiles.
|
||||
'--exclude', srcimg,
|
||||
'--exclude', 'Desktop DB',
|
||||
'--exclude', 'Desktop DF',
|
||||
'--exclude', 'Trash',
|
||||
'--recursive',
|
||||
'-tX', # consider times and xattrs
|
||||
'--delete', # may delete things on the vMac drive
|
||||
]
|
||||
|
||||
try:
|
||||
devnode, mountpoint = run(['hdiutil', 'attach', '-nobrowse', srcimg], stdout=PIPE, check=True).stdout.decode('ascii').split()
|
||||
mountpoint = mountpoint.rstrip('/') # try to deal in clean paths
|
||||
|
||||
rsync_list = [] # (local_base_path, relative_path_components, rsync_code) tuple
|
||||
|
||||
# rsync /src/dir/without/trailing/slash/DIRNAME /dest/dir/with/trailing/slash/
|
||||
# results in /dest/dir/with/trailing/slash/DIRNAME
|
||||
rsync = run(['rsync', *rsync_opts, './', mountpoint+'/'], stdout=PIPE, check=True)
|
||||
new_rsync_lines = rsync.stdout.decode('utf8').splitlines()
|
||||
|
||||
for new_rsync_line in new_rsync_lines:
|
||||
spcidx = new_rsync_line.index(' ')
|
||||
code_part = new_rsync_line[:spcidx]
|
||||
path_part = new_rsync_line[spcidx+1:].lstrip()
|
||||
|
||||
if '._' in path_part:
|
||||
raise ValueError('AppleDouble-style name detected: %s' % path_part)
|
||||
|
||||
rsync_list.append((code_part, path_part))
|
||||
|
||||
finally:
|
||||
run(['hdiutil', 'detach', mountpoint], check=True, stdout=DEVNULL)
|
||||
|
||||
|
||||
|
||||
# for rsync_code, path in rsync_list:
|
||||
# # Get rid of the silly AppleDouble prefix
|
||||
# a, b = path.splitext(rsync_path)
|
||||
# if b.startswith('._'):
|
||||
# b = b[2:]
|
||||
# path = path.join(a, b)
|
||||
|
||||
|
||||
# try:
|
||||
|
||||
|
||||
|
||||
|
||||
run(['hmount', srcimg], stdout=DEVNULL, check=True)
|
||||
|
||||
try:
|
||||
for rsync_code, rsync_path in rsync_list:
|
||||
# print([local_dir, rsync_code, rsync_path])
|
||||
|
||||
# Suggest: replace with code to make fuck/marry/kill plan for each file
|
||||
|
||||
|
||||
# first, parse the rsync commands!
|
||||
if rsync_code.startswith('*'):
|
||||
if rsync_code == '*deleting':
|
||||
print('-', rsync_path)
|
||||
|
||||
# similar to the folder creation code below
|
||||
if rsync_path.endswith('/'):
|
||||
cmdname = 'hrmdir'
|
||||
else:
|
||||
cmdname = 'hdel'
|
||||
|
||||
a, b = path.split(rsync_path.rstrip('/'))
|
||||
hfs_dir_comps = a.split('/') if a else []
|
||||
hfs_deleteme = b
|
||||
|
||||
h_reccd(hfs_dir_comps)
|
||||
run([cmdname, hfs_deleteme], check=True)
|
||||
|
||||
else:
|
||||
raise ValueError('Unknown extended rsync code: %s' % rsync_code)
|
||||
|
||||
elif rsync_code.startswith('.'):
|
||||
pass
|
||||
|
||||
else:
|
||||
utype, ftype, cdiff, sdiff, tdiff, pdiff, odiff, gdiff, *_ = rsync_code
|
||||
|
||||
if utype == '>':
|
||||
if ftype == 'f':
|
||||
print('+', rsync_path)
|
||||
|
||||
hfs_dir = ':' + path.join(path.dirname(rsync_path), '').replace(*'/:')
|
||||
|
||||
with tempfile.NamedTemporaryFile(prefix='/tmp/', suffix='.bin') as f:
|
||||
macbin_tmp = f.name
|
||||
|
||||
h_topdir()
|
||||
ensure_fourccs(rsync_path)
|
||||
run(['macbinary', 'encode', '--overwrite', '-o', macbin_tmp, rsync_path], check=True)
|
||||
run(['hcopy', '-m', macbin_tmp, hfs_dir], check=True)
|
||||
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
elif utype == 'c':
|
||||
if ftype == 'd':
|
||||
a, b = path.split(rsync_path.rstrip('/'))
|
||||
hfs_dir_comps = a.split('/') if a else []
|
||||
hfs_mkdir_name = b
|
||||
|
||||
h_reccd(hfs_dir_comps)
|
||||
run(['hmkdir', hfs_mkdir_name], check=True)
|
||||
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
elif utype == '.':
|
||||
pass
|
||||
|
||||
else:
|
||||
raise ValueError('Unknown rsync update code: %s' % rsync_code)
|
||||
|
||||
finally:
|
||||
run(['humount'], check=True)
|
||||
|
||||
|
||||
|
||||
|
||||
# YXcstpoguax path/to/file
|
||||
# |||||||||||
|
||||
# `----------- the type of update being done::
|
||||
# |||||||||| <: file is being transferred to the remote host (sent).
|
||||
# |||||||||| >: file is being transferred to the local host (received).
|
||||
# |||||||||| c: local change/creation for the item, such as:
|
||||
# |||||||||| - the creation of a directory
|
||||
# |||||||||| - the changing of a symlink,
|
||||
# |||||||||| - etc.
|
||||
# |||||||||| h: the item is a hard link to another item (requires --hard-links).
|
||||
# |||||||||| .: the item is not being updated (though it might have attributes that are being modified).
|
||||
# |||||||||| *: means that the rest of the itemized-output area contains a message (e.g. "deleting").
|
||||
# ||||||||||
|
||||
# `---------- the file type:
|
||||
# ||||||||| f for a file,
|
||||
# ||||||||| d for a directory,
|
||||
# ||||||||| L for a symlink,
|
||||
# ||||||||| D for a device,
|
||||
# ||||||||| S for a special file (e.g. named sockets and fifos).
|
||||
# |||||||||
|
||||
# `--------- c: different checksum (for regular files)
|
||||
# |||||||| changed value (for symlink, device, and special file)
|
||||
# `-------- s: Size is different
|
||||
# `------- t: Modification time is different
|
||||
# `------ p: Permission are different
|
||||
# `----- o: Owner is different
|
||||
# `---- g: Group is different
|
||||
# `--- u: The u slot is reserved for future use.
|
||||
# `-- a: The ACL information changed
|
31
hsyncback
Executable file
31
hsyncback
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from sys import argv
|
||||
from subprocess import run, PIPE, DEVNULL
|
||||
from os import path
|
||||
|
||||
srcimg = 'SourceForEmulator.dmg'
|
||||
|
||||
rsync_opts = [
|
||||
'--exclude', '.*', # Down with dotfiles.
|
||||
'--exclude', srcimg,
|
||||
'--exclude', 'Desktop DB',
|
||||
'--exclude', 'Desktop DF',
|
||||
'--exclude', 'Trash',
|
||||
'--recursive',
|
||||
'-tX', # consider times and xattrs
|
||||
'--delete', # may delete things on the vMac drive
|
||||
# '--itemize-changes',
|
||||
]
|
||||
|
||||
|
||||
|
||||
devnode, mountpoint = run(['hdiutil', 'attach', '-nobrowse', srcimg], stdout=PIPE, check=True).stdout.decode('ascii').split()
|
||||
|
||||
try:
|
||||
mountpoint = mountpoint.rstrip('/') # try to deal in clean paths
|
||||
|
||||
run(['rsync', *rsync_opts, mountpoint+'/', './'], check=True)
|
||||
|
||||
finally:
|
||||
run(['hdiutil', 'detach', mountpoint], stdout=DEVNULL, check=True)
|
Loading…
x
Reference in New Issue
Block a user