Skip to main content

Clean-room MPW IIgs toolchain in Python: 65816 assembler, OMF linker, Rez resource compiler

Project description

gsasm

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.

Results

Rebuilt from the original source and verified byte-identical to the shipping binaries:

  • ROM 03 — all 262,144 bytes
  • The GS/OS kernelprodos, 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 12 mapped toolbox toolsets (186,110/186,110 bytes) — including the multi-segment ExpressLoad tools (Window/Menu/Control Managers, QDAux, TextEdit …) with their linker-generated ~JumpTable segments and full relocation dictionaries
  • The whole System 6.0.1 System Disk — every one of the 30 files the disk harness rebuilds from source is logically byte-exact, and the packed 800K disk image matches physically, all 819,264 bytes
  • Resource forks (gsrez): Sys.Resources (24,337 bytes, 143 resources including embedded code resources) and EasyMount — the latter byte-exact across both forks, code and resources, from source to shipping file
  • Instruction encoding: 100% — every instruction in the ~97,000-line corpus encodes to the same bytes as the original listings
  • 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 — was eventually falsified as a nameable assembler or harness bug 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.

Install

pip install git+https://github.com/emdeejay/gsasm.git

Or install from a local clone in editable mode:

git clone https://github.com/emdeejay/gsasm.git
cd gsasm
pip install -e .

Requires Python 3.9+. No dependencies outside the standard library.

CLI

gsasm — assembler

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/Monitor

Include 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 — linker

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.load

For 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 — resource compiler

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. TypesIIGS.r-style include files are searched through -I directories; read files (e.g. linked code resources) through --read-dir. --meta sets fork-header fields (creation timestamp etc.) for byte-exact reproduction work.

gsrez sys.resources.r -I ./rincludes --read-dir ./build -o SYS.RESOURCES -t "F9  "

Python API

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)

Source dialect

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 engineMACRO/ENDM, positional parameters (&1&n), keyword parameters, WHILE/ENDWHILE, IF/ELSE/ENDIF, GOTO/AGO/AIF, MEXIT, ANOP, builtins (&sysdate, &systime, …)
  • DirectivesPROC (with TEMPORG/ENTRY/EXPORT forms), ENTRY/EXPORT/IMPORT, RECORD/ENDR (templates and typed DS instances), 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-PROC namespaces
  • Expression evaluator — MPW operator precedence, byte extraction (#<x, #>x, #^x), shifts, the one's-complement operator, MSB ON character constants
  • OMF v2 emitterCONST/LCONST/DS, LEXPR/BEXPR/EXPR/RELEXPR, GLOBAL/GEQU, SUPER relocation dictionaries, cross-segment and import references, faithful record chunking

Sources are read as MacRoman with classic-Mac line endings, matching real MPW files.

Module layout

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)

Tests

python3 tests/run_fixtures.py

The 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.

Validation harnesses (work/)

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).

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

Background

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.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gsasm-0.3.0.tar.gz (3.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gsasm-0.3.0-py3-none-any.whl (178.9 kB view details)

Uploaded Python 3

File details

Details for the file gsasm-0.3.0.tar.gz.

File metadata

  • Download URL: gsasm-0.3.0.tar.gz
  • Upload date:
  • Size: 3.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gsasm-0.3.0.tar.gz
Algorithm Hash digest
SHA256 7ce4d086d229ee12cc7bba1490c3a81cbc8425860438c0c4bec2ee4b572c0e3d
MD5 065cdfb5d02e1f7a56c646affecbbbde
BLAKE2b-256 ffe1681d41cce2a5dba132272b7345feb00cf5f898893f2feb87ce0ca653f454

See more details on using hashes here.

Provenance

The following attestation bundles were made for gsasm-0.3.0.tar.gz:

Publisher: publish.yml on emdeejay/gsasm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gsasm-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: gsasm-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 178.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gsasm-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 41c7b2b8910271fe14cad0939430756de3601a284032c3b1f71977169525c58b
MD5 8b817934996dc7ac9854085eb802b7fa
BLAKE2b-256 2ea7a91d06a9440f7b092542835ab68ac0fb4cea9db2f83618d8e2f7a6fda724

See more details on using hashes here.

Provenance

The following attestation bundles were made for gsasm-0.3.0-py3-none-any.whl:

Publisher: publish.yml on emdeejay/gsasm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page