Some of you will recognise me from Sonic Retro, where I make hacks of the various sonic games. I recently made this 'demo' with stef's devkit that got me interested in spritesmind.
Anyway, I hope you find this an interesting read, it documents my journey to attempt to create the smallest Megadrive ROM that actually does something.
So, first I need some code that does something basic - this is to check that the code actually worked, and that it's relativly small.
The best thing I could think of for this task was to make the screen go green :) The code is a carbon copy from the checksum error routine from 'most' ROMs.
So here is the first revision, with the checksum code pasted after the header I ripped from Sonic 1.
Code: Select all
Vectors: dc.l $FFFE00, CodeStart, CodeStart, CodeStart; 0
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 4
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 8
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 12
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 16
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 20
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 24
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 28
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 32
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 36
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 40
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 44
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 48
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 52
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 56
dc.l CodeStart, CodeStart, CodeStart, CodeStart; 60
Header: dc.b 'SEGA MEGA DRIVE ' ; Console name
asc_110: dc.b '(C)SEGA 1991.APR' ; Copyright/Date
DomesticName: dc.b 'SONIC THE HEDGEHOG ' ; Domestic Name
asc_150: dc.b 'SONIC THE HEDGEHOG ' ; International Name
dc.b 'GM 00001009-00' ; Version
Checksum: dc.w $264A ; DATA XREF: ROM:00000332o
; Checksum
asc_190: dc.b 'J ' ; I/O Support
RomStartLoc: dc.l 0 ; ROM Start
RomEndLoc: dc.l $7FFFF ; DATA XREF: ROM:00000322o
; ROM End
RamStartLoc: dc.l $FF0000 ; RAM Start
RamEndLoc: dc.l $FFFFFF ; RAM End
asc_1B0: dc.b ' ' ; Notes
dc.b ' '
dc.b 'JUE ' ; Country
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
CodeStart:
move.l #$C0000000,($C00004).l
move.w #$F0,($C00000).l
bra.s CodeStart
Now, there is a lot of spare space in the header. How about we try storing the code in there? Then we just have to branch into it and we'll save loads of space.
So, we change CodeStart to this;
Code: Select all
CodeStart:
bra.s asc_1B0
Code: Select all
move.l #$C0000000,($C00004).l
move.w #$F0,($C00000).l
jmp $1B0
Code: Select all
23FCC000000000C0000433FC00F000C000004EF9000001B0
Code: Select all
00000000 00FF FE00 0000 0200 0000 0200 0000 0200 ................
00000010 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000020 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000030 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000040 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000050 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000060 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000070 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000080 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000090 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000A0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000B0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000C0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000D0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000E0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000F0 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000100 5345 4741 204D 4547 4120 4452 4956 4520 SEGA MEGA DRIVE
00000110 2843 2953 4547 4120 3139 3931 2E41 5052 (C)SEGA 1991.APR
00000120 534F 4E49 4320 5448 4520 2020 2020 2020 SONIC THE
00000130 2020 2020 2020 2020 4845 4447 4548 4F47 HEDGEHOG
00000140 2020 2020 2020 2020 2020 2020 2020 2020
00000150 534F 4E49 4320 5448 4520 2020 2020 2020 SONIC THE
00000160 2020 2020 2020 2020 4845 4447 4548 4F47 HEDGEHOG
00000170 2020 2020 2020 2020 2020 2020 2020 2020
00000180 474D 2030 3030 3031 3030 392D 3030 264A GM 00001009-00&J
00000190 4A20 2020 2020 2020 2020 2020 2020 2020 J
000001A0 0000 0000 0007 FFFF 00FF 0000 00FF FFFF ................
000001B0 23FC C000 0000 00C0 0004 33FC 00F0 00C0 #.........3.....
000001C0 0000 4EF9 0000 01B0 2020 2020 2020 2020 ..N.....
000001D0 2020 2020 2020 2020 2020 2020 2020 2020
000001E0 2020 2020 2020 2020 2020 2020 2020 2020
000001F0 4A55 4520 2020 2020 2020 2020 2020 2020 JUE
00000200 60AE `.
If you take these bytes from the first line;
00000000 00FF FE00 0000 0200 0000 0200 0000 0200 ................
These point to the location in the ROM where the code starts, we can change this to define a new start location for our code.
So if we change it to say, 0001B0, it'll point to the code in the header and we can remove the trailing branch from the end of the code.
Code: Select all
00000000 00FF FE00 0000 01B0 0000 0200 0000 0200 ................
00000010 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000020 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000030 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000040 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000050 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000060 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000070 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000080 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000090 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000A0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000B0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000C0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000D0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000E0 0000 0200 0000 0200 0000 0200 0000 0200 ................
000000F0 0000 0200 0000 0200 0000 0200 0000 0200 ................
00000100 5345 4741 204D 4547 4120 4452 4956 4520 SEGA MEGA DRIVE
00000110 2843 2953 4547 4120 3139 3931 2E41 5052 (C)SEGA 1991.APR
00000120 534F 4E49 4320 5448 4520 2020 2020 2020 SONIC THE
00000130 2020 2020 2020 2020 4845 4447 4548 4F47 HEDGEHOG
00000140 2020 2020 2020 2020 2020 2020 2020 2020
00000150 534F 4E49 4320 5448 4520 2020 2020 2020 SONIC THE
00000160 2020 2020 2020 2020 4845 4447 4548 4F47 HEDGEHOG
00000170 2020 2020 2020 2020 2020 2020 2020 2020
00000180 474D 2030 3030 3031 3030 392D 3030 264A GM 00001009-00&J
00000190 4A20 2020 2020 2020 2020 2020 2020 2020 J
000001A0 0000 0000 0007 FFFF 00FF 0000 00FF FFFF ................
000001B0 23FC C000 0000 00C0 0004 33FC 00F0 00C0 #.........3.....
000001C0 0000 4EF9 0000 01B0 2020 2020 2020 2020 ..N.....
000001D0 2020 2020 2020 2020 2020 2020 2020 2020
000001E0 2020 2020 2020 2020 2020 2020 2020 2020
000001F0 4A55 4520 2020 2020 2020 2020 2020 2020 JUE
Turns out, a lot of the header isn't required. In fact, most of it - the only *requirement* is the first 8 bytes. So if we set the start of the code to $8... we can just paste our code straight afterwards removing almost the entire header.
Code: Select all
00000000 00FF FE00 0000 0008 23FC C000 0000 00C0 ........#.......
00000010 0004 33FC 00F0 00C0 0000 4EF9 0000 0008 ..3.......N.....
Of course, we can remove the last three words to bring it down to 24 bytes, too. I'm told this will break eventually, but it works in regen, so I'm keeping it.
We can't get any smaller than this. It's just not possible.
That's what I thought, until I came up with this dirty trick;
Code: Select all
00000000 4E71 23FC C000 0000 00C0 0004 33FC 00F0 Nq#.........3...
00000010 00C0 ..
Part of the code is used to set the start of the code. A nop (4E71) is added to align it to the correct place and then the code sets the start to $0. This then runs through the code again, ignores the nop, and executes the rest as normal - so the bytes used to set the address of the code are reused as part of the code.
I don't know how well I explained that or if you will understand it. Please ask if you don't get it.
===
From this point I'd pretty much called it a day, there wasn't much more i could do to optimise this any further. I sent the ROMs off to TmEE to find out which ones will run on hardware. Unfortunatly none :(. Well, at least they were running on Regen!
Anyway, after this he sent me the smallest ROM he could think of that runs on hardware. 38 bytes! Apparently, the MD required some VDP initilisation that I overlooked. Here is his code;
Code: Select all
; Smallest possible MD program to run on most hardware
DC.L $00FFFE00 ; Default A7, Padding
DC.L CodeStart ; Default PC
CodeStart:
LEA $C00004, A0 ; Makes things a little bit more tighter ;)
MOVE.L #$80148700, (A0) ; Initialize VDP into enough nice state
MOVE.W #$8134, (A0)
MOVE.L #$C0000000, (A0)
MOVE.W #$0FF0, -4(A0)
BRA.B CodeStart
Anyhoo, as you can see the code is pretty compact leaving little room for optimisation. That doesn't mean I didn't try anyway ;)
Here is my final piece of code. With use of the trick I demonstrated before, and a little shifting around of the code, I managed to get it down to 32 bytes. Just trace the execution of this thing, it's beautiful.
Code: Select all
Start:
bra.s CodeStart
dc.w $20BC ; this and instruction below are executed as;
dc.l $C0000000 ; move.l #$C0000000, (a0)
CodeStart:
move.w #$F0,-4(a0) ; does nothing the first run through ;)
lea $C00004,a0
move.l #$80148700,(a0)
move.w #$8134,(a0)
bra.s Start+2
Code: Select all
00000000 6006 20BC C000 0000 317C 00F0 FFFC 41F9 `. .....1|....A.
00000010 00C0 0004 20BC 8014 8700 30BC 8134 60E2 .... .....0..4`.
On a side note, qiuu suggested that the smallest valid ROM (that doesn't do anything) would be 2 bytes long (assuming padding is 00s). bra.s -2 / '60FE'. This actually works in Regen :D
And that's about all I have to say for my first post. Hopefully next time I post a demo, it'll do a more than just this ;)