Difference between revisions of "GMA85"
| (2 intermediate revisions by the same user not shown) | |||
| Line 35: | Line 35: | ||
C64 — perform the check: | C64 — perform the check: | ||
| − | # The drive seeks an out-of-range track — '''track 37''' in every GMA85 | + | # 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). |
| − | + | # It searches for a short, deliberately non-standard byte marker (<code>$69</code> ... <code>$A9</code>, 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. | |
| − | + | # On a match, it reads two raw 256-byte blocks off the disk into its own RAM. | |
| − | # It searches for a short, deliberately non-standard byte marker | + | # 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. |
| − | + | # 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. | |
| − | |||
| − | |||
| − | |||
| − | # On a match, it reads two raw 256-byte blocks off the disk into its own | ||
| − | |||
| − | # It reports success to the C64 unconditionally — the two blocks it just | ||
| − | |||
| − | |||
| − | # The C64 continues loading regardless of what (if anything) was in | ||
| − | |||
| − | |||
| − | |||
| − | |||
Because nothing downstream ever consumes the read data, a disk copy only | Because nothing downstream ever consumes the read data, a disk copy only | ||
| Line 58: | Line 45: | ||
to pass — confirmed experimentally by corrupting the surrounding data on | to pass — confirmed experimentally by corrupting the surrounding data on | ||
a real GMA85-derived disk and observing no change in behavior. | 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: | ||
| + | |||
| + | <pre> | ||
| + | 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 | ||
| + | </pre> | ||
| + | |||
| + | 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 <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 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: <code>$A9</code> in nine of | ||
| + | the ten surveyed titles, <code>$A3</code> on Chimera and Willow Pattern only | ||
| + | — see "Known GMA85 titles" below. | ||
== The drive-side program: annotated disassembly == | == The drive-side program: annotated disassembly == | ||
| Line 239: | Line 285: | ||
== See also == | == See also == | ||
| − | |||
| − | |||
* [[Robin of the Wood]] | * [[Robin of the Wood]] | ||
Latest revision as of 00:12, 29 August 2026
Contents
Pages that refer to this protection
- Descriptions of old disk copy protections (← links)
- GMA86 (← links)
- GMA87 (← links)
- Rasputin / Chickin Chase (Firebird) (← links)
- Chimera / Willow Pattern (Firebird) (← links)
- Nodes of Yesod (← links)
- GMA92 (← links)
- Booty / Cylu (Firebird) (← links)
- Microcosm / Gerry the Germ (Firebird) (← links)
- The Arc of Yesod / Nodes of Yesod (Firebird) (← links)
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:
- 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).
- 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. - On a match, it reads two raw 256-byte blocks off the disk into its own RAM.
- 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.
- 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.