By Perseverance, the Snail Reached the Ark

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

Image

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 Image
26D5     C9 EE   CMP #$EE

And look, more self-modifying code.

*26D5:CA
*26D5L
26D5        CA   DEX
26D6  EE D9 06   INC $06D9 Image
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 Image  from #$00 to #$FF.
26DE     61 A0   ADC ($A0,X)


*26DE:60
*26DEL
26DE        60   RTS

And now we’re back on the stack.

Image

$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 Image
260C        A8   TAY

Oh joy.

*260C:A9
*260CL
260C     A9 27   LDA #$27
260E  EE 11 06   INC $0611 Image
2611        17   ???


*2611:18
*2611L
2611        18   CLC
2612  EE 15 06   INC $0615 Image
2615        68   PLA


*2615:69
*2615L
2615     69 D9   ADC #$D9
2617  EE 1A 06   INC $061A Image
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     Image 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 $B200which 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

We flutter for a day, but think it’s forever.

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.

image

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.

In Which the Floodgates Burst Open

*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     ImageStore 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 Image
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

In Which We Go Completely Insane

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 whengoes 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 Image
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 Image  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.

In Which We are Restored to Sanity (Maybe)

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.

Image

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.

Every exit is an entrace to somewhere.

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.

Two wrongs make a write.

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

image

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.

The Right Ones in the Right Order

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.

A Man, a Plan, a Canal, &c.

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 Image  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

Image

Boom! The entire game is on tracks $02-$1A of a standard 16-sector disk. Now we get to write an RWTS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.227.69