GMA89
Contents
Pages that refer to this protection
- Descriptions of old disk copy protections (← links)
- Zamzara (← links)
- GMA88 (← links)
- GMA90 (← links)
- Tusker (← links)
- Eye of Horus (← links)
- Star Trek: The Rebel Universe (← links)
- Ghostbusters II (← links)
- Maze Mania (← links)
- Hot Rod (← links)
- Christmas Collection (← links)
- Heatwave (← links)
- Quelle Action Hits (← links)
- Power Hits (← links)
- Wicked (← links)
- Altered Beast (← links)
- Chambers of Shaolin (← links)
- Power Drift (← links)
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
- 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 sameCMP/ROLtechnique used throughout the whole GMA family. - The result is sent to the C64 over a raster-line-synced
$DD00handshake (via a hijackedGETINvector) and used as a single repeating-byte XOR key over almost the entire game, decrypted in place in one pass. - 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.
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 $C800 → PHA → ...
→ 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.