Difference between revisions of "Bangkok Knights"
| Line 113: | Line 113: | ||
[[File:bangkok_knights_rene_s1.g64]] | [[File:bangkok_knights_rene_s1.g64]] | ||
| + | |||
| + | == Why Bangkok Knights does not load on a 1541-II == | ||
| + | |||
| + | Bangkok Knights loads on a 1541 but hangs on a 1541-II. The cause is not | ||
| + | the [[Para Protect]] track-40 read. Every ROM routine its drive-side reader | ||
| + | calls (<code>$F6D0</code>, <code>$F8E0</code>, <code>$F24B</code>, | ||
| + | <code>$F969</code>, the job loop at <code>$F31B</code>) is byte-identical in | ||
| + | both drive ROMs: 1541 325302-01 + 901229-05, and 1541-II 251968-03. The | ||
| + | failure also happens earlier, about 5 seconds into loading, while the head | ||
| + | is still on track 18. | ||
| + | |||
| + | === The faulty loop === | ||
| + | |||
| + | The cause is a drive-side loop in the game's boot chain. It copies drive ROM | ||
| + | from <code>$C000</code> over code at <code>$0700</code> that has already run, | ||
| + | presumably to erase it: | ||
| + | |||
| + | <pre> | ||
| + | $0727 A2 29 LDX #$29 | ||
| + | $0729 BD 00 C0 LDA $C000,X | ||
| + | $072C 9D 00 07 STA $0700,X | ||
| + | $072F CA DEX | ||
| + | $0730 10 F7 BPL $0729 | ||
| + | </pre> | ||
| + | |||
| + | The counter starts one byte too high. The first store | ||
| + | (<code>X = $29</code>) writes ROM byte <code>$C029</code> over the loop's | ||
| + | own opcode at <code>$0729</code>, so the next pass executes whatever that | ||
| + | ROM byte is. | ||
| + | |||
| + | === On a 1541 === | ||
| + | |||
| + | The 1541 ROM area <code>$C000</code>–<code>$C0FF</code> is filler, so | ||
| + | <code>$C029</code> = <code>$AA</code>, which is <code>TAX</code>: | ||
| + | |||
| + | # <code>$0729</code> now reads <code>AA 00 C0</code>. The next pass executes | ||
| + | <code>TAX</code>, so X = <code>$AA</code>. | ||
| + | # <code>$072A</code> is <code>$00</code>, a <code>BRK</code>. The drive's IRQ | ||
| + | handler at <code>$FE67</code> doesn't check for BRK and returns with | ||
| + | <code>RTI</code> to <code>$072C</code>. | ||
| + | # <code>STA $0700,X</code> writes to <code>$07AA</code>. Then | ||
| + | <code>DEX</code> gives <code>$A9</code>, which is negative, so | ||
| + | <code>BPL</code> falls through. | ||
| + | |||
| + | The loop ends after copying a single byte, and loading carries on as if | ||
| + | nothing happened. | ||
| + | |||
| + | === On a 1541-II === | ||
| + | |||
| + | The 1541-II ROM holds its copyright text in that area: | ||
| + | <code>"…COPYRIGHT (C)1982,1985,1987 COMMODORE ELECTRONICS…"</code>. So | ||
| + | <code>$C029</code> = <code>$4C</code> (the letter "L"), which is | ||
| + | <code>JMP</code>: | ||
| + | |||
| + | # <code>$0729</code> now reads <code>4C 00 C0</code>, which is | ||
| + | <code>JMP $C000</code>. | ||
| + | # The drive executes the copyright text as code and ends up at | ||
| + | <code>$C05F</code>. That is inside a helper routine that exists only in | ||
| + | the 1541-II ROM (<code>$C04E</code>, called from the format code at | ||
| + | <code>$FCAE</code>). | ||
| + | # The helper waits for byte-ready at <code>$C066</code>, which never comes, | ||
| + | so the drive hangs there. | ||
| + | # The C64 waits forever for the drive. | ||
| + | |||
| + | === Comparison with After Burner === | ||
| + | |||
| + | After Burner uses the same buffer-2 loader and contains the same loop: | ||
| + | |||
| + | <pre> | ||
| + | $072E A2 2F LDX #$2F | ||
| + | $0730 BD 00 C0 LDA $C000,X | ||
| + | $0733 9D 00 07 STA $0700,X | ||
| + | $0736 CA DEX | ||
| + | $0737 10 F7 BPL $0730 | ||
| + | </pre> | ||
| + | |||
| + | Here the copy covers <code>$0700</code>–<code>$072F</code> and stops one | ||
| + | byte short of the loop itself, so After Burner works on both drives. In | ||
| + | Bangkok Knights the counter is one too high for where the loop sits. This | ||
| + | looks like an off-by-one bug that the 1541's <code>$AA</code> filler | ||
| + | happened to hide, not a deliberate 1541-II check. | ||
| + | |||
| + | === Minimal patch that makes the game work on 1541-II === | ||
| + | |||
| + | The loop is stored unencrypted on the disk in '''track 18, sector 18'''. | ||
| + | The drive loads this sector to <code>$0700</code> with an ordinary DOS READ | ||
| + | job, but it has two quirks: | ||
| + | |||
| + | * Its data block ID is <code>$12</code> instead of <code>$07</code>. | ||
| + | * Its data checksum (<code>$F2</code>) is reused afterwards as a decryption key (<code>LDA $3A / STA $C1</code> at <code>$0705</code>), so the sector's checksum must not change. | ||
| + | |||
| + | A two-byte patch fixes the problem and leaves the checksum unchanged: | ||
| + | |||
| + | {| class="wikitable" | ||
| + | ! Sector offset !! Address !! Old !! New !! Effect | ||
| + | |- | ||
| + | | <code>$2A</code> || <code>$072A</code> || <code>$00</code> || <code>$C9</code> || <code>LDA $C000,X</code> becomes <code>LDA $C0C9,X</code>. With X = <code>$29</code> it reads <code>$C0F2</code>, which is filler <code>$AA</code> in both ROMs. | ||
| + | |- | ||
| + | | <code>$AA</code> || <code>$07AA</code> || <code>$00</code> || <code>$C9</code> || Compensates the checksum (<code>$00 XOR $C9</code> twice, so the XOR stays <code>$F2</code>). The loop overwrites this byte with <code>$AA</code> before anything reads it. | ||
| + | |} | ||
Latest revision as of 02:17, 4 October 2026
Contents
Source 1
| Property | Data |
|---|---|
| Title | Bangkok Knights |
| Publisher and/or Developer | System 3 Software Ltd., Activision Ltd. |
| Year | 1987 |
| Disk(s) | 1 |
| Number of Index Holes | 1 |
| Media Type | 5.25 DSDD |
| Retail, Budget or Compilation (with name) | Retail |
| Country of Release | UK |
| Language(s) | English |
| Platform | C64 |
| NTSC or PAL | PAL |
| Protection | Para Protect |
| Working? | Yes (requires 1541, protection check does not work on 1541-II) |
| Archived | 8 Jul 2014 Rakki |
| Verified by | enigma |
Purchased from: ebay.co.uk / gurl_gamer
Streams
File:Streams BangkokKnights Rakki.zip
G64
File:BangkokKnights Rakki s0.g64
File:BangkokKnights Rakki s1.g64
Source 2
| Property | Data |
|---|---|
| Title | Bangkok Knights |
| Publisher and/or Developer | Boeder/bitstar Games, System 3 Software Ltd., Activision Ltd. |
| Year | 03/1992 |
| Disk(s) | 1 |
| Number of Index Holes | 1 |
| Media Type | 5.25 DSDD |
| Retail, Budget or Compilation (with name) | Budget |
| Country of Release | DE |
| Language(s) | English |
| Platform | C64 |
| NTSC or PAL | PAL |
| Protection | None |
| Working? | Yes, Stream has an error on side 1 track 31 sector 11, but could be repaired in G64. |
| Archived | 18 Dec 2022 Rene |
| Verified by | enigma |
Streams
File:Streams bangkok knights rene.zip
G64
File:Bangkok knights rene s0.g64
File:Bangkok knights rene s1.g64
Why Bangkok Knights does not load on a 1541-II
Bangkok Knights loads on a 1541 but hangs on a 1541-II. The cause is not
the Para Protect track-40 read. Every ROM routine its drive-side reader
calls ($F6D0, $F8E0, $F24B,
$F969, the job loop at $F31B) is byte-identical in
both drive ROMs: 1541 325302-01 + 901229-05, and 1541-II 251968-03. The
failure also happens earlier, about 5 seconds into loading, while the head
is still on track 18.
The faulty loop
The cause is a drive-side loop in the game's boot chain. It copies drive ROM
from $C000 over code at $0700 that has already run,
presumably to erase it:
$0727 A2 29 LDX #$29 $0729 BD 00 C0 LDA $C000,X $072C 9D 00 07 STA $0700,X $072F CA DEX $0730 10 F7 BPL $0729
The counter starts one byte too high. The first store
(X = $29) writes ROM byte $C029 over the loop's
own opcode at $0729, so the next pass executes whatever that
ROM byte is.
On a 1541
The 1541 ROM area $C000–$C0FF is filler, so
$C029 = $AA, which is TAX:
$0729now readsAA 00 C0. The next pass executes
TAX, so X =$AA.
$072Ais$00, aBRK. The drive's IRQ
handler at$FE67doesn't check for BRK and returns withRTIto$072C.
STA $0700,Xwrites to$07AA. Then
DEXgives$A9, which is negative, soBPLfalls through.
The loop ends after copying a single byte, and loading carries on as if nothing happened.
On a 1541-II
The 1541-II ROM holds its copyright text in that area:
"…COPYRIGHT (C)1982,1985,1987 COMMODORE ELECTRONICS…". So
$C029 = $4C (the letter "L"), which is
JMP:
$0729now reads4C 00 C0, which is
JMP $C000.
- The drive executes the copyright text as code and ends up at
$C05F. That is inside a helper routine that exists only in the 1541-II ROM ($C04E, called from the format code at$FCAE).
- The helper waits for byte-ready at
$C066, which never comes,
so the drive hangs there.
- The C64 waits forever for the drive.
Comparison with After Burner
After Burner uses the same buffer-2 loader and contains the same loop:
$072E A2 2F LDX #$2F $0730 BD 00 C0 LDA $C000,X $0733 9D 00 07 STA $0700,X $0736 CA DEX $0737 10 F7 BPL $0730
Here the copy covers $0700–$072F and stops one
byte short of the loop itself, so After Burner works on both drives. In
Bangkok Knights the counter is one too high for where the loop sits. This
looks like an off-by-one bug that the 1541's $AA filler
happened to hide, not a deliberate 1541-II check.
Minimal patch that makes the game work on 1541-II
The loop is stored unencrypted on the disk in track 18, sector 18.
The drive loads this sector to $0700 with an ordinary DOS READ
job, but it has two quirks:
- Its data block ID is
$12instead of$07. - Its data checksum (
$F2) is reused afterwards as a decryption key (LDA $3A / STA $C1at$0705), so the sector's checksum must not change.
A two-byte patch fixes the problem and leaves the checksum unchanged:
| Sector offset | Address | Old | New | Effect |
|---|---|---|---|---|
$2A |
$072A |
$00 |
$C9 |
LDA $C000,X becomes LDA $C0C9,X. With X = $29 it reads $C0F2, which is filler $AA in both ROMs.
|
$AA |
$07AA |
$00 |
$C9 |
Compensates the checksum ($00 XOR $C9 twice, so the XOR stays $F2). The loop overwrites this byte with $AA before anything reads it.
|