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 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.
| Property | Value |
|---|---|
| Boot signature | DOS\0 |
| MD5 | 4293476ee4229d76803fbc749234136c |
| SHA-256 | 153a3bef09fa40b6f95a8c0f33aa21ececa6f4de9cf1f1726b672b1239d604f5 |
| Filesystem | Custom 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:
| Read | ADF offset | Bytes | RAM | Purpose |
|---|---|---|---|---|
| 1 | $000400 | $1200 | $20000 | Resident stage-1 loader |
| 2 | $0ad400 | $1b800 | $62cf0 | Cracktro 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=$9980in 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.
| Evidence | What it tells us |
|---|---|
$62cf0 entry and RTS | The payload is called as an initialization stage, not simply copied and forgotten. |
Interrupt vector $6c → $62de4 | The intro installs code that can continue running asynchronously. |
graphics.library, custom-chip accesses | Graphics/display setup is part of the payload's environment. |
CRACKED IN 1990 and publisher/design credits | This is the cracktro/credit screen payload. |
introbeat plus six sample names | The 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-finetimest-99:snare-bigfunst-99:hhop-alrightst-99:piano-oasisst-99:snare-pinkst-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.
| Region | Start side-track | Tracks | Packed | Output trailer |
|---|---|---|---|---|
| 0 | 1 | 29 | $27700 | $32f50 |
| 1 | 30 | 7 | $08c24 | $0cc24 |
| 2 | 37 | 4 | $04ff0 | $08790 |
| 3 | 41 | 6 | $06e40 | $0a6c8 |
| 4 | 47 | 10 | $0d014 | $11d80 |
| 5 | 57 | 9 | $0c458 | $1d8ca |
| 6 | 66 | 9 | $0ba0c | $1dc73 |
| 7 | 75 | 7 | $098ec | $1e979 |
| 8 | 82 | 10 | $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 image | Still 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.
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.
Crack intro: the preloaded stage-2 payload displays the INTACT title and credits Defjam, CCS, ACC, and Crosstechnics.
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.
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.
| Property | Value |
|---|---|
| Boot signature | DOS\0 |
| MD5 | 4293476ee4229d76803fbc749234136c |
| SHA-256 | 153a3bef09fa40b6f95a8c0f33aa21ececa6f4de9cf1f1726b672b1239d604f5 |
| Filesystem | Not a clean mountable AmigaDOS filesystem; the nominal root area contains loader payload bytes. |
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:
| Read | ADF offset | Length | RAM destination | Meaning |
|---|---|---|---|---|
| 1 | $000400 | $1200 | $20000 | Stage-1 loader |
| 2 | $0ad400 | $1b800 | $62cf0 | Crack-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 1990introbeat- 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:
| Region | Start | Tracks | Packed bytes | Output length |
|---|---|---|---|---|
| 0 | 1 | 29 | $27700 | $32f50 |
| 1 | 30 | 7 | $08c24 | $0cc24 |
| 2 | 37 | 4 | $04ff0 | $08790 |
| 3 | 41 | 6 | $06e40 | $0a6c8 |
| 4 | 47 | 10 | $0d014 | $11d80 |
| 5 | 57 | 9 | $0c458 | $1d8ca |
| 6 | 66 | 9 | $0ba0c | $1dc73 |
| 7 | 75 | 7 | $098ec | $1e979 |
| 8 | 82 | 10 | $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
introbeatProTracker 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
Enregistrer un commentaire