Difference between revisions of "Para Protect"
| Line 1: | Line 1: | ||
| − | + | = Para Protect = | |
| + | |||
| + | This analysis is based on the state of the wiki from 3 Oct 2026. | ||
| + | |||
| + | '''Para Protect''' is a Commodore 64 disk protection found on 47 game and | ||
| + | compilation pages of this wiki, among them several Elite compilations | ||
| + | (''Best of Elite'', ''Hit Pak'', ''6*Pak'', ''Fist 'n' Throttles''). The disks announce it | ||
| + | openly: the boot file is usually called <code>PROTECTED BY PARA…</code>, or | ||
| + | the directory carries "PARA-PROTECT" art. | ||
| + | |||
| + | In short: '''one 256-byte block of the program is stored on track 40, | ||
| + | sector 14''' rather than on tracks 1–35. The Para fast loader fetches that | ||
| + | block as an ordinary part of loading the game. A copy made with a | ||
| + | 35-track copier doesn't have the block, so the loader waits for it forever. | ||
| + | |||
| + | This is not a signature check in the usual sense. Nothing read from | ||
| + | track 40 is compared against a constant, and there is no compare-and-branch | ||
| + | to patch out. The track-40 block is simply missing code or data the game | ||
| + | needs (start-up code, the next loader stage, or a data block), so a copy | ||
| + | can't be fixed without knowing what belongs there. | ||
| + | |||
| + | == Disk layout == | ||
| + | |||
| + | <pre> | ||
| + | Track 1 ─────────────────────────────────────── 35 | ||
| + | ordinary CBM DOS layout, the visible file is | ||
| + | the Para boot (load address $00AE) | ||
| + | |||
| + | Track 36-39 unused / unformatted | ||
| + | |||
| + | Track 40 normally formatted CBM track: | ||
| + | $08 headers / $07 data blocks, valid | ||
| + | checksums, same disk ID as tracks 1-35 | ||
| + | sector 14 program block <-- read by the loader | ||
| + | sector 15, 16 further program blocks (multi-load | ||
| + | disks only, one per load entry) | ||
| + | all others zero-filled | ||
| + | </pre> | ||
| + | |||
| + | * The track-40 sectors themselves are completely standard. There is no sync manipulation, bad checksum, or non-standard GCR. The protection relies only on the track lying outside the 35 tracks that copiers and D64 images normally cover. | ||
| + | * Some masters format only the payload sectors: Deliverance and Hellfire Attack have only sectors 14–16 on track 40; P47 Thunderbolt, Xenophobe and ''Six Appeal'' Disk 2 have only sector 14. | ||
| + | * The payload differs from title to title; no two titles share one. Examples: | ||
| + | ** Deliverance: start-up code (<code>SEI / LDA #$35 / STA $01 …</code>), loaded to <code>$C000</code> and jumped to. | ||
| + | ** American Indoor Soccer: the next loader stage, loaded to <code>$6000</code> during boot. | ||
| + | * Where sector 14 differs between dumps of the same game, the cause is a different build of the game, not disk damage; checksums are valid in all cases: | ||
| + | ** Hypaball: 3 bytes differ. | ||
| + | ** Empire!: 3 bytes differ. | ||
| + | ** After Burner (European version): a 9-byte code change. | ||
| + | * On Elite compilation disks, sectors 14, 15 and (where present) 16 belong to the different games on the disk. | ||
| + | |||
| + | == Boot == | ||
| + | |||
| + | The boot file is the recognisable part of the scheme: | ||
| + | |||
| + | * It is the only real file on the disk side, and it '''loads to <code>$00AE</code>''', which is the KERNAL's own load-address pointer. | ||
| + | * The first bytes loaded overwrite <code>$AE/$AF</code> while the LOAD is still running. That redirects the rest of the file through zero page, the stack page and the <code>$0300</code> vector table. | ||
| + | * <code>ISTOP</code> is redirected to <code>$00ED</code>, so Para code is already running before the KERNAL <code>LOAD"*",8,1</code> returns, so no <code>RUN</code> is needed. | ||
| + | |||
| + | The boot then uploads drive code. From then on the C64 requests blocks by | ||
| + | track/sector over a 2-bit <code>$DD00</code>/<code>$1800</code> protocol | ||
| + | with raster-timed handshakes. | ||
| + | |||
| + | == Two families == | ||
| + | |||
| + | Every Para disk examined reads '''track 40, sector 14 first'''. The | ||
| + | differences lie in the loader around that read. There are two code bases: | ||
| + | |||
| + | {| class="wikitable" | ||
| + | ! Family !! Boot !! Track-40 read !! Titles | ||
| + | |- | ||
| + | | '''A''' || <code>$00AE</code> boot, 508–1584 bytes; per-title tables and data on a common code base || custom reader running as a DOS <code>$E0</code> (EXECUTE) job || all titles except family B (94 sides, including all Elite compilations) | ||
| + | |- | ||
| + | | '''B''' || <code>$00AE</code> boot, always 843 bytes; different code base (about 25% byte similarity to family A) || stock DOS <code>$80</code> (READ) job into buffer 1 (<code>$0400</code>) || Hypaball, Empire!, Bobby Bearing, The Sentinel, Druid (14 sides; the 1986–87 Odin, Firebird and Softek titles) | ||
| + | |} | ||
| + | |||
| + | === Family A: the drive-side reader === | ||
| + | |||
| + | The C64 sends four bytes to the drive program at <code>$0703</code>: block | ||
| + | count, track, sector, and a follow-up command. The drive program puts | ||
| + | these into <code>$C1</code>/<code>$0C</code>/<code>$0D</code> and submits an | ||
| + | '''EXECUTE job for buffer 3'''. The stock ROM job loop therefore does the | ||
| + | head stepping to the requested track. The job code at <code>$0600</code> | ||
| + | then reads the sector with its own loop, not the ROM's. | ||
| + | |||
| + | Captured from drive RAM at the moment the head steps to track 40 | ||
| + | (Thundercats; <code>$0C</code>=40, <code>$0D</code>=14, <code>$C1</code>=1): | ||
| + | |||
| + | <pre> | ||
| + | ; ---- drive main loop: receive request, run the job ---- | ||
| + | $0716 20 5B 07 JSR $075B ; receive byte (2-bit $1800 protocol) | ||
| + | $0719 85 C1 STA $C1 ; number of blocks | ||
| + | $071B 20 5B 07 JSR $075B | ||
| + | $071E 85 0C STA $0C ; track (job 3 header table entry) | ||
| + | $0720 20 5B 07 JSR $075B | ||
| + | $0723 85 0D STA $0D ; sector | ||
| + | $0725 20 5B 07 JSR $075B | ||
| + | $0728 48 PHA ; command after the transfer | ||
| + | $0729 58 CLI | ||
| + | $072A A9 E0 LDA #$E0 | ||
| + | $072C 85 03 STA $03 ; job 3 = EXECUTE -> ROM seeks, then JMP $0600 | ||
| + | $072E A5 03 LDA $03 | ||
| + | $0730 30 FC BMI $072E ; wait for job end | ||
| + | $0732 C9 7F CMP #$7F | ||
| + | $0734 90 F4 BCC $072A ; any other status -> simply run it again | ||
| + | $0736 A5 0C LDA $0C | ||
| + | $0738 C9 24 CMP #$24 | ||
| + | $073A 90 0C BCC $0748 ; ended on a track >= 36 (i.e. track 40): | ||
| + | $073C A9 12 LDA #$12 ; put the head back on track 18 | ||
| + | $073E 85 0C STA $0C ; with an ordinary DOS READ job | ||
| + | $0740 A9 80 LDA #$80 | ||
| + | $0742 85 03 STA $03 | ||
| + | ... | ||
| + | $0748 68 PLA | ||
| + | $0749 C9 01 CMP #$01 ; 1 = wait for the next request | ||
| + | $074B F0 BC BEQ $0709 | ||
| + | $074D C9 02 CMP #$02 ; 2 = park, hold the bus | ||
| + | $074F F0 03 BEQ $0754 | ||
| + | $0751 6C FC FF JMP ($FFFC) ; anything else = reset the drive | ||
| + | |||
| + | ; ---- EXECUTE job, buffer 3 ---- | ||
| + | $0600 A9 03 LDA #$03 | ||
| + | $0602 85 31 STA $31 ; buffer pointer -> $0300 | ||
| + | $0604 A5 12 LDA $12 ; build the wanted header: | ||
| + | $0606 85 16 STA $16 ; ID1, ID2 (from the disk's own ID) | ||
| + | $0608 A5 13 LDA $13 | ||
| + | $060A 85 17 STA $17 | ||
| + | $060C A5 0C LDA $0C | ||
| + | $060E 85 18 STA $18 ; track | ||
| + | $0610 85 22 STA $22 | ||
| + | $0612 A5 0D LDA $0D | ||
| + | $0614 85 19 STA $19 ; sector | ||
| + | $0616 A9 00 LDA #$00 | ||
| + | $0618 45 16 EOR $16 | ||
| + | $061A 45 17 EOR $17 | ||
| + | $061C 45 18 EOR $18 | ||
| + | $061E 45 19 EOR $19 | ||
| + | $0620 85 1A STA $1A ; header checksum | ||
| + | $0622 20 34 F9 JSR $F934 ; ROM: GCR-encode header -> $24-$2B | ||
| + | $0625 20 A8 06 JSR $06A8 ; wait for sync, Y=0 | ||
| + | $0628 50 FE BVC $0628 | ||
| + | $062A B8 CLV | ||
| + | $062B AD 01 1C LDA $1C01 | ||
| + | $062E D9 24 00 CMP $0024,Y ; compare 8 GCR header bytes | ||
| + | $0631 D0 F2 BNE $0625 ; mismatch -> next sync, NO retry limit | ||
| + | $0633 C8 INY | ||
| + | $0634 C0 08 CPY #$08 | ||
| + | $0636 D0 F0 BNE $0628 | ||
| + | $0638 20 A8 06 JSR $06A8 ; header found: sync of the data block | ||
| + | ... ; read 256 + 70 raw GCR bytes | ||
| + | $0654 20 E0 F8 JSR $F8E0 ; ROM: GCR-decode the data block | ||
| + | $0657 B9 00 03 LDA $0300,Y | ||
| + | $065A 20 B4 06 JSR $06B4 ; send byte to C64 (2 bits per $1800 write) | ||
| + | $065D C8 INY | ||
| + | $065E D0 F7 BNE $0657 | ||
| + | $0660 A5 0C LDA $0C ; next sector: interleave 8 | ||
| + | $0662 20 4B F2 JSR $F24B ; (ROM: speed zone of the track) | ||
| + | $0665 BD FF 06 LDA $06FF,X ; sectors per track | ||
| + | ... ; on wrap: INC $0C and step the head | ||
| + | $068C A9 96 LDA #$96 ; itself (timer $96 per half-track) | ||
| + | ... | ||
| + | $069C C6 C1 DEC $C1 ; more blocks? | ||
| + | $069E D0 05 BNE $06A5 | ||
| + | $06A0 A9 7F LDA #$7F | ||
| + | $06A2 4C 69 F9 JMP $F969 ; job end, status $7F | ||
| + | $06A5 4C 04 06 JMP $0604 | ||
| + | |||
| + | $06A8 2C 00 1C BIT $1C00 ; sync wait | ||
| + | $06AB 30 FB BMI $06A8 | ||
| + | </pre> | ||
| + | |||
| + | The header search at <code>$0625</code>–<code>$0636</code> has no retry | ||
| + | counter. On a disk without track 40 the head steps out to track 40 and the | ||
| + | job loops through <code>$06A8</code>/<code>$06AB</code> forever, waiting | ||
| + | for a header that doesn't exist. No error is reported, and the C64 sits in | ||
| + | its receive loop. Once the track-40 block has been read, the drive moves | ||
| + | the head back to track 18 with an ordinary DOS READ job. | ||
| + | |||
| + | === Family A: reader variants === | ||
| + | |||
| + | There are five versions of the drive reader, all with the same request | ||
| + | protocol and the same track-40 read. Three are one-byte or one-instruction | ||
| + | edits of the standard reader; two are a re-implementation in buffer 2. | ||
| + | |||
| + | {| class="wikitable" | ||
| + | ! Variant !! Difference to the standard reader !! Titles | ||
| + | |- | ||
| + | | '''A-std''' || — || American Indoor Soccer, Rick Dangerous 2, Deliverance, Thundercats, Winter Olympiad 88 (Tynesoft), Superstar Ice Hockey, Rimrunner, SDI, Heatseeker, Hellfire Attack, Overlander, Buggy Boy, Gazza II, Pac-Land, Stormlord, Vixen, Track & Field, The Last Ninja, Kinetik, Star Wars, Evening Star (side 1), Galactic Games, Xenophobe, MicroProse Soccer, She-Fox, The Fury, Troll, Beyond the Ice Palace, ''Soccer Stars'' Disk 2, all Elite compilations | ||
| + | |- | ||
| + | | '''A-interleave1''' || 1 byte: sector step <code>ADC #$01</code> instead of <code>ADC #$08</code>, so consecutive sectors are read. The game reads 3 blocks, track 40 sectors 14–16, in one request. || F-16 Combat Pilot | ||
| + | |- | ||
| + | | '''A-fixedT35''' || 1 instruction: the sectors-per-track lookup uses a fixed <code>LDA #$23</code> (track 35) instead of the current track. Makes no difference on track 40. || P47 Thunderbolt, ''Six Appeal'' Disk 2 (= P47) | ||
| + | |- | ||
| + | | '''A-buf2''' || Re-implemented: job in buffer 2 (<code>$0500</code>), parameters in <code>$0A/$0B</code>, header built with ROM <code>$F6D0</code>. Interleave 8 and step timer <code>#$96</code> as in A-std. || Bangkok Knights | ||
| + | |- | ||
| + | | '''A-buf2-interleave1''' || As A-buf2, plus interleave 1 with a hard 21-sector wrap (<code>CMP #$15</code>) and step timer <code>#$94</code> || After Burner | ||
| + | |} | ||
| + | |||
| + | === Family A: when track 40 is read === | ||
| + | |||
| + | Family A disks read the track-40 block at one of two points. This is | ||
| + | independent of which reader variant they use. | ||
| + | |||
| + | * '''During boot.''' The zero-page boot loader fetches its next stage from track 40, sector 14 (e.g. American Indoor Soccer, Thundercats). The read happens 2–40 seconds into the boot, so a copy hangs while still loading. | ||
| + | * '''At game start.''' The boot holds a title screen or menu and a table-driven loader (<code>LDA $0335,X / $033C,X / $0343,X / $034A,X</code> = destination page, block count, track, sector). Each menu entry pairs the main game body with '''one start-up block on track 40'''. In Deliverance, entries 1, 2 and 3 load track 40, sectors 14, 15 and 16 to <code>$C000</code>, followed by <code>JMP $C000</code>. The Elite compilations work the same way per game. A copy shows the title screen and menu normally and hangs only after the key press or menu selection (seen on The Last Ninja, Stormlord, Bangkok Knights and ''Best of Elite'' Vol. 1 disk 1A). | ||
| + | |||
| + | === Family B === | ||
| + | |||
| + | Family B also reads track 40, sector 14, but uses an | ||
| + | '''unmodified DOS <code>$80</code> READ job in buffer 1'''. The drive's | ||
| + | job queue at the moment of the seek shows <code>$01 = $80</code> with | ||
| + | header table entry track 40, sector 14. The stock ROM reader with its | ||
| + | normal checksum handling does the read; there is no custom GCR code. | ||
| + | What a copy without track 40 does in family B was not determined. In the | ||
| + | test run, both the original and the 35-track copy ended in a machine reset | ||
| + | in the emulator. | ||
| + | |||
| + | === Elite <code>MENU</code> boot === | ||
| + | |||
| + | Elite's compilation disks (''Best of Elite'' Vol. 1/2, ''Hit Pak Top 10'', | ||
| + | ''6*Pak'' Vol. 3 Disk 2, ''Fist 'n' Throttles'') boot a <code>MENU</code> | ||
| + | file (load address <code>$0326</code>, "PARA-PROTECT" directory art) | ||
| + | instead of loading the <code>$00AE</code> boot directly. The selected | ||
| + | game's family-A Para boot is then loaded from the menu, so these disks use | ||
| + | the A-std reader. | ||
| + | |||
| + | == Copying == | ||
| + | |||
| + | * Track 40 holds only standard, checksum-valid sectors. There are no sync tricks, unusual densities or deliberate errors, so a copy only has to include track 40. | ||
| + | * A 35-track copy or D64 image loses the block. Family A then hangs with the drive head on track 40 (see the reader above). Depending on the title, that happens during loading or only after the title-screen key press. | ||
| + | * Bangkok Knights' page notes that the check fails on a 1541-II; under VICE it was traced with the drive type set to 1541. | ||
| + | |||
| + | == Verification on this wiki == | ||
| + | |||
| + | All 122 disk sides on this wiki listed as Para Protect (90 sources) were | ||
| + | checked. Each side was checked statically from its G64 and by tracing | ||
| + | under VICE with true drive emulation, with a watchpoint on the drive | ||
| + | moving the head to track 40. | ||
| + | |||
| + | * '''All 108 bootable Para sides''' have a checksum-valid track 40 with the payload in sector 14 (plus 15/16 where the loader table asks for them). | ||
| + | * All 108 were confirmed to read it: | ||
| + | ** 104 by stopping the emulation at the track-40 job, either on that side or on a side with a byte-identical boot file. | ||
| + | ** 4 by booting a 35-track copy next to the original: the original loads, and the copy stops in the reader's sync wait for track 40, sector 14. | ||
| + | * The remaining 14 sides are data sides without a boot of their own, loaded by the protected side's loader. They have no track 40 and need none. | ||
| + | * '''Frank Bruno's Boxing''' is a special case: | ||
| + | ** Side 1 carries the Para directory art and the same side-2 Para boot as ''Best of Elite'' Vol. 1 disk 2B. | ||
| + | ** The game itself boots from side 0, which has no Para code. It loads side 1 by filename through the KERNAL (<code>"BOXER n"</code>, <code>"ANIMB n"</code>), so track 40 is never read. | ||
| + | ** Neither the Frank Bruno side 1 stream nor the ''Best of Elite'' 2B side contains a track 40. | ||
| + | ** The Para label on this release is nominal. | ||
==== Pages that refer to this protection ==== | ==== Pages that refer to this protection ==== | ||
{{Special:Whatlinkshere/Para Protect}} | {{Special:Whatlinkshere/Para Protect}} | ||
Latest revision as of 01:38, 4 October 2026
Contents
Para Protect
This analysis is based on the state of the wiki from 3 Oct 2026.
Para Protect is a Commodore 64 disk protection found on 47 game and
compilation pages of this wiki, among them several Elite compilations
(Best of Elite, Hit Pak, 6*Pak, Fist 'n' Throttles). The disks announce it
openly: the boot file is usually called PROTECTED BY PARA…, or
the directory carries "PARA-PROTECT" art.
In short: one 256-byte block of the program is stored on track 40, sector 14 rather than on tracks 1–35. The Para fast loader fetches that block as an ordinary part of loading the game. A copy made with a 35-track copier doesn't have the block, so the loader waits for it forever.
This is not a signature check in the usual sense. Nothing read from track 40 is compared against a constant, and there is no compare-and-branch to patch out. The track-40 block is simply missing code or data the game needs (start-up code, the next loader stage, or a data block), so a copy can't be fixed without knowing what belongs there.
Disk layout
Track 1 ─────────────────────────────────────── 35
ordinary CBM DOS layout, the visible file is
the Para boot (load address $00AE)
Track 36-39 unused / unformatted
Track 40 normally formatted CBM track:
$08 headers / $07 data blocks, valid
checksums, same disk ID as tracks 1-35
sector 14 program block <-- read by the loader
sector 15, 16 further program blocks (multi-load
disks only, one per load entry)
all others zero-filled
- The track-40 sectors themselves are completely standard. There is no sync manipulation, bad checksum, or non-standard GCR. The protection relies only on the track lying outside the 35 tracks that copiers and D64 images normally cover.
- Some masters format only the payload sectors: Deliverance and Hellfire Attack have only sectors 14–16 on track 40; P47 Thunderbolt, Xenophobe and Six Appeal Disk 2 have only sector 14.
- The payload differs from title to title; no two titles share one. Examples:
- Deliverance: start-up code (
SEI / LDA #$35 / STA $01 …), loaded to$C000and jumped to. - American Indoor Soccer: the next loader stage, loaded to
$6000during boot.
- Deliverance: start-up code (
- Where sector 14 differs between dumps of the same game, the cause is a different build of the game, not disk damage; checksums are valid in all cases:
- Hypaball: 3 bytes differ.
- Empire!: 3 bytes differ.
- After Burner (European version): a 9-byte code change.
- On Elite compilation disks, sectors 14, 15 and (where present) 16 belong to the different games on the disk.
Boot
The boot file is the recognisable part of the scheme:
- It is the only real file on the disk side, and it loads to
$00AE, which is the KERNAL's own load-address pointer. - The first bytes loaded overwrite
$AE/$AFwhile the LOAD is still running. That redirects the rest of the file through zero page, the stack page and the$0300vector table. ISTOPis redirected to$00ED, so Para code is already running before the KERNALLOAD"*",8,1returns, so noRUNis needed.
The boot then uploads drive code. From then on the C64 requests blocks by
track/sector over a 2-bit $DD00/$1800 protocol
with raster-timed handshakes.
Two families
Every Para disk examined reads track 40, sector 14 first. The differences lie in the loader around that read. There are two code bases:
| Family | Boot | Track-40 read | Titles |
|---|---|---|---|
| A | $00AE boot, 508–1584 bytes; per-title tables and data on a common code base |
custom reader running as a DOS $E0 (EXECUTE) job |
all titles except family B (94 sides, including all Elite compilations) |
| B | $00AE boot, always 843 bytes; different code base (about 25% byte similarity to family A) |
stock DOS $80 (READ) job into buffer 1 ($0400) |
Hypaball, Empire!, Bobby Bearing, The Sentinel, Druid (14 sides; the 1986–87 Odin, Firebird and Softek titles) |
Family A: the drive-side reader
The C64 sends four bytes to the drive program at $0703: block
count, track, sector, and a follow-up command. The drive program puts
these into $C1/$0C/$0D and submits an
EXECUTE job for buffer 3. The stock ROM job loop therefore does the
head stepping to the requested track. The job code at $0600
then reads the sector with its own loop, not the ROM's.
Captured from drive RAM at the moment the head steps to track 40
(Thundercats; $0C=40, $0D=14, $C1=1):
; ---- drive main loop: receive request, run the job ---- $0716 20 5B 07 JSR $075B ; receive byte (2-bit $1800 protocol) $0719 85 C1 STA $C1 ; number of blocks $071B 20 5B 07 JSR $075B $071E 85 0C STA $0C ; track (job 3 header table entry) $0720 20 5B 07 JSR $075B $0723 85 0D STA $0D ; sector $0725 20 5B 07 JSR $075B $0728 48 PHA ; command after the transfer $0729 58 CLI $072A A9 E0 LDA #$E0 $072C 85 03 STA $03 ; job 3 = EXECUTE -> ROM seeks, then JMP $0600 $072E A5 03 LDA $03 $0730 30 FC BMI $072E ; wait for job end $0732 C9 7F CMP #$7F $0734 90 F4 BCC $072A ; any other status -> simply run it again $0736 A5 0C LDA $0C $0738 C9 24 CMP #$24 $073A 90 0C BCC $0748 ; ended on a track >= 36 (i.e. track 40): $073C A9 12 LDA #$12 ; put the head back on track 18 $073E 85 0C STA $0C ; with an ordinary DOS READ job $0740 A9 80 LDA #$80 $0742 85 03 STA $03 ... $0748 68 PLA $0749 C9 01 CMP #$01 ; 1 = wait for the next request $074B F0 BC BEQ $0709 $074D C9 02 CMP #$02 ; 2 = park, hold the bus $074F F0 03 BEQ $0754 $0751 6C FC FF JMP ($FFFC) ; anything else = reset the drive ; ---- EXECUTE job, buffer 3 ---- $0600 A9 03 LDA #$03 $0602 85 31 STA $31 ; buffer pointer -> $0300 $0604 A5 12 LDA $12 ; build the wanted header: $0606 85 16 STA $16 ; ID1, ID2 (from the disk's own ID) $0608 A5 13 LDA $13 $060A 85 17 STA $17 $060C A5 0C LDA $0C $060E 85 18 STA $18 ; track $0610 85 22 STA $22 $0612 A5 0D LDA $0D $0614 85 19 STA $19 ; sector $0616 A9 00 LDA #$00 $0618 45 16 EOR $16 $061A 45 17 EOR $17 $061C 45 18 EOR $18 $061E 45 19 EOR $19 $0620 85 1A STA $1A ; header checksum $0622 20 34 F9 JSR $F934 ; ROM: GCR-encode header -> $24-$2B $0625 20 A8 06 JSR $06A8 ; wait for sync, Y=0 $0628 50 FE BVC $0628 $062A B8 CLV $062B AD 01 1C LDA $1C01 $062E D9 24 00 CMP $0024,Y ; compare 8 GCR header bytes $0631 D0 F2 BNE $0625 ; mismatch -> next sync, NO retry limit $0633 C8 INY $0634 C0 08 CPY #$08 $0636 D0 F0 BNE $0628 $0638 20 A8 06 JSR $06A8 ; header found: sync of the data block ... ; read 256 + 70 raw GCR bytes $0654 20 E0 F8 JSR $F8E0 ; ROM: GCR-decode the data block $0657 B9 00 03 LDA $0300,Y $065A 20 B4 06 JSR $06B4 ; send byte to C64 (2 bits per $1800 write) $065D C8 INY $065E D0 F7 BNE $0657 $0660 A5 0C LDA $0C ; next sector: interleave 8 $0662 20 4B F2 JSR $F24B ; (ROM: speed zone of the track) $0665 BD FF 06 LDA $06FF,X ; sectors per track ... ; on wrap: INC $0C and step the head $068C A9 96 LDA #$96 ; itself (timer $96 per half-track) ... $069C C6 C1 DEC $C1 ; more blocks? $069E D0 05 BNE $06A5 $06A0 A9 7F LDA #$7F $06A2 4C 69 F9 JMP $F969 ; job end, status $7F $06A5 4C 04 06 JMP $0604 $06A8 2C 00 1C BIT $1C00 ; sync wait $06AB 30 FB BMI $06A8
The header search at $0625–$0636 has no retry
counter. On a disk without track 40 the head steps out to track 40 and the
job loops through $06A8/$06AB forever, waiting
for a header that doesn't exist. No error is reported, and the C64 sits in
its receive loop. Once the track-40 block has been read, the drive moves
the head back to track 18 with an ordinary DOS READ job.
Family A: reader variants
There are five versions of the drive reader, all with the same request protocol and the same track-40 read. Three are one-byte or one-instruction edits of the standard reader; two are a re-implementation in buffer 2.
| Variant | Difference to the standard reader | Titles |
|---|---|---|
| A-std | — | American Indoor Soccer, Rick Dangerous 2, Deliverance, Thundercats, Winter Olympiad 88 (Tynesoft), Superstar Ice Hockey, Rimrunner, SDI, Heatseeker, Hellfire Attack, Overlander, Buggy Boy, Gazza II, Pac-Land, Stormlord, Vixen, Track & Field, The Last Ninja, Kinetik, Star Wars, Evening Star (side 1), Galactic Games, Xenophobe, MicroProse Soccer, She-Fox, The Fury, Troll, Beyond the Ice Palace, Soccer Stars Disk 2, all Elite compilations |
| A-interleave1 | 1 byte: sector step ADC #$01 instead of ADC #$08, so consecutive sectors are read. The game reads 3 blocks, track 40 sectors 14–16, in one request. |
F-16 Combat Pilot |
| A-fixedT35 | 1 instruction: the sectors-per-track lookup uses a fixed LDA #$23 (track 35) instead of the current track. Makes no difference on track 40. |
P47 Thunderbolt, Six Appeal Disk 2 (= P47) |
| A-buf2 | Re-implemented: job in buffer 2 ($0500), parameters in $0A/$0B, header built with ROM $F6D0. Interleave 8 and step timer #$96 as in A-std. |
Bangkok Knights |
| A-buf2-interleave1 | As A-buf2, plus interleave 1 with a hard 21-sector wrap (CMP #$15) and step timer #$94 |
After Burner |
Family A: when track 40 is read
Family A disks read the track-40 block at one of two points. This is independent of which reader variant they use.
- During boot. The zero-page boot loader fetches its next stage from track 40, sector 14 (e.g. American Indoor Soccer, Thundercats). The read happens 2–40 seconds into the boot, so a copy hangs while still loading.
- At game start. The boot holds a title screen or menu and a table-driven loader (
LDA $0335,X / $033C,X / $0343,X / $034A,X= destination page, block count, track, sector). Each menu entry pairs the main game body with one start-up block on track 40. In Deliverance, entries 1, 2 and 3 load track 40, sectors 14, 15 and 16 to$C000, followed byJMP $C000. The Elite compilations work the same way per game. A copy shows the title screen and menu normally and hangs only after the key press or menu selection (seen on The Last Ninja, Stormlord, Bangkok Knights and Best of Elite Vol. 1 disk 1A).
Family B
Family B also reads track 40, sector 14, but uses an
unmodified DOS $80 READ job in buffer 1. The drive's
job queue at the moment of the seek shows $01 = $80 with
header table entry track 40, sector 14. The stock ROM reader with its
normal checksum handling does the read; there is no custom GCR code.
What a copy without track 40 does in family B was not determined. In the
test run, both the original and the 35-track copy ended in a machine reset
in the emulator.
Elite MENU boot
Elite's compilation disks (Best of Elite Vol. 1/2, Hit Pak Top 10,
6*Pak Vol. 3 Disk 2, Fist 'n' Throttles) boot a MENU
file (load address $0326, "PARA-PROTECT" directory art)
instead of loading the $00AE boot directly. The selected
game's family-A Para boot is then loaded from the menu, so these disks use
the A-std reader.
Copying
- Track 40 holds only standard, checksum-valid sectors. There are no sync tricks, unusual densities or deliberate errors, so a copy only has to include track 40.
- A 35-track copy or D64 image loses the block. Family A then hangs with the drive head on track 40 (see the reader above). Depending on the title, that happens during loading or only after the title-screen key press.
- Bangkok Knights' page notes that the check fails on a 1541-II; under VICE it was traced with the drive type set to 1541.
Verification on this wiki
All 122 disk sides on this wiki listed as Para Protect (90 sources) were checked. Each side was checked statically from its G64 and by tracing under VICE with true drive emulation, with a watchpoint on the drive moving the head to track 40.
- All 108 bootable Para sides have a checksum-valid track 40 with the payload in sector 14 (plus 15/16 where the loader table asks for them).
- All 108 were confirmed to read it:
- 104 by stopping the emulation at the track-40 job, either on that side or on a side with a byte-identical boot file.
- 4 by booting a 35-track copy next to the original: the original loads, and the copy stops in the reader's sync wait for track 40, sector 14.
- The remaining 14 sides are data sides without a boot of their own, loaded by the protected side's loader. They have no track 40 and need none.
- Frank Bruno's Boxing is a special case:
- Side 1 carries the Para directory art and the same side-2 Para boot as Best of Elite Vol. 1 disk 2B.
- The game itself boots from side 0, which has no Para code. It loads side 1 by filename through the KERNAL (
"BOXER n","ANIMB n"), so track 40 is never read. - Neither the Frank Bruno side 1 stream nor the Best of Elite 2B side contains a track 40.
- The Para label on this release is nominal.
Pages that refer to this protection
- Descriptions of old disk copy protections (← links)
- Deliverance (← links)
- American Indoor Soccer (← links)
- Rick Dangerous 2 (← links)
- Hypaball (← links)
- Winter Olympiad 88 (← links)
- Thundercats (← links)
- Superstar Ice Hockey (← links)
- Soccer Stars (← links)
- Rimrunner (← links)
- Empire! (← links)
- Best of Elite: Vol. 2 (← links)
- Best of Elite: Vol. 1 (← links)
- Bangkok Knights (← links)
- SDI: Strategic Defense Initiative (← links)
- After Burner (← links)
- 6*Pak Vol. 3 (← links)
- Heatseeker (← links)
- Hellfire Attack (← links)
- Overlander (← links)
- Fist 'n' Throttles (← links)
- F-16 Combat Pilot (← links)
- Bobby Bearing (← links)
- Buggy Boy (← links)
- Gazza II (← links)
- Pac-Land (← links)
- Six Appeal (← links)
- Stormlord (← links)
- The Sentinel (← links)
- Vixen (← links)
- Six Sizzlers (← links)
- Druid (← links)
- Track & Field (← links)
- The Last Ninja (← links)
- Games Crazy! (← links)
- P47 Thunderbolt (← links)
- Kinetik (← links)
- Star Wars (← links)
- Evening Star / Southern Belle (← links)
- Hit Pak: Top 10 Collection (← links)
- Frank Bruno's Boxing (← links)
- Xenophobe (← links)
- MicroProse Soccer (← links)
- She-Fox (← links)
- The Fury (← links)
- Troll (← links)
- Galactic Games (← links)
- Beyond the Ice Palace (← links)