Sega Genesis Programming Part 1: Palettes

 

Why am I doing this?

If you poke around this site you'll notice I like to try out random programming ideas like building randomly generated mazes in C#, messing around with Android sprites, or trying to do anything in Atari 2600 BASIC. Those were all fun but none really satisfied my urge for some old-school programming. You can make games that look old-school in C# or Java but you can't really say you did "old-school" programming with them. I'm talking about dealing with low-level instructions and registers or about racking your brain to trim a couple instructions or bytes out of a program. Real old-school programming.

It's something I've been thinking about for a while - maybe because I enjoy mental anguish, maybe to satisfy some 30 year-old dream of writing console games, or maybe because I just want to learn something new. Whatever twisted psychological reason is behind it, I decided to try programming a Sega Genesis game.

There are C compilers for the Genesis but I'm going the assembler route because from what I've read that's the preferred method. On older consoles your code often needs to be highly optimized which makes assembler the way to go. The last time I touched assembler was December 1998 when I finished the final project for a class. That was X86 assembler which isn't all that similar to 68K. If I follow-through on this it will be like going back to school.

I'm going to take this slow and tackle one thing at a time. This article will cover one specific topic, depending how far I get with this crazy idea I may post others.

My plan of attack for learning how to program a Sega Genesis goes something like:

  1. Build a Sega Genesis development environment
  2. Just get a ROM to compile and boot
  3. Load up a few palettes
  4. Load up some scenery
  5. Add a sprite that can be controlled with the d-pad
  6. Play some music
  7. Figure out some minimal game demo

Most of this article is going to be about #2 but let's talk about #1 a little. At this point I've given up on Windows, not out of some grudge against Microsoft but because I think it peaked with Windows 7 and is kind of a mess to use today. Maybe I'll save that rant for a future article where I compare how to do simple tasks in Windows 95 vs Windows 10.

Anyway, at the time of this writing I'm running Ubuntu 14.04 64-bit. Linux was released in 1991 and didn't become popular (relatively speaking) until after the Sega Genesis was abandoned. As such there aren't exactly a lot of Sega Genesis development tools for Linux. Most of the tools floating around are for DOS but there are some for Windows, there are various ways to run them Linux. Here's what I tried:

68K assembler included on a 3.5" floppy with a college textbook on 68000 assembly I bought on eBay for $3 - I ran this in a FreeDOS virtual machine and it worked great. I also tried it in DOSBox and it crashed, not the application but DOSBox itself. I'm not too surprised, DOSBox likely hasn't been tested for it. DOSBox also isn't intended as a way to do development so it may lack emulation of some instructions needed to do so. I'm completely guessing there. Anyway, despite how great this worked it's kind of miserable to work with the editor in FreeDOS (although it's better than the MS-DOS equivalent).

Psygnosis development kit - This is floating around on various sites and I'm not sure whether it's legal to use it. I don't think they released it to public domain but also aren't sending people distributing it takedown notices. This also runs on DOS and has the advantage of including Sega CD development tools. This also ran perfectly in FreeDOS and MS-DOS (which I randomly decided to try). It also caused DOSBox to crash.

EASy68K - This is my recommendation for Windows users. I installed this in a Windows 2000 virtual machine (the one I keep around for whenever I have to do VB6 work) and it ran just fine. The UI is very friendly and being on Windows means you can run Gens KMod which makes debugging a little easier. At some point if I continue playing around with Genesis programming I may find the Linux version of Gens to be inadequate.

Here's a random screenshot that demonstrates it's possible to compile a Genesis game in EASy68K:

Easy86K

All three of these might run great in WINE, I didn't try because I decided to pursue a pure Linux solution and stumbled across VASM.

Like a lot of Linux development tools this is a "build it yourself" solution, it's really easy though. Just download the package and run make with Makefile.68k. That produces files called vasmm68k_mot and vobjdump, move them to /usr/local/bin. Now you can run it with a command-line like: vasmm68k_mot -o OUTPUT.bin -Fbin -no-opt -nosym SOURCE.X68

With some command-line changes you can also produce Amiga 500 executables which is kind of neat. You can also play around with the optimizations if you have time, I decided to just disable all for simplicity.

Alright, so at this point I have a working assembler and can get a Genesis game to build and run. There's a lot of boilerplate code you need just to get a game to start. I'm not covering that here but the source code I post will include it along with references to where I got it from.

With the first two steps out of the way it's on to try loading a few palettes. I spent a bunch of time learning how palettes work when making Aridia. The Genesis VDP stores 4 palettes of 16 colors for a maximum of 64 distinct on-screen colors. Once you factor in transparency it's usually slightly lower than 64 in a real game. The palette entries have an R (red), G (green), and B (blue) value that's combined into one 16-bit number. The R, G, and B values can range from 0-7 so only 12-bits are actually used. It's just like how RGB colors work, only there are 8^3 (512) total combinations instead of 256^3 (16,777,216).

For me to visualize colors I have to start with the 16-bit RGB value and then take it down to the nearest 9-bit RGB value. Maybe because the 68k uses big-endian numbers, Genesis palettes are really BGR meaning the blue value is stored first, then green, then red.

So, as a random example - rgb(160,128,144) converts to binary as 10100000 10000000 10010000. Flipping the b and g values and dropping off the last 4 bits gets us 1001 1000 1010. Putting those all together into a single binary number 100110001010 which converts to a hex value of 098A. 098A is what we send to the VDP to get the color rgb(160,128,144) loaded.

Apologies if that explanation was confusing.

Anyway... there are a lot of tutorials out there and this one isn't necessarily any better, it's possibly worse. I had varying degrees of success with these other tutorials though. Perhaps different assemblers use slightly different syntaxes so the examples provided didn't work in my configuration. The closest I got with any of them was one that loaded palette data but the colors were all off. So I went on to figure this out from scratch.

Initially I went with a brute force approach:


move.w	#$0F0F,($00C00000)	; rgb(240,000,240) => 1111 0000 1111 => 0F0F
move.w	#$0115,($00C00000)	; rgb(080,048,032) => 0010 0011 0101 => 0115
move.w	#$0356,($00C00000)	; rgb(096,080,048) => 0011 0101 0110 => 0356
move.w	#$0139,($00C00000)	; rgb(144,096,016) => 0001 0011 1001 => 0139
[...]

What's going on here is pretty simple - color values are being fed directly into the VDP control port. That's obviously not sustainable for a real program so what we need is a subroutine that loads palettes stored in the ROM.

The first step is, well, storing palettes in the ROM. We need to pick 64 colors, let's go with a four seasons theme for these palettes. As in, imagine there was a game with some background scenery that looked like different times of the year. We'll structure the palettes like this:


;---------------------------------------------------------------------
; seasonal background palettes
; entry 0 = transparency color
; entries 1-3 = tree bark colors
; entries 4-5 = evergreen tree needle colors
; entries 6-9 = leaf colors
; entries 10-12 = ground colors
; entries 13-15 = colors for other scenery
;---------------------------------------------------------------------

For spring we want colors that resemble budding trees maybe with white or pink flowers for accent:


PaletteSpring:
dc.w	$0F0F	; rgb(240,000,240) => 1111 0000 1111 => 0F0F
dc.w	$0115	; rgb(080,048,032) => 0010 0011 0101 => 0115
dc.w	$0356	; rgb(096,080,048) => 0011 0101 0110 => 0356
dc.w	$0139	; rgb(144,096,016) => 0001 0011 1001 => 0139
dc.w	$0031	; rgb(016,048,000) => 0000 0011 0001 => 0031
dc.w	$0033	; rgb(048,048,000) => 0000 0011 0011 => 0033
dc.w	$0785	; rgb(112,128,032) => 0111 1000 0101 => 0785
dc.w	$05CD	; rgb(208,192,080) => 0101 1100 1101 => 05CD
dc.w	$0A9D	; rgb(208,144,160) => 1010 1001 1101 => 0A9D
dc.w	$0EEE	; rgb(224,224,224) => 1110 1110 1110 => 0EEE
dc.w	$0154	; rgb(064,080,016) => 0001 0101 0100 => 0154
dc.w	$04EE	; rgb(224,224,080) => 0100 1110 1110 => 04EE
dc.w	$098A	; rgb(160,128,144) => 1001 1000 1010 => 098A
dc.w	$0743	; rgb(048,064,112) => 0111 0100 0011 => 0743
dc.w	$0F95	; rgb(080,144,240) => 1111 1001 0101 => 0F95
dc.w	$079A	; rgb(160,144,112) => 0111 1001 1010 => 079A

Summer will be very green with some yellowish flowers, this is really the least scenic of the seasons:


PaletteSummer:
dc.w	$0F0F	; rgb(240,000,240) => 1111 0000 1111 => 0F0F
dc.w	$0115	; rgb(080,048,032) => 0010 0011 0101 => 0115
dc.w	$0356	; rgb(096,080,048) => 0011 0101 0110 => 0356
dc.w	$0139	; rgb(144,096,016) => 0001 0011 1001 => 0139
dc.w	$0031	; rgb(016,048,000) => 0000 0011 0001 => 0031
dc.w	$0033	; rgb(048,048,000) => 0000 0011 0011 => 0033
dc.w	$0485	; rgb(112,128,064) => 0100 1000 0101 => 0485
dc.w	$0145	; rgb(084,064,016) => 0001 0100 0101 => 0145
dc.w	$0488	; rgb(128,128,064) => 0100 1000 1000 => 0488
dc.w	$03CC	; rgb(192,192,048) => 0011 1100 1100 => 03CC
dc.w	$0165	; rgb(080,096,016) => 0001 0110 0101 => 0165
dc.w	$0142	; rgb(032,064,016) => 0001 0100 0010 => 0142
dc.w	$04EE	; rgb(224,224,064) => 0100 1110 1110 => 04EE
dc.w	$034E	; rgb(224,064,048) => 0011 0100 1110 => 034E
dc.w	$056D	; rgb(208,096,080) => 0101 0110 1101 => 056D
dc.w	$0899	; rgb(144,144,128) => 1000 1001 1001 => 0899

Fall has so many different shades of brown, yellow, and red with a hint of green:


PaletteFall:
dc.w	$0F0F	; rgb(240,000,240) => 1111 0000 1111 => 0F0F
dc.w	$0115	; rgb(080,048,032) => 0010 0011 0101 => 0115
dc.w	$0356	; rgb(096,080,048) => 0011 0101 0110 => 0356
dc.w	$0139	; rgb(144,096,016) => 0001 0011 1001 => 0139
dc.w	$0031	; rgb(016,048,000) => 0000 0011 0001 => 0031
dc.w	$0033	; rgb(048,048,000) => 0000 0011 0011 => 0033
dc.w	$0238	; rgb(128,048,032) => 0010 0011 1000 => 0238
dc.w	$034D	; rgb(208,064,048) => 0011 0100 1101 => 034D
dc.w	$029E	; rgb(224,144,032) => 0010 1001 1110 => 029E
dc.w	$04EF	; rgb(240,224,064) => 0100 1110 1111 => 04EF
dc.w	$059A	; rgb(160,144,080) => 0101 1001 1010 => 059A
dc.w	$07BA	; rgb(160,176,112) => 0111 1011 1010 => 07BA
dc.w	$029D	; rgb(208,144,032) => 0010 1001 1101 => 029D
dc.w	$045F	; rgb(240,080,064) => 0100 0101 1111 => 045F
dc.w	$03CF	; rgb(240,192,048) => 0011 1100 1111 => 03CF
dc.w	$0DEE	; rgb(224,224,208) => 1101 1110 1110 => 0DEE

And for winter we'll have different shades of snow with a couple evergreens mixed in:


PaletteWinter:
dc.w	$0F0F	; rgb(240,000,240) => 1111 0000 1111 => 0F0F
dc.w	$0115	; rgb(080,048,032) => 0010 0011 0101 => 0115
dc.w	$0356	; rgb(096,080,048) => 0011 0101 0110 => 0356
dc.w	$0139	; rgb(144,096,016) => 0001 0011 1001 => 0139
dc.w	$0031	; rgb(016,048,000) => 0000 0011 0001 => 0031
dc.w	$0033	; rgb(048,048,000) => 0000 0011 0011 => 0033
dc.w	$0FEE	; rgb(224,224,240) => 1111 1110 1110 => 0FEE
dc.w	$0FED	; rgb(208,224,240) => 1111 1110 1101 => 0FED
dc.w	$0FEC	; rgb(192,224,240) => 1111 1110 1100 => 0FEC
dc.w	$0ECD	; rgb(208,192,224) => 1110 1100 1101 => 0ECD
dc.w	$0FFF	; rgb(240,240,240) => 1111 1111 1111 => 0FFF
dc.w	$0EED	; rgb(208,224,224) => 1110 1110 1101 => 0EED
dc.w	$0ABB	; rgb(176,176,160) => 1010 1011 1011 => 0ABB
dc.w	$0358	; rgb(128,080,048) => 0011 0101 1000 => 0358
dc.w	$0345	; rgb(080,064,048) => 0011 0100 0101 => 0345
dc.w	$0DCC	; rgb(192,192,208) => 1101 1100 1100 => 0DCC

Now we're going to create a subroutine to load palettes. It will need the starting address of the first palette to load, which it will look for in a0. It will also need the number of palettes to load which it will look for in d0.


;---------------------------------------------------------------------
; LoadPalette
; a0 = address of first palette to load
; d0 = number of palettes to load (1-4)
;---------------------------------------------------------------------
LoadPalette:
  move.l	#$C0000000,($00C00004) ; set up VDP write to CRAM
  mulu	#$10,d0	; multiply number of palettes by 16
  sub.w	#$01,d0	; subtrack 1 since the loop counts to 0
LoadPaletteLoop:
  move.w	(a0)+,($00C00000)	; write the palette data
  dbf	d0,LoadPaletteLoop		; decrement value of d0 and loop if not 0
  rts

Now need to call it, let's load all four palettes:


;setup call to LoadPalette
lea	PaletteSpring,a0	; address of the starting palette to a0
move.w #$04,d0			; number of palettes to load
jsr	LoadPalette

If everything works the VDP should contain palettes that look like this:

Palettes

It can also be called to load some non-sequential palettes:


;setup call to LoadPalette
lea	PaletteSpring,a0	; address of the starting palette to a0
move.w #$01,d0			; number of palettes to load
jsr	LoadPalette
;setup call to LoadPalette
lea	PaletteWinter,a0	; address of the starting palette to a0
move.w #$01,d0			; number of palettes to load
jsr	LoadPalette

This is the amazing thing about Genesis programming... it took several hours to write and debug just a couple of lines. This is such a small amount of code but there's a lot of satisfaction from producing it. Assuming I keep at this, I hope the next few trials feel the same way.

Download the latest source code on GitHub

 

Related