Why does Microsoft SoftCard CP/M 2.20 hang with a Videx Videoterm while 2.23 boots fine? To answer that question, I built two disassemblers and reverse engineered the boot diskette's first three tracks to produce the full CP/M OS commented source code.
A tour of the four pieces that make up SoftCard CP/M 2.20: the 6502 boot loader, the BIOS, the BDOS, and the CCP. What each is for, the interface it presents to the parts above it, and how it works, with links to the recovered source for every one.
One source file per operating-system component, each decoded at its true runtime address, with shared include files for the contracts between them and a sector de-interleave nobody warns you about. How the recovered SoftCard CP/M 2.20 source tree is laid out, and why.
The toolchain that turns the recovered SoftCard CP/M source files into binaries: ca65 and ld65 for the 6502 parts, sjasmplus for the Z-80, and a thin pipeline that knows each file's load address, include path, and embedded dependencies. One source in, one fixed-size binary out.
How the four compiled binaries map to physical sectors on the first three tracks of the floppy, why each 256-byte page lands where it does, and the tooling that scatters them back through the sector interleave to produce a disk image byte-identical to the 1980 original.
Three boot tracks rebuilt from source is an operating system, not yet a disk. What stands between the reconstructed system area and a floppy you can boot from nothing but source: the file system, the directory, and the shipped utilities, most of which already have annotated source of their own.
A trustworthy disassembler in two stages: a round-trip byte-identity check wired in as a permanent regression test, and the harder problem of telling instructions from data on a disk that holds both 6502 and Z-80 machine code with no boundary marked.
A CP/M .COM file is raw Z-80 bytes with no marker for code versus data and no way to follow a computed jump statically. Run it under an emulator with a BDOS shim, let execution mark the code bytes, and seed a recursive-descent disassembler from there. The decompile-file verb, worked on CPM60.COM.
Scaling the decompiler from one .COM file to an entire SoftCard CP/M floppy: fingerprinting the image, the CP/M sector skew and the .dsk/.po interleave, splitting 6502 from Z-80 by who fetched the byte, and decompile-os / decompile-disk / list-files.
Every source byte in the decompiled CP/M distribution is a byte that shipped on the disk, not a byte captured from a machine already running and rewriting itself. The 924-byte forensics behind the rule, and why the "185 patches" count was an extraction artifact.
Single-source rebuilds with sjasmplus DISP/MODULE/INCBIN/IFNDEF (GBASIC and CPM60.COM worked), plus diffing 2.20 against 2.23 down to the 11-byte device check and the two console ownership paths.
Part 6 of the Decompiler series: the whole Microsoft SoftCard CP/M distribution (three releases, 2.23 44K, 2.23 60K, 2.20) recovered as annotated source that rebuilds the original disk images byte for byte, completing the series.
I added a re-creation of a 1980 Apple ][ display card to the A2FPGA board. It worked, then a report said it stopped CP/M from starting. The same failure showed up on a real card too. The hunt for why, and what the wrong answer left behind once I finally had the right one.
I handed the CP/M boot failure to an AI assistant. It came back fast and certain, then built test after test that all confirmed it. The catch was that every test rested on the same wrong assumption, so none of them could ever disagree.
The real reason CP/M 2.20 hangs with an 80-column card. The expansion cards share one memory window and take turns owning it, and the SoftCard's processor switch knocks the owner loose one beat too soon. Two correct parts, fatal only together.
Chasing one boot failure produced two tools that outgrew it: an emulator that boots the real operating system, and a decompiler that turns a 1980 floppy into readable source and rebuilds it byte-for-byte. Then the project took apart the whole CP/M distribution.
Static disassembly reads the SoftCard CP/M bytes perfectly and still cannot say which CPU owns the $C800 window at the instant of a fetch. That timing is where the 2.20 hang lives. So I built a whole emulated Apple ][ to find it, and held it to a hard bar before I believed it.
The core architecture of the SoftCard replica: a single 64KB address space, two instruction-complete CPU cores that never run at once, the real four-window Z-80 to Apple address map, and a bus switch built from one memory access at $C700.
Part 3 of The Emulator series. Booting CP/M means servicing four peripheral interfaces from outside: the $BE11 sector-read hook, the monitor SAVE/RESTORE entries as PC hooks, the cross-CPU RPC channel through $45-$48 and the $03D0/$03D1 operand, and the Z-80 reading $E000/$E010 directly.
Model the Videx Videoterm faithfully enough that its expansion-ROM window ownership becomes observable: the $C800-$CFFF claim and release, the 6845, the paged VRAM, the real firmware, and an arbiter that can log a fault.
The emulator first booted 2.20 with a Videoterm because the $C800 window switch-out went unmodeled. Add that one bus rule and the field reports reproduce: 38 million window faults for 2.20, zero for 2.23. Plus the earlier run that confirmed a wrong theory, and why it agreed with garbage.
Disassembly told me everything the code said and almost nothing about what the system did. Here are the four things in a SoftCard CP/M system I could only see by running a replica, plus the bar I made my emulator clear before I'd trust a word it told me.
How I built the machine in software: two CPU cores, one shared 64 KB, a bus switch made of a Python exception, a disk controller that cheats honestly, monitor ROM as breakpoints, a Videx with real ownership rules, and a language card, plus the bugs of mine that impersonated the system.
Once my replica booted, it told me things disassembly couldn't: a live trace of 2.20's console path, the bug rendered as a two-line rhythm in the fault log, the BIOS's origin settled in two probes, a boot-time patcher caught in the act, and the old failure matrix reproduced cell for cell.
Hand it a Microsoft SoftCard CP/M disk image and it maps how the disk boots, diffs one version against another, turns the disk into readable annotated assembly, and rebuilds a byte-identical image from that source. What each verb does, how to run it, and what it shows you.
To find why a 1980 CP/M disk wouldn't boot, I had to read its 6502 and Z-80 machine code, and no tool gave me source I could trust. So I wrote two disassemblers. Getting valid source back took a few tries. Teaching them to tell code from data took far longer.
Adding a second expansion-ROM-capable card to the A2FPGA exposed four latent bus timing bugs in the upstream codebase — bugs that had been present for approximately two years and went undetected because only one emulated card had ever used C8-space at the same time.
The ThunderClock Plus is the only Apple II clock card with a built-in ProDOS driver. Emulating it means implementing a NEC uPD1990AC serial calendar clock in SystemVerilog — and exploiting a USB-C power trick to give the FPGA battery-like time persistence.
Getting 80 columns of text from VRAM to pixels on a modern HDMI display: the Videx VideoTerm rendering pipeline in SystemVerilog, C8-space ownership, and the Pascal boot hang that required reading a 1983 firmware disassembly to fix.
The Videx VideoTerm was the 80-column card that turned the Apple II into a business machine. Emulating it on an FPGA means reverse-engineering a 1981 MC6845 CRTC design and understanding why slot 3 is unlike every other slot in the machine.
The decision to replace Smalltalk-80's 1980 display model with GPU-accelerated rendering: deleting Form, BitBlt, and Pen from the codebase and building Wise Owl Smalltalk on SDL2 and SkiaSharp.
Getting a Smalltalk-80 system to display something requires implementing BitBlt, the 1980 graphics primitive at the foundation of everything visible. Then saving the live heap so you never have to do cold start again.
The Smalltalk-80 file-out is not a complete description of a running system. Some globals only ever existed interactively — they're not in any source file, but the code that uses them was written assuming they exist.
The Smalltalk-80 bytecode interpreter: execution contexts, method lookup through the metaclass chain, non-local block returns, and the BlockClosure bug that took weeks to find.
The cold-start loader reads a JSON description of every Smalltalk class and builds a complete, correctly-linked heap from scratch. The garbage collector tells you when you got it wrong.
To build a Smalltalk-80 VM you need a compiler. But Smalltalk bytecode is useless without a running Smalltalk. The escape: a JSON intermediate representation and an empirically-discovered grammar.
Smalltalk-80's object model is the most consistent in computing history. Understanding it — metaclasses, live images, and all — is the first step toward building one from scratch.
Every nibbler command demonstrated against Apple Panic — actual outputs, what each line means, and how to read the results. The reference guide for analysing a copy-protected Apple II WOZ disk image from first scan to disassembled source.
What a recursive-descent disassembler finds in 27KB of 1981 Apple II game code: an XOR sprite engine, speaker timing loops, enemy AI state machines with three behavioral trees, BCD scoring, 104 subroutines, and a memory layout where every byte has a reason.
Sector 1 fails its checksum every time. Three theories, each plausible, each wrong. The real cause turns out to be a fundamental property of how WOZ files represent circular disk tracks — and it was in our own bit-stream reader the whole time.
Disassembling the Apple Panic boot loader reveals self-modifying code, a mathematically elegant GCR table corruption, a custom post-decode permutation, and a per-track marker scheme that makes the disk illegible unless you already know the key.
How the Disk II's radical hardware design made Apple II copy protection possible — and what a flux-level disk image reveals about a 1981 floppy that defeated nearly every copy tool of its era.