Track Skew Protection

From Software Archive
Revision as of 11:28, 10 October 2026 by Enigma (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Track Skew Protection

The protection does not use any extra track, bad sector or signature. It checks the track skew: the angular position of each track relative to its neighbour. A duplicator writes all tracks in a fixed relation to each other. A normal disk copier writes each track independently, starting at an arbitrary point of the rotation, so the copied data is correct but the tracks' positions relative to each other are random.

The same drive routine, encrypted the same way (XOR $37), was found on releases by Ariolasoft, Rushware and others (see Releases). The description below follows Power-Pack (Rushware, 1987, sources 2, 3 and 6).

Disk layout

  • Tracks 1–35 use the ordinary CBM DOS layout. There is nothing beyond track 35.
  • Disk ID and DOS type in the BAM are $14 $14 (control characters).
  • The first directory entry is named LOADER",8,1 and shown with 0 blocks. The file is actually about 1,800 bytes long and loads to $0300. Its first bytes overwrite the BASIC vectors IERROR, IMAIN and ICRNCH with $0340, so it starts itself after loading.
  • The loader contains a BASIC menu ("POWER-PACK DISK COMPILATION … WHICH GAME DO YOU REQUIRE ?"). Before the menu it loads "POWER",8,1 and starts it with SYS 7280 ($1C70).
  • POWER (607 bytes) is identical on all four disk sides and contains the check.
  • Other releases carry the routine in their own loader files, for example FUGGER/XX on Die Fugger.

The check

C64 side (POWER)

POWER decrypts itself in three stages: $1C90 with EOR $12, then $1D2B with EOR $20, then $1DDD with EOR $37. After that it:

  1. uploads 256 bytes of drive code with M-W to drive $0300 (in 32-byte chunks);
  2. starts it with M-E $0300;
  3. reads the result table back with M-R from drive $0381 into C64 $5000;
  4. compares 14 bytes of the table with a fixed table.
$1CC2  A9 50       LDA #$50
$1CC4  8D F2 1C    STA $1CF2         ; patch the compare below: $E000 -> $5000
$1CC7  4C EE 1C    JMP $1CEE
...
$1CEE  A2 00       LDX #$00
$1CF0  BD 00 50    LDA $5000,X       ; table read from the drive
$1CF3  DD D0 1C    CMP $1CD0,X       ; expected: 04 05 06 ... 11
$1CF6  D0 08       BNE $1D00
$1CF8  E8          INX
$1CF9  E0 0E       CPX #$0E          ; 14 entries (tracks 2-15)
$1CFB  90 F3       BCC $1CF0
$1CFD  A9 00       LDA #$00          ; all equal: A = $00
$1CFF  2C A9 37    BIT $37A9         ; (hides "LDA #$37" at $1D00)
$1D02  85 02       STA $02
$1D04  C9 37       CMP #$37
$1D06  D0 04       BNE $1D0C
$1D08  A9 00       LDA #$00          ; mismatch: $01 = $00 -> all ROMs and
$1D0A  85 01       STA $01           ;   I/O switched off, the machine crashes
$1D0C  A9 00       LDA #$00
$1D0E  8D A6 02    STA $02A6
$1D11  60          RTS

On a mismatch the code writes $00 to the processor port $01. That switches off BASIC, KERNAL and I/O, and the program crashes on return.

Drive side ($0300)

The drive code measures, for each track from 16 down to 2, which sector header the drive meets first after arriving on that track:

$0308  A9 10       LDA #$10
$030A  85 06       STA $06           ; job 0 track: 16 (counts down)
$030C  A9 0F       LDA #$0F
$030E  85 07       STA $07           ; index: 15 (counts down)
$0310  78          SEI
$0311  A9 20       LDA #$20
$0313  8D 07 1C    STA $1C07         ; fast stepper timing
$0316  8D 05 1C    STA $1C05
$0319  58          CLI
$031A  A9 B0       LDA #$B0
$031C  85 00       STA $00           ; SEEK job: head to track $06,
$031E  A5 00       LDA $00           ;   synchronized to a header there
$0320  30 FC       BMI $031E
...
$032D  A9 F0       LDA #$F0          ; further job $F0
$032F  85 00       STA $00
...
$033C  A9 FE       LDA #$FE
$033E  20 76 D6    JSR $D676         ; ROM: step the head one track
$0341  A9 00       LDA #$00          ;   (two half-steps) behind DOS's back
$0343  85 22       STA $22           ; DOS: "current track unknown"
$0345  A9 B0       LDA #$B0
$0347  85 00       STA $00           ; SEEK again: DOS must read the first
$0349  A5 00       LDA $00           ;   header that passes under the head
$034B  30 FC       BMI $0349
...
$0354  A5 19       LDA $19           ; sector number of that header
$0356  A6 07       LDX $07
$0358  9D 80 03    STA $0380,X       ; -> result table $0381-$038F
$035B  C6 06       DEC $06
$035D  C6 07       DEC $07
$035F  D0 B0       BNE $0311         ; next track
...
$0370  ...                           ; finally: wipe $0300-$036F (ROR)
  • The time from reading a header, through stepping the head, to arriving on the next track is always the same. So the first sector found on arrival depends only on the angular offset between the two neighbouring tracks.
  • On the original disk, this sector number rises by exactly one per track. The expected table for tracks 2–15 is 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11.
  • After measuring, the drive code overwrites itself ($0300–$036F) with a ROR loop.

Notes:

  • The Newsroom: the drive routine is present on disk 1 (track 33, sector 16, unencrypted), but no program on the disk uses it and no check was found. The Newsroom is therefore not listed as using this protection.

Copying and imaging

  • A copy made track by track with an ordinary copier has the correct data but random track positions. The table doesn't match, and the loader crashes.
  • A G64 passes only if the conversion preserves each track's angular position relative to the index hole. A D64 image cannot pass.
  • Verified: rotating only track 9 of a working image by a third of a revolution changes exactly the two table entries that involve track 9 (… 0A 04 13 0D … instead of … 0A 0B 0C 0D …), and the check fails. The data on the track is unchanged.

Pages that refer to this protection