GMA85

From Software Archive
Revision as of 22:11, 28 August 2026 by Enigma (talk | contribs)
Jump to navigation Jump to search

Pages that refer to this protection

Notable Chimera / Willow Pattern (Firebird) uses $A3 as marker terminator instead of $A9.

GMA85

GMA85 is a Commodore 64 disk copy protection scheme used across a run of Firebird Software titles from 1985/86, and the earliest confirmed member of the "GMA" protection family — the same lineage that continued as GMA86 and GMA87. Its name follows the family's own convention: each disk's directory label carries a build-date stamp (gma<DDMMYY>), and several GMA85 disks spell the scheme name out directly (e.g. gma85 on Booty).

Where GMA86 and GMA87 measure a disk-specific signature from sync length variation on a deliberately malformed track and use it as an XOR decryption key, GMA85 does not derive or use a key at all. It performs the same out-of-range-track handshake, but the check stops at confirming the track's marker byte is present — nothing about its surrounding content is ever read, compared, transmitted, or used to decrypt anything. This makes GMA85 the scheme's ancestral, unencrypted form: a presence gate, not a cipher.

Mechanism

Every GMA85 title surveyed (10 games across 11 disk sides, from six compilation releases) runs the identical loader chain used by the whole GMA family: a small boot stub (typically named firebird) loads and hands off to a second-stage loader (gma1), which uploads a short machine-code program to the 1541 disk drive's own memory over the serial bus (using the standard DOS M-W/M-E memory-write/execute commands) and has the drive itself — not the C64 — perform the check:

  1. The drive seeks an out-of-range track — track 37 in every GMA85
 title found so far, one track short of GMA86/87's Cholo/Triaxos (38) and
 Eagles/Firelord/Uridium/Iridis Alpha (39).
  1. It searches for a short, deliberately non-standard byte marker
 ($69 ... $A9, byte-aligned to the raw GCR
 shift register rather than the usual 5-bit nibble boundaries — the same
 marker convention used across the whole GMA family) with a 90-attempt
 retry budget.
  1. On a match, it reads two raw 256-byte blocks off the disk into its own
 RAM.
  1. It reports success to the C64 unconditionally — the two blocks it just
 read are never folded into a value, compared against anything, or sent
 anywhere. They are simply discarded.
  1. The C64 continues loading regardless of what (if anything) was in
 those blocks. On a genuine failure — no out-of-range track, or no
 marker found on it — the drive instead hangs permanently, the same
 "fail by hanging, never by producing wrong output" behavior used
 throughout the GMA family.

Because nothing downstream ever consumes the read data, a disk copy only needs to preserve the out-of-range track's existence and its marker byte to pass — confirmed experimentally by corrupting the surrounding data on a real GMA85-derived disk and observing no change in behavior.

The critical disk pattern

Tracks 1–35 are a completely ordinary CBM DOS layout; track 37 sits outside the range any stock-formatted disk ever uses, reachable only by a loader that steps the drive head there directly. It carries the same three-times-repeating structure the whole GMA family shares on every out-of-range track it uses:

 Track  1 ──────────────────────────────────────────  35
           ordinary CBM DOS layout ($08 header /
           $07 data blocks, correct checksums)

 Track 37   3x repeating group:
              sync ~32-41
              bits 0110100101
              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

Directly confirmed against a real disk's own G64 text dump (Robin of the Wood, which — as established below — runs this exact GMA85 drive code unchanged): 40 total sync entries on the track, 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 that 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.

There is nothing further. This is the whole physical basis of GMA85's presence-only design: a full sync-length scan across the out-of-range track band found every sync in the ordinary 32-40 range, zero outliers anywhere. Compare this to GMA86 and GMA87's real-key titles, whose target track carries a fourth, extra tail after this same three-group structure — a deliberately malformed region of elongated sync runs (87 and 136 "1"-bits, versus the normal ~32-41) that a pulse-width measurement folds into a key byte. GMA85's target track simply doesn't have that tail — there is no elongated-sync region anywhere on it for a measurement to read, which is exactly why the drive-side code never attempts one: it reads two raw 256-byte blocks off the ordinary structure above (the marker's own trailing salt/filler bytes and whatever follows) and discards them unconditionally, rather than timing anything. The physical difference between GMA85 and its two successors is visible directly in the raw track layout, not just in the code that reads it: the "key" mechanism in GMA86/87 exists only because the disk carries a region purpose-built for it, and GMA85's disks simply don't.

The one confirmed byte-level difference between GMA85 titles themselves is the marker's terminating byte: $A9 in nine of the ten surveyed titles, $A3 on Chimera and Willow Pattern only — see "Known GMA85 titles" below.

The drive-side program: annotated disassembly

Captured live (frozen at the exact instant the drive's own DRVTRK variable becomes 37, before resuming) and confirmed byte-for-byte identical across all ten titles surveyed, save for the one-byte marker terminator noted below. This 141-byte program is uploaded into 1541 drive RAM at $0300 by the C64-side loader's M-W command sequence, then run there via M-E $0364:

; ---- IEC bus init ----
$0300  A9 0F       LDA #$0F
$0302  8D 00 18    STA $1800
$0305  8D 00 18    STA $1800
$0308  AD 00 1C    LDA $1C00
$030B  29 9F       AND #$9F
$030D  8D 00 1C    STA $1C00

; ---- sync-marker search, 90-attempt retry budget ----
$0310  A0 5A       LDY #$5A            ; Y = 90 - sync-retry budget
$0312  88          DEY                 ; <-- retry entry point
$0313  D0 05       BNE $031A
$0315  A9 02       LDA #$02            ; error $02 = HEADER NOT FOUND
$0317  4C 69 F9    JMP $F969           ; ERRR - retries exhausted, report failure
$031A  2C 00 1C    BIT $1C00
$031D  30 FB       BMI $031A           ; sync-wait poll
$031F  AD 01 1C    LDA $1C01           ; discard first raw byte after sync
$0322  B8          CLV
$0323  A2 04       LDX #$04
$0325  50 FE       BVC $0325           ; CLV/BVC byte-ready wait (classic 1541 trick)
$0327  B8          CLV
$0328  AD 01 1C    LDA $1C01           ; read raw GCR byte
$032B  9D 00 05    STA $0500,X         ; store into $0500-$0504 (5 bytes)
$032E  CA          DEX
$032F  10 F4       BPL $0325
$0331  C9 69       CMP #$69            ; last byte read must be $69
$0333  D0 DD       BNE $0312           ; mismatch -> retry
$0335  AD 01 05    LDA $0501           ; first byte read (2nd overall)
$0338  C9 A9       CMP #$A9            ; must be $A9 ($A3 on Chimera/
                                       ;   Willow Pattern - the only
                                       ;   variation found across all 10 titles)
$033A  D0 D6       BNE $0312           ; mismatch -> retry
                                        ; signature "69 xx xx xx A9" confirmed
                                        ; - same marker convention every
                                        ;   later GMA86/87 title uses too

; ---- read a raw 256-byte block off the track - NOT a pulse-width fold ----
; ---- this is where GMA86/87's real signature measurement would go;   ----
; ---- GMA85 just reads bytes and does nothing further with them      ----
$033C  2C 00 1C    BIT $1C00
$033F  30 FB       BMI $033C           ; wait for next sync
$0341  B8          CLV
$0342  AD 01 1C    LDA $1C01
$0345  A2 00       LDX #$00
$0347  50 FE       BVC $0347
$0349  B8          CLV
$034A  AD 01 1C    LDA $1C01
$034D  9D 08 05    STA $0508,X         ; store into $0508-$05FF (256 bytes,
$0350  E8          INX                 ;   X wraps 0 -> 0)
$0351  D0 F4       BNE $0347

; ---- read a SECOND raw 256-byte block ----
$0353  50 FE       BVC $0353
$0355  B8          CLV
$0356  AD 01 1C    LDA $1C01
$0359  9D 08 06    STA $0608,X         ; store into $0608-$06FF (256 bytes)
$035C  E8          INX
$035D  D0 F4       BNE $0353

; ---- report success. NO FOLD, NO COMPARE, NO SEND - the two 256-byte ----
; ---- blocks just read are never referenced again anywhere.          ----
$035F  A9 01       LDA #$01
$0361  4C 69 F9    JMP $F969           ; status = SUCCESS

; ---- job-dispatch caller (the M-E $0364 entry point) ----
$0364  20 42 D0    JSR $D042           ; (uncross-referenced ROM call)
$0367  A9 25       LDA #$25            ; $25 = 37 decimal - THE TARGET TRACK
$0369  85 06       STA $06
$036B  A9 01       LDA #$01
$036D  85 07       STA $07
$036F  20 18 C1    JSR $C118           ; (ROM call)
$0372  A9 E0       LDA #$E0
$0374  85 00       STA $00             ; submit EXECUTE job (buffer 0)
$0376  A5 00       LDA $00
$0378  30 FC       BMI $0376           ; wait for job completion
$037A  C9 02       CMP #$02
$037C  90 06       BCC $0384           ; status < 2 (success) -> continue
$037E  4C 7E 03    JMP $037E           ; status >= 2 (FAILURE) -> INFINITE SELF-LOOP,
                                       ;   the same "hang, never produce wrong
                                       ;   output" failure convention used
                                       ;   throughout the whole GMA family
$0381  20 2C C1    JSR $C12C           ; (unreached in the traced path)

; ---- bus reset - NOT a nibble-send sequence like GMA86/87's receivers ----
$0384  A9 00       LDA #$00
$0386  8D 00 18    STA $1800
$0389  8D 00 18    STA $1800
$038C  60          RTS

The C64-side loader (gma1) that uploads and triggers this program takes one of two cosmetically different but functionally equivalent forms across the surveyed titles. Seven titles (Rasputin, Chicken Chase, Nodes of Yesod, Microcosm, Gerry the Germ, The Arc of Yesod) follow the trigger with a check that looks, at first glance, like it might validate the result:

$0819  LDA #$00 / JSR SETMSG / LDA #$FF / STA $0329
       JSR <screen setup>
       JSR <Y/N prompt handler>
       LDA #$03 / JSR <load file "gma3">
       JSR $C800                    ; the upload trigger
       LDA $02
       EOR #$97
       BEQ <continue>
       JMP ($FFFC)                  ; "failure": jump through the RESET vector

This is not a validation of anything disk-specific: $02 is unconditionally set to $97 by the shared "M-" command-channel opener every time it runs, regardless of anything measured on the disk - confirmed by reading that routine's own code, which contains a bare LDA #$97 / STA $02 with no input dependency at all. That doesn't make the check pointless, though: sitting where it does - buried between the CHKOUT call and the 'M' character send, nowhere near the actual JMP ($FFFC) failure branch it enables - it reads much more plausibly as a deliberate code-integrity tripwire than an accident. A cracker who patches out or skips the JSR $C800 upload sequence entirely (a very natural thing to try against what looks like a copy-protection call) would leave $02 at whatever it held before, and this check would then correctly detect that the real routine never ran - without needing an obvious, easily-spotted "compare against a fixed value" pattern anywhere near the branch it guards. It just can't detect a bad disk the way GMA86/87's real signature checks can, since nothing about its value depends on anything read from the drive. The other four titles (Chimera, Willow Pattern, Booty, Cylu) skip this check entirely and simply discard the JSR $C800 result, reaching the identical outcome by an even more direct route.

Redundancy, not security

The marker is written three separate times on the target track, each copy carrying different, unchecked "salt" bytes rather than identical copies of one payload. Combined with the marker-search code's own 90-attempt retry budget, this points to plain reliability engineering — protection against a single degraded or misread copy on aging media — rather than any security purpose. The three copies share the byte-level layout used across the wider GMA family's non-signature tracks generally, not something specific to GMA85.

Relationship to GMA86 and GMA87

GMA85's presence-only check and the sync-length key-derivation scheme used by later GMA86/GMA87 titles share the same underlying seek/search/job-dispatch infrastructure — the same drive-upload plumbing, the same marker convention, the same hang-on-failure behavior. The later schemes add a measurement step (folding ten raw pulse-width samples from a deliberately malformed sync region into a single byte) and put that byte to work, either sent to the C64 for a bulk in-place decrypt or baked into a second drive-resident program for a per-sector decrypt on the 1541 itself. GMA85 has neither addition; it is what the scheme looked like before that layer existed.

At least one GMA86-era release (Robin of the Wood) shipped with this exact GMA85 drive program carried forward unchanged, rather than adopting the newer sync-length mechanism most other titles from that period use — worth noting as a specific case, not as evidence that GMA86 titles generally lack a key.

Variations

All GMA85 titles were independently confirmed to run byte-identical drive-side code, differing only in one cosmetic detail: the marker's terminating byte is $A9 in every title except Chimera and Willow Pattern, which use $A3.

See also