GMA89

From Software Archive
Revision as of 00:53, 29 August 2026 by Enigma (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Pages that refer to this protection

GMA89

GMA89 is a Commodore 64 disk copy protection scheme used across a run of 1989 releases from Firebird, Hewson, Activision, and other publishers, and the fifth confirmed generation of the "GMA" protection family — successor to GMA88, predecessor to GMA90. Each disk's own BAM label follows the family's running build-date convention, and the loader chain keeps the same as/gm1 file-naming pattern used throughout the family: a boot stub loads at $02A7 and relocates itself over the KERNAL's page-2/3 RAM vector table exactly as documented for GMA85-88, then loads gm1, which uploads a signature-measurement program to 1541 drive RAM via M-W/M-E and triggers it via a fixed, hardcoded JSR $C800.

GMA89 continues GMA88's complete architectural consolidation. Of 17 surveyed titles (spanning over 30 individually traced disk images), every one that has a genuine, load-bearing check at all uses the identical mechanism: measurement on track 38, consumed as a C64-side bulk XOR decrypt. Two titles are the exception — not a new mechanism variant, but confirmed to have no functioning protection whatsoever in every locally-archived copy (see "Two titles with no functioning check" below), a finding new to this generation and not seen anywhere in GMA85 through GMA88.

Mechanism

  1. The drive seeks to track 38 ($26), searches for the family's standard byte-aligned marker (69 xx xx xx A9), and measures ten raw sync-to-sync pulse-width samples there, folding them into a single byte via the same CMP/ROL technique used throughout the whole GMA family.
  2. The result is sent to the C64 over a raster-line-synced $DD00 handshake (via a hijacked GETIN vector) and used as a single repeating-byte XOR key over almost the entire game, decrypted in place in one pass.
  3. 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, 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; track 38 sits outside the range any stock-formatted disk ever uses. Confirmed directly from a genuine GMA89 disk's own G64 — read straight off a g64conv text dump, no live tracing required — the raw sync-length values on Ghostbusters II's track 38 are:

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

 Track 38, raw sync sequence (Ghostbusters II, measured key = $18):

     8, 40,40,40,40,40,40,   119, 56,56,56, 184,184, 56,56,56,   1,1,13,9,5,1,8,2,...
     └──── ordinary syncs ────┘  └────────── the anomalous region ─────────┘  └─ gap/filler noise ─
                                  (10 raw samples the drive's pulse-width
                                   measurement loop actually reads)

Discarding the first sample (40) and taking the second (119) as the reference, the remaining eight (56,56,56,184,184,56,56,56) compared against it via CMP/ROL — longer-or-equal → 1, shorter → 0 — give 1,1,1,0,0,1,1,1... folding to exactly $18, matching this disk's live-confirmed key exactly. This is not a coincidence specific to one disk: the same window-selection rule, applied cold (no prior knowledge of the answer) to two more already-live-confirmed GMA89 disks — Christmas Collection's and Heatwave's own side 0 — independently reproduced their known keys ($4D and $63) too, confirming the raw sync N values g64conv reports are the literal numbers the drive's measurement loop consumes, with no unit conversion needed anywhere in the GMA family.

This static technique resolved real open questions in this generation's survey rather than just serving as a curiosity: Christmas Collection and Heatwave are both physically flippable compilation disks whose "back" side can't be reached by directly autostarting it (the real game-select and disk-swap flow only exists on the front side). Reading each back side's own raw track 38 directly — no boot, no swap, no human needed — showed both carry the identical signature as their own front side, resolving the question without ever driving the interactive flow.

Signature measurement: the shared drive-side routine

Byte-for-byte identical to the shared GMA85-88 signature-measurement program. Captured live for Ghostbusters II via a breakpoint freeze on the drive's own DRVTRK variable becoming 38:

; ---- 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
$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 sync-length result: $18 on Ghostbusters II

; ---- send the result byte to the C64 via VIA1 $1800, nibble-out ----
$035F  2C 00 18    BIT $1800
$0362  10 FB       BPL $035F           ; wait for IEC bus ready
$0364  A9 10       LDA #$10
$0366  8D 00 18    STA $1800
$0369  2C 00 18    BIT $1800
$036C  30 FB       BMI $0369
$036E  8A          TXA                 ; A = result byte
$036F  4A          LSR A               ; \  send high nibble
$0370  4A          LSR A               ;  |
$0371  4A          LSR A               ;  |
$0372  4A          LSR A               ; /
$0373  8D 00 18    STA $1800
$0376  0A          ASL A
$0377  29 0F       AND #$0F
$0379  8D 00 18    STA $1800
$037C  8A          TXA
$037D  29 0F       AND #$0F            ; \  send low nibble
$037F  8D 00 18    STA $1800           ;  |
$0382  0A          ASL A               ;  |
$0383  29 0F       AND #$0F            ;  |
$0385  8D 00 18    STA $1800           ; /
$0388  A9 0F       LDA #$0F
$038A  EA          NOP
$038B  8D 00 18    STA $1800
$038E  A9 01       LDA #$01
$0390  4C 69 F9    JMP $F969

; ---- job dispatch (M-E entry point): target track 38, submit EXECUTE job ----
$0397  A9 26       LDA #$26            ; $26 = 38 decimal - THE TARGET TRACK, every title
$0399  85 06       STA $06
$039B  A9 01       LDA #$01
$039D  85 07       STA $07
$039F  20 18 C1    JSR $C118
$03A2  A9 E0       LDA #$E0
$03A4  85 00       STA $00
$03A6  A5 00       LDA $00
$03A8  30 FC       BMI $03A6
$03AA  C9 02       CMP #$02
$03AC  90 06       BCC $03B4
$03AE  4C AE 03    JMP $03AE           ; failure -> infinite self-loop, family convention
$03B1  20 2C C1    JSR $C12C
$03B4  60          RTS                 ; plain RTS - no second-stage upload, C64-side only

The C64-side decrypt

Reached through the same JSR $C800PHA → ... → PLA/TAX handoff idiom used throughout the family. Ghostbusters II's own main loop:

L03A6:  BNE L03A6
        LDA #$37
        STA $01
        LDA #$00
        JSR sub_0461         ; load file 0
        JSR $C800              ; the fixed drive-upload-and-execute trigger,
                               ;   collects the measured key byte in A
        PHA                   ; stash the measured key
        LDA #$01
        JSR sub_0461          ; load file 1
        JSR sub_03EC
        LDA #$02
        JSR sub_0461          ; load file 2
        LDA #$03
        JSR sub_0461          ; load file 3
        LDA #$04
        JSR sub_0461          ; load file 4
        JSR sub_0449           ; KERNAL vector setup
        LDX #$59
L03D1:  LDA $2000,X
        STA $02A7,X
        DEX
        BPL L03D1
        PLA                   ; recover the measured key
        TAX                   ; X = $18 (this disk's measured key)
        JSR sub_041B            ; run the decrypt loop
        LDA #$37
        STA $01
        CLI
        LDA #$00
        JSR SETMSG
        JMP $02A7               ; jump into the freshly-decrypted code

The decrypt loop itself, a self-modifying EOR/STA walk that (like several GMA88 titles) reaches into the $D000-$DFFF I/O page, requiring the same deliberate memory-config bank-toggle documented for GMA88's Arac:

sub_041B:
        SEI
        LDY #$00
L041E:  LDA #$24               ; bank OUT I/O ($D000-$DFFF reads/writes as RAM)
        STA $01
        TXA
        EOR $D000,Y              ; self-modifying operand, walks upward through
        STA $D000,Y               ;   the I/O-page-shadowed RAM
        PHA
        LDA #$27               ; bank I/O back IN
        STA $01
        PLA
        INY
        BNE L041E
        INC $0425
        INC $0428
        LDA $0428
        CMP #$E0                ; end page $E0
        BNE L041E
        LDA #$37
        STA $01                 ; restore normal memory configuration
        LDA #$00
        STA $D418
        RTS

No comparison against any stored "correct" value ever happens — a wrong key just produces garbage code, the same failure convention used throughout the whole GMA family.

Relationship to GMA88 and GMA90

GMA89 continues GMA88's architecture without deviation wherever a functioning check exists at all: track 38, the identical shared drive-side signature-measurement routine, C64-side bulk-decrypt consumption. What's new to this generation is the discovery — via direct disk and code comparison, not assumption — that not every wiki-tagged GMA89 disk still has a working check: cracking and possibly other forms of loader modification are visible in this generation's archived material in a way they weren't in GMA85 through GMA88. GMA90 continues the same core architecture unbroken for every title surveyed there.

See also