Difference between revisions of "GMA86"
| (4 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
| − | [[GMA86 Robin of the Wood]] | + | = Pages that refer to this protection = |
| + | {{Special:Whatlinkshere/GMA86}} | ||
| + | |||
| + | = GMA86 = | ||
| + | |||
| + | '''GMA86''' is a Commodore 64 disk copy protection scheme spanning a run of | ||
| + | 1986 releases from Firebird, Domark, Alligata, Hewson, Robtek, Thalamus and | ||
| + | others, and the middle generation of the "GMA" protection family — successor | ||
| + | to [[GMA85]], predecessor to [[GMA87]]. It shares that family's naming | ||
| + | convention: each disk's BAM label carries a build-date stamp | ||
| + | (<code>gma<DDMMYY></code>, e.g. <code>gma140486</code> = 14 Apr 1986 for | ||
| + | ''Robin of the Wood''), and the loader chain keeps the same file-naming | ||
| + | pattern used throughout the family (a boot stub → <code>gm1</code>/<code>gma1</code> | ||
| + | second-stage loader → game data files). | ||
| + | |||
| + | Where '''GMA85''' never derives a key at all, and '''GMA87''''s surveyed | ||
| + | titles converge on one architecture (a C64-side bulk decrypt, with Eagles as | ||
| + | a single confirmed drive-side exception), '''GMA86 is the generation where a | ||
| + | real, disk-specific decryption key first becomes the norm — but it is not | ||
| + | architecturally uniform.''' Of eleven surveyed titles, seven use a C64-side | ||
| + | bulk decrypt, three use a drive-side per-sector decrypt otherwise thought | ||
| + | unique to Eagles, and one carries the ancestral GMA85 presence-only check | ||
| + | forward unchanged, with no key at all. All three mechanisms share the exact | ||
| + | same underlying drive-upload plumbing and sync-length measurement primitive; | ||
| + | what differs is only what happens to the measured byte afterward, and which | ||
| + | out-of-range track it's read from. | ||
| + | |||
| + | == Mechanism == | ||
| + | |||
| + | Every GMA86 title uploads a short machine-code program to the 1541's own | ||
| + | drive RAM over the serial bus (the standard DOS <code>M-W</code>/<code>M-E</code> | ||
| + | commands), and has the '''drive itself''' seek an out-of-range track, search | ||
| + | for the family's standard byte-aligned marker (<code>69 xx xx xx A9</code>, | ||
| + | not decoded to the usual 5-bit GCR nibble boundaries), and — for two of the | ||
| + | three mechanisms below — measure ten raw sync-to-sync pulse-width samples | ||
| + | there, folding them into a single byte via the same <code>CMP</code>/<code>ROL</code> | ||
| + | technique documented for GMA87's Cholo and Triaxos. Which out-of-range track | ||
| + | is targeted correlates exactly with which of the three mechanisms a given | ||
| + | title uses: | ||
| + | |||
| + | # '''Track 38''' (<code>$26</code>) — '''C64-side bulk decrypt.''' The measured byte is sent to the C64 over a raster-synced <code>$DD00</code> handshake and used as a single repeating-byte XOR key over almost the entire game, decrypted in place in one pass before jumping straight into the result. Seven of eleven surveyed titles: Sanxion, Split Personalities, Cyrus II Chess, The Sentry, Filemaster 64, Pub Games, and The Graphic Adventure Creator. | ||
| + | # '''Track 39''' (<code>$27</code>) — '''drive-side per-sector decrypt.''' The measured byte is instead baked as a literal <code>EOR #imm</code> operand into a second, freshly-uploaded drive-resident program, and the 1541's own 6502 decrypts every subsequent sector before it ever reaches the C64, streaming the result out over a bit-banged serial link one byte at a time. Three of eleven surveyed titles, all Hewson-published: Firelord, Uridium, and Iridis Alpha — matching GMA87's Eagles almost byte-for-byte, including the identical <code>$035C</code>/<code>$035D</code> key-injection address. | ||
| + | # '''Track 37''' (<code>$25</code>) — '''presence-only, no key.''' The drive reads two raw 256-byte blocks off the track and reports success unconditionally; nothing is ever folded, compared, or sent. This is the ancestral [[GMA85]] mechanism, carried forward unmodified. One of eleven surveyed titles: Robin of the Wood, whose disk-label date (<code>gma140486</code>) places it firmly within the GMA86 era despite running GMA85-generation drive code. | ||
| + | |||
| + | On any genuine failure — marker not found within the retry budget, or a | ||
| + | failed job status — the drive hangs permanently in an infinite self-loop | ||
| + | rather than reporting an error the C64 could act on, the same "hang, never | ||
| + | produce wrong output" convention used throughout the whole GMA family. | ||
| + | |||
| + | == The critical disk pattern == | ||
| + | |||
| + | Tracks 1–35 are a completely ordinary CBM DOS layout; tracks 36–40 sit | ||
| + | outside the range any stock-formatted disk ever uses, reachable only by a | ||
| + | loader that steps the drive head there directly. Every out-of-range track in | ||
| + | this band — confirmed directly against a GMA86 disk's own G64 text dump, not | ||
| + | inferred — carries the same three-times-repeating structure the whole GMA | ||
| + | family shares: | ||
| + | |||
| + | <pre> | ||
| + | Track 1 ───────────────────────────────────────── 35 | ||
| + | ordinary CBM DOS layout ($08 header / | ||
| + | $07 data blocks, correct checksums) | ||
| + | |||
| + | Track 36 ┐ | ||
| + | Track 37 │ 3x repeating group, every one of tracks 36-40: | ||
| + | Track 38 ├─ sync ~32-41 | ||
| + | Track 39 │ bits 0110100101 | ||
| + | Track 40 ┘ bytes <2-byte per-track "salt"> 56 56 ... 55 57 | ||
| + | bits 111111 | ||
| + | |||
| + | sync ~32-41 | ||
| + | gcr 09 <- non-standard block-ID, not $07/$08 | ||
| + | bytes 59 59 59 59 59 55 ... (mostly $55 filler) | ||
| + | bits 111111 | ||
| + | </pre> | ||
| + | |||
| + | '''Confirmed directly on Robin of the Wood's track 37''' (a genuine GMA86 | ||
| + | disk, not just inferred from GMA85/87): 40 total sync entries, 6 tagged | ||
| + | <code>gcr 09</code>, of which exactly 3 carry the <code>69</code>/<code>A9</code> | ||
| + | marker pair — each preceded by a ''different'', otherwise-unchecked 2-byte | ||
| + | salt (<code>a3 a3</code>, <code>a7 a7</code>, <code>a9 a9</code> on this | ||
| + | disk). Zeroing all three salt pairs while leaving the marker byte itself | ||
| + | untouched still boots the game perfectly — plain reliability engineering | ||
| + | against a single degraded copy, not a checked payload; the drive only needs | ||
| + | to land on any one of the three. | ||
| + | |||
| + | The three mechanisms in this generation diverge on what — if anything — | ||
| + | lives past that shared structure: | ||
| + | |||
| + | <pre> | ||
| + | ─────────────────────────────────────────────────────────────────── | ||
| + | Track 37 (presence-only titles, GMA85-inherited): NOTHING further. | ||
| + | Confirmed directly by full sync-length scan across tracks 34-42 on | ||
| + | a genuine GMA86 disk - every sync on track 37 falls in the ordinary | ||
| + | 32-40 range, zero outliers anywhere. There is no elongated-sync | ||
| + | region to measure, because this mechanism never measures anything - | ||
| + | it only confirms the marker is physically present. | ||
| + | |||
| + | ─────────────────────────────────────────────────────────────────── | ||
| + | Track 38 (C64-side titles) / Track 39 (drive-side titles): | ||
| + | a FOURTH, extra tail after the third repeating group - | ||
| + | |||
| + | sync 87 1-bits <- anomalous: ~2x a normal sync | ||
| + | gcr 0f | ||
| + | sync 136 1-bits <- anomalous: ~3x a normal sync | ||
| + | gcr 0f | ||
| + | ...long run of $AA filler... | ||
| + | |||
| + | ^^^^^^^^^^^^^^^^^^^^ | ||
| + | THIS is what the drive-side signature-measurement code | ||
| + | (below) actually reads: 10 raw sync-to-sync pulse-width | ||
| + | samples across exactly this irregular tail, byte-aligned | ||
| + | to the raw GCR shift register rather than the salt/gcr-09 | ||
| + | structure above. Same anomaly shape independently | ||
| + | confirmed for GMA85/GMA87's own track-38/41 signature | ||
| + | regions - a single continuous run of unevenly-lengthened | ||
| + | syncs, not a third repeat of the marker structure. | ||
| + | </pre> | ||
| + | |||
| + | This is the concrete, physical reason GMA86 splits into three mechanisms | ||
| + | where GMA85 has only one: track 37's structure is the same as every other | ||
| + | out-of-range track on the disk, carrying nothing a naive nibble copier would | ||
| + | need to preserve beyond the marker itself, while tracks 38 and 39 each carry | ||
| + | a deliberately malformed timing region that only a flux-faithful copy | ||
| + | reproduces — the physical difference between "no key" and "a real, | ||
| + | disk-specific key" is visible directly in the raw track layout, not just in | ||
| + | the code that reads it. | ||
| + | |||
| + | == Signature measurement: the shared drive-side routine == | ||
| + | |||
| + | Confirmed byte-for-byte identical (Firelord, Uridium, Iridis Alpha) or | ||
| + | structurally identical down to the exact same offsets (the C64-side titles, | ||
| + | confirmed point-by-point against Cholo's own upload routine) to the | ||
| + | mechanism already documented in detail for GMA87. The only things that vary | ||
| + | between tracks 38 and 39 are the literal target-track constant and, for the | ||
| + | drive-side variant, what happens to the result afterward (see below): | ||
| + | |||
| + | <pre> | ||
| + | ; ---- sync-marker search, 90-attempt retry budget ---- | ||
| + | $0300 AD 00 1C LDA $1C00 | ||
| + | $0303 29 9F AND #$9F | ||
| + | $0305 8D 00 1C STA $1C00 | ||
| + | $0308 A0 5A LDY #$5A ; Y = 90 - sync-retry budget | ||
| + | $030A 88 DEY ; <-- retry entry point | ||
| + | $030B D0 05 BNE $0312 | ||
| + | $030D A9 02 LDA #$02 ; error $02 = HEADER NOT FOUND | ||
| + | $030F 4C 69 F9 JMP $F969 ; ERRR - retries exhausted, report failure | ||
| + | $0312 2C 00 1C BIT $1C00 | ||
| + | $0315 30 FB BMI $0312 ; sync-wait poll | ||
| + | $0317 AD 01 1C LDA $1C01 ; discard first raw byte after sync | ||
| + | $031A B8 CLV | ||
| + | $031B A2 04 LDX #$04 | ||
| + | $031D 50 FE BVC $031D ; CLV/BVC byte-ready wait (classic 1541 trick) | ||
| + | $031F B8 CLV | ||
| + | $0320 AD 01 1C LDA $1C01 ; read raw GCR byte | ||
| + | $0323 9D 00 05 STA $0500,X ; store into $0500-$0504 (5 bytes) | ||
| + | $0326 CA DEX | ||
| + | $0327 10 F4 BPL $031D | ||
| + | $0329 C9 A9 CMP #$A9 ; last byte read must be $A9 | ||
| + | $032B D0 DD BNE $030A ; mismatch -> retry | ||
| + | $032D AD 04 05 LDA $0504 ; first byte read (2nd overall) | ||
| + | $0330 C9 69 CMP #$69 ; must be $69 | ||
| + | $0332 D0 D6 BNE $030A ; mismatch -> retry | ||
| + | ; signature "69 xx xx xx A9" confirmed | ||
| + | |||
| + | ; ---- 10-sample raw pulse-width (sync-length) measurement ---- | ||
| + | $0334 A0 00 LDY #$00 | ||
| + | $0336 2C 00 1C BIT $1C00 | ||
| + | $0339 30 FB BMI $0336 ; wait for next sync | ||
| + | $033B A2 00 LDX #$00 | ||
| + | $033D E8 INX ; <-- pulse-width measurement loop | ||
| + | $033E 2C 00 1C BIT $1C00 | ||
| + | $0341 10 FA BPL $033D ; count iterations (X) while bit7=0 | ||
| + | $0343 8A TXA | ||
| + | $0344 99 00 05 STA $0500,Y ; store sample[Y] | ||
| + | $0347 C8 INY | ||
| + | $0348 C0 0A CPY #$0A ; 10 samples total | ||
| + | $034A D0 EA BNE $0336 | ||
| + | |||
| + | ; ---- fold 10 samples into an 8-bit result via CMP/ROL ---- | ||
| + | $034C A2 02 LDX #$02 ; sample[0] discarded, sample[1] = reference | ||
| + | $034E BD 00 05 LDA $0500,X | ||
| + | $0351 CD 01 05 CMP $0501 ; compare sample[X] to reference | ||
| + | $0354 2E 0A 05 ROL $050A ; roll carry (>=ref->1, <ref->0) into result byte | ||
| + | $0357 E8 INX | ||
| + | $0358 E0 0A CPX #$0A ; samples[2..9], 8 comparisons -> 8-bit result | ||
| + | $035A D0 F2 BNE $034E | ||
| + | $035C AE 0A 05 LDX $050A ; X = computed 8-bit sync-length result | ||
| + | |||
| + | ; ---- job setup: target track, submit EXECUTE job, check result ---- | ||
| + | LDA #<track> ; $26 (38) for the C64-side variant, | ||
| + | STA $06 ; $27 (39) for the drive-side variant | ||
| + | ... | ||
| + | JMP <infinite self-loop> ; on failure status - hang forever | ||
| + | </pre> | ||
| + | |||
| + | The remainder — how the result byte is used — is where the three GMA86 | ||
| + | mechanisms diverge. | ||
| + | |||
| + | == Variant A: C64-side bulk decrypt == | ||
| + | |||
| + | The measured byte is sent to the C64 the same way GMA87's Cholo/Triaxos do | ||
| + | it: a raster-line-synced read of CIA2's <code>$DD00</code>, decoded through | ||
| + | a small set of fixed lookup tables, landing in <code>A</code> at the return | ||
| + | of a hijacked <code>GETIN</code> vector. The main loader stashes it | ||
| + | (<code>PHA</code>) immediately after the upload-and-execute call, recovers | ||
| + | it later (<code>PLA</code>/<code>TAX</code>), and feeds it into a | ||
| + | self-modifying XOR loop that walks upward through memory decrypting the | ||
| + | game in place before jumping straight into the result — confirmed | ||
| + | byte-for-byte structurally identical (just different target addresses and | ||
| + | end-page constants) across all seven C64-side titles. | ||
| + | |||
| + | The Graphic Adventure Creator's build (target track <code>$26</code> = 38, | ||
| + | confirmed directly) is the cleanest captured example: | ||
| + | |||
| + | <pre> | ||
| + | $401F JSR $C800 ; the fixed drive-upload-and-execute trigger | ||
| + | $4022 PHA ; stash the measured key | ||
| + | $4023 LDA #$00 / JSR $42AB | ||
| + | $4028 PLA ; recover it | ||
| + | $4029 TAX ; X = $C3 (this disk's measured key) | ||
| + | $402A LDA $01 / PHA / AND #$FE / STA $01 ; bank out BASIC ROM | ||
| + | $4031 TXA / EOR $C900 / STA $C900 ; self-modifying decrypt | ||
| + | $4038 INC $4033 / INC $4036 / BNE $4031 | ||
| + | $4040 INC $4034 / INC $4037 | ||
| + | LDA $4034 / CMP #$C9 / BNE $4031 | ||
| + | $404D PLA / STA $01 ; restore memory config | ||
| + | $4050 ... ; standard KERNAL cold-start-ish sequence | ||
| + | </pre> | ||
| + | |||
| + | == Variant B: drive-side per-sector decrypt == | ||
| + | |||
| + | Firelord, Uridium, and Iridis Alpha instead move the decrypt onto the 1541 | ||
| + | itself, matching GMA87's Eagles almost line-for-line: once the signature | ||
| + | measurement above completes, a '''second''' program is uploaded into drive | ||
| + | RAM, overwriting the first. It contains a GCR sector-read loop, the standard | ||
| + | DOS ROM's own <code>GCRBIN</code>/<code>CHKBLK</code> header-verification | ||
| + | calls, and — at the identical <code>$035C</code>/<code>$035D</code> address | ||
| + | in every confirmed example — the measured byte baked directly in as an | ||
| + | <code>EOR #imm</code> immediate operand, decrypting each data byte as it's | ||
| + | read and streaming the already-decrypted result to the C64 over the | ||
| + | bit-banged serial link. Confirmed live for Uridium (measured key | ||
| + | <code>$97</code>): | ||
| + | |||
| + | <pre> | ||
| + | $0359 BD 00 06 LDA $0600,Y ; raw (still-encrypted) byte off the sector buffer | ||
| + | $035C 49 97 EOR #$97 ; the measured sync-length byte, baked in as a | ||
| + | ; literal immediate operand at upload time | ||
| + | $035E AA TAX ; decrypted byte ready to send/store | ||
| + | ... | ||
| + | </pre> | ||
| + | |||
| + | Because the key is injected directly into freshly-uploaded drive code | ||
| + | rather than sent as data, a hunt for <code>EOR #<key></code> | ||
| + | (<code>49 xx</code>) across drive RAM after the upload reliably locates this | ||
| + | instruction — the same technique used to confirm all three drive-side | ||
| + | titles here and Eagles in GMA87. All three drive-side GMA86 titles are | ||
| + | Hewson-published, matching Eagles' own publisher; the C64-side titles found | ||
| + | in this survey are Domark, Alligata, Firebird, Ariolasoft, Robtek, and | ||
| + | Thalamus releases — a suggestive publisher-level correlation, though not | ||
| + | established as a rule (Firelord's own publisher wasn't independently | ||
| + | confirmed, and no non-Hewson drive-side counterexample was specifically | ||
| + | sought). | ||
| + | |||
| + | == Variant C: presence-only, no key == | ||
| − | + | Robin of the Wood's disk-label date places it within the GMA86 era, but its | |
| + | drive-side program is confirmed byte-for-byte identical to the [[GMA85]] | ||
| + | generation's: target track 37, two raw 256-byte blocks read and discarded, | ||
| + | success reported unconditionally. Confirmed experimentally by corrupting the | ||
| + | track's surrounding "salt" bytes while preserving the marker — no change in | ||
| + | behavior. No sync-length measurement of any kind occurs on this title's | ||
| + | out-of-range track. See [[GMA85]] for the full annotated disassembly of this | ||
| + | mechanism, including the C64-side loader's <code>$02</code>/<code>EOR #$97</code> | ||
| + | check some (but not all) titles of that generation carry — a plausible | ||
| + | code-integrity tripwire against a patched-out upload call, not a | ||
| + | disk-content validation, and not specific to Robin of the Wood; it belongs | ||
| + | to the inherited GMA85 loader, not to anything introduced in GMA86. | ||
| − | + | == Relationship to GMA85 and GMA87 == | |
| + | |||
| + | GMA86 sits directly between the two: it inherits GMA85's presence-only | ||
| + | mechanism wholesale (still shipping on at least one GMA86-labeled title), | ||
| + | and it establishes — a full year before GMA87 — both real-key architectures | ||
| + | GMA87 titles use, including the drive-side variant later found in Eagles | ||
| + | and, until this survey, thought to be an isolated architectural outlier | ||
| + | within the whole GMA family. It isn't: the drive-side decrypt is an | ||
| + | established technique in this author's toolkit that predates Eagles by at | ||
| + | least a year, reused essentially unchanged. | ||
| + | |||
| + | == See also == | ||
| + | |||
| + | * [[GMA85]] | ||
| + | * [[GMA87]] | ||
| + | * [[Robin of the Wood]] | ||
Latest revision as of 00:15, 29 August 2026
Pages that refer to this protection
- Descriptions of old disk copy protections (← links)
- Iridis Alpha (← links)
- GMA87 (← links)
- Sanxion (← links)
- The Sentry (← links)
- Uridium (← links)
- The Graphic Adventure Creator (← links)
- GMA85 (← links)
- Split Personalities (← links)
- Robin of the Wood (← links)
- Pub Games (← links)
- Cyrus II Chess (← links)
- Firelord (← links)
- Filemaster 64 (← links)
GMA86
GMA86 is a Commodore 64 disk copy protection scheme spanning a run of
1986 releases from Firebird, Domark, Alligata, Hewson, Robtek, Thalamus and
others, and the middle generation of the "GMA" protection family — successor
to GMA85, predecessor to GMA87. It shares that family's naming
convention: each disk's BAM label carries a build-date stamp
(gma<DDMMYY>, e.g. gma140486 = 14 Apr 1986 for
Robin of the Wood), and the loader chain keeps the same file-naming
pattern used throughout the family (a boot stub → gm1/gma1
second-stage loader → game data files).
Where GMA85 never derives a key at all, and GMA87's surveyed titles converge on one architecture (a C64-side bulk decrypt, with Eagles as a single confirmed drive-side exception), GMA86 is the generation where a real, disk-specific decryption key first becomes the norm — but it is not architecturally uniform. Of eleven surveyed titles, seven use a C64-side bulk decrypt, three use a drive-side per-sector decrypt otherwise thought unique to Eagles, and one carries the ancestral GMA85 presence-only check forward unchanged, with no key at all. All three mechanisms share the exact same underlying drive-upload plumbing and sync-length measurement primitive; what differs is only what happens to the measured byte afterward, and which out-of-range track it's read from.
Mechanism
Every GMA86 title uploads a short machine-code program to the 1541's own
drive RAM over the serial bus (the standard DOS M-W/M-E
commands), and has the drive itself seek an out-of-range track, search
for the family's standard byte-aligned marker (69 xx xx xx A9,
not decoded to the usual 5-bit GCR nibble boundaries), and — for two of the
three mechanisms below — measure ten raw sync-to-sync pulse-width samples
there, folding them into a single byte via the same CMP/ROL
technique documented for GMA87's Cholo and Triaxos. Which out-of-range track
is targeted correlates exactly with which of the three mechanisms a given
title uses:
- Track 38 (
$26) — C64-side bulk decrypt. The measured byte is sent to the C64 over a raster-synced$DD00handshake and used as a single repeating-byte XOR key over almost the entire game, decrypted in place in one pass before jumping straight into the result. Seven of eleven surveyed titles: Sanxion, Split Personalities, Cyrus II Chess, The Sentry, Filemaster 64, Pub Games, and The Graphic Adventure Creator. - Track 39 (
$27) — drive-side per-sector decrypt. The measured byte is instead baked as a literalEOR #immoperand into a second, freshly-uploaded drive-resident program, and the 1541's own 6502 decrypts every subsequent sector before it ever reaches the C64, streaming the result out over a bit-banged serial link one byte at a time. Three of eleven surveyed titles, all Hewson-published: Firelord, Uridium, and Iridis Alpha — matching GMA87's Eagles almost byte-for-byte, including the identical$035C/$035Dkey-injection address. - Track 37 (
$25) — presence-only, no key. The drive reads two raw 256-byte blocks off the track and reports success unconditionally; nothing is ever folded, compared, or sent. This is the ancestral GMA85 mechanism, carried forward unmodified. One of eleven surveyed titles: Robin of the Wood, whose disk-label date (gma140486) places it firmly within the GMA86 era despite running GMA85-generation drive code.
On any genuine failure — marker not found within the retry budget, or a failed job status — the drive hangs permanently in an infinite self-loop rather than reporting an error the C64 could act on, the same "hang, never produce wrong output" convention used throughout the whole GMA family.
The critical disk pattern
Tracks 1–35 are a completely ordinary CBM DOS layout; tracks 36–40 sit outside the range any stock-formatted disk ever uses, reachable only by a loader that steps the drive head there directly. Every out-of-range track in this band — confirmed directly against a GMA86 disk's own G64 text dump, not inferred — carries the same three-times-repeating structure the whole GMA family shares:
Track 1 ───────────────────────────────────────── 35
ordinary CBM DOS layout ($08 header /
$07 data blocks, correct checksums)
Track 36 ┐
Track 37 │ 3x repeating group, every one of tracks 36-40:
Track 38 ├─ sync ~32-41
Track 39 │ bits 0110100101
Track 40 ┘ bytes <2-byte per-track "salt"> 56 56 ... 55 57
bits 111111
sync ~32-41
gcr 09 <- non-standard block-ID, not $07/$08
bytes 59 59 59 59 59 55 ... (mostly $55 filler)
bits 111111
Confirmed directly on Robin of the Wood's track 37 (a genuine GMA86
disk, not just inferred from GMA85/87): 40 total sync entries, 6 tagged
gcr 09, of which exactly 3 carry the 69/A9
marker pair — each preceded by a different, otherwise-unchecked 2-byte
salt (a3 a3, a7 a7, a9 a9 on this
disk). Zeroing all three salt pairs while leaving the marker byte itself
untouched still boots the game perfectly — plain reliability engineering
against a single degraded copy, not a checked payload; the drive only needs
to land on any one of the three.
The three mechanisms in this generation diverge on what — if anything — lives past that shared structure:
───────────────────────────────────────────────────────────────────
Track 37 (presence-only titles, GMA85-inherited): NOTHING further.
Confirmed directly by full sync-length scan across tracks 34-42 on
a genuine GMA86 disk - every sync on track 37 falls in the ordinary
32-40 range, zero outliers anywhere. There is no elongated-sync
region to measure, because this mechanism never measures anything -
it only confirms the marker is physically present.
───────────────────────────────────────────────────────────────────
Track 38 (C64-side titles) / Track 39 (drive-side titles):
a FOURTH, extra tail after the third repeating group -
sync 87 1-bits <- anomalous: ~2x a normal sync
gcr 0f
sync 136 1-bits <- anomalous: ~3x a normal sync
gcr 0f
...long run of $AA filler...
^^^^^^^^^^^^^^^^^^^^
THIS is what the drive-side signature-measurement code
(below) actually reads: 10 raw sync-to-sync pulse-width
samples across exactly this irregular tail, byte-aligned
to the raw GCR shift register rather than the salt/gcr-09
structure above. Same anomaly shape independently
confirmed for GMA85/GMA87's own track-38/41 signature
regions - a single continuous run of unevenly-lengthened
syncs, not a third repeat of the marker structure.
This is the concrete, physical reason GMA86 splits into three mechanisms where GMA85 has only one: track 37's structure is the same as every other out-of-range track on the disk, carrying nothing a naive nibble copier would need to preserve beyond the marker itself, while tracks 38 and 39 each carry a deliberately malformed timing region that only a flux-faithful copy reproduces — the physical difference between "no key" and "a real, disk-specific key" is visible directly in the raw track layout, not just in the code that reads it.
Confirmed byte-for-byte identical (Firelord, Uridium, Iridis Alpha) or structurally identical down to the exact same offsets (the C64-side titles, confirmed point-by-point against Cholo's own upload routine) to the mechanism already documented in detail for GMA87. The only things that vary between tracks 38 and 39 are the literal target-track constant and, for the drive-side variant, what happens to the result afterward (see below):
; ---- sync-marker search, 90-attempt retry budget ----
$0300 AD 00 1C LDA $1C00
$0303 29 9F AND #$9F
$0305 8D 00 1C STA $1C00
$0308 A0 5A LDY #$5A ; Y = 90 - sync-retry budget
$030A 88 DEY ; <-- retry entry point
$030B D0 05 BNE $0312
$030D A9 02 LDA #$02 ; error $02 = HEADER NOT FOUND
$030F 4C 69 F9 JMP $F969 ; ERRR - retries exhausted, report failure
$0312 2C 00 1C BIT $1C00
$0315 30 FB BMI $0312 ; sync-wait poll
$0317 AD 01 1C LDA $1C01 ; discard first raw byte after sync
$031A B8 CLV
$031B A2 04 LDX #$04
$031D 50 FE BVC $031D ; CLV/BVC byte-ready wait (classic 1541 trick)
$031F B8 CLV
$0320 AD 01 1C LDA $1C01 ; read raw GCR byte
$0323 9D 00 05 STA $0500,X ; store into $0500-$0504 (5 bytes)
$0326 CA DEX
$0327 10 F4 BPL $031D
$0329 C9 A9 CMP #$A9 ; last byte read must be $A9
$032B D0 DD BNE $030A ; mismatch -> retry
$032D AD 04 05 LDA $0504 ; first byte read (2nd overall)
$0330 C9 69 CMP #$69 ; must be $69
$0332 D0 D6 BNE $030A ; mismatch -> retry
; signature "69 xx xx xx A9" confirmed
; ---- 10-sample raw pulse-width (sync-length) measurement ----
$0334 A0 00 LDY #$00
$0336 2C 00 1C BIT $1C00
$0339 30 FB BMI $0336 ; wait for next sync
$033B A2 00 LDX #$00
$033D E8 INX ; <-- pulse-width measurement loop
$033E 2C 00 1C BIT $1C00
$0341 10 FA BPL $033D ; count iterations (X) while bit7=0
$0343 8A TXA
$0344 99 00 05 STA $0500,Y ; store sample[Y]
$0347 C8 INY
$0348 C0 0A CPY #$0A ; 10 samples total
$034A D0 EA BNE $0336
; ---- fold 10 samples into an 8-bit result via CMP/ROL ----
$034C A2 02 LDX #$02 ; sample[0] discarded, sample[1] = reference
$034E BD 00 05 LDA $0500,X
$0351 CD 01 05 CMP $0501 ; compare sample[X] to reference
$0354 2E 0A 05 ROL $050A ; roll carry (>=ref->1, <ref->0) into result byte
$0357 E8 INX
$0358 E0 0A CPX #$0A ; samples[2..9], 8 comparisons -> 8-bit result
$035A D0 F2 BNE $034E
$035C AE 0A 05 LDX $050A ; X = computed 8-bit sync-length result
; ---- job setup: target track, submit EXECUTE job, check result ----
LDA #<track> ; $26 (38) for the C64-side variant,
STA $06 ; $27 (39) for the drive-side variant
...
JMP <infinite self-loop> ; on failure status - hang forever
The remainder — how the result byte is used — is where the three GMA86 mechanisms diverge.
Variant A: C64-side bulk decrypt
The measured byte is sent to the C64 the same way GMA87's Cholo/Triaxos do
it: a raster-line-synced read of CIA2's $DD00, decoded through
a small set of fixed lookup tables, landing in A at the return
of a hijacked GETIN vector. The main loader stashes it
(PHA) immediately after the upload-and-execute call, recovers
it later (PLA/TAX), and feeds it into a
self-modifying XOR loop that walks upward through memory decrypting the
game in place before jumping straight into the result — confirmed
byte-for-byte structurally identical (just different target addresses and
end-page constants) across all seven C64-side titles.
The Graphic Adventure Creator's build (target track $26 = 38,
confirmed directly) is the cleanest captured example:
$401F JSR $C800 ; the fixed drive-upload-and-execute trigger
$4022 PHA ; stash the measured key
$4023 LDA #$00 / JSR $42AB
$4028 PLA ; recover it
$4029 TAX ; X = $C3 (this disk's measured key)
$402A LDA $01 / PHA / AND #$FE / STA $01 ; bank out BASIC ROM
$4031 TXA / EOR $C900 / STA $C900 ; self-modifying decrypt
$4038 INC $4033 / INC $4036 / BNE $4031
$4040 INC $4034 / INC $4037
LDA $4034 / CMP #$C9 / BNE $4031
$404D PLA / STA $01 ; restore memory config
$4050 ... ; standard KERNAL cold-start-ish sequence
Variant B: drive-side per-sector decrypt
Firelord, Uridium, and Iridis Alpha instead move the decrypt onto the 1541
itself, matching GMA87's Eagles almost line-for-line: once the signature
measurement above completes, a second program is uploaded into drive
RAM, overwriting the first. It contains a GCR sector-read loop, the standard
DOS ROM's own GCRBIN/CHKBLK header-verification
calls, and — at the identical $035C/$035D address
in every confirmed example — the measured byte baked directly in as an
EOR #imm immediate operand, decrypting each data byte as it's
read and streaming the already-decrypted result to the C64 over the
bit-banged serial link. Confirmed live for Uridium (measured key
$97):
$0359 BD 00 06 LDA $0600,Y ; raw (still-encrypted) byte off the sector buffer
$035C 49 97 EOR #$97 ; the measured sync-length byte, baked in as a
; literal immediate operand at upload time
$035E AA TAX ; decrypted byte ready to send/store
...
Because the key is injected directly into freshly-uploaded drive code
rather than sent as data, a hunt for EOR #<key>
(49 xx) across drive RAM after the upload reliably locates this
instruction — the same technique used to confirm all three drive-side
titles here and Eagles in GMA87. All three drive-side GMA86 titles are
Hewson-published, matching Eagles' own publisher; the C64-side titles found
in this survey are Domark, Alligata, Firebird, Ariolasoft, Robtek, and
Thalamus releases — a suggestive publisher-level correlation, though not
established as a rule (Firelord's own publisher wasn't independently
confirmed, and no non-Hewson drive-side counterexample was specifically
sought).
Variant C: presence-only, no key
Robin of the Wood's disk-label date places it within the GMA86 era, but its
drive-side program is confirmed byte-for-byte identical to the GMA85
generation's: target track 37, two raw 256-byte blocks read and discarded,
success reported unconditionally. Confirmed experimentally by corrupting the
track's surrounding "salt" bytes while preserving the marker — no change in
behavior. No sync-length measurement of any kind occurs on this title's
out-of-range track. See GMA85 for the full annotated disassembly of this
mechanism, including the C64-side loader's $02/EOR #$97
check some (but not all) titles of that generation carry — a plausible
code-integrity tripwire against a patched-out upload call, not a
disk-content validation, and not specific to Robin of the Wood; it belongs
to the inherited GMA85 loader, not to anything introduced in GMA86.
Relationship to GMA85 and GMA87
GMA86 sits directly between the two: it inherits GMA85's presence-only mechanism wholesale (still shipping on at least one GMA86-labeled title), and it establishes — a full year before GMA87 — both real-key architectures GMA87 titles use, including the drive-side variant later found in Eagles and, until this survey, thought to be an isolated architectural outlier within the whole GMA family. It isn't: the drive-side decrypt is an established technique in this author's toolkit that predates Eagles by at least a year, reused essentially unchanged.