GMA86: Difference between revisions

From Software Archive
Jump to navigation Jump to search
No edit summary
No edit summary
Line 2: Line 2:
{{Special:Whatlinkshere/GMA86}}
{{Special:Whatlinkshere/GMA86}}


= Description =
= GMA86 =
An exception is [[Robin of the Wood]], that shows GMA86, but uses the same protection code as GMA85.
 
'''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&lt;DDMMYY&gt;</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.
 
== 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 #&lt;key&gt;</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]]

Revision as of 00:51, 28 August 2026

Pages that refer to this protection

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:

  1. Track 38 ($26) — C64-side bulk decrypt. The
 measured byte is sent to the C64 over a raster-synced $DD00
 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.
  1. Track 39 ($27) — drive-side per-sector decrypt.
 The measured byte is instead baked as a literal EOR #imm
 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 $035C/$035D
 key-injection address.
  1. 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.

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):

; ---- 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.

See also