Modding the Amiga boot hand
This tweet by @AndroidArts with a redesigned Amiga boot screen piqued my interest:
I love that design – stylish, and still true to the original! (By the way, go check out Arne’s pixels and various other projects, they’re awesome!)
Make it real
Naturally, the question occurs: Can we make this real and make the Kickstart ROM actually use this picture instead of good old lefty?
As you might have guessed, this isn’t simply a task of swapping out some bitmap graphics in the ROM file. That Kickstart hand isn’t stored as a bitmap at all! Instead, it uses a custom vector data format, with line segments, color changes and fill commands. (I’ve used that data before in the 512-byte intro spoke.) It does use bitmaps for the disk label and the version string, but those are tiny; most of the image is generated by lines and flood fills.
This way, the image data can be stored in a very compact form. The vector data takes up 412 bytes and the bitmaps come in at 288 bytes. Note that the bitmaps only use 2 bitplanes for the “AMIGA” text and 1 bitplane for the other texts. Again, very compact!
The actual drawing is done with built-in library functions: SetDrMd, Move, Draw, Flood, BltTemplate
A tight fit
Knowing that there won’t be much room to include our replacement picture, we need to gauge how much space we need. Storing the image uncompressed was out of the question: A 4-color screen in 320×200 pixels would take up 16,000 bytes. A cropped version with 144×120 pixels would need 4,320 bytes – still too much!
So, we need a kind of compression. I converted the picture into raw bitplanes and tested all possible bitplane combinations and different horizontal shifts to see what compressed best.
(If you’re unfamiliar with Amiga graphics hardware: Bitmaps are organized in separate bitplanes, where each bitplane defines a bit of the resulting palette index. A 4-color image uses 2 bitplanes, for palette indexes %00, %01, %10 and %11.)
In the end, Shrinkler won, using the 4th bitplane arrangement and no horizontal shift. This configuration compressed the image from 4,320 to 758 bytes.
Filename | X shift | Bitplane combination | Size |
---|---|---|---|
kickhandsmol-shift1-col0.shr | 1 | #0 | 793 |
kickhandsmol-shift2-col0.shr | 2 | #0 | 793 |
kickhandsmol-shift1-col3.shr | 1 | #3 | 789 |
kickhandsmol-shift2-col3.shr | 2 | #3 | 788 |
kickhandsmol-shift0-col0.shr | 0 | #0 | 782 |
kickhandsmol-shift2-col1.shr | 2 | #1 | 777 |
kickhandsmol-shift0-col3.shr | 0 | #3 | 776 |
kickhandsmol-shift1-col1.shr | 1 | #1 | 773 |
kickhandsmol-shift2-col4.shr | 2 | #4 | 771 |
kickhandsmol-shift1-col4.shr | 1 | #4 | 768 |
kickhandsmol-shift0-col1.shr | 0 | #1 | 766 |
kickhandsmol-shift0-col4.shr | 0 | #4 | 758 |
I also did a test run with Salvador/ZX0; it achieved 886 bytes in the smallest configuration. While the Salvador decruncher code is about 30 bytes smaller than the Shrinkler decruncher, that was too much of a difference to be a net win.
Target space
On to the hard part: Mess up the Kickstart ROM! Kickstart 1.3 revision 34.5, specifically. For one, because it’s already included in the picture, and also because a fixed goal is easier. This ROM should work with most non-AGA Amigas (500, 2000, 3000, maybe even CDTV).
Luckily, the code responsible for displaying the boot prompt isn’t scattered all over the Kickstart ROM, but consists of a single, pretty much self-contained block.
Address in ROM | What |
---|---|
$FE8732 | Boot prompt display module |
$FE8C9C | Start of next module |
The difference is 1,386 bytes. That’s how much space we will have for everything. Sounds doable!
Reverse engineering
What does the built-in boot prompt routine do, and where can we shove in our code? Basically, it contains:
- Function to show the boot prompt
- Allocate some memory
- Set up a viewport, a view, a rastport, a copperlist
- Initialize a bitmap of 320×200 pixels with 2 bitplanes
- Parse and draw the vector data
- Blit the disk label and version string
- Load palette
- Activate bitplane DMA
- Function to disable the boot prompt
- Disable bitplane DMA
- Free memory
- Vector data
- Bitmap data
Nice: We can probably re-use the screen setup and do our thing there. The clean-up code can stay, too.
Replace it piece by piece
As a first experiment, I changed the palette contained in the prompt display module. (And I enabled the sprite DMA for fun: Turns out the mouse pointer is already initialized at this point in the boot process! You can’t move it, though.)
Thankfully that worked, and with that checked off, the code-replacement pipeline was set up:
- Take a disassembled copy of the original ROM module
- Modify some stuff
- Assemble it
- Overwrite a portion of the Kickstart ROM with our assembled code
- Test it in the emulator
- Repeat
At this point, WinUAE would complain about an incorrect ROM checksum, but since Kickstart doesn’t verify its own checksum, it would boot anyway. Maybe a task for later…
To test the setup, I started with some background color flashing to verify that our code gets executed at the right time.
Then I cropped the bitmap (and the viewport) from 320×200 down to 144×120 pixels and directly decompressed the image data into the bitmap. Also, the palette was set up.
Cool, the decompression kind of worked, even if the image looks a bit garbled. But the smaller viewport is now crammed into the top left corner! Luckily, graphics.library’s View structure provides fields for X and Y offsets. Let’s test them!
V_XOFF=180+48 V_YOFF=140-28 ; view in a0 move.w #V_XOFF,v_DxOffset(a0) move.w #V_YOFF,v_DyOffset(a0)
Why are the graphics scrambled up? Because my bitmap extractor had a bug and wrote bytes in the wrong order. With that fixed:
Now, we’ll just re-arrange the color palette to match the bitmap order…
Coolcoolcool. The picture is displayed nicely, and when a disk is inserted, it runs!
A final patch
But, as it turns out, it only worked for the trackmo demos I tested it with. As soon as a DOS disk was inserted, such as the original Workbench disk, it was Guru time!
What’s happening? While demos usually take over the whole system right in the boot block, standard boot blocks will return to the Kickstart boot process and Kickstart will call the “disable boot image” method, which lives in our boot prompt module. Since we’ve messed around with everything, that call will end up somewhere in the middle of our code and crash!
So, we need to find where the “boot hand image off” method is called in the ROM and patch that.
The original code for that was at address $FA8984
, but there is no code that uses
that address directly! With the help of breakpoints in WinUAE, the culprit was still found,
a relative sub-routine branch:
00fe86b4 6100 02ce bsr.w #$02ce == $00fe8984
The final modification of our ROM:
- Determine how many bytes we shifted the “boot image off” entrypoint
- Add that delta to
$02ce
- Overwrite the
bsr
argument at address$FE86B4+2
with that value
And now we’re done! We even have 112 bytes left, hmmm…
See it in action!
I used my ACA500plus to install and boot from the custom Kickstart image.
Get it!
I can’t just post the resulting ROM file here, since Kickstart 1.3 is still copyrighted software. (In fact, I even took care to mask out the exact byte values in the ROM dump picture above to avoid accidentally posting the full ROM contents.)
But if you already have a dump of the Kickstart 1.3 revision 34.5 ROM, I can provide you with a kind of patch, containing only
- my code,
- Arne’s compressed picture, and
- Blueberry’s Shrinkler decompress code.
It comes as a bash script:
Requires a POSIX-y environment. Under Windows, WSL will do. Uses bash, dd, md5sum, base64, tail. Usage:
./kickhandpatch.sh <kick13.rom> <patched.rom>
The script will try to verify the input and output files. Example:
# # Make script executable # $ chmod a+x kickhandpatch.sh # # Create patch ROM file # $ ./kickhandpatch.sh kick13.rom patched.rom OK: kick13.rom seems to be the right version. Writing patched.rom... 165558+0 records in 165558+0 records out 165558 bytes (166 kB, 162 KiB) copied, 2.3432 s, 70.7 kB/s 122+0 records in 122+0 records out 122 bytes copied, 0.0029947 s, 40.7 kB/s 95188+0 records in 95188+0 records out 95188 bytes (95 kB, 93 KiB) copied, 1.54148 s, 61.8 kB/s Done. OK: File size 262144 OK: Patched file looking good (md5sum as expected)
As mentioned earlier, the Kickstart checksum is currently not re-calculated, and WinUAE or your ACA500plus will complain, but it should run nevertheless. In theory, it should™ be 1:1 compatible with Kickstart 1.3.
What’s next
Apart form correctly updating the Kickstart checksum, Arne already suggested adding a time-based “insert disk” message. That could be tricky, as the original boot prompt implementation does not really offer a time-based callback (like a “tick” method).
Then again, 112 bytes still free uncompressed offer a lot of room for activities… :)
Update: For the Kickstart checksum, there’s SumKick (contained in MKick19), and apparently, the Kick 2.0+ boot screen is waiting to be patched as well.
Amiga Style
- B.S.I. – Byte Scene Investigation
- Coppenheimer
- Worms VBI
- strss
- Modding the Amiga boot hand
- rotz
- Amiga BBS ANSI animations
- wchrmas pattern
- Teletext on Amiga – the real world’s first!
- More…
Blog
- Keming war gestern
- Worms DeCoded
- VC³ 2024, an early present
- Fun with Worms DC custom levels
- Modding the Windows boot hand
- Unit Arrr
- I just favican’t
- I consider myself more of a Renaissance man
- Zerosphere on Analogue Pocket
- Amiga Topaz 1.4, part 2
- More…