Skip to content

Latest commit

 

History

357 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 — 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 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 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 ~JumpTable segments 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.System P8 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.

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.10+. 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. 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  "

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

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

With 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

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.

About

Clean-room Apple IIgs AsmIIgs assembler and OMF linker in Python

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages