A clean-room Python reimplementation of the MPW IIgs cross-development
toolchain — the AsmIIgs assembler, LinkIIgs linker, the RezIIgs
resource compiler, the MakeBinIIgs/OverlayIIgs/catenate packagers, and
the ExpressLoad relinker — validated byte-for-byte against the Apple IIgs
ROM 03 and the GS/OS System 6.0.1 shipping binaries.
Apple built the IIgs ROM and GS/OS on a 68k Mac under MPW Shell. Running that
chain today means SheepShaver → a Mac OS 7 image → MPW → the GS cross-tools.
gsasm replaces it with pure Python — no emulator, no dependencies outside
the standard library.
Rebuilt from the original source and verified byte-identical to the shipping binaries:
- ROM 03 — the full 262,144-byte image is byte-identical to the real ROM; ~261,377 bytes (99%) are rebuilt byte-exact from source, with the FC/FD/FE Toolbox banks placed from the captured image (~767-byte residual)
- The GS/OS kernel —
prodos,GS.OS(38,805/38,805),Start.GS.OS,Error.Msg, the Loader, and P8 (ProDOS 8 compatibility kernel, 17,128/17,128 including its overlay packaging) - All 8 buildable FSTs (ProDOS, HFS, Char, High Sierra, DOS 3.3, Pascal, MS-DOS, AppleShare — 111,584/111,584 bytes) and all 12 device drivers (94,948/94,948)
- All 14 mapped toolbox toolsets (193,357/193,357 bytes) — including the
multi-segment ExpressLoad tools (Window/Menu/Control Managers, QDAux,
TextEdit …) with their linker-generated
~JumpTablesegments and full relocation dictionaries - The complete System 6.0.1 seven-disk set — every disk image
reconstructs physically byte-identical, all 819,264 bytes each: the 181
forks with producing source (assembler or Rez) are rebuilt and overlaid
from source (Install 39, System Disk 41, SystemTools1 38, SystemTools2 52,
Fonts 3, synthLAB 2, Apple II Setup 6 — including the HFS Disk 7, written
through a2til's allocation-preserving overlay engine), and every one of the
243 substitutes is carried verbatim under a per-fork census with dated,
structured search evidence — 235 because no source exists in any archive,
8 because the source is Pascal or BASIC and this is an assembler. The
completion gate (
work/manifestcheck.py --tier-a) passes on all six machine-checked conditions - Resource forks (
gsrez):Sys.Resources,EasyMount, all 19 Control-Panel CDEVs, the Finder (both forks — the 146 KB application itself rebuilds from source), the Installer, and Teach — the Rez template compilation and fork assembly are byte-exact from source (the CDEVs' embedded code resources, some of them Pascal/C-compiled, are gold-fed) - Desktop apps, drivers & NDAs beyond the System Disk (Disks 3–5): the
BASIC.SystemP8 command interpreter, four data forks (CDRemote, EasyAccess, Pioneer2000/4200), five more resource forks (FindFile, Apple.Bowl, MediaControl & VideoMix NDAs), and the NoteSeq & VideoMix toolsets (two of the 14 above, which ship out here on Disks 3–5) — all byte-exact - Instruction encoding: 100% — every opcode and addressing mode in the ~97,000-line corpus matches (97,202/97,202 opcode bytes); the linked shipping binaries are the byte-exact proof of operand values
- 61/61 ROM objects link-identical — for every module, linking
gsasm's object or Apple's original produces the same load image
Every "proven limit" recorded along the way — a GS.OS "94-byte external floor", "absent" AppleShare sources, "unclosable" ExpressLoad relocation encodings, a "case-B wall", assorted "verified-no-source" claims, and even a diagnosed bug that turned out not to exist — was eventually falsified as a nameable assembler, linker, or harness issue and closed. The full accounting, with the evidence trail for each, is in docs/RESULTS.md.
Byte-exact reproduction also makes the shipping binaries subtractable. For a
worked example — recovering a lost community bug fix from a modified HFS.FST
with no source, by subtracting the original gsasm rebuilds — see
docs/notes/hfs-fst-6.0.4-carry-bug.md.
pip install git+https://github.com/emdeejay/gsasm.gitOr install from a local clone in editable mode:
git clone https://github.com/emdeejay/gsasm.git
cd gsasm
pip install -e .Requires Python 3.10+. No dependencies outside the standard library.
gsasm <source.asm> [-I <incdir>] [-d KEY=VAL] [-o <out.obj>]
Assembles an MPW IIgs-dialect 65816 source file and writes an OMF v2 object file.
# assemble a single file
gsasm MyTool.asm -I ./includes -o MyTool.obj
# pass command-line defines (equivalent to the -d flag in MPW IIgs)
gsasm ROMDataMgr.asm -I ./includes -d Big=1
# multiple include directories
gsasm monitor.aii -I ./includes -I ./romsrc/MonitorInclude directories are searched in order for files referenced by INCLUDE
directives. The source file's own directory is not searched implicitly — add
-I . if you need it.
gslink <file.obj> [-o <out.load>]
Links a single multi-segment OMF object file. All LEXPR/BEXPR/EXPR
relocation records and RELEXPR (relative branch) records are fully evaluated
against the collected GLOBAL symbol table. The output is a single-segment OMF
load file with a flat body.
gslink MyTool.obj # writes MyTool.out
gslink MyTool.obj -o MyTool.loadFor multi-object linking — libraries, segment naming/placement, the
LinkIIgs -apw recipe used by the GS/OS build scripts — use
gsasm/linkiigs.py; for ROM bank layout see work/linkrom.py.
gsrez <source.r> [-I <incdir>]... [-o <out>] [-t <filetype>] [-c <creator>]
[--read-dir <dir>]... [--meta KEY=VAL]...
Compiles a Rez source file (the MPW IIgs RezIIgs dialect: type templates,
resource bodies, read statements, the C-style preprocessor) into an Apple
IIgs resource fork, written as a raw fork image. A clean-room TypesIIGS.r
ships with gsasm (gsasm/rez/include/, searched after any -I directories),
so #include "TypesIIGS.r" works out of the box for the corpus-validated
templates — docs/REZ_TYPES_PLAN.md records how each was derived and which
are covered. read files (e.g. linked code resources) are searched through
--read-dir. --meta sets fork-header fields (creation timestamp etc.) for
byte-exact reproduction work.
gsrez sys.resources.r --read-dir ./build -o SYS.RESOURCES -t "F9 "from gsasm import asm, omf, link
# Assemble (include paths are positional; defines optional)
a = asm.assemble("MyTool.asm", ["./includes"], defines={"Big": 1})
if a.errors:
raise SystemExit("\n".join(a.errors))
# Emit an OMF object
obj_bytes = omf.emit(a)
with open("MyTool.obj", "wb") as f:
f.write(obj_bytes)
# Link to a load file
load_bytes = link.link(obj_bytes)
with open("MyTool.out", "wb") as f:
f.write(load_bytes)
# Parse an existing OMF file
header = omf.parse_header(obj_bytes)
records, _ = omf.parse_records(obj_bytes, header["DISPDATA"],
numlen=header["NUMLEN"])
for offset, record_type, data in records:
print(offset, record_type, data)gsasm implements the MPW IIgs (AsmIIgs) source dialect as used in the
ROM 03 and System 6.0.1 source trees. Notable features:
- 65816 instruction set — full addressing-mode selection (dp/abs/long,
cross-bank rules),
MVN/MVP,PEA/PEI/PER - Macro engine —
MACRO/ENDM, positional parameters (&1…&n), keyword parameters,WHILE/ENDWHILE,IF/ELSE/ENDIF,GOTO/AGO/AIF,MEXIT,ANOP, builtins (&sysdate,&systime, …) - Directives —
PROC(withTEMPORG/ENTRY/EXPORTforms),ENTRY/EXPORT/IMPORT,RECORD/ENDR(templates and typedDSinstances),WITH,DC/DCB/DS,ORG,SEG,INCLUDE,MSB/LONGA/LONGI/CASE ON|OFF,OBJEND - Label scoping —
@-local labels scoped to the nearest enclosing non-@label (per MPW Assembler Reference p. 17); per-PROCnamespaces - Expression evaluator — MPW operator precedence, byte extraction
(
#<x,#>x,#^x), shifts, the≈one's-complement operator,MSB ONcharacter constants - OMF v2 emitter —
CONST/LCONST/DS,LEXPR/BEXPR/EXPR/RELEXPR,GLOBAL/GEQU,SUPERrelocation dictionaries, cross-segment and import references, faithful record chunking
Sources are read as MacRoman with classic-Mac line endings, matching real MPW files.
gsasm/
__init__.py
__main__.py CLI entry points (gsasm, gslink)
m65816.py 65816 opcode table and addressing-mode encoding
expr.py MPW expression evaluator
asm.py Multi-pass assembler: macro engine, symbols, segments
omf.py OMF v2 parser and emitter
link.py Single-object OMF linker
linkiigs.py General LinkIIgs: multi-object, libraries, -apw recipe,
segment naming and placement
makebin.py MakeBinIIgs / OverlayIIgs / catenate packaging
expressload.py ExpressLoad relinker (fast-load format + SUPER records)
python3 tests/run_fixtures.pyThe tests/ fixture suite runs on a bare checkout — no reference material
needed. Each fixture is an original source pinning one discovered dialect or
OMF behavior, with expected bytes minted only while the full golden-corpus
validation passes. See tests/README.md for how blessing
works and why the expected bytes are trustworthy.
The work/ scripts are the differential-validation harnesses used during
development. They compare rebuilt output against captured artifacts of the
original build — Apple's source, listings, objects, and shipping binaries —
which are copyrighted and not included (everything under ref/ and
work/romsrc/ is gitignored; supply your own).
The harnesses that read or rebuild the shipping disk images (diskcheck.py,
drivercheck.py, disk7check.py, manifestcheck.py and friends) also need
a2til, the downstream ProDOS/HFS/2IMG disk-image library — a separate
checkout, deliberately never vendored here. They all
locate it through one mechanism (work/_common.ensure_a2til_on_path):
A2TIL_PATH=/path/to/a2til python3 work/diskcheck.pyWith A2TIL_PATH unset the search falls back to a checkout beside this one
(../a2til), then ~/src/a2til. An A2TIL_PATH that is set but does not
hold an a2til package is a hard error with a clear message — never a silent
fall-back to another layout.
| Script | What it validates |
|---|---|
gate.py |
Runs every harness below against a committed baseline; fails on any regression |
buildrom.py |
Reconstructs rom.03 and verifies it byte-identical to the shipping ROM |
bytecheck.py |
Instruction encoding against the .lst Object Code column |
objcheck.py |
Emitted .obj files record-by-record against the originals |
linkcheck.py |
Differential link: original vs gsasm object through the same linker |
toolcheck.py |
System/Tools/ToolNNN toolsets vs the shipping files |
fstcheck.py |
System/FSTs/* vs the shipping files |
drivercheck.py |
System/Drivers/* vs the shipping files |
kernelcheck.py |
prodos, GS.OS, Start.GS.OS, Error.Msg, P8 |
diskcheck.py |
Whole System 6.0.1 disk-image files (needs the a2til sibling tools; set A2TIL_PATH) |
linkrom.py |
The LinkIIgs-equivalent ROM bank layout |
hfs.py |
Minimal HFS reader for extracting sources from .hfv images |
The ROM 03 and System 6.0.1 sources were assembled with AsmIIgs (circa
1989–1993), Apple's 65816 cross-assembler for MPW, distributed through APDA
with the rest of the MPW IIgs cross-development tools. The tools' own source
code was never published, so this project is a clean-room reimplementation:
behaviour was reverse-engineered from captured .obj and .lst files. Given
source, listings, and objects from a known-good build, every discrepancy
between gsasm's output and the original is a measurable bug, and every
target that passes a differential comparison is proven correct rather than
assumed. The same method then extended, tool by tool, to the rest of the MPW
IIgs chain until whole shipping binaries reproduced.
The OMF (Object Module Format) specification is documented in the Apple IIgs Toolbox Reference and the Apple IIgs GS/OS Reference.
MIT — see LICENSE.