Two versions, two BIOS layouts: 2.20 vs 2.23 staging compared

5 min read
apple-iiz80cpmsoftcardreverse-engineeringretrocomputingcpm-videx-series

Detail for Part 9 — Every Difference: A Complete Inventory.

Now that the LOAD_CPM mechanism is understood, running it on 2.20 is straightforward. Extended reconstruct_staging.py to handle both DOS33_INTERLEAVE (for .dsk) and PRODOS_INTERLEAVE (for .po), pointed it at CPM220Disk1.po, and out came staging_220.bin, sysimg_220.bin, newdisk_220.bin.

LOAD_CPM is the same routine in both versions — same 29 sectors, same starting position (trk0:$0B), same staging at $8000-$9CFF. PREP_HANDOFF splits the staging the same way. Confirmed by reading the bytes back. So the mechanism we documented for 2.23 applies unchanged to 2.20.

But the content at specific staging offsets differs:

Item2.202.23
BIOS jump table starts at staging offset$1700$1900
BIOS first 1 KB lands at Apple address$0A00 (start of newdisk)$0C00 (end of newdisk)
Softcard banner stringabsentpresent at end of sysimg
BIOS load address (Z-80)$DACC$FAB8

So 2.20’s BIOS jump table is 2 sectors (512 bytes) earlier in the staging. After PREP_HANDOFF, 2.20’s BIOS first 1 KB sits at the beginning of the new-disk-routines area at Apple $0A00, which means the Z-80 disk callbacks (which take the rest of $0A00-$0FFF) live AFTER it at Apple $0E00-$0FFF. In 2.23 the layout is reversed: Z-80 callbacks first at Apple $0A00-$0BFF, BIOS first 1 KB at the end at $0C00-$0FFF.

This is a meaningful organizational change, not just an address-arithmetic difference. The content of the on-disk staging was deliberately reorganized between versions.

Verified the 2.20 BIOS jump table by disassembling newdisk_220.bin as Z-80 starting at $0A00:

$0A00: C3 A8 DE  JP DEA8   ; BOOT
$0A03: C3 CC DA  JP DACC   ; WBOOT (jumps to BIOS base)
$0A06: C3 08 DB  JP DB08   ; CONST
$0A09: C3 12 DB  JP DB12   ; CONIN
$0A0C: C3 43 DB  JP DB43   ; CONOUT
... (15 entries total)
$0A2D: AF        XOR A     ; LISTST stub: return 0
$0A2E: C9        RET

Targets cluster in 2.20’s BIOS range $DACC-$DEA8, just as 2.23’s targets cluster in its $FAB8-$FED1 range. Same structural pattern, different absolute addresses.

Also confirmed: 2.20 has no Softcard substring anywhere in its sysimg or newdisk. The boot banner string visible at A> time was added in 2.23. So if you boot 2.20, the screen probably stays blanker on entry — no banner gets printed.

Same architecture, different content; same load mechanism, different layout; same purpose, different cosmetics. Sets up the diff cleanly: anything 2.20 and 2.23 share at the byte level is unchanged Microsoft code; anything that differs is the actual delta worth investigating.

Status: 2.20 reconstruction complete. Both versions extracted in parallel. Now have all the static data needed for routine-by-routine diffing once the cold-boot generator is understood.