# TF MultiMenu and Genesis Cartridge Port

Posted by Tursi, 15 December 2014 · 98 views
Today I worked on a menu program. Because I'm lazy, I used Basiegaxorz, a BASIC compiler for the Genny.
I decided it would be simple enough - three banners (one for each game). They would be greyscale unless selected. Up and down to select, and start to start.
First I had to decide sizes. I decided to work with the 320x224 mode. Vertically dividing 224 by 3 gives 74.67 pixels. But 74.6 isn't a multiple of 8, and we have a tile-based display. So to make life easier, I rolled that down to the nearest multiple of 8, which is 72 pixels. (72 x 3 = 216 pixels, so there's actually one unused tile row. That's fine.)
Width was arbitrary, but I decided that I wanted to horizontally stagger the images, so I picked 256 pixels wide as a rough value. That gives me images that are 32x9 tiles in size - 228 tiles total for each one. No problem!
I started with the box covers for TF2, TF3, and Lightening Force (I originally was going to use the European TF4, but I decided I liked the US version better for the tiny changes it has, and I didn't want to deal with region changes). A few minutes on each in Photoshop to overlay the titles on the image in appropriate locations, and I had the images I wanted.
To convert to Genesis format, I used "Retro Graphics Toolkit" from here: https://github.com/ComputerNerd/Retro-Graphics-Toolkit
It took a little time to figure it out, but it does produce pretty decent results with minimal work, and has convenient output modes for working with BEX (including a clipboard output, which I used.)
First thing to do upon opening it is File->Tilemaps->Import Image to Tilemap. This loads the image file into the tilemap area for conversion. It asks whether you want to append or overwrite tiles, for simplicity I always overwrote and treated each image independently.
On the Plane Mapping/Block Editor tab, you can view the image - here I have selected Tilemap Actions->Toggle Truecolor Viewing, just to show you the image. I suggest that you leave it OFF so it's easier to tell what step you're at. (The image will be black at this point instead).
Next, we need to create a palette. The Genesis has a 64-color active palette, split into 4 16-color palettes for the sake of tiles. I decided to give each image a 16-color palette, and then I could just grey out individual palettes for the effect. But, this tool will happily use all the colors, while color 0 in each palette is transparent. To make life easier, I selected color 0 on the Palette Editor tab, made sure it was black (my desired background color), and then selected 'locked'. This tells the tool that images are allowed to USE the color (and in this case they are), but they are not allowed to CHANGE that color.
Next it was simply a matter of Palette Actions->Generate Optimal Palette with X amount of colors. (long menu name). The tool asks how many colors you want - well, I have 16 in the desired palette and one is locked, so I asked for 15. It then asks if you would like all tiles on the tilemap to be set to row 0 ('row 0' also means 'palette 0', or 'the first 16 colors'). So, yes, that's the only one we're using. Next, a color reduction algorithm (I am happy with the default) and the color space (I prefer YUV). Finally, a palette will appear! (Note the 'locked' radio button set for color 0).
Now we just have to dither the image down to this palette. Select Tilemap Actions->Dither Tilemap as Image. It asks whether to Dither Entire Image at once, or Dither each Palette Row Separately. Well, we only have one palette row anyway, so do it all at once. Switch over to the Plane Mapping/block editor, and you should see the image, dithered. (If you don't like it, you can play with the palette settings).
Next, I opened up the basic editor. We need to export three sets of data from the converter -- the palette, the tilemap, and the tile bitmaps themselves.
So File->Palettes->Save Palette. First entry to save is 0, last entry is 15 (0-15 is 16 colors). It then asks for the type to save, drop down and select 'BEX' (which is some abbreviation for the BASIC compiler). It can also export Binary, C, and ASM. Finally, It asks Clipboard or File - I wanted clipboard. Then, I just hit paste in the BEX editor.
Repeat the process for the tilemap: File->Tilemaps->Save tilemap and if nes attributes (another long name, and we're not doing NES. But never mind.) Select type as BEX, output to clipboard, and then you are asked if you'd like a compression algorithm. For me, this project, I left it uncompressed. Then over to BEX and paste the data into the editor.
Finally for the tiles themselves, File->Tiles->Save Tiles. Select type BEX, clipboard, and uncompressed. Then paste into the editor (this will have the most data.)
BEX export always uses the same label names, and since we're planning more than one, I went ahead and renamed the labels, too, prefixing with "tf3" in this case.
I repeated the process with the other two images, until all the data was converted and in place. Now I needed to draw the screen. There's just one small issue to overcome -- all the images are saved as though they use palette row 0. So it is necessary, as we load the tiles to video memory, to update them for where we /really/ want them to be.
Originally, I had thought I'd use a staggered layout, as I mentioned, and put text on the bottom row. But that actually looked rather disorganized and cramped, so, I went for an aligned layout with no text. With that decided, my palette choices became:
- row 0 - black (was originally for text)
- row 1 - TF2 palette
- row 2 - TF3 palette
- row 3 - TF4 palette
So first it was necessary to set up the system. I decided to put everything on plane B for simplicity (the only reason to set text plane was so the 'cls' function would work and save me some trouble ;) ):

    	OPTION TITLE,"Thunder Force Multicart"
    	OPTION NOLOADFONT
    	BGCOLOR 0,0

    	setgfxplane SCROLL_B
    	settextplane SCROLL_B
    	valt
    	palettes palDat,0,0,16
    	palettes tf2palDat,1,0,16
    	palettes tf3palDat,2,0,16
    	palettes tf4palDat,3,0,16
    	loadtiles tf2tileDat,288,128
    	loadtiles tf3tileDat,288,416
    	loadtiles tf4tileDat,288,704

Here we set the cart title, skip loading the font, set the background color to row 0, color 0 (black!), and set the working planes. "valt" is supposed to wait for vertical blank, this prevents 'snow' on the screen caused by updating video memory as the beam is being drawn. (Or is it only CRAM? Anyway...)
The "palettes" function loads each palette to color RAM - palDat is set to all zeros (and doesn't really need to be loaded now that there's no text), the others load the TF palettes, into each palette row. Finally, the three calls to loadtiles loads the bitmap data into memory - each image is 288 tiles and loads as the listed character offset. TF2 loads at 128 because, again, originally I was leaving room for a font.
With that everything is ready to go, but there is nothing onscreen - we need to load the tilemaps. The BEX compiler came with examples, one of which included a very nice assembly subroutine to draw a block of tiles, called DrawTiles16. One very nice feature of this subroutine is the ability to add an offset to the tiles. This solves our needs of both character offset (such as 128), AND palette row offset. In a 16-bit tilemap entry, bits 13 and 14 specify the palette to use. Since all the images were saved for palette zero, we can just add the offset we need. So these lines are added to display the screen:

    	DrawTiles16 lblptr&(tf2mapDat),0,0,32,9,&h2080	' palette 1, char 128
    	DrawTiles16 lblptr&(tf3mapDat),7,9,32,9,&h41a0	' palette 2, char 416
    	DrawTiles16 lblptr&(tf4mapDat),0,18,32,9,&h62c0	' palette 3, char 704

(and of course the DrawTiles16 sub is included in the code too).
I wasn't super fond of the staggering, as I noted, but at least the image was up successfully! Now I needed a way to make the palette greyscale.
One way is to just precalculate the palette and load it as needed, but I wasn't up to that. Instead, I decided I could do it in code. I decided to create an assembly function (although BASIC may have been quick enough for just 16 colors!) - mostly for the practice. To keep the code simple, rather than doing a true greyscale, I decided to just copy the green channel to red and blue.
Genesis colors are rather simply defined in 16 bits as 0000bbb0 ggg0 rrr0. Note that although only 9 bits are used for color, there is padding to easily support 12 bit color. It's almost too bad we never saw that enhancement!
The following subroutine is used to load a palette, but changes each color to grey before storing it in the VDP:

    ' GreyPalette lblptr&(<palDat>), <pal num>, <pal idx>, cnt
    Declare Asm Sub GreyPalette(d5.l, d0.w, d1.w, d2.w)
    	; not really grey, we just copy the green channel to all three guns
    	move.w	#$2700,sr	; disable interrupts
    	movea.l d5,a0		; make address pointer for data
    	move.l #$c0000000,d4	; CRAM address in VDP
    	lsl.w #$5,d0		; make palette number into offset
    	lsl.w #$1,d1		; make index into offset
    	add.w d1,d0		; combine offsets
    	clr.l d1		; prepare long word
    	move.w d0,d1		; just to be sure the high word is cleared
    	lsl.l #8,d1		; VDP addresses are mostly in the high word (CCAAAAAA AAAAAAAA 00000000 CCCC00AA)
    	lsl.l #8,d1		; the lowest two address bits in the dword are the most significant A15,A14
    	add.l d1,d4		; add offset
    	move.l d4,$c00004	; set VDP address into CRAM
    @1:
    	move.w (a0)+,d7		; palette entry is 0000BBB0 GGG0RRR0
    	and.w #$00f0,d7
    	move.w d7,d3
    	lsl.w #4,d3
    	or.w d3,d7		; copy to blue
    	lsr.w #4,d7		; shift to red
    	or.w d3,d7		; load blue again
    	move.w d7,$c00000	; store in VDP data address (auto increments, hopefully)
    	dbf d2,@1		; loop around as necessary
    	move.w #$2000,sr	; restore interrupts
    End Sub

With that coded, I changed the palettes calls to instead call "GreyPalette" like so:

    	GreyPalette lblptr&(tf2palDat),1,0,16
    	GreyPalette lblptr&(tf3palDat),2,0,16
    	GreyPalette lblptr&(tf4palDat),3,0,16

(I admit I was forgetting if there was another way to safely clear just the high word of a 32-bit register, but this works.)
Finally, since I didn't like the offsets, I centered all the images by changing the 'x' coordinates in the "DrawTiles16" calls:

    	DrawTiles16 lblptr&(tf2mapDat),4,0,32,9,&h2080	' palette 1, char 128
    	DrawTiles16 lblptr&(tf3mapDat),4,9,32,9,&h41a0	' palette 2, char 416
    	DrawTiles16 lblptr&(tf4mapDat),4,18,32,9,&h62c0	' palette 3, char 704

Success! Now joypad input, and highlighting the images. This code is mostly done -- to make an image colored, just call 'palettes' and load its palette into the right place. To make it grey, call GreyPalette instead. Because it was easier to hard code the values, and I needed it in several places, I created a variable 'opt' to track which was selected, and created two subroutines, greyit and lightit. Each simply does the appropriate thing based on the value of 'opt'.

    ' warning: select case without a case else will crash on invalid numbers
    greyit:
    	valt
    	select case opt
    		case 0
    			GreyPalette lblptr&(tf2palDat),1,0,16
    			exit select
    		case 1
    			GreyPalette lblptr&(tf3palDat),2,0,16
    			exit select
    		case 2
    			GreyPalette lblptr&(tf4palDat),3,0,16
    			exit select
    	end select
    	return

    lightit:
    	valt
    	select case opt
    		case 0
    			palettes tf2palDat,1,0,16
    			exit select
    		case 1
    			palettes tf3palDat,2,0,16
    			exit select
    		case 2
    			palettes tf4palDat,3,0,16
    			exit select
    	end select
    	return

Now I needed three variables - one for the option, one for the joystick, and one for the last joystick value (used to prevent automatic and very fast repeating). I prefer explicit, so at the top of the program:

    	dim joy as integer
    	dim old as integer
    	dim opt as integer

Then, after the DrawTiles16 calls, we can init and run our main loop:

    	old = 0
    	opt = 0
    	gosub lightit

    	while (1)
    		joy=joypad(0)
    		old = joy XOR old	' mask new bits
    		old = joy and old	' gives us just new presses

    		if old AND &h001 then
    			' up
    			if opt > 0 then
    				gosub greyit
    				opt=opt-1
    				gosub lightit
    			endif
    		endif
    		if old AND &h002 then
    			' down
    			if opt < 2 then
    				gosub greyit
    				opt=opt+1
    				gosub lightit
    			endif
    		endif
    		if old AND &h080 then
    			' start

    		endif

    		old=joy
    	wend

The first 'gosub lightit' makes sure the default option is lit before we start, since we don't update the screen unless we move. The three lines around the joypad are a clever trick I saw in Thunder Force 3 -- the end result of those three instructions is that 'joy' has the current set of inputs, and 'old' has ONLY the /new/ inputs. Very handy for a menu like this, and easier than simply waiting for the user to release the button (like I used to). Note the "old=joy" at the bottom of the loop - without that it doesn't work!
The "if old AND &h001 then" checks whether 'up' in set in 'old' -- the bit values come from the BEX documentation. If up WAS just pressed, and opt is greater than 0 (can't go up at the top), then we first gosub 'greyit' which will grey the current selection, then subtract 1 from opt, then call 'lightit' so the new selection is lit. We do essentially the same thing in the opposite direction for down.
Finally, there's a selection for 'start', but we aren't that far yet.
And it works! So far! Next entry delves into bank switching, because I should have been in bed four hours ago.... ;)