Start

heckmeck!

Nerd content and
cringe since 1999
Alexander Grupe
Losso/AttentionWhore

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.

Animation by Buddha/Spreadpoint

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.

FilenameX shiftBitplane combinationSize
kickhandsmol-shift1-col0.shr1#0793
kickhandsmol-shift2-col0.shr2#0793
kickhandsmol-shift1-col3.shr1#3789
kickhandsmol-shift2-col3.shr2#3788
kickhandsmol-shift0-col0.shr0#0782
kickhandsmol-shift2-col1.shr2#1777
kickhandsmol-shift0-col3.shr0#3776
kickhandsmol-shift1-col1.shr1#1773
kickhandsmol-shift2-col4.shr2#4771
kickhandsmol-shift1-col4.shr1#4768
kickhandsmol-shift0-col1.shr0#1766
kickhandsmol-shift0-col4.shr0#4758

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 ROMWhat
$FE8732Boot prompt display module
$FE8C9CStart of next module

The difference is 1,386 bytes. That’s how much space we will have for everything. Sounds doable!

Size comparison: 256 KB of ROM v. boot prompt section

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…

Well, what can you do

To test the setup, I started with some background color flashing to verify that our code gets executed at the right time.

Trippy

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.

Smol

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)
Scrambled egg, centered

Why are the graphics scrambled up? Because my bitmap extractor had a bug and wrote bytes in the wrong order. With that fixed:

Nearly good

Now, we’ll just re-arrange the color palette to match the bitmap order…

Voilà!

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.

previous next close
eie