Skip to content

Repository files navigation

MD+MBAS17

A disassembly of MasterDOS 2.3 + MasterBASIC 1.7 for the SAM Coupé, built as assembler source that can be proved correct: assembling it reproduces the original file byte for byte.

What the file is

dumps/MasterBasicMasterDos.bin is a SAM Coupé CODE file of 32640 bytes: a nine-byte header followed by the 32631 bytes it declares.

The sector chain has already been followed. A disc sector holds 510 bytes of payload and two link bytes naming the next one, and those links are not in this file — 64 × 510 is exactly 32640, where 64 × 512 would be 32768. So it is the payloads concatenated, nothing more.

The header is part of the loaded image rather than something to skip: it sits at offset 0 of the first half, so BOOT begins at &4009 and &4000&4008 is the header itself.

It is two 16320-byte halves that end up in different RAM pages, and that is the one thing to understand before reading any of it:

file 016319 MasterDOS 2.3, with MasterBASIC's patches spliced in
file 1632032639 MasterBASIC 1.7

Both halves are assembled to run at &4000&7FBF, and each is written to see the other at &8000&BFBF. Which is which depends on the paging at the time: with the DOS at &4000 the extension is at &8000, and when the extension takes over the two swap. That is why the DOS's message pointer at &4210 holds &9200 while the extension calls &BD79 — each is reaching into the other page.

So it is two address spaces, not one, and it is disassembled as two files. A reference to &8000&BFBF is resolved against the other half and written with its label under a DOS_ or MB_ prefix.

Which one to read

Read listings/clean/. Two files, one per half, and they are the reading copy: every routine headed with what it does and why, no number left as a bare constant where a name would say more, and written for someone who knows Z80 but not this machine. Conclusions only.

listings/disasm/ is the same code with the working notes left in — where a name came from, what an earlier reading got wrong, which claims are still open. Read it when you want the argument rather than the answer, or when you are checking something and need to know how firmly it is held. Both hold only what can be shown: names carried from sources that assemble to the same bytes, tables read from the ROM, and descriptions from the MasterBASIC manual. Where something could not be established, they say so rather than guessing.

listings/speculate/ is a reading, not a record. It is the same two listings with a derived register contract on every routine and a machine-composed guess at what each one is for, every guess marked with a leading ?. It is useful for finding your way around 2,118 routines; it is not evidence. Its README sets out where it is wrong and why.

postinstall-syspage.asm is a third kind again: a reconstruction. MasterBASIC builds seven blocks into the ROM's own system page at boot and points CMDV, EDITV, RST8V and five more vectors at them, so the code the ROM actually calls lives at &45A2, &46CC, &484D and &4BA0 and appears in the two listings only at the addresses it was stored at -- and one of the blocks is not stored here at all, being assembled partly out of the ROM's own PUT. tools/syspage.py builds that page and disassembles it where it really runs, writing it beside the listings it belongs with: listings/clean/postinstall-syspage.asm to read, and listings/disasm/postinstall-syspage.asm with the working notes. It cannot be verified by assembling -- there is no original to compare it with -- and it says so at the top of itself.

listings/clean/, listings/disasm/ and listings/speculate/ all assemble to the original bytes — everything added is a comment — so all six files are checked on every build.

Each tree has a base.asm that INCLUDEs both halves, and that is what is assembled: the two halves are ORGed at &4000, where they run, and DUMPed to a page each so they do not overlap in the output. It is one assembly and still two answers — each half is compared with its own half of the image, so a mismatch says which one it is in. base.asm also holds the equates both halves need, said once, and the equates that bridge between the halves, written as references rather than as numbers so that the assembler checks them.

Rebuilding

python -m pip install pyz80
tools/build.sh

Exit status is 0 only if all six listings come back byte-identical:

listings/disasm/masterdos.asm: BYTE-IDENTICAL
listings/disasm/masterbasic.asm: BYTE-IDENTICAL
listings/clean/masterdos.asm: BYTE-IDENTICAL
listings/clean/masterbasic.asm: BYTE-IDENTICAL
listings/speculate/masterdos.asm: BYTE-IDENTICAL
listings/speculate/masterbasic.asm: BYTE-IDENTICAL

The build assembles the annotated MasterDOS source and the SAM ROM first, for their symbol tables and BASIC token tables, then disassembles the image against them, rebuilds the system page, then reassembles everything it wrote and compares. It prints the byte census and the description count as it goes, so the figures below are measured on every run rather than remembered. Nothing is left to be checked by eye.

The two reference trees are submodules:

git submodule update --init

Where things live

dumps/ the image being disassembled, and the hardware captures it is checked against
listings/clean/ the reading copy — start here; also the ROM's system page as MasterBASIC leaves it, and the base.asm that assembles both halves
listings/disasm/ the same code with the working notes left in
listings/speculate/ the same again, with a machine's reading of every routine
notes/ hand-written names and descriptions, fed into both
tools/ the disassembler and the passes that annotate it
docs/ how it works, the idiom guide, how it is built, the manual
docs/original/ the MasterBASIC manual as scanned, and its errata
ref/masterdos/ annotated MasterDOS 2.3 source (submodule)
ref/samrom/ SAM Coupé ROM 3.0 source (submodule)
diskimages/ the original disk images
design/ what this project set out to do

docs/how-it-works.md is the narrative: what MasterBASIC does, how it gets the ROM to call it, how it finds the ROM in the first place, and what is still unexplained. Read that before the listings.

docs/idioms.md collects the tricks that recur across the image, the SAM ROM and MasterDOS -- the paging conventions, inline call parameters, the &21 skip, bit merges, self-modifying operands, and how to tell that a block of code is not running where it is stored -- each with the code and a line-by-line reading. Worth having open beside the listings.

docs/evidence-wanted.md is the short list of things this project cannot settle by reading -- what to capture on a real machine or an emulator, and what each would decide.

docs/disc.md follows one read and one write all the way down, from the BASIC statement to the bytes moving through the WD1772 -- the layer the other documents leave to the DOS.

docs/disassembly.md is the other long form: how the listings are built, what each pass contributes, and what is still open.

docs/bugs.md is the defects found in MasterDOS itself — three confirmed and one suspected so far. The listings cannot be corrected, since they assemble to the original image byte for byte, so a defect is written down there and explained where it sits.

docs/nmi-hook.md is a how-to rather than a description: what to do with the snapshot button's user vector, how to install one from BASIC, what your routine is entered with, and the two ways back into the machine.

docs/masterbasic-keywords.md and docs/masterbasic-tokens.md are the reference pair: the 28 keywords MasterBASIC adds, and the mechanism by which a DOS gets a word into a tokeniser that lives in ROM.

docs/tokens.md widens that to every token the machine knows -- the ROM's, MasterDOS's and MasterBASIC's in one table, generated from KEYWTAB and MBKEYS by tools/tokentab.py rather than typed. docs/sam-basic-grammar.txt is the same list in machine-readable form with the syntax of each keyword beside it, enough to write a tokeniser, a lister, a syntax highlighter or a completion list against.

Adding your own knowledge

notes/*.txt is the way in for anything you work out yourself. Adding a name costs one line and no code:

MB &5934 SERINIT
    Set up the SCC2691 for LPRINT MODE 2.

AFTER CHECK_WRITE_STATUS : read the controller status through the patched port
RENAME ULA BORDER
EQU STKEND : end of the calculator stack
DOS &4220-&42BC data DVAR
DOS &4835 value DISKCTL_0_BASE

Eight kinds of entry, by address or by label name. Hand-written entries beat anything the tools worked out, and a disagreement is reported rather than resolved silently — as is a name that matches nothing, or one that matches an address in both pages. A typo is reported and skipped; it cannot break the build. The rules are at the top of tools/notes.py, with examples in notes/example.txt.

Where it stands

Every one of the 32640 bytes is accounted for:

bytes
Code 28376 (86.9%)
Variables and other data 2636
Inline call parameters 893
Message and keyword text 696
RST &08 codes 29
Pointer tables 10
Unclassified 0

No label lands inside an instruction. 365 of the 2421 labelled addresses carry a description -- some written by hand after reading the code, the rest the annotated MasterDOS author's, carried across where the two instruction streams still agree. That is a minority of the 2,118 routines the disassembler segments, and the remainder are named but not explained. The build prints these counts, so they are measured rather than remembered.

Credit

MasterDOS and MasterBASIC were written by Andrew J. A. Wright — the author of the SAM Coupé ROM itself, which is why the DOS calls internal ROM addresses under the ROM's own label names. The manual in docs/original/ was scanned by Steve Parry-Thomas; the annotated MasterDOS and ROM sources are separate projects, included here as submodules.

About

An AI-assisted annotated disassembly of the MasterBASIC (and MasterDOS) source code

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages