How an Amiga Crack Loaded Intact: Boot Block, Cracktro and Game

A small Amiga archaeology exercise. We are going to follow one disk from the first boot read, through its cracktro, into the game handoff—and separate what the bytes prove from what still needs an emulator.

There is something wonderfully revealing about old Amiga software: “loading a game” could mean taking over the whole machine. The disk might not contain a neat directory of files. It might contain a boot block, a custom floppy reader, a depacker, a music module, and a lot of carefully arranged bytes that only make sense when the loader puts everything back together.

That is exactly what turned up in Intact, the 1990 vertical shooter from Sphinx Software and CrossTechnics. I started with the disk image named Intact (1990)(Sphinx)[cr Defjam - CCS - ACC].adf and followed the code statically. I did not mount the image or execute it, so this is a “read the evidence” reconstruction rather than a claim that I have reproduced every screen.

First, what is Intact?

Intact is an OCS Amiga shoot-'em-up: a ship moves through vertically scrolling levels, fights enemy formations, and collects credits that can be spent on upgrades. It is the kind of game where the end result is fast action, but the startup path is a small systems-programming project in its own right.

For context, the screenshot below is from Lemon Amiga's Intact entry. It helps identify the game; it is not being presented as a frame recovered from this ADF.

Intact title and gameplay screen

Intact screenshot, reproduced from Lemon Amiga for identification and context.

The disk gives us a useful first clue

The ADF is 901,120 bytes, which is exactly the size expected for an 80-track, double-sided, 11-sector Amiga floppy image. The boot block begins with DOS\0 and has a valid Amiga boot checksum. That sounds like a normal disk, but the area where an AmigaDOS root block would normally be found contains high-entropy loader data rather than a clean filesystem header.

In other words: the disk keeps enough of the Amiga boot contract to start, then uses its own rules for the rest. A directory tool is looking in the wrong place.

PropertyValue
Boot signatureDOS\0
MD54293476ee4229d76803fbc749234136c
SHA-256153a3bef09fa40b6f95a8c0f33aa21ececa6f4de9cf1f1726b672b1239d604f5
FilesystemCustom bootable track layout, not a clean mountable AmigaDOS volume.

Step 1: the boot block loads two pieces

The first executable code is in the boot block itself. It builds an Amiga I/O request, uses read command 2, and performs two reads before jumping into the first one:

ReadADF offsetBytesRAMPurpose
1$000400$1200$20000Resident stage-1 loader
2$0ad400$1b800$62cf0Cracktro payload
; immediate values reconstructed from the boot block
move.l  #$1200,$24(a1)       ; read length
move.l  #$20000,$28(a1)      ; destination
move.l  #$400,$2c(a1)        ; disk offset
move.w  #2,$1c(a1)            ; device read
jsr     -$1c8(a6)

move.l  #$1b800,$24(a1)
move.l  #$62cf0,$28(a1)
move.l  #$ad400,$2c(a1)
move.w  #2,$1c(a1)
jsr     -$1c8(a6)

jmp     $20000

This is the key transition. The Amiga does not open a file called “loader”, and it does not launch a normal C/Intro program from a directory. The boot block copies raw bytes to known addresses and jumps to them.

Step 2: stage 1 becomes its own disk driver

Once running at $20000, the resident loader allocates nine destination buffers. It then talks directly to the floppy hardware through CIA and custom-chip registers. The track reader:

  • selects the drive, side, direction, and head-step controls;
  • starts disk DMA with DSKLEN=$9980 in read mode;
  • searches for the Amiga MFM sync word $4489;
  • separates odd and even MFM words with $55555555;
  • places sectors by their sector numbers; and
  • checks the decoded XOR checksum.

That explains how a disk can look “wrong” to AmigaDOS and still boot perfectly. The loader is treating the floppy as a physical track stream, not as a collection of ordinary files.

Step 3: the cracktro is a preloaded mini-demo

The second boot read put $1b800 bytes at $62cf0. During stage-1 initialization, code at loader offset $1cc calls a small wrapper at $4b2; that wrapper calls the payload at $62cf0 and returns.

The payload is much more than a text string. Its entry code initializes pointers and custom-chip state, installs an interrupt vector at address $6c pointing into the payload at $62de4, calls several setup routines, and returns with RTS. The loader therefore gives the intro a chance to install its graphics, audio, and interrupt machinery while the rest of the startup continues.

EvidenceWhat it tells us
$62cf0 entry and RTSThe payload is called as an initialization stage, not simply copied and forgotten.
Interrupt vector $6c$62de4The intro installs code that can continue running asynchronously.
graphics.library, custom-chip accessesGraphics/display setup is part of the payload's environment.
CRACKED IN 1990 and publisher/design creditsThis is the cracktro/credit screen payload.
introbeat plus six sample namesThe intro carries its own tracker music and samples.

The friendly way to think about it is: this is a tiny demo embedded in the loading process. It establishes a visual/audio presence, shows the group's credits, and can use interrupts while the loader does the less glamorous work of reading and expanding the game.

There is one important caveat. Static code proves the payload is loaded, called, and equipped to drive graphics/audio. It does not prove the exact order of the screens, whether the music starts before or after every track read, or precisely when the cracktro hands the display back. Those are emulator questions.

The soundtrack survived intact

At payload offset $dcac there is a complete standard 4-channel ProTracker module named introbeat. It has the M.K. signature, 22 song positions, 20 patterns, and six named 8-bit sample streams:

  • st-99:bdrum-finetime
  • st-99:snare-bigfun
  • st-99:hhop-alright
  • st-99:piano-oasis
  • st-99:snare-pink
  • st-99:bass-oasis

The module was extracted byte-for-byte from the payload. That is a nice example of the difference between “not everything is decoded yet” and “nothing can be recovered”: the cracktro's music is already a normal, recognizable Amiga artifact.

Step 4: the game is stored as nine packed regions

The loader contains a descriptor table at file offset $f88. Each 12-byte entry says where a region will go, which side-track it starts on, how many tracks to read, and how many packed bytes to pass to the depacker.

RegionStart side-trackTracksPackedOutput trailer
0129$27700$32f50
1307$08c24$0cc24
2374$04ff0$08790
3416$06e40$0a6c8
44710$0d014$11d80
5579$0c458$1d8ca
6669$0ba0c$1dc73
7757$098ec$1e979
88210$0ceb4$1ec5c

The “output trailer” column is the final output length stored at the end of each packed stream. It is not a decompressed file sitting on disk; it is a value the depacker reads while working backwards.

So what is the packing?

This is where the phrase custom backward-LZ-style depacker comes from. The label is descriptive, not a guess at a commercial packer name.

The routine is embedded in the loader at offset $6ca. It starts at the end of a packed region, consumes a least-significant-bit-first bitstream using a 68000-specific refill/anchor convention, and writes output toward the beginning of the destination. Its copy loop is unusually helpful:

move.b  -1(a2,d2.l),-(a2)

That means “take a byte from a backward distance and move the destination backwards by one byte.” The routine has literal runs, reference classes, and several distance forms. From the instructions we can describe:

  • a 3-bit literal count, including an extended-count branch;
  • a 2-bit reference class;
  • fixed and variable copy lengths;
  • 8-, 14-, 12-, and 16-bit backward-distance paths; and
  • a shared flag path selecting the 12- or 16-bit distance form.

Each stream ends with a 12-byte trailer. The final longword supplies the output length, the preceding longword is skipped by this routine, and the next longword seeds the initial bit reservoir.

That is enough to understand the shape of the codec and to avoid calling it PowerPacker or ByteKiller without evidence. It is not yet enough to reproduce all nine files: the current standalone translation still reaches impossible references on every exact stream. The likely missing piece is in the precise bitstream framing/refill behavior, not in the overall idea that the data is backward-output compressed.

The last move: handing control to the game

After the intro call and the track/decrunch sequence, stage 1 explicitly decrunches the $08c24-byte region to $25b48. It copies a 28-byte stub into low memory at $400 and jumps there. The stub copies 52,264 bytes from $25b48 to $1f0e0, then jumps to $50000.

; low-memory handoff stub
lea     $25b48,a1
lea     $1f0e0,a2
moveq   #$3309,d0
.copy:
move.l  (a1)+,(a2)+
dbra    d0,.copy       ; $330a longwords = 52,264 bytes
jmp     $50000

This is the final statically visible transfer into the loaded game/main-program address space. It is a very compact ending to a fairly elaborate startup: read tracks, expand regions, then jump into the reconstructed program.

What is confirmed, and what is still open?

Confirmed from the imageStill to explore
Two boot-block reads and their RAM destinations.The exact bit alignment/refill rule needed to decode all nine regions.
A raw MFM reader with sync, deinterleave, sector placement, and checksum logic.Which expanded region contains code, sprites, fonts, level data, or other assets.
The cracktro entry, interrupt-vector installation, credits, and tracker module.The exact order and timing of the intro screens and music relative to loading.
Nine packed regions, their track ranges, lengths, and final handoff addresses.A byte-level comparison with an untouched original disk to identify the crack delta.
The final copy-and-jump sequence.An isolated emulator run to confirm the live handoff at $50000.

There are also some tempting leads that deserve restraint. The loader contains strings such as Intact:C/Intro, but the disk is not a normal AmigaDOS filesystem, so that string should not be turned into a claim that a real C/Intro file exists. Likewise, a write to the 68000 bus-error vector is an observation in the sample, not proof of malicious behavior.

Watching the three stages run

Once FS-UAE was configured with an Amiga 500 profile, the loader could be followed visually as well as statically. These frames are a little like three snapshots from a relay race: the ROM/bootstrap stage, the cracktro taking over the display, and the game receiving control.

FS-UAE Amiga boot stage

Boot stage: the emulator is at the machine's ROM/bootstrap display before the disk's custom loader has drawn its own screen. This frame is a separate no-disk boot reference because the ADF's transition from boot reads to custom graphics is too quick to pause cleanly.

Intact cracktro credits

Crack intro: the preloaded stage-2 payload displays the INTACT title and credits Defjam, CCS, ACC, and Crosstechnics.

Intact game screen after cracktro

Post-intro game screen: the run has moved beyond the cracktro into the game's own display, confirming the handoff described by the loader analysis.

The runtime check confirms the broad chain: boot code, crack-intro payload, then game-side code. It does not by itself identify every packed region or prove the exact decompressor semantics; those remain byte-level questions for the disassembly.

Why this is a fun kind of reverse engineering

The crack did not merely remove a copy-protection check. It changed the computer's relationship with the disk. The Amiga boots a small program, that program becomes a disk driver, the disk driver reconstructs several memory regions, and a separate mini-demo announces the people who made the altered version. By the time the game starts, the “file” has been transformed several times.

What we can already recover is surprisingly tangible: the boot code, the raw-track loader, the descriptor table, the cracktro strings and setup logic, the complete introbeat module, and all nine packed regions. What remains is the satisfying hard part: making the depacker agree with the original 68000 on every bit, then identifying the assets hidden behind those streams.

Method note: this article is based on a static, read-only analysis. The ADF was not mounted and no code from it was executed. The companion notes retain the disassembly, hashes, packed-region extracts, soundtrack, and decoder experiment for future work.

Case study: a 1990 Amiga disk image, reconstructed statically from its boot block, 68000 loader, raw MFM track reader, packed regions, and crack intro.

When an Amiga game “loads” from floppy, the interesting part is often hidden before the title screen. In this case the disk is not a normal AmigaDOS volume with a tidy C/ directory. It is a bootable custom track loader: the boot block places two pieces of code in RAM, the resident loader takes over the floppy hardware, a crack intro is invoked, and the game is finally handed off through low memory.

This post documents what can be established from the file Intact (1990)(Sphinx)[cr Defjam - CCS - ACC].adf without executing it. The analysis is deliberately static and reproducible; the recovered artifacts and disassembly are retained alongside the notes.

The game and the disk

Intact is a 1990 Sphinx Software / CrossTechnics vertical shooter for the Commodore Amiga. The analyzed image is a 901,120-byte ADF, consistent with an 80-track, double-sided, 11-sector disk image.

PropertyValue
Boot signatureDOS\0
MD54293476ee4229d76803fbc749234136c
SHA-256153a3bef09fa40b6f95a8c0f33aa21ececa6f4de9cf1f1726b672b1239d604f5
FilesystemNot a clean mountable AmigaDOS filesystem; the nominal root area contains loader payload bytes.

Intact gameplay/title screen

Intact screenshot, reproduced from Lemon Amiga's Intact page for identification/context.

Boot: two reads, then a jump

The boot block contains executable 68000 code at offset $0c. It uses the Amiga device read command twice:

ReadADF offsetLengthRAM destinationMeaning
1$000400$1200$20000Stage-1 loader
2$0ad400$1b800$62cf0Crack-intro payload
; boot-block excerpt — immediate values from the image
move.l  #$1200,$24(a1)       ; read length
move.l  #$20000,$28(a1)      ; destination
move.l  #$400,$2c(a1)        ; disk offset
move.w  #2,$1c(a1)            ; command: read
jsr     -$1c8(a6)

move.l  #$1b800,$24(a1)
move.l  #$62cf0,$28(a1)
move.l  #$ad400,$2c(a1)
move.w  #2,$1c(a1)
jsr     -$1c8(a6)

jmp     $20000

The first stage is therefore not found by opening a file named “loader”. The boot path reads bytes directly into memory and transfers control to them. The intro payload is also already resident before stage 1 begins.

Stage 1: raw MFM instead of AmigaDOS

The resident loader allocates nine destination regions, controls the floppy via CIA/custom-chip registers, starts disk DMA in read mode with DSKLEN=$9980, searches for sync word $4489, deinterleaves odd/even MFM words with $55555555, places sectors, and checks the decoded XOR checksum.

This is the important crack technique: the loader is reading the physical track stream rather than asking AmigaDOS to resolve files and sectors. That allows a nonstandard disk layout and makes directory-based inspection misleading.

The crack intro is a separate payload

During stage-1 initialization, a call at loader offset $1cc reaches a wrapper at $4b2, which calls the preloaded payload at $62cf0 and returns. The payload initializes custom-chip state, installs an interrupt vector at $6c pointing into the payload at $62de4, runs initialization routines, and returns with RTS.

Its embedded strings make the purpose clear:

  • PUBLISHED BY# CCA SCC MAJFED #GAME DESIGN BY#CROSSTECHNICS.
  • CRACKED IN 1990
  • introbeat
  • six st-99: sample labels

At payload offset $dcac there is a complete 4-channel ProTracker module named introbeat. It was recovered byte-for-byte, including six raw samples. This is a directly recoverable crack-intro artifact; it is separate from the still-packed game regions.

Nine packed regions

The loader's descriptor table records each region's starting side-track, track count, packed length, and runtime destination. The packed lengths are:

RegionStartTracksPacked bytesOutput length
0129$27700$32f50
1307$08c24$0cc24
2374$04ff0$08790
3416$06e40$0a6c8
44710$0d014$11d80
5579$0c458$1d8ca
6669$0ba0c$1dc73
7757$098ec$1e979
88210$0ceb4$1ec5c

Why call it a custom backward-LZ-style codec?

The decruncher is embedded in the loader at offset $6ca. It does not carry a standard PP20 or ByteKiller identification header. The code reads from the end of the packed stream, emits toward the start of the destination, and copies bytes using:

move.b  -1(a2,d2.l),-(a2)

That instruction is the signature of a backward-output copy loop. The routine has literal runs, reference classes, and several backward-distance widths (8, 14, 12, and 16 bits). Its trailer supplies the output length, a skipped longword, and the initial bit-reservoir value.

We translated that grammar into a standalone decoder, but validation still fails on all nine exact streams with impossible references. So the article should say “embedded backward-output depacker” or “backward-LZ-style codec,” not claim a known commercial packer.

The final handoff

After the intro call and track/decrunch sequence, stage 1 explicitly decrunches the $8c24-byte region to $25b48. It copies a 28-byte stub to low memory at $400, and that stub copies 52,264 bytes to $1f0e0 before jumping to $50000.

; low-memory handoff stub
lea     $25b48,a1
lea     $1f0e0,a2
moveq   #$3309,d0
.copy:
move.l  (a1)+,(a2)+
dbra    d0,.copy       ; $330a longwords = 52,264 bytes
jmp     $50000

That jump is the final statically identified transfer into the loaded game/main-program address space. The exact visual timing of the crack intro, and the identity of each decompressed region, need emulator confirmation or a fully validated decoder.

What the disk preserves

  • A valid boot block and a custom raw-track loader.
  • The separate crack intro, including credits, graphics/audio setup, and the introbeat ProTracker module.
  • Nine packed game regions with a descriptor table and output-length trailers.
  • A reproducible evidence bundle with hashes, disassembly, packed-region extracts, and the current decoder experiment.

Method and limits

This was a static, read-only reconstruction. The ADF was not mounted and no code from it was executed. “Confirmed” means visible in the boot block, extracted bytes, strings, or instruction flow. “Expected game handoff” describes the final jump target; it is not a claim that the game modules have already been successfully unpacked.

The working notes and artifacts are kept in the analysis bundle. The most useful companion document is Intact (1990) crack load process: boot to game, which records the chronological sequence and evidence pointers.

 

Commentaires