Search This Blog

Bivertless High Contrast Game Boy Backlighting

Challenge accepted.

Want to skip the hubbub and see results? Scroll to the bottom.Anything in green is an annotation.

Lets get started. With Game Boy backlight modifications, playing your childhood games in the dark has never been so fun. The backlight installation itself is one of the more advanced modifications in the hardware modification scene. One major side effect of the backlight when installed normally is the bad contrast. To increase the contrast, polarizer film is used to invert the color. Since black is essentially "no light", and white is "light", this creates a high contrast but with another issue - all your games look inverted!

This is where the bivert chip comes in. It is a simple 25 cent chip that does an inversion of incoming signals. The only downside to this is its another part to buy and fit into the Game Boy.

But what if we could avoid that chip all together? What if we did it all in software? Well, we can! For the majority of games, with little to no manual intervention, we can invert the graphics. Note this only focuses on the DMG because of its grey scale properties, making this proof of concept simple. An explanation is given below. The same could theoretically be applied to CGB games with some further work.

Lets first give a brief explanation about how graphics in the Game Boy work.

The Game Boy's screen is split into 3 "layers". There is the background layer, the window layer, and the sprite layer which is special, because sprites can be placed on top on below the window layer.

There are 3 "registers" in memory that we can write to. These registers are called BGP, OGP0, OGP1. These are acronyms which stand for "BackGroundPalette", "ObjectBackgroundPalette0", and "ObjectBackgroundPalette1" respectively. Objects are synonymous with sprites. You can see the Game Boy differentiates the colors of the sprite and the background.

To modify the color we modify these palettes. The colors are 0 (white), 1 (light grey), 2 (dark grey), 3 (black). We want to invert the colors automatically though, not by hand. Inversion is easy - it's just the complement of whatever we have. Since we have %11100100 - a palette of 4 colors, every color is 2 bits - and the complement of 1 is 0, and 0 is 1, we just change all 1s to 0s and 0s to 1s. This gives us 011011.

This would not work with CGB because simply "inverting the palette" would just change the colors used on certain parts of the sprite, not the actual colors themselves. CGB has special "color registers" for that, which we would modify instead. This works for the DMG because everything uses a grey scale.

Now that we know how it works, we need to search the ROM for places where the palette register is written to. There are 6 a lot of instructions total that cover all palettes. They load the contents of CPU register a into the palette registers.

Nitro2k01 has mentioned I'm missing ld [$FF00 + c], a and ld [r16], a. They are similar to the above except they use registers to "target" the palette addresses.

The difference between these is ldh is a special instruction to "LoaD High(RAM)". It allows the instruction to take 2 bytes instead of the 3 ld takes, because it always references memory at $FF(NN).

We start this search from $150 in the ROM, because that's where all game code must start. Once we find a match, then we have to do some educated guessing. Unfortunately there is no way to be 100% sure when register a has palette information written to it. It is safe to say though that this palette changing probably happens 1 or 2 instructions before a write to the palette register. So we search for ld a, which is $3E. There are some issues with this, like what if the palette is loaded from ROM? What if a palette is loaded into another register such as b, then into a? This is where we run into problems, but it doesn't appear to be a common pattern in DMG games. CGB is another story.

So far I have only tested this with my own homebrew and Tetris.

The rest of the article goes on about a technical "what-if" scenario that I encountered accidentally.Tetris fails with the above method, so we have to take a more invasive, aggressive approach. Tetris doesn't touch the palette registers at all, and uses the default configuration when the Game Boy is powered on. (My searching code was skipping over bytes, messing up alignment. It works for Tetris.) There is a possibility that the palette register is not touched at all, maybe in a homebrew application. How do we change the palette without modifying code? The answer is unused interrupt routines.

The Game Boy executes these routines based on "triggers" (I am trying to explain this to people who have never heard of or don't understand what an interrupt is). A trigger can be "one frame has been rendered", or "a button was pressed". The Game Boy also provides reset interrupt routines, which can be used any way the software dev sees fit. The main issue here is we only have 8 bytes to work with between routines. What doesn't help either is these routines need jump or return instructions to also go back to where they were called from, taking more space. jp takes up 3 bytes, leaving us with 5 bytes. How on Earth do we fit our palette setting routine in there?!

We use a chain of reset interrupts. The idea is we break our palette setting routine in parts, fit what we can into the 5 bytes, and jump to the next reset interrupt. To save space on these "local" jumps, we can use the jr instruction which is 2 bytes, rather than 3. One thing we forgot though is how do we avoid overwriting game code that uses these routines?

In all Game Boy games, the vertical blank interrupt is used to indicate the screen is done drawing. This promises us that this code will always run. The code found in the routine is most often a single jp instruction to the routine that handles what happens when this trigger is fired. We can copy that instruction and add it to the end of our reset interrupt chain. And that's it!

dj505Gaming from Reddit took this "inverted" ROM and loaded it up. This worked just as expected.

Messed up the default palette. You can see the palette is wrong in the TETRIS title gradient.