====================
== amiga.inet6.se ==
====================

Amiga Bootblock Demo #1

sizecoding amiga bootblock assembler

Coding an Amiga bootblock demo

Once upon a time, me and my friends would be watching and later coding demos for the Amiga computers under the group name DeadZone. While there were many directions such demos would take, from small intros and cracktros to multiple-disk megademos, I quite liked to code bootblocks. The size challenge was a definite limit on what you could cram into those two sectors and still have a bootable disk.

Development environment

I have several setups with hardfile-based Amiga emulation environments, and I quite enjoy coding inside those in DevPac like in the old days, with MonAm for debugging (and GenAm for makefile-style assembling) but due to the fact that bootblock development ultimately ends up with having to reboot to test it, it was easier to move to a dev-env outside of AmigaOS. DevPac does have a nice flag to force all code to be PC-relative, which is good both because I never know where the OS will load the bootblock code and there will be no relocation done on the labels in the program, but also because PC-relative code usually is smaller than fixed 32-bit addresses.

Luckily, vasm has a nice DevPac-compatible mode, so I could just move the sources over and make a small build script that used vasm for assembly, editing in emacs and then continue development from my laptop. Also, I added a small program that calculates the amiga bootblock checksum, something I will return to later.

(Picture below shows one of my favourite bootblocks from the late 80s from the Swedish group DigiTech, that shows a lot of code and less gfx)

Another bootblock demo from DigiTech

Presentation

When starting to code this bootblock I aimed for it to be the basis for a presentation. I visit the ACG-G summer and winter Amiga meetups where there are slots for a bit of “show-and-tell” and I decided to let this bootblock demo be the source material for my presentation there this year, even if it wasn’t finished by then.

Challenges when doing bootblock demos

The obvious one is the restricted size of course, the amiga roms will read the first two sectors (2*512 bytes) of your floppy and if the checksum is ok, execute from 12 bytes into them until it returns.

There is a certain amount of things a bootblock must do, the older ones open dos.library and the newer (ks2.0+) also touch on expansion.library, so those strings also need to appear in the bootblocks. The code that does this eat up somewhere around 80-90 bytes, and there has to be a filesystem identifier DOS\0 or DOS\1, a valid 32bit checksum and a pointer to which sector holds the first directory sector, usually sector 880. Apart from this, the rest of the space is unused and available to you.

Amiga Bootblock

Graphics in the bootblock

As with any demo, one wants to paint some graphics. Preferably this would be the group logo, made by your teams graphics artist, and in as many colors as you can allow given other restraints. But in the context of 1024 bytes, the amount of colorful logos you can squeeze in is very limited, so I went for another image, a silouette of a french bulldogg. I was hoping that the simple one-colored black/white image with long runs of ones or zeroes would compress really well.

Bulldogg

Image compression

Since the image is 320x200 pixels, it still takes up 8000 bytes. This needs to compress a lot more than 1:8 in order to have a chance of fitting. Converting the raw image to PNG ends up giving you a file that is over 2k in size as will gzipping, so one needs to get more creative than that.

During this time I sent the image to Christopher Jam (cjam of F4CG) who tried his various C64 compressors on it, but none of those tests gave the huge ratios I was hoping for. Finally, cjam figured out that the image could be stored not as RLEd data but as three sets of coordinates, one list of which vertical Y line the coordinate belong to, and two separate tables that tell you at which pixel it should start painting and where it ends.

This would let you store the image as horizontal lines, but as a set of pixels to plot out, then you run a bit-fill over the image to start and stop the filling of bits in between the pixel pairs. This new format ended up being three lists of 400 bytes each. Even if the image is only 200 pixels high, some lines have several start/stop pairs, so it just happened to end up being 1200 bytes exactly. Now, these lists have obvious patterns to them, and the Y list was linearly increasing numbers, so all of them allowed for delta encoding to make the byte array use only small integer values, which certainly helps compression afterwards.

Compressing the whole bootblock

Ok, so the 8k image is down to 1200 bytes, plus some extra for the extraction and unpacking. The three tables compress really well, and the code is shrinking quite a bit. Using the ZX0 packer makes the current result become around 774 bytes, including the overhead, the ZX0 unpacker and the packed graphics showing code. Several hundred bytes available, lets add some code!

I decided to try to handle 68000-060, Kickstart 1.3 -> 3.x so a few calls extra to flush instruction cache after unpacking new code if Exec has CacheFlushU(), and disabling AGA Fmode, since the extra memory fetches AGA needs makes the screens use modulo to cover extra fetches, so if AGA is in effect, my image gets distorted due to the modulo added to each display line.

Testing other compressors

I started researching other packers for small intros, found Shrinkler so I expanded my build.sh script to test packing with both ZX0 and Shrinkler with all the possible settings. Later on I added BitPickler (from the above-mentioned cjam) and Louzy77 to this setup aswell, building four different bootblocks each with their own unpacker and seeing which of them still would fit in under 1024 bytes. For a long while, they would all end up fitting, but as long as I had space to spare I wanted to add more effects, so to me it was only important that one of them still fit.

Coding up more effects

Of course things need to move in the demo to give more life to it so I decided to give copper bars a chance, and to be a bit less ordinary I wanted to make the copper bar really stretched, a bit like how one stretches the palette on C64 to make an impression of having more than the available 16 colors.

C64 palette stretching

If you don’t stretch the amiga palette you only have 16 true grayscales, from 0x000 to 0xFFF, and such copper bars have been done to death. I had an older idea I never implemented in the old days where you add blue, then red, then lastly green, then two of them in the same combinations to slowly move from 0x444, 0x445, 0x544 … to 0x555 and so forth.

This gives you a list that is some 106 entries long, and for reasons it is far better if it is a power-of-two entries long, so I wanted to stretch it to 128, and this is where Bresenhams line algorithm fits.

Apart from being good for drawing lines, it works fine for gradients aswell, so a variant of the line algo gets modified to “draw” 128 values from 0 to 0xFFF with a few repeats here and there to fill out 128 values from 106, and then back again to 0x000, so it becomes 256 entries in total which is nice for wrapping around easily.

Front or Background color

I initially had it paint this using the copper as a per-line background color change, but after testing I found it nicer if it was changing the (single) foreground color instead, so I let it slide upwards as the color of the bulldogg.

More effects, if we can pack even better

After this, I had used up a hundred more bytes in the packed file, and had to start researching how to reduce size even more. When I began I let the assembler make large

label1:
  dc.l 0
label2:
  ds.b 1000

reservations filled with zeroes knowing that the compressor would reduce that to very few bytes, even if I had the whole bitmap and copper taking up some 8000+1000 bytes. When checking the output these long runs will end up becoming runs of bytes in the compressed output aswell, leaving some room for improvement.

In the end, I chose to not include the zeroes in the input but rather just mark the start and clear it with a small loop. During boot you actually can call AllocMem() but during boot you can be quite certain that 0x60000 isn’t in use neither for code, data or stack so I am just unpacking the code to this address and clearing the area after my program and placing the bitmap over it. This ends up saving some 30-40-50 bytes, so I kept it like that. It also means I can add some more moving effects, so back to one of my earlier favourites, sideways-moving starfields.

How to move stars

One of the odd things on Amiga is that it can show sprites even if you turn off the sprite DMA. The DMA is handling when to start and when to stop painting sprites, but the actual painting of the pixels for the sprite doesn’t need this DMA. If you disable it, the same data will keep repeating on every line, which is what I want for the starfield anyhow. It’s easy to hardcode the sprite data to a single bit, then have the per-line copperlist that already changes foreground color to also change the X position of sprite 0.

Now you need different start positions and speeds for these stars, and then in turn you need an RNG that is not totally crap. Again, a decent RNG takes space which I don’t have, so some kind of MULU #2459,D0 had to be the first RNG. I changed the constant later, but the idea was to multiply with a large constant and let it wrap around.

It takes a few attempts to get it “right”, where right in this context might not be scientific as much as “doesn’t line them all up”. You choose a value and see if it looks like crap and if it does, then try another value. This is one of the places where you see if the RNG is good or bad quite easily by just plotting out the output and see if there are lots of obvious patterns, like diagonal groupings of numbers and so on.

Some packers falling off

Suddenly, widening the copper list, setting up the sprite, having an RNG for both large values (X positions) and small (X speeds that should not be zero) meant that it was now showing the differences between the various compressors. Shrinkler and BitPickler compressed best, but they had the largest unpackers which of course is related and expected. They both started to exceed the 1024 bytes limit. I managed to make BitPickler fit again by hardcoding the various values and constants in the unpacker, but it also meant one had to re-analyze this for every change.

Louzy77 has a 40-byte unpacker, so that part of it is awesome, but it packs a lot worse, so it evens out in the end. I was over a 1000 at this point and suddenly each change made a huge difference. While I tried to be aware of size coding at all times, now I had to watch every small change, like when I changed the color bar from changing the background to foreground color. It is a small change of an $180 somewhere to a $182 which shouldn’t affect much, but this can have an effect that gives or takes a few bytes from your compressed data, depending on if it creates or destroys some kind of pattern that the compressor was previously seeing.

I started to see all results go over 1024 bytes at times, when testing out ideas to add to the bootblock, so it was a bit tough to get anything in there by now.

Last small edits

To save space, I moved a bit of code and data around, which had some unexpected side effects. The list of pixels that made up the bulldogg outline had the Y table last, and since no Y value was 0 I used the empty space behind it as the implicit list terminator. Then I moved code around a bit to make some relative jump offsets smaller, then I moved data around even more and suddenly the dog image had an extra line across its feet. This line was an inversion so in the blank areas it painted pixels, and in the area where it should have pixels it was blank. It took me a while to figure out the copperlist had moved away, and it starts with $00e2 which (on big endian systems) has a $00 first, so it had been the hidden array terminator zero. I had to move things around again so that a zero still appears after, and this led me to improve another list I was using.

In the old demos one would code, the graphics used to take up 20-40-60k of data, the samples for the music and the music data itself could easily eat up 50-100k more, and the code would be 1-2-3k at most, including the music player and all that. This was very visible if you placed your code and data in different AmigaOS hunks/sections (like unix ELF sections) when your assembler outputs the size of each hunk as it writes out the binary. In those days, having slightly inefficient init/exit was not a problem, your binary would be 100-200k unpacked and perhaps 50-100k compressed.

A few bytes here and there wasn’t an issue worth considering then. In my case it is, so for setting the various graphics settings in the $DFFxxx registers, I had made a table from which I would read the xxx offset, and the value to write. If you do not use a table like this, you end up coding a lot of MOVE.W ,$DFFxxx which in itself doesn’t eat much space for a single instruction, but in the long run becomes a lot of data if you have many values to set, so I let it be a table with a small loop to set the respective values.

This is nice, especially since DMACON needs to be written twice in the correct order, once for all the DMA channels to disable, and once for all the DMA you want to enable since it has a SET/CLR bit one needs to use to tell if you are clearing or setting specified bits. This list was now after my Y coordinate list, and the first value was not starting with $00. I had previously reversed the list, mostly so that my counter would go downwards to save me a CMP instruction, but in doing so the needed $00 wasn’t in the right place. A small reorder of values and now the graphics glitch was fixed again.

Then I wanted to write out “IcePic of DeadZone” or something to that effect in the image, but I was down to very few remaining bytes and writing text with a font and all that (even with ROM routines) was not possible, so I settled for pixelling it out, but even that was a challenge. In the end, I could have 5 MOVE.L instructions with bit values which paints pixels that read ICEPIC/DZ in thin letters. At 320x200 its still quite readable.

The really final change

This bootblock was test-compressed by 4 different packers, one testing all 9 settings, another bruteforcing all combinarions of 3 settings and one flag on/off, one hardcoding all constants in the unpacker and in the end, only ZX0 could make it under 1024 bytes. It actually ended up being 1020, which allowed for one last trick. As I mentioned above, the bootblocks have an additive checksum which needs to be valid and I had the C code to calculate it, but I could run it over the first 1020 bytes, then figure out exactly which 4 bytes to write in the last 4 bytes at the end to make the checksum any 32bit value I wished. Since this text is more or less the same as my “how to code a bootblock demo on Amiga” presentation for the 2026 summer Amiga meetup for the ACG-G community, I decided to set the checksum to ‘ACGG’ by setting the last 4 bytes to 5c 18 99 64.

In the end, the bootblock looks like this in vAmiga on MacOS: Screenshot of bootblock running

The ADF file is available here

A screen recording in .MOV format also available here

There might be a follow-up with a walkthrough of the code with commentary on decisions made at various points, but lets leave that for later.