I can’t touch the code on the stack, because it’s used as a decryption key. I mean, I could theoretically change a few bytes of it, then calculate the proper decrypted bytes on zero page by hand. But no.
Instead, I’m just going to copy this latest disk routine wholesale. It’s short and has no external dependencies, so why not? Then I can capture the decrypted zero page and see where that JMP ($0028) is headed.
*BLOAD TRACE5
*9734<2126.2166M
Here’s the entire disassembly listing of boot trace #6:
96F8 A9 05 LDA #$05 Patch boot0 so it calls my routine instead of
96FA 8D 38 08 STA $0838 jumping to $0301.
96FD A9 97 LDA #$97
96FF 8D 39 08 STA $0839
9702 4C 01 08 JMP $0801 Start the boot.
9705 84 48 STY $48 (Callback #1 is here.) Reproduce the
9707 A0 00 LDY #$00 decryption loop that was originally at $0320.
9709 B9 00 03 LDA $0300,Y
970C 45 48 EOR $48
970E 99 00 01 STA $0100,Y
9711 C8 INY
9712 D0 F5 BNE $9709
9714 A9 21 LDA #$21 Patch the stack so it jumps to my callback #2
9716 8D D4 01 STA $01D4 instead of continuing to $0500.
9719 A9 97 LDA #$97
971B 8D D5 01 STA $01D5
971E A2 CF LDX #$CF Continue the boot.
9720 9A TXS
9721 60 RTS
9722 A9 4C LDA #$4C (Callback #2.) Set up callback #3 instead of
9724 8D 99 05 STA $0599 passing control to the disk read routine at
9727 A9 34 LDA #$34 $0126.
9729 8D 9A 05 STA $059A
972C A9 97 LDA #$97
972E 8D 9B 05 STA $059B
9731 4C 00 05 JMP $0500 Continue the boot.
9734 BD 8C C0 LDA $C08C,X (Callback #3.) Disk read routine copied
9737 10 FB BPL $9734 wholesale from $0126..$0166 that reads a sector
9739 C9 BF CMP #$BF and decrypts it into zero page.
973B D0 F7 BNE $9734
973D BD 8C C0 LDA $C08C,X
9740 10 FB BPL $973D
9742 C9 BE CMP #$BE
9744 D0 F3 BNE $9739
9746 BD 8C C0 LDA $C08C,X
9749 10 FB BPL $9746
974B C9 D4 CMP #$D4
974D D0 F3 BNE $9742
974F A0 00 LDY #$00
9751 BD 8C C0 LDA $C08C,X
9754 10 FB BPL $9751
9756 38 SEC
9757 2A ROL
9758 8D 00 02 STA $0200
975B BD 8C C0 LDA $C08C,X
975E 10 FB BPL $975B
9760 2D 00 02 AND $0200
9763 59 00 01 EOR $0100,Y
9766 99 00 00 STA $0000,Y
9769 C8 INY
976A D0 E5 BNE $9751
976C BD 8C C0 LDA $C08C,X
976F 10 FB BPL $976C
9771 C9 D5 CMP #$D5
9773 D0 BF BNE $9734
Execution falls through here.
9775 A0 00 LDY #$00 Now capture the decrypted zero page.
9777 B9 00 00 LDA $0000,Y
977A 99 00 20 STA $2000,Y
977D C8 INY
977E D0 F7 BNE $9777
9780 AD E8 C0 LDA $C0E8 Turn off the slot 6 drive motor.
9783 4C 00 C5 JMP $C500 Reboot to my work disk.
*BSAVE TRACE6,A$9600,L$186
*9600G Whew. Let’s do it.
…reboots slot 6…
…reboots slot 5…
]BSAVE BOOT3
0000-00FF,A$2000,L$100
]CALL -151
*2028.2029
2028 D0 06
OK, the JMP ($0028) points to $06D0 that I captured earlier. It’s part of the second chunk we read into the text page. (Not the first chunk that was copied to $BD00+ then overwritten.) So it’s in the “BOOT2 0500-07FF” file, not the “BOOT1 0400-07FF” file.
*BLOAD BOOT2 0500-07FF,A$2500
*26D0L
26D0 A2 00 LDX #$00
26D2 EE D5 06 INC $06D5
26D5 C9 EE CMP #$EE
And look, more self-modifying code.
*26D5:CA
*26D5L
26D5 CA DEX
26D6 EE D9 06 INC $06D9
26D9 0F ???
*26D9:10
*26D9L
26D9 10 FB BPL $26D6 Branch is never taken, because we just DEX’d
26DB CE DE 06 DEC $06DE from #$00 to #$FF.
26DE 61 A0 ADC ($A0,X)
*26DE:60
*26DEL
26DE 60 RTS
And now we’re back on the stack.
$05FF + 1 = $0600, which is already in memory at $2600.
*2600L
2600 A0 00 LDY #$00 Destroy stack by pushing the same value $100
2602 48 PHA times.
2603 88 DEY
2604 D0 FC BNE $2602
I guess we’re done with all that code on the stack page. I mean, I hope we’re done with it, since it all just disappeared.
2606 A2 FF LDX #$FF Reset the stack pointer.
2608 9A TXS
2609 EE 0C 06 INC $060C
260C A8 TAY
Oh joy.
*260C:A9
*260CL
260C A9 27 LDA #$27
260E EE 11 06 INC $0611
2611 17 ???
*2611:18
*2611L
2611 18 CLC
2612 EE 15 06 INC $0615
2615 68 PLA
*2615:69
*2615L
2615 69 D9 ADC #$D9
2617 EE 1A 06 INC $061A
261A 4B ???
*261A:4C
*261AL
261A 4C 90 FD JMP $FD90
Wait, what?
*FD90L
FD90 D0 5B BNE $FDED
Despite the fact that the accumulator is #$00 (because #$27 + #$D9 = #$00), the INC at $0617 affects the Z register and causes this branch to be taken, because the final value of $061A was not zero.
*FDEDL
FDED 6C 36 00 JMP ($0036)
Of course, this is the standard output character routine, which routes through the output vector at ($0036). And we just set that vector, along with the rest of zero page. So what is it?
*2036.2037
2036 6F BF
Let’s see, $BD00..$BFFF was copied earlier from $0500..$07FF, but from the first time we read into the text page, not the second time we read into text page. So it’s in the “BOOT1 0400-07FF” file, not the “BOOT2 0500-07FF” file.
*BLOAD BOOT1 0400-07FF,A$2400
*FE89G FE93G Disconnect DOS.
*BD00<2500.27FFM Move code into place.
*BF6FL
BF6F C9 07 CMP #$07
BF71 90 03 BCC $BF76
BF73 6C 3A 00 JMP ($003A)
*203A.203B
203A F0 FD
BF76 85 5F STA $5F Save input value.
BF78 A8 TAY Use value as an index into an array.
BF79 B9 68 BF LDA $BF68,Y
BF7C 8D 82 BF STA $BF82 Self-modifying code alert—this changes the
BF7F A9 00 LDA #$00 upcoming JSR at $BF81.
BF81 20 D0 BE JSR $BED0
Amazing. So this output vector does actually print characters through the standard $FDF0 text print routine, but only if the character to be printed is at least #$07. If it’s less than #$07, the character is treated as a command. Each command gets routed to a different routine somewhere in $BExx. The low byte of each routine is stored in the array at $BF68, and the STA at $BF7C modifies the JSR at $BF81 to call the appropriate address.
*BF68.
BF68 D0 DF D0 D0 FD FD D0
Since A = #$00 this time, the call is unchanged and we JSR $BED0. Other input values may call $BEDF or $BEFD instead.
*BED0L
BED0 A5 60 LDA $60 Use the value of $C050 to produce a
BED2 4D 50 C0 EOR $C050 pseudo-random number between #$01 and
BED5 85 60 STA $60 #$0E.
BED7 29 0F AND #$0F
BED9 F0 F5 BEQ $BED0 Not #$00.
BEDB C9 0F CMP #$0F Not #$0F.
BEDD F0 F1 BEQ $BED0
BEDF 20 66 F8 JSR $F866 Set the lo-res plotting color (in zero page $30)
to the random-ish value we just produced.
BEE2 A9 17 LDA #$17 Fill the lo-res graphics screen with blocks of
BEE4 48 PHA that color.
BEE5 20 47 F8 JSR $F847 Calculates the base address for this line in
BEE8 A0 27 LDY #$27 memory and puts it in $26/$27.
BEEA A5 30 LDA $30
BEEC 91 26 STA ($26),Y
BEEE 88 DEY
BEEF 10 FB BPL $BEEC
BEF1 68 PLA
BEF2 38 SEC Do it for all 24 ($17) rows of the screen.
BEF3 E9 01 SBC #$01
BEF5 10 ED BPL $BEE4
BEF7 AD 56 C0 LDA $C056 Switch to lo-res graphics mode.
BEFA AD 54 C0 LDA $C054
BEFD 60 RTS
This explains why the original disk fills the screen with a different color every time it boots. But wait, these commands do so much more than just fill the screen.
Continuing from $BF84…
BF84 A5 5F LDA $5F
BF86 C9 04 CMP #$04
BF88 D0 03 BNE $BF8D
BF8A 4C 00 BD JMP $BD00
If A = #$04, we exit via $BD00, which I’ll investigate later.
BF8D C9 05 CMP #$05
BF8F D0 03 BNE $BF94
BF91 6C 82 BF JMP ($BF82)
If A = #$05, we exit via ($BF82), which is the same thing we just called via the self-modified JSR at $BF81.
For all other values of A, we do this:
BF94 20 B0 BE JSR $BEB0
*BEB0L
BEB0 A2 60 LDX #$60 Another layer of encryption!
BEB2 BD 9F BF LDA $BF9F,X
BEB5 5D 00 BE EOR $BE00,X
BEB8 9D 9F BF STA $BF9F,X This is decrypting the code that we’re about
BEBB CA DEX to run.
BEBC 10 F4 BPL $BEB2
BEBE AE 66 BF LDX $BF66
BEC1 60 RTS
This is self-contained, so I can just run it right now and see what ends up at $BF9F.
*BEB0G
Continuing from $BF97…
BF97 A0 00 LDY #$00
BF99 A9 B2 LDA #$B2
BF9B 84 44 STY $44
BF9D 85 45 STA $45
BF9F BD 89 C0 LDA $C089,X Everything beyond this point was encrypted,
but we just decrypted it in $BEB0.
BFA2 BD 8C C0 LDA $C08C,X Find a 3-nibble prologue, which varies, based
BFA5 10 FB BPL $BFA2 on whatever is in the zero page. $40/$41/$42
BFA7 C5 40 CMP $40 for now.
BFA9 D0 F7 BNE $BFA2
BFAB BD 8C C0 LDA $C08C,X
BFAE 10 FB BPL $BFAB
BFB0 C5 41 CMP $41
BFB2 D0 F3 BNE $BFA7
BFB4 BD 8C C0 LDA $C08C,X
BFB7 10 FB BPL $BFB4
BFB9 C5 42 CMP $42
BFBB D0 F3 BNE $BFB0
BFBD BD 8C C0 LDA $C08C,X Read 4-4-encoded data.
BFC0 10 FB BPL $BFBD
BFC2 38 SEC
BFC3 2A ROL
BFC4 85 46 STA $46
BFC6 BD 8C C0 LDA $C08C,X
BFC9 10 FB BPL $BFC6
BFCB 25 46 AND $46
BFCD 91 44 STA ($44),Y Store in memory starting at $B200, which was
BFCF C8 INY set at $BF9B.
BFD0 D0 EB BNE $BFBD
BFD2 E6 45 INC $45
BFD4 BD 8C C0 LDA $C08C,X
BFD7 10 FB BPL $BFD4
BFD9 C5 43 CMP $43
BFDB D0 BA BNE $BF97
BFDD A5 45 LDA $45 Read into $B200, $B300, and $B400, then stop.
BFDF 49 B5 EOR #$B5
BFE1 D0 DA BNE $BFBD
BFE3 48 PHA ; A=00
BFE4 A5 45 LDA $45 ;
A=B5
BFE6 49 8E EOR #$8E ;
A=3B
BFE8 48 PHA
BFE9 60 RTS
So we push #$00 and #$3B to the stack, then exit via RTS. That will return to $003C, which is in memory at $203C. And that’s the code we just read from disk, which means I get to set up another boot trace to capture it.
*203CL
203C 4C 00 B2 JMP $B200
I’ll reboot my work disk again, since I disconnected DOS to examine the code at $BD00..$BFFF.
*C500G
…
]CALL -151
*BLOAD TRACE6
.
[same as previous trace, up to
and including the inline disk
read routine copied from
$0126 that decrypts a sector
into zero page]
.
9775 A9 80 LDA #$80 Change the JMP address at $003C so it points
9777 85 3D STA $3D to my callback instead of continuing to $B200.
9779 A9 97 LDA #$97
977B 85 3E STA $3E
977D 4C 00 06 JMP $0600 Continue the boot.
9780 A2 03 LDX #$03 (Callback is here.) Copy the new code to the
9782 B9 00 B2 LDA $B200,Y graphics page so it survives a reboot.
9785 99 00 22 STA $2200,Y
9788 C8 INY
9789 D0 F7 BNE $9782
978B EE 84 97 INC $9784
978E EE 87 97 INC $9787
9791 CA DEX
9792 D0 EE BNE $9782
9794 AD E8 C0 LDA $C0E8 Reboot to my work disk.
9797 4C 00 C5 JMP $C500
*BSAVE TRACE7,A$9600,L$19A
*9600G
…reboots slot 6…
…reboots slot 5…
]BSAVE
OBJ.B200-B4FF,A$2200,L$300
]CALL -151
*B200<2200.24FFM
*B200L
B200 A9 04 LDA #$04
B202 20 00 B4 JSR $B400
B205 A9 00 LDA #$00
B207 85 5A STA $5A
B209 20 00 B3 JSR $B300
B20C 4C 00 B5 JMP $B500
$B400 is a disk seek routine, identical to the one at $BE00. (It even has the same dual entry points for seeking by half track and quarter track, at $B400 and $B403.) There’s nothing at $B500 yet, so the routine at $B300 must be another disk read.
*B300L
B300 A0 00 LDY #$00 Some zero page initialization.
B302 A9 B5 LDA #$B5
B304 84 59 STY $59
B306 48 PHA
B307 20 30 B3 JSR $B330
*B330L
B330 48 PHA More zero page initialization.
B331 A5 5A LDA $5A
B333 29 07 AND #$07
B335 A8 TAY
B336 B9 50 B3 LDA $B350,Y
B339 85 50 STA $50
B33B A5 5A LDA $5A
B33D 4A LSR
B33E 09 AA ORA #$AA
B340 85 51 STA $51
B342 A5 5A LDA $5A
B344 09 AA ORA #$AA
B346 85 52 STA $52
B348 68 PLA
B349 E6 5A INC $5A
B34B 4C 60 B3 JMP $B360
*B350.
B350 D5 B5 B7 BC DF D4 B4 DB
That could be an array of nibbles. Maybe a rotating prologue? Or a decryption key? What’s this? Oh, another disk read routine.
*B360L
B360 85 54 STA $54
B362 A2 02 LDX #$02
B364 86 57 STX $57
B366 A0 00 LDY #$00
B368 A5 54 LDA $54
B36A 84 55 STY $55
B36C 85 56 STA $56
B36E AE 66 BF LDX $BF66 Find a 3-nibble prologue that varies, based on
B371 BD 8C C0 LDA $C08C,X the zero page locations that were initialized at
B374 10 FB BPL $B371 $B330 based on the array at $B350.
B376 C5 50 CMP $50
B378 D0 F7 BNE $B371
B37A BD 8C C0 LDA $C08C,X
B37D 10 FB BPL $B37A
B37F C5 51 CMP $51
B381 D0 F3 BNE $B376
B383 BD 8C C0 LDA $C08C,X
B386 10 FB BPL $B383
B388 C5 52 CMP $52
B38A D0 F3 BNE $B37F
B38C BD 8C C0 LDA $C08C,X Read a 4-4-encoded sector.
B38F 10 FB BPL $B38C
B391 2A ROL
B392 85 58 STA $58
B394 BD 8C C0 LDA $C08C,X
B397 10 FB BPL $B394
B399 25 58 AND $58
B39B 91 55 STA ($55),Y Store the data into ($55).
B39D C8 INY
B39E D0 EC BNE $B38C
B3A0 0E FF FF ASL $FFFF Find a 1-nibble epilogue. (D4)
B3A3 BD 8C C0 LDA $C08C,X
B3A6 10 FB BPL $B3A3
B3A8 C9 D4 CMP #$D4
B3AA D0 B6 BNE $B362
B3AC E6 56 INC $56
B3AE C6 57 DEC $57
B3B0 D0 DA BNE $B38C
B3B2 60 RTS
Let’s see: $57 is the sector count. Initially #$02 (set at $B364), decremented at $B3AE.
$56 is the target page in memory. Set at $B36C to the accumulator, which is set at $B368 to the value of address $54, which is set at $B360 to the accumulator, which is set at $B348 by the PLA, which was pushed to the stack at $B330, which was originally set at $B302 to a constant value of #$B5. Then $56 is incremented (at $B3AC) after reading and decoding $100 bytes worth of data from disk.
$55 is #$00, as set at $B36A.
So this reads two sectors into $B500..$B6FF and returns to the caller. Backtracking to $B30A…
B30A A4 59 LDY $59 $59 is initially #$00, set at $B304.
B30C 18 CLC
B30D AD 65 BF LDA $BF65 Current phase. (track × 2)
B310 79 28 B3 ADC $B328,Y New phase.
B313 20 03 B4 JSR $B403 Move the drive head to the new phase, but
using the second entry point, which uses a
reduced timing loop!
B316 68 PLA This pulls the value that was pushed to the
stack at $B306 , which was the target memory
page to store the data being read from disk by
the routine at $B360.
B317 18 CLC page += 2
B318 69 02 ADC #$02
B31A A4 59 LDY $59 counter += 1
B31C C8 INY
B31D C0 04 CPY #$04 Loop for four iterations.
B31F 90 E3 BCC $B304
B321 60 RTS
So we’re reading two sectors at a time, four times, into $B500+— so we’re loading into $B500..$BCFF. That completely fills the gap in memory between the code at $B200..$B4FF (this chunk) and the code at $BD00..$BFFF (copied much earlier), which strongly suggests that my analysis is correct.
But what’s going on with the weird drive seeking?
There is some definite weirdness here, and it is centered around the array at $B328. At $B200, we called the main entry point for the drive seek routine at $B400 to seek to track 2. Now, after reading two sectors, we’re calling the secondary entry point (at $B403) to seek… where exactly?
*B328.
B328 01 FF 01 00 00 00 00 00
Aha! This array is the differential to get the drive to seek forward or back. At $B200, we seeked to track 2. The first time through this loop at $B304, we read two sectors into $B500..$B6FF, then add 1 to the current phase, because $B328 = #$01. Normally this would seek forward a half track, to track 2.5, but because we’re using the reduced timing loop, we only seek forward by a quarter track, to track 2.25.
The second time through the loop, we read two sectors into $B700..$B8FF, then subtract 1 from the phase (because $B329 = #$FF) and seek backwards by a quarter track. Now we’re back on track 2.0.
The third time, we read two sectors from track 2.25 into $B900.. $BAFF, then seek forward by a quarter track, because $B32A = #$01.
The fourth and final time, we read the final two sectors from track 2.25 into $BB00..$BCFF.
This explains the fluttering noise the original disk makes during this phase of the boot. It’s flipping back and forth between adjacent quarter tracks, reading two sectors from each.
Boy am I glad I’m not trying to copy this disk with a generic bit copier. That would be nearly impossible, even if I knew exactly which tracks were split like this.
*BLOAD TRACE7
.
. [same as previous trace]
.
9780 A9 8D LDA #$8D Interrupt the boot at $B20C after it calls $B300
9782 8D 0D B2 STA $B20D but before it jumps to the new code at $B500.
9785 A9 97 LDA #$97
9787 8D 0E B2 STA $B20E
978A 4C 00 B2 JMP $B200 Continue the boot.
978D A2 08 LDX #$08 (Callback is here.) Capture the code at
978F A0 00 LDY #$00 $B500 .. $BCFF so it survives a reboot.
9791 B9 00 B5 LDA $B500,Y
9794 99 00 25 STA $2500,Y
9797 C8 INY
9798 D0 F7 BNE $9791
979A EE 93 97 INC $9793
979D EE 96 97 INC $9796
97A0 CA DEX
97A1 D0 EE BNE $9791
97A3 AD E8 C0 LDA $C0E8 Reboot to my work disk.
97A6 4C 00 C5 JMP $C500
*BSAVE TRACE8,A$9600,L$1A9
*9600G
…reboots slot 6…
…reboots slot 5…
]BSAVE
OBJ.B500-BCFF,A$2500,L$800
]CALL -151
*B500<2500.2CFFM
*B500L
B500 AE 5F 00 LDX $005F This is the same command ID, saved at $BF76,
that was printed earlier, passed to the routine
at $BF6F via $FDED.
B503 BD 80 B5 LDA $B580,X Use command ID as an index into this new
array.
B506 8D 0A B5 STA $B50A Store the array value in the middle of the
next JSR instruction, and call it.
B509 20 50 B5 JSR $B550 Modified based on the previous lookup.
*B580.
B580 50 58 68 70 00 00 58
The high byte of the JSR address never changes, so depending on the command ID, we’re calling one of five functions. This is a nice, compact jump table.
00=>$B550 01=>$B558 02=>$B568
03=>$B570 06=>$B558
*B550L *B568L
B550 A9 09 LDA #$09 B568 A9 31 LDA #$31
B552 A0 00 LDY #$00 B56A A0 00 LDY #$00
B554 4C 00 BA JMP $BA00 B56C 4C 00 BA JMP $BA00
*B558L *B570L
B558 A9 19 LDA #$19 B570 A9 41 LDA #$41
B55A A0 00 LDY #$00 B572 A0 A0 LDY #$A0
B55C 20 00 BA JSR $BA00 B574 4C 00 BA JMP $BA00
B55F A9 29 LDA #$29
B561 A0 68 LDY #$68
B563 4C 00 BA JMP $BA00
Those all look quite similar. Let’s see what is loaded at $BA00.
*BA00L
BA00 48 PHA Save the two input parameters. (A & Y)
BA01 84 58 STY $58
BA03 20 00 BE JSR $BE00 Seek the drive to a new phase, given in A.
BA06 A2 00 LDX #$00 Copy a number of bytes from $B900 , Y to $BB00.
BA08 A4 58 LDY $58
BA0A B9 00 B9 LDA $B900,Y
BA0D 9D 00 BB STA $BB00,X
BA10 C8 INY
BA11 E8 INX
BA12 E0 0C CPX #$0C $0C bytes. Always exactly $0C bytes.
BA14 90 F4 BCC $BA0A
What’s at $B900? All kinds of fun stuff.
*B900. B930 38 39 3A 3B 3C 3D 3E 3F
B900 08 09 0A 0B 0C 0D 0E 0F B938 60 61 62 63 64 65 66 67
B908 10 11 12 13 14 15 16 17 B940 68 69 6A 6B 6C 6D 6E 6F
B910 18 19 1A 1B 1C 1D 1E 1F B948 70 71 72 73 74 75 76 77
B918 20 21 22 23 24 25 26 27 B950 78 79 7A 7B 7C 7D 7E 7F
B920 28 29 2A 2B 2C 2D 2E 2F B958 80 81 82 83 84 85 86 87
B928 30 31 32 33 34 35 36 37 B960 00 00 00 00 00 00 00 00
That looks suspiciously like a set of high bytes for addresses in main memory. Note how it starts at #$08 (immediately after the text page), then later jumps from #$3F to #$60, skipping over hi-res page 2.
Continuing from $BA16…
BA16 20 30 BA JSR $BA30
*BA30L
BA30 AD 65 BF LDA $BF65 Current phase.
BA33 4A LSR Convert it to a track number.
BA34 A2 03 LDX #$03
BA36 29 0F AND #$0F Use the track MOD $10 as the index to an
BA38 A8 TAY array, then store it in the zero page.
BA39 B9 10 BC LDA $BC10,Y
BA3C 95 50 STA $50,X
BA3E C8 INY
BA3F 98 TYA
BA40 CA DEX
BA41 10 F3 BPL $BA36
*BC10.
BC10 F7 F5 EF EE DF DD D6 BE
BC18 BD BA B7 B6 AF AD AB AA
All of those are valid nibbles. Maybe this is setting up another rotating prologue for the next disk read routine?
Continuing from $BA43:
BA43 4C 0C BB JMP $BB0C
*BB0CL
Yet another disk read routine.
BB0C A2 0C LDX #$0C I think $54 is the sector count and $55 is the
BB0E 86 54 STX $54 logical sector number.
BB10 A0 00 LDY #$00
BB12 8C 54 BB STY $BB54
BB15 84 55 STY $55
BB17 AE 66 BF LDX $BF66 Find a 3-nibble prologue that varies by track,
BB1A BD 8C C0 LDA $C08C,X set up at $BA39 .
BB1D 10 FB BPL $BB1A
BB1F C5 50 CMP $50
BB21 D0 F7 BNE $BB1A
BB23 BD 8C C0 LDA $C08C,X
BB26 10 FB BPL $BB23
BB28 C5 51 CMP $51
BB2A D0 EE BNE $BB1A
BB2C BD 8C C0 LDA $C08C,X
BB2F 10 FB BPL $BB2C
BB31 C5 52 CMP $52
BB33 D0 E5 BNE $BB1A
BB35 A4 55 LDY $55 Logical sector number, initialized to #$00 at
$BB15.
BB37 B9 00 BB LDA $BB00,Y Use the sector number as an index into the
$0C-length page array we set up at $BA06 .
BB3A 8D 55 BB STA $BB55 Modify the upcoming code.
BB3D E6 55 INC $55
BB3F BC 8C C0 LDY $C08C,X Get the actual byte.
BB42 10 FB BPL $BB3F
BB44 B9 00 BC LDA $BC00,Y
BB47 0A ASL
BB48 0A ASL
BB49 0A ASL
BB4A 0A ASL
BB4B BC 8C C0 LDY $C08C,X
BB4E 10 FB BPL $BB4B
BB50 19 00 BC ORA $BC00,Y
BB53 8D 00 FF STA $FF00 Modified earlier at $BB3A to be the desired
BB56 EE 54 BB INC $BB54 page in memory.
BB59 D0 E4 BNE $BB3F
BB5B EE 55 BB INC $BB55
BB5E BD 8C C0 LDA $C08C,X Find a 1-nibble epilogue, which also varies by
BB61 10 FB BPL $BB5E track.
BB63 C5 53 CMP $53
BB65 D0 A5 BNE $BB0C
BB67 C6 54 DEC $54 Loop for all $0C sectors.
BB69 D0 CA BNE $BB35
BB6B 60 RTS
So we’ve read $0C sectors from the current track, which is the most you can fit on a track with this kind of “4-and-4” nibble encoding scheme.
Continuing from $BA19:
BA19 A5 58 LDA $58 Increment the pointer to the next memory
BA1B 18 CLC page.
BA1C 69 0C ADC #$0C
BA1E A8 TAY
BA1F B9 00 B9 LDA $B900,Y If the next page is #$00, we’re done.
BA22 F0 07 BEQ $BA2B
BA24 68 PLA Otherwise loop back, where we’ll move the
BA25 18 CLC drive head one full track forward and read
BA26 69 02 ADC #$02 another $0C sectors.
BA28 D0 D6 BNE $BA00
BA2B 68 PLA Execution continues here from $BA22.
BA2C 60 RTS
Now we have a whole bunch of new stuff in memory. In this case, $B550 started on track 4. 5 (A = #$09 on entry to $BA00) and filled $0800..$3FFF and $6000..$87FF. If we print a different character, the routine at $B500 will route through one of the other subroutines—$B558, $B568, or $B570. Each of them starts on a different track (A) and uses a different starting index (Y) into the page array at $B900. The underlying routine at $BA00 doesn’t know anything else; it just seeks and reads $0C sectors per track until the target page = #$00.
Continuing from $B50C…
B50C 20 00 B7 JSR $B700
*B700L
B700 A2 00 LDX #$00 Look, another decryption loop.
B702 BD 00 B6 LDA $B600,X
B705 5D 00 BE EOR $BE00,X
B708 9D 00 03 STA $0300,X
B70B E8 INX
B70C E0 D0 CPX #$D0
B70E 90 F2 BCC $B702
B710 CE 13 B7 DEC $B713
B713 6D 09 B7 ADC $B709
B716 60 RTS
And more self-modifying code that will jump to the newly decrypted code at $0300.
*B713:6C
*B713L
B713 6C 09 B7 JMP ($B709)
To recap: after seven boot traces, the bootloader prints a null character via $FD90, which jumps to $FDED, which jumps to ($0036), which jumps to $BF6F, which calls $BEB0, which decrypts the code at $BF9F and returns just in time to execute it. $BF9F reads three sectors into $B200-$B4FF, pushes #$00/#$3B to the stack and exits via RTS, which returns to $003C, which jumps to $B200. $B200 reads 8 sectors into $B500-$BCFF from tracks 2 and 2. 5, shifting between the adjacent quarter tracks every two sectors, then jumps to $B500, which calls $B5[50|58|68|70], which reads actual game code from multiple tracks starting at track 4. 5, 9. 5, 24. 5, or 32. 5. Then it calls $B700, which decrypts $B600 into $0300 (using $BE00+ as the decryption key) and exits via a jump to $0300.
I’m sure the code at $0300 will be straightforward and easy to understand.1
The code at $B600 is decrypted with the code at $BE00 as the key. That was originally copied from the text page the first time, not the second time.
*BLOAD BOOT1 0400-07FF,A$2400
*BE00<2600.26FFM ; move key
into place
*B710:60 ; stop after loop
*B700G ; decrypt
*300L
0300 A0 00 LDY #$00 Wipe almost everything we’ve already loaded
0302 98 TYA at the top of main memory!
0303 99 00 B1 STA $B100,Y
0306 C8 INY
0307 D0 F9 BNE $0302
0309 EE 05 03 INC $0305
030C AE 05 03 LDX $0305
030F E0 BD CPX #$BD Stop at $BD00.
0311 90 F0 BCC $0303
OK, so all we’re left with in memory is the RWTS at $BD00..$BFFF (including the $FDED vector at $BF6F) and the single page at $B000. Oh, and the game, but who cares about that?
Moving on, we find yet another disk read routine!
0313 A9 07 LDA #$07
0315 20 80 03 JSR $0380
*380L
0380 20 00 BE JSR $BE00 Drive seek. (A = #$07, so track 3.5.)
0383 A2 03 LDX #$03 Pull four bytes from the stack, thus negating
0385 68 PLA the JSR that got us here at $0315 and the JSR
0386 CA DEX before that at $B50C.
0387 10 FC BPL $0385
0389 4C 18 03 JMP $0318 Continue by jumping directly to the place we
would have returned to, if we hadn’t just
popped the stack, which we did.
*318L
0318 AE 66 BF LDX $BF66
031B A4 5F LDY $5F Y is command ID, the character we printed
way back when.
031D BD 8C C0 LDA $C08C,X Find a 3-nibble prologue. (D4 D5 D7)
0320 10 FB BPL $031D
0322 C9 D4 CMP #$D4
0324 D0 F7 BNE $031D
0326 BD 8C C0 LDA $C08C,X
0329 10 FB BPL $0326
032B C9 D5 CMP #$D5
032D D0 F3 BNE $0322
032F BD 8C C0 LDA $C08C,X
0332 10 FB BPL $032F
0334 C9 D7 CMP #$D7
0336 D0 F3 BNE $032B
0338 88 DEY Branch when Y goes negative.
0339 30 08 BMI $0343
033B 20 51 03 JSR $0351 Read one byte from disk, store it in $5E.
(Subroutine not shown.)
033E 20 51 03 JSR $0351 Read one more byte from disk.
0341 D0 F5 BNE $0338 Loop back, unless the byte is #$00.
OK, I see it. It was hard to follow at first because the exit condition was checked before I knew it was a loop. But this is a loop. On track 3.5, there is a 3-nibble prologue D4 D5 D7, then an array of values. Each value is two bytes. We’re just finding the Nth value in the array. But to what end?
0343 20 51 03 JSR $0351 Execution continues here from $0339 . Read
0346 48 PHA two more bytes from disk and push them to
0347 20 51 03 JSR $0351 the stack.
034A 48 PHA
Oh God. A new return address. That’s what this is: an array of addresses, indexed by the command ID. That’s what we’re looping through, and eventually pushing to the stack: the entry point for this block of the game.
But the entry point for each block is read directly from disk, so I have no idea what any of them are. Add that to the list of things I get to come back to later.
034B BD 88 C0 LDA $C088,X Turn off the drive motor.
034E 4C 62 03 JMP $0362
*362L
0362 A0 00 LDY #$00 Wipe this routine from memory.
0364 99 00 03 STA $0300,Y
0367 C8 INY
0368 C0 65 CPY #$65
036A 90 F8 BCC $0364
036C A9 BE LDA #$BE Push several values to the stack.
036E 48 PHA
036F A9 AF LDA #$AF
0371 48 PHA
0372 A9 34 LDA #$34
0374 48 PHA
0375 CE 78 03 DEC $0378
0378 29 CE AND #$CE
More self-modifying code!
*378:28
*378L
0378 28 PLP Pop that #$34 off the stack, but use it as
0379 CE 7C 03 DEC $037C status registers. This is weird, but legal; if it
037C 61 60 ADC ($60,X) turns out to matter, I can figure out exactly
which status bits get set and cleared.
*37C:60
*37CL
037C 60 RTS
Now we return to $BEB0 because we pushed #$BE/#$AF/#$34 but then popped #$34. The routine at $BEB0 re-encrypts the code at $BF9F (because now we’ve XOR’d it twice so it’s back to its original form) and exits via RTS, which returns to the address we pushed to the stack at $0346, which we read from track 3. 5— and varies based on the command we’re still executing, which is really the character we printed via the output vector. This is all completely insane.
Since the JSR $B700 at $B50C never returns (because of the crazy stack manipulation at $0383), that’s the last chance I’ll get to interrupt the boot and capture this chunk of game code in memory. I won’t know what the entry point is (because it’s read from disk), but one thing at a time.
*BLOAD TRACE8
.
. [same as previous trace]
.
978D A9 4C LDA #$4C Unconditionally break after loading the game
978F 8D 0C B5 STA $B50C code into main memory.
9792 A9 59 LDA #$59
9794 8D 0D B5 STA $B50D
9797 A9 FF LDA #$FF
9799 8D 0E B5 STA $B50E
979C 4C 00 B5 JMP $B500 Continue the boot.
*BSAVE TRACE9,A$9600,L$19F
*9600G
…reboots slot 6…
…read read read…
<beep>
Success!
*C050 C054 C057 C052
[displays a very nice picture
of a gumball machine which
is featured in the game’s
introduction sequence]
*C051
OK, let’s save it. According to the table at $B900, we filled $0800..$3FFF and $6000..$87FF. $0800+ is overwritten on reboot by the boot sector and later by the HELLO program on my work disk. $8000+ is also overwritten by Diversi-DOS 64K, which is annoying but not insurmountable. So I’ll save this in pieces.
*C500G ]BSAVE BLOCK
… 00.0800-1FFF,A$2800,L$1800
]BSAVE BLOCK ]BRUN TRACE9
00.2000-3FFF,A$2000,L$2000 …reboots slot 6…
]BRUN TRACE9 <beep>
…reboots slot 6… *2000<6000.87FFM
<beep> *C500G
*2800<800.1FFFM …
*C500G ]BSAVE BLOCK
00.6000-87FF,A$2000,L$2800
Now what? Well this is only the first chunk of game code, loaded by printing a null character. By setting up another trace and changing the value of zero page $5F, I can route $B500 through a different subroutine at $B558 or $B568 or $B570 and load a different chunk of game code.
]CALL -151
*BLOAD OBJ.B500-BCFF,A$B500
According to the lookup table at $B580, $B500 routed through $B558 to load the game. Here is that routine:
*B558L
B558 A9 19 LDA #$19
B55A A0 00 LDY #$00
B55C 20 00 BA JSR $BA00
B55F A9 29 LDA #$29
B561 A0 68 LDY #$68
B563 4C 00 BA JMP $BA00
The first call to $BA00 will fill up the same parts of memory as we filled when the character (in $5F) was #$00—$0800..$3FFF and $6000..$87FF. But it starts reading from disk at phase $19 (track $0C 1/2), so it’s a completely different chunk of code.
The second call to $BA00 starts reading at phase $29 (track $14 1/2), and it looks at $B900 + Y = $B968 to get the list of pages to fill in memory.
*B968.
B968 88 89 8A 8B 8C 8D 8E 8F B980 A0 A1 A2 A3 A4 A5 A6 A7
B970 90 91 92 93 94 95 96 97 B988 A8 A9 AA AB AC AD AE AF
B978 98 99 9A 9B 9C 9D 9E 9F B990 B2 B2 B2 B2 B2 B2 B2 B2
B998 00 00 00 00 00 00 00 00
The first call to $BA00 stopped just shy of $8800, and that’s exactly where we pick up in the second call. I’m guessing that $B200 isn’t really used, but the track read routine at $BA00 is “dumb” in that it always reads exactly $0C sectors from each track. So we’re filling up $8800..$AFFF, then reading the rest of the last track into $B200 over and over.
Let’s capture it:
*BLOAD TRACE9
.
. [same as previous trace]
.
978D A9 4C LDA #$4C Again, break to the monitor at $B50C instead
978F 8D 0C B5 STA $B50C of continuing to $B700.
9792 A9 59 LDA #$59
9794 8D 0D B5 STA $B50D
9797 A9 FF LDA #$FF
9799 8D 0E B5 STA $B50E
979C A9 01 LDA #$01 Change the character being printed to #$01
979E 85 5F STA $5F just before the bootloader uses it to load the
appropriate chunk of game code.
97A0 4C 00 B5 JMP $B500 Continue the boot.
*BSAVE TRACE10,A$9600,L$1A3
*9600G
…reboots slot 6…
…read read read…
<beep>
*C050 C054 C057 C052
[displays a very nice picture
of the main game screen]
*C051
*C500G
…
]BSAVE BLOCK
01.2000-3FFF,A$2000,L$2000
]BRUN TRACE10
…reboots slot 6…
<beep>
*2800<800.1FFFM
*C500G
…
]BSAVE BLOCK
01.0800-1FFF,A$2800,L$1800
]BRUN TRACE9
…reboots slot 6…
<beep>
*2000<6000.AFFFM
*C500G
…
]BSAVE BLOCK
01.6000-AFFF,A$2000,L$5000
And similarly with blocks 2 and 3. (These are not shown here, but you can look at TRACE11 and TRACE12 on my work disk.) Blocks 4 and 5 get special-cased earlier (at $BF86 and $BF8D, respectively), so they never reach $B500 to load anything from disk. Block 6 is the same as block 1.
That’s it. I’ve captured all the game code. Here’s what the game looks like at this point:
]CATALOG *B 003 TRACE7
C1983 DSR^C#254 B 005 OBJ.B200-B4FF
019 FREE *B 003 TRACE8
A 002 HELLO B 010 OBJ.B500-BCFF
B 003 BOOT0 *B 003 TRACE9
*B 003 TRACE B 026 BLOCK 00.0800-1FFF
B 003 BOOT1 0300-03FF B 034 BLOCK 00.2000-3FFF
*B 003 TRACE2 B 042 BLOCK 00.6000-87FF
B 003 BOOT1 0100-01FF *B 003 TRACE10
*B 003 TRACE3 B 026 BLOCK 01.0800-1FFF
B 006 BOOT1 0400-07FF B 034 BLOCK 01.2000-3FFF
*B 003 TRACE4 B 082 BLOCK 01.6000-AFFF
B 005 BOOT2 0500-07FF *B 003 TRACE11
*B 003 TRACE5 B 026 BLOCK 02.0800-1FFF
B 003 BOOT2 B000-B0FF B 034 BLOCK 02.2000-3FFF
B 003 BOOT2 0100-01FF B 042 BLOCK 02.6000-87FF
*B 003 TRACE6 *B 003 TRACE12
B 003 BOOT3 0000-00FF B 034 BLOCK 03.2000-3FFF
It’s… it’s beautiful.
I’ve captured all the blocks of the game code (I think), but I still have no idea how to run it. The entry points for each block are read directly from disk, in the loop at $031D.
Rather than try to boot-trace every possible block, I’m going to load up the original disk in a nibble editor and do the calculations myself. The array of entry points is on track 3. 5. Firing up Copy II Plus nibble editor, I searched for the same 3-nibble prologue “D4 D5 D7” that the code at $031D searches for, and lo and behold!
After the “D4 D5 D7” prologue, I find an array of 4-and-4-encoded nibbles starting at offset $1DC6. Breaking them down into pairs and decoding them with the 4-4 encoding scheme, I get this list of bytes:
COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------
TRACK: 03.50 START: 1800 LENGTH: 3DFF
^^^^^
1DA0: FA AA FA AA FA AA FA AA VIEW
1DA8: EB FA FF AE EA EB FF AE
1DB0: EB EA FC FF FF FF FF FF
1DB8: FF FF FF FF FF FF FF FF
1DC0: FF FF FF D4 D5 D7 AF AF <-1DC3
^^^^^^^^
1DC8: EE BE BA BB FE FA AA BA
1DD0: BA BE FF FF AB FF FF FF
1DD8: AB FF FF FF AB FF BB AB FIND:
1DE0: BB FF AA AA AA AA AA AA D4 D5 D7
---------------------------------------
A TO ANALYZE DATA ESC TO QUIT
? FOR HELP SCREEN / CHANGE PARMS
Q FOR NEXT TRACK SPACE TO RE-READ
nibbles |
byte |
nibbles |
byte |
|
AF AF |
#$0F |
|
EE BE |
#$9C |
BA BB |
#$31 |
|
FE FA |
#$F8 |
AA BA |
#$10 |
|
BA BE |
#$34 |
FF FF |
#$FF |
|
AB FF |
#$57 |
FF FF |
#$FF |
|
AB FF |
#$57 |
FF FF |
#$FF |
|
AB FF |
#$57 |
BB AB |
#$23 |
|
BB FF |
#$77 |
And now—maybe!—I have my list of entry points for each block of the game code. Only one way to know for sure!
]PR#5
…
]CALL -151
*800:0 N 801<800.BEFEM Clear main memory so I’m not accidentally
relying on random stuff left over from all my
other testing.
*BLOAD BLOCK Load all of block 0 into place.
00.0800-1FFF,A$800
*BLOAD BLOCK
00.2000-3FFF,A$2000
*BLOAD BLOCK
00.6000-87FF,A$6000
*F9DG Jump to the entry point I found on track 3.5.
[displays the game intro (+1, since the original code pushes it to the
sequence] stack and returns to it.)
*does a little happy dance in
my chair*
We have no further use for the original disk. Now would be an excellent time to take it out of the drive and store it in a cool, dry place.
Remember when I said I’d look at $BD00 later? The time has come. Later is now.
The output vector at $BF6F has special case handling if A = #$04. Instead of continuing to $0300 and $B500, it jumps directly to $BD00. What’s so special about $BD00?
The code at $BD00 was moved there very early in the boot process, from page $0500 on the text screen. (The first time we loaded code into the text screen, not the second time.) So it’s in BOOT1 0400-07FF on my work disk.
]PR#5
…
]BLOAD BOOT1 0400-07FF,A$2400
]CALL -151
*BD00<2500.25FFM
*BD00L
BD00 AE 66 BF LDX $BF66 Turn on drive motor.
BD03 BD 89 C0 LDA $C089,X
BD06 A9 64 LDA #$64 Wait for drive to settle.
BD08 20 A8 FC JSR $FCA8
BD0B A9 10 LDA #$10 Seek to phase $10 (track 8).
BD0D 20 00 BE JSR $BE00
BD10 A9 02 LDA #$02 Seek to phase $02 (track 1).
BD12 20 00 BE JSR $BE00
BD15 A0 FF LDY #$FF Initialize data latches.
BD17 BD 8D C0 LDA $C08D,X
BD1A BD 8E C0 LDA $C08E,X
BD1D 9D 8F C0 STA $C08F,X
BD20 1D 8C C0 ORA $C08C,X
BD23 A9 80 LDA #$80 Wait.
BD25 20 A8 FC JSR $FCA8
BD28 20 A8 FC JSR $FCA8
BD2B BD 8D C0 LDA $C08D,X Oh God
BD2E BD 8E C0 LDA $C08E,X
BD31 98 TYA
BD32 9D 8F C0 STA $C08F,X
BD35 1D 8C C0 ORA $C08C,X
BD38 48 PHA
BD39 68 PLA
BD3A C1 00 CMP ($00,X)
BD3C C1 00 CMP ($00,X)
BD3E EA NOP
BD3F C8 INY
BD40 9D 8D C0 STA $C08D,X Oh my
BD43 1D 8C C0 ORA $C08C,X
BD46 B9 8F BD LDA $BD8F,Y
BD49 D0 EF BNE $BD3A
BD4B A8 TAY
BD4C EA NOP
BD4D EA NOP
BD4E B9 00 B0 LDA $B000,Y ← !
BD51 48 PHA
BD52 4A LSR
BD53 09 AA ORA #$AA
BD55 9D 8D C0 STA $C08D,X Oh God Oh God Oh God
BD58 DD 8C C0 CMP $C08C,X
BD5B C1 00 CMP ($00,X)
BD5D EA NOP
BD5E EA NOP
BD5F 48 PHA
BD60 68 PLA
BD61 68 PLA
BD62 09 AA ORA #$AA
BD64 9D 8D C0 STA $C08D,X
BD67 DD 8C C0 CMP $C08C,X
BD6A 48 PHA
BD6B 68 PLA
BD6C C8 INY
BD6D D0 DF BNE $BD4E
BD6F A9 D5 LDA #$D5
BD71 C1 00 CMP ($00,X)
BD73 EA NOP
BD74 EA NOP
BD75 9D 8D C0 STA $C08D,X
BD78 1D 8C C0 ORA $C08C,X
BD7B A9 08 LDA #$08
BD7D 20 A8 FC JSR $FCA8
BD80 BD 8E C0 LDA $C08E,X
BD83 BD 8C C0 LDA $C08C,X
BD86 A9 07 LDA #$07 Seek back to track 3.5.
BD88 20 00 BE JSR $BE00
BD8B BD 88 C0 LDA $C088,X Turn off drive motor and exit gracefully.
BD8E 60 RTS
This is a disk write routine. It’s taking the data at $B000 (that mystery sector that was loaded even earlier in the boot) and writing it to track 1—because high scores.
That’s what’s at $B000. High scores.2
Why is this so distressing? Because it means I’ll get to include a full read/write RWTS on my crack (which I haven’t even starting building yet, but soon!) so it can save high scores like the original game. Because anything less is obviously unacceptable.
Let’s step back from the low-level code for a moment and talk about how this game interacts with the disk at a high level.
There is no runtime protection check. All the “protection” is structural: data is stored on whole tracks, half tracks, and even some consecutive quarter tracks. Once the game code is in memory, there are no nibble checks or secondary protections.
The game code itself contains no disk code. They’re completely isolated. I proved this by loading the game code from my work disk and jumping to the entry point. (I tested the animated introduction, but you can also run the game itself by loading the block $01 files into memory and jumping to $31F9. The game runs until you finish the level and it tries to load the first cut scene from disk.)
The game code communicates with the disk subsystem through the output vector, i.e., by printing #$00..#$06 to $FDED. The disk code handles filling the screen with a pseudo-random color, reading the right chunks from the right places on disk and putting them into the right places in memory, then jumping to the correct address to continue. (In the case of printing #$04, it handles writing the data in memory to the correct place on disk.)
Game code lives at $0800..$AFFF, the zero page, and one page at $B000 for high scores. The disk subsystem clobbers the text screen at $0400 using lo-res graphics for the color fills. All memory above $B100 is available; in fact, most of it is wiped (at $0300) after every disk command.
This is great news. It gives us total flexibility to recreate the game from its constituent pieces.
Here’s the plan: First we’ll write the game code to a standard 16-sector disk. Then we’ll write a bootloader and RWTS that can read the game code into memory. Finally, we’ll write some glue code to mimic the original output vector at$BF6F, so I don’t need to change any game code. Then we’ll declare victory and take a much needed nap.
Looking at the length of each block and dividing by 16, I can space everything out on separate tracks and still have plenty of room. This means each block can start on its own track, which saves a few bytes by being able to hard-code the starting sector for each block. The disk map arrangement is shown on page 263.
I wrote a build script in BASIC to take all the chunks of game code I captured way back on page 251.
] PR5
10 REM MAKE GUMBALL
11 REM S6 , D1 = BLANK DISK
12 REM S5 , D1 = WORK DISK
20 D$ = CHR$ (4)
29 REM Load the first part of block 0:
30 PRINT D$ " BLOAD BLOCK 00.0800 -1 FFF , A$1000 "
40 PRINT D$ " BLOAD BLOCK 00.2000 -3 FFF , A$2800 "
49 REM Write it to tracks $02 - $05 :
50 PAGE = 16: COUNT = 56: TRK = 2: SEC = 0: GOSUB 1000
59 ROM Load the second part of block 0:
60 PRINT D$ " BLOAD BLOCK 00.6000 -87 FF , A$6000 "
69 REM Write it to tracks $06 - $08 :
70 PAGE = 96: COUNT = 40: TRK = 6: SEC = 0: GOSUB 1000
79 REM And so on , for all the other blocks :
80 PRINT D$ " BLOAD BLOCK 01.0800 -1 FFF , A$1000 "
90 PRINT D$ " BLOAD BLOCK 01.2000 -3 FFF , A$2800 "
100 PAGE = 16: COUNT = 56: TRK = 9: SEC = 0: GOSUB 1000
110 PRINT D$ " BLOAD BLOCK 01.6000 - AFFF , A$6000 "
120 PAGE = 96: COUNT = 80: TRK = 13: SEC = 0: GOSUB 1000
130 PRINT D$ " BLOAD BLOCK 02.0800 -1 FFF , A$1000 "
tr |
memory range |
notes |
00 |
$BD00..$BFFF |
Gumboot |
01 |
$B000..$B3FF |
scores/zpage/glue |
02 |
$0800..$17FF |
block 0 |
03 |
$1800..$27FF |
block 0 |
04 |
$2800..$37FF |
block 0 |
05 |
$3800..$3FFF |
block 0 |
06 |
$6000..$67FF |
block 0 |
07 |
$6800..$77FF |
block 0 |
08 |
$7000..$87FF |
block 0 |
09 |
$0800..$17FF |
block 1 |
0A |
$1800..$27FF |
block 1 |
0B |
$2800..$37FF |
block 1 |
0C |
$3800..$3FFF |
block 1 |
0D |
$6000..$6FFF |
block 1 |
0E |
$7000..$7FFF |
block 1 |
0F |
$8000..$8FFF |
block 1 |
10 |
$9000..$9FFF |
block 1 |
11 |
$A000..$AFFF |
block 1 |
12 |
$0800..$17FF |
block 2 |
13 |
$1800..$27FF |
block 2 |
14 |
$2800..$37FF |
block 2 |
15 |
$3800..$3FFF |
block 2 |
16 |
$6000..$6FFF |
block 2 |
17 |
$7000..$7FFF |
block 2 |
18 |
$8000..$87FF |
block 2 |
19 |
$2000..$2FFF |
block 3 |
1A |
$3000..$3FFF |
block 3 |
Disk Mapping of our Cracked Disk
140 PRINT D$ " BLOAD BLOCK 02.2000 -3 FFF , A$2800 "
150 PAGE = 16: COUNT = 56: TRK = 18: SEC = 0: GOSUB 1000
160 PRINT D$ " BLOAD BLOCK 02.6000 -87 FF , A$6000 "
170 PAGE = 96: COUNT = 40: TRK = 22: SEC = 0: GOSUB 1000
180 PRINT D$ " BLOAD BLOCK 03.2000 -3 FFF , A$2000 "
190 PAGE = 32: COUNT = 32: TRK = 25: SEC = 0: GOSUB 1000
200 PRINT D$ " BLOAD BOOT2 0500 -07 FF , A$2500 "
210 PAGE = 39: COUNT = 1: TRK = 1: SEC = 0: GOSUB 1000
220 PRINT D$ " BLOAD BOOT3 0000 -00 FF , A$1000 "
230 POKE 4150 ,0: POKE 4151 ,178: REM SET ( $36 ) TO $B200
240 PAGE = 16: COUNT = 1: TRK = 1: SEC = 7: GOSUB 1000
999 END
1000 REM WRITE TO DISK
1010 PRINT D$ " BLOAD WRITE "
1020 POKE 908 , TRK
1030 POKE 909 , SEC
1040 POKE 913 , PAGE
1050 POKE 769 , COUNT
1060 CALL 768
1070 RETURN
] SAVE MAKE
The BASIC program relies on a short assembly language routine to do the actual writing to disk. Here is that routine, loaded on line 1010:
]CALL -151
0300 A9 D1 LDA #$D1 Page count, set from BASIC.
0302 85 FF STA $FF
0304 A9 00 LDA #$00 Logical sector, incremented.
0306 85 FE STA $FE
0308 A9 03 LDA #$03 Call RWTS to write sector.
030A A0 88 LDY #$88
030C 20 D9 03 JSR $03D9
030F E6 FE INC $FE Increment logical sector, wrap around from
0311 A4 FE LDY $FE $0F to $00 and increment track.
0313 C0 10 CPY #$10
0315 D0 07 BNE $031E
0317 A0 00 LDY #$00
0319 84 FE STY $FE
031B EE 8C 03 INC $038C
031E B9 40 03 LDA $0340,Y Convert logical to physical sector.
0321 8D 8D 03 STA $038D
0324 EE 91 03 INC $0391 Increment page to write.
0327 C6 FF DEC $FF Loop until done with all sectors.
0329 D0 DD BNE $0308
032B 60 RTS
*340.34F
logical to physical sector mapping
0340 00 07 0E 06 0D 05 0C 04
0348 0B 03 0A 02 09 01 08 0F
*388.397
Boom! The entire game is on tracks $02-$1A of a standard 16-sector disk. Now we get to write an RWTS.