Rainbow Arts Track 37 Protection: Difference between revisions
Description of the Rainbow Arts Track 37 Protection (Turrican) |
Signature on both disk sides (also in the stream of source 5); G64 track 37 length note |
||
| (One intermediate revision by the same user not shown) | |||
| Line 6: | Line 6: | ||
* The track has '''exactly one sync'''. Because there is no other sync, the byte boundaries are fixed for the whole track: after the sync the drive reads the alternating bit pattern as $55 bytes. | * The track has '''exactly one sync'''. Because there is no other sync, the byte boundaries are fixed for the whole track: after the sync the drive reads the alternating bit pattern as $55 bytes. | ||
* On the original disk the sync is followed by '''4095 bytes $55''' and then by GCR coded $00 bytes (<code>52 94 A5 29 4A ...</code>) up to the sync. This was measured in the KryoFlux stream of [[Turrican]] source 5 (identical in all revolutions); the NBZ of source 1 shows the same layout. | * On the original disk the sync is followed by '''4095 bytes $55''' and then by GCR coded $00 bytes (<code>52 94 A5 29 4A ...</code>) up to the sync. This was measured in the KryoFlux stream of [[Turrican]] source 5 (identical in all revolutions); the NBZ of source 1 shows the same layout. | ||
* Both disk sides carry the signature on track 37 (G64 of [[Turrican]] source 1, KryoFlux stream of source 5; on side 1 of source 5 track 37 is in the file <code>track64.1.raw</code> because the stream of that side is shifted by 4 tracks). | |||
* The directory header shows "2369" in place of the disk ID and DOS type (<code>--->TURRICAN<--- 2369</code>), while the sector headers use the disk ID "XX". The same characters "2369" occur as constants in the drive code (see below). The Turrican releases with other protections have a normal directory header (disk ID and "2A"). | |||
* The data blocks of the program files mostly start with the marker $02 instead of $07, and their checksum is the XOR of the data bytes EOR $69 (also for some blocks with the marker $07). Only the custom fast loader reads these blocks; a normal DOS read reports them as errors (22 or 23). | * The data blocks of the program files mostly start with the marker $02 instead of $07, and their checksum is the XOR of the data bytes EOR $69 (also for some blocks with the marker $07). Only the custom fast loader reads these blocks; a normal DOS read reports them as errors (22 or 23). | ||
| Line 13: | Line 15: | ||
# The drive code copies a prepared page to the zero page and jumps to $0000. It does not use JMP or BRK: it pushes $FF twice (read from $FFFB) and executes RTS, so the return address $FFFF + 1 wraps to $0000. | # The drive code copies a prepared page to the zero page and jumps to $0000. It does not use JMP or BRK: it pushes $FF twice (read from $FFFB) and executes RTS, so the return address $FFFF + 1 wraps to $0000. | ||
# The code in the zero page starts VIA timer 2 ($1C08/$1C09) and | # The code in the zero page starts VIA timer 2 ($1C08/$1C09), computes values from the encrypted area $0300-$06FF, from ROM bytes at $F500 and from a series of <code>EOR #</code> constants (the characters "ACU 2369"), and uses the result in several self-modifying delay loops. Then it decrypts $0300-$07FF: for each byte it runs <code>EOR $1C08 / EOR $1C09 / EOR $00,X</code> with X from $00 to $FF, so the key consists of the timer values and of '''all 256 bytes of the zero page''', i.e. of the code itself. The decryption therefore only works with cycle exact timing, and changing any byte of the zero page code garbles the decrypted code. | ||
# After the decryption the code reads three bytes of the drive ROM: if $D2B7 contains $E5, it jumps to an address computed from $E5B8 and $E5B9 instead of $0300. $E5B8 and $E5B9 are two characters ("BM") of the version string "CBM DOS V2.6 1541" in the ROM. A second test later in the loader checks the ROM byte $EBC2 for $F2. None of 46 tested drive ROMs, Commodore ROMs as well as speeder and third-party ROMs, takes these branches. As part of the zero page code these bytes are also part of the decryption key. | |||
# The decrypted code issues a SEEK job on track 18 and then an EXECUTE job for buffer $0400 on track 37, which runs the check. | # The decrypted code issues a SEEK job on track 18 and then an EXECUTE job for buffer $0400 on track 37, which runs the check. | ||
| Line 45: | Line 48: | ||
* VICE (tested with 3.10) does not emulate the input latch of the VIA: <code>LDA $1C01</code> always returns the newest byte. The first byte of the check is then $BF or $AF (the end of the bit pattern before the sync), the check fails and the game stops at "LOADING". In a test where only this first byte was set to $55, the game loaded to the title screen. | * VICE (tested with 3.10) does not emulate the input latch of the VIA: <code>LDA $1C01</code> always returns the newest byte. The first byte of the check is then $BF or $AF (the end of the bit pattern before the sync), the check fails and the game stops at "LOADING". In a test where only this first byte was set to $55, the game loaded to the title screen. | ||
* Denise 1.1.1 and later emulates the latching; the game runs there. | * Denise 1.1.1 and later emulates the latching; the game runs there. | ||
* G64 images must contain track 37 with the single sync and the $55 run. A G64 converted from the NIB/NBZ with nibconv (default settings) keeps track 37 | * G64 images must contain track 37 with the single sync and the $55 run. A G64 converted from the NIB/NBZ with nibconv (default settings) keeps the single sync, the $55 run and the $00 area of track 37; because nibconv cuts the track at 7239 bytes, the $55 run is about 4190-4200 bytes long instead of 4095, which makes no difference for the check. | ||
== Pages that refer to this protection == | == Pages that refer to this protection == | ||
{{Special:Whatlinkshere/Rainbow_Arts_Track_37_Protection}} | {{Special:Whatlinkshere/Rainbow_Arts_Track_37_Protection}} | ||
Latest revision as of 22:49, 9 November 2026
A disk protection used by Rainbow Arts on Turrican (1990). The drive code checks a signature on track 37: a track with a single sync, followed by a long run of bytes that read as $55. The check only passes because the first byte comes from the input latch of the drive's VIA instead of from the disk. Emulators without this latch behaviour (e.g. VICE) fail the check.
Disk signature
- Track 37 is recorded at the bit rate of speed zone 2 (as on tracks 18-24). The 1541 DOS also reads tracks 36 and above at zone 2.
- The track has exactly one sync. Because there is no other sync, the byte boundaries are fixed for the whole track: after the sync the drive reads the alternating bit pattern as $55 bytes.
- On the original disk the sync is followed by 4095 bytes $55 and then by GCR coded $00 bytes (
52 94 A5 29 4A ...) up to the sync. This was measured in the KryoFlux stream of Turrican source 5 (identical in all revolutions); the NBZ of source 1 shows the same layout. - Both disk sides carry the signature on track 37 (G64 of Turrican source 1, KryoFlux stream of source 5; on side 1 of source 5 track 37 is in the file
track64.1.rawbecause the stream of that side is shifted by 4 tracks). - The directory header shows "2369" in place of the disk ID and DOS type (
--->TURRICAN<--- 2369), while the sector headers use the disk ID "XX". The same characters "2369" occur as constants in the drive code (see below). The Turrican releases with other protections have a normal directory header (disk ID and "2A"). - The data blocks of the program files mostly start with the marker $02 instead of $07, and their checksum is the XOR of the data bytes EOR $69 (also for some blocks with the marker $07). Only the custom fast loader reads these blocks; a normal DOS read reports them as errors (22 or 23).
Loader
The boot file SCHLUMPF loads to $0326 and starts itself through the KERNAL output vector. It loads the fast loader and uploads the drive code.
- The drive code copies a prepared page to the zero page and jumps to $0000. It does not use JMP or BRK: it pushes $FF twice (read from $FFFB) and executes RTS, so the return address $FFFF + 1 wraps to $0000.
- The code in the zero page starts VIA timer 2 ($1C08/$1C09), computes values from the encrypted area $0300-$06FF, from ROM bytes at $F500 and from a series of
EOR #constants (the characters "ACU 2369"), and uses the result in several self-modifying delay loops. Then it decrypts $0300-$07FF: for each byte it runsEOR $1C08 / EOR $1C09 / EOR $00,Xwith X from $00 to $FF, so the key consists of the timer values and of all 256 bytes of the zero page, i.e. of the code itself. The decryption therefore only works with cycle exact timing, and changing any byte of the zero page code garbles the decrypted code. - After the decryption the code reads three bytes of the drive ROM: if $D2B7 contains $E5, it jumps to an address computed from $E5B8 and $E5B9 instead of $0300. $E5B8 and $E5B9 are two characters ("BM") of the version string "CBM DOS V2.6 1541" in the ROM. A second test later in the loader checks the ROM byte $EBC2 for $F2. None of 46 tested drive ROMs, Commodore ROMs as well as speeder and third-party ROMs, takes these branches. As part of the zero page code these bytes are also part of the decryption key.
- The decrypted code issues a SEEK job on track 18 and then an EXECUTE job for buffer $0400 on track 37, which runs the check.
Check
0419 BIT $1C00 wait until SYNC is active
BMI $0419 (BIT also copies bit 6 of $1C00 to the V flag)
LDX #$10 4096 bytes
LDY #$00
0422 BVC $0422 wait for BYTE READY
CLV
LDA $1C01 read a byte
CMP #$55
BNE fail
DEY / BNE $0422 / DEX / BNE $0422
JMP $F418 job finished OK
The first byte is not read from the disk:
- Bit 6 of $1C00 is a speed zone bit. On track 37 (zone 2) it is 1, so after the wait loop the V flag is always set. The first BVC does not wait, and the first
LDA $1C01happens while the sync is still being read, without a new byte. - The 6522 VIA of the 1541 latches port A on every BYTE READY (input latching is enabled), but only while the interrupt flag of CA1 is clear; reading $1C01 clears it. So the latch still holds the first byte that arrived after the last read of $1C01. That last read was in the SEEK job on track 18: the 8th GCR byte of a sector header. The next byte, the 9th GCR byte of the header, is always $55 (the header ends with $0F $0F).
- The first comparison therefore uses the latched $55 from track 18, and the following 4095 comparisons use the $55 bytes of track 37. This is exactly the length of the $55 run on the original disk. A drive or emulator that delivered a real disk byte on the first read would read 4096 bytes from track 37, and the last one would not be $55.
If the check fails
The drive code writes $02 (an illegal opcode that halts the 6502) over the JMP instruction at the end of a cleanup routine, copies that routine to $0110, erases the drive code at $0300-$07FF and runs into the halt. The drive stops and the C64 waits forever on the "LOADING" screen. Nothing is written to the disk.
Emulation
- VICE (tested with 3.10) does not emulate the input latch of the VIA:
LDA $1C01always returns the newest byte. The first byte of the check is then $BF or $AF (the end of the bit pattern before the sync), the check fails and the game stops at "LOADING". In a test where only this first byte was set to $55, the game loaded to the title screen. - Denise 1.1.1 and later emulates the latching; the game runs there.
- G64 images must contain track 37 with the single sync and the $55 run. A G64 converted from the NIB/NBZ with nibconv (default settings) keeps the single sync, the $55 run and the $00 area of track 37; because nibconv cuts the track at 7239 bytes, the $55 run is about 4190-4200 bytes long instead of 4095, which makes no difference for the check.