I wrote this a while ago as a guitar tune:
I was never very happy with most of the original tune. It has a soaring melody that’s really the middle of a musical idea. It needs a solid buildup that I couldn’t come up with, so I settled for a descending baseline copout. This chiptune version removes the unsuccessful parts and adds new ideas, but it doesn’t solve the original problem. It loops, hence the abrupt cutoff in the MP3 version.
I wanted to create a system for showing text with proportional fonts on the GBA. Typically on tile-based systems you’ll see fixed-width text with characters that align to tile boundaries. This is easy to do and efficient for the hardware. It’s exactly what I did in Professor Sinister. The new project I’m working on is going to have significantly more text and legibility is crucial, so I wanted to use proportional fonts instead of fixed-width.
First, some information about the GBA’s video system. The GBA’s screen resolution is 240x160px. When in tile mode, the screen is divided into a grid of 8x8px tiles. The hardware supports up to 4 layers of these tile grids overlaid on top of each other. In 4-bit color tile mode, in which each byte holds two pixels, ~19K of VRAM would be required per layer to store a full-screen bitmap in tile form. In addition to the tile bitmaps each layer requires a map to tell the video system how to lay out the tile bitmaps on screen. Each entry in the map is 16 bits wide. That’s another ~1K required per tile layer. Presenting a unique full-screen bitmap in all 4 tile layers simultaneously would require 75K of VRAM for the tile bitmaps and ~4.6K for the maps. The GBA has 64K of VRAM.
The upshot of this is simple: the GBA has nowhere enough VRAM. It can’t possibly show 4 full-screen bitmaps in tile mode. The best it can do is ~3.5 screens of tiles (2048) in 4bpp tile mode or 1.7 screens of tiles (1024) in 8bpp tile mode, but neither scenario leaves room for the tile maps.
In bitmap modes the video system is even more limited: two bitmaps (mode 4; 8bpp; 37.5K each) or one bitmap (mode 3; 16bpp; 75K). These modes eat into sprite memory and sacrifice sprites for bitmap data.
Keeping all of that in mind, how do you use a tile-based layout to show proportional font glyphs? Why, you ignore the benefits and constraints of the tile system and treat it like a bitmap, of course.
The font rendering system I used in Chuckie Egg et al has its roots in the rendering system I created for Woopsi, but with the warts removed. Give it a bitmap object to render into and the renderer will happily use the bitmap’s setPixel() method to draw text. The bitmap itself translates from the screen co-ordinates supplied by the renderer into co-ordinates that match its internal structure. That’s how I got graphics showing up on the 3DS with its weirdly-oriented framebuffer: I created a new bitmap class specifically for the 3DS.
Making tile mode on the GBA behave like a bitmap is a similar problem, but it requires some more setup. The first thing to do is to decide how many tiles to use. Using the full size of a tile layer isn’t advisable because, as we’ve seen, the number of tiles available is severely limited. I wanted the text to pop over the bottom of the screen like the conversation text in Pokemon. I figured I’d need 5 lines of 10px tall text. Add a couple of pixels spacing between each line and that gives me 60px; round it off with a couple of pixels padding above and below the text and I need 64px, which neatly fits into 8 vertical tiles. The screen is 30 tiles wide, so I need to reserve 240 tiles solely for the use of the text renderer. That’s a sizeable chunk of the 2048 tiles available in 4bpp mode, but at least we’re not using the 600 tiles we’d need for a full screen of text.
I’m keeping the first (zeroth) tile bitmap in VRAM empty, so that means I’m reserving tile bitmaps 1 through 241. At this point I can set up the tile map for the tile layer I’m using to display text: rows 12 to 19 need to contain tile bitmaps 1 to 241. Note that the layer is actually 32 tiles wide, not 30, but as two columns of tiles are offscreen and I’ll never scroll the layer I’m leaving those extra columns empty. Also note that the tile bitmaps must be sequential in VRAM or you’ll introduce an extra layer of complexity.
With that done I can feed VRAM+32 (ie the second tile bitmap) into my new bitmap class as its bitmap origin address and teach it how to translate screen co-ordinates to tile bitmap co-ordinates. This is how pixels are laid out in VRAM:
+-----------------+-----------------+ +-----------------+-----------------+ | 000 001 002 003 | 004 005 006 007 | | 064 065 066 067 | 068 069 070 071 | +-----------------+-----------------+ +-----------------+-----------------+ | 008 009 010 011 | 012 013 014 015 | | 072 073 074 075 | 076 077 078 079 | +-----------------+-----------------+ +-----------------+-----------------+ | 016 017 018 019 | 020 021 022 023 | | 080 081 082 083 | 084 085 086 087 | +-----------------+-----------------+ +-----------------+-----------------+ | 024 025 026 027 | 028 029 030 031 | | 088 089 090 091 | 092 093 094 095 | +-----------------+-----------------+ +-----------------+-----------------+ | 032 033 034 035 | 036 037 038 039 | | 096 097 098 099 | 100 101 102 103 | +-----------------+-----------------+ +-----------------+-----------------+ | 040 041 042 043 | 044 045 046 047 | | 104 105 106 107 | 108 109 110 111 | +-----------------+-----------------+ +-----------------+-----------------+ | 048 049 050 051 | 052 053 054 055 | | 112 113 114 115 | 116 117 118 119 | +-----------------+-----------------+ +-----------------+-----------------+ | 056 057 058 059 | 060 061 062 063 | | 120 121 122 123 | 124 125 126 127 | +-----------------+-----------------+ +-----------------+-----------------+
Each large box represents a tile bitmap. Each cell in the box represents a u16. The numbers represent the index of each 4-byte pixel.
To translate from screen co-ordinates to VRAM index, locate the tile that contains the point:
const int tileSize = 8; const int pixelsPerTile = 8; const int tilesPerRow = 30; int tileX = point.x / tileSize; int tileY = point.y / tileSize;
Figure out the index of that tile:
int tileOffset = (tileX * tileSize * tileSize / pixelsPerTile) + (tileY * tileSize * tileSize * tilesPerRow / pixelsPerTile);
Find the pixel within the tile:
int x = point.x % 8; int y = point.y % 8;
Find the offset of the byte containing the pixel:
int offset = tileOffset + x + (y * tileSize);
That has far too many mods and divides. We can take advantage of the fact that almost every calculation is a power of 2 and use some bitwise magic to produce this faster but mostly-illegible one-liner:
#define offsetForPoint(x, y) (((x) & ~0x7) << 3) + ((((y) & ~0x7) << 3) * 30) + ((x) & 0x7) + (((y) & 0x7) << 3)
The define above, coupled with the VRAM address we supplied to the class when we created it and the knowledge that VRAM can only be addressed as u16s, we can create a setPixel() method that the font renderer can write to.
What’s particularly neat about reusing the existing rendering routines is that they can efficiently clip to a rectangle, allowing me to avoid the jerkiness of text rendering seen in something like Pokemon. That game renders text to the screen one character at a time, so the speed at which text appears varies with the width of the characters being rendered. With this rendering system I can smoothly slide the clipping rect across the screen and rely on the renderer to draw fractions of a character - or multiple characters if necessary - at a consistent pace.
With the game finally complete, I can share some playing tips for flying around the levels in Professor Sinister like a pro.
While approaching a ladder, hold Up (or Down if you want to descend). The Professor will grab the ladder and start climbing as he passes it. There’s no need to try and line yourself up with the ladder before pressing Up.
Quickly Ascend Ladders #1
Hold down Up and Jump at the same time while climbing a ladder for a little vertical speed boost.
Quickly Ascend Ladders #2
While approaching a ladder, jump towards it. Hold down Up when you get close to it. The Professor will grab the ladder and start ascending, bypassing the lower rungs of the ladder.
Quickly Ascend Ladders #3
While climbing a ladder, with Professor Sinister’s head roughly level with the platform you’re trying to reach, hold Left or Right and press Jump. The Professor will quickly jump off the ladder right onto the platform.
Jump Between Ladders
It’s possible to jump off one ladder while holding Up and grab another nearby ladder. You can’t complete level 7 without mastering this.
You can jump from one platform to a lower platform instead of descending a ladder or dropping down (which would eliminate horizontal movement).
You can bounce off platforms or the level walls to get quickly from one place to another.
Jump Through Platforms
You can bypass much of level 7 by jumping through the little platform to the right when you start. Walk up to it, hold Right and press Jump. Now you can skip to those last few eggs.
The tricks above exploit the way the game’s engine works in order to zip around the levels more quickly. These are techniques that you are encouraged to use, particularly in later levels as the game gets increasingly difficult.
The cheat codes here are not encouraged and may spoil your enjoyment of the game.
To enter a cheat code:
- Start a game;
- Press “Start” to pause;
- Enter the code;
A message will appear on screen every time a code is recognized. Entering the code a second time will remove its effect.
- Code: Up, Up, Down, Down, Left, Right, Left, Right, B, A.
- Effect: Prints a message.
- Code: Up, Down, A, B, A, Down, Up, Left, Right, Left, A.
- Effect: Enables level skip. Press Select to end the current level and skip to the next.
- Code: L, L, R, L, L, R, L, L, R, R, R.
- Effect: Enables slow motion mode. Hold R for slow motion. Hold L and R for extra-slow motion.
- Code: Down, Up, L, L, A, R, Down.
- Effect: Enables invulnerability mode. Robots and the floating eye cannot hurt Professor Sinister (but falling in holes will).
- Code: A, B, Right, Left, Right, Left, Down, Down, Up, Up.
- Effect: Prints a message.
In celebration of Simian Zombie’s 12th birthday, here’s an updated version of Professor Sinister and his Marauding Mechanicals:
- Added ladder mount/dismount assist to make it easier to use ladders.
- Added cheat codes for level skip, invulnerability and slow motion mode.
- Added raster effect on title screen.
- Added high score table with persistent scores.
- Fixed robot position when they collect power packs.
- Fixed memory leaks.
- Fixed crashes.
- Fixed graphical glitches when switching between scenes.
- Levels reset correctly when the game loops around.
- Game loops through correct set of levels.
- Updated title screen scrolling text.
I had to try out the GBA backlight mod for myself. I’ve been slowly collecting the pieces necessary to perform the mod:
- SNES-style reproduction GBA shell (eBay; $13);
- Glass Famicom-style screen lens (RetroModding; $5);
- GBA with a 40-pin motherboard ($30);
- AGS-101 screen to 40-pin motherboard type “B” adapter cable (God of Gaming; $8);
- AGS-101 screen (God of Gaming; $78);
- 25 watt Weller soldering iron with a fine tip (Lowe’s; $20);
- Precision X-acto knife (Home Depot; $4);
- Electrical tape (Lowe’s; $4)
- Tri-wing screwdriver (possibly Lik-Sang; I’ve had it for years)
I ordered the shell just over a year ago when the SNES design first appeared, and got the matching screen lens about 6 months ago. I found a cheap GBA at a local retro toy fair last month. The most expensive part was the screen; they were half the price last year. The rumor is that the screens are no longer being produced, pushing up the prices, but I wonder if Donald Golf’s trade war is contributing to the inflating cost.
The mod instructions are simple:
- Disassemble GBA;
- Cut away excess plastic from top of shell so that larger screen fits;
- Connect the screen to the adapter cable;
- Connect the adapter cable to the motherboard;
- Solder additional wire from adapter cable to the motherboard;
- Assemble GBA.
Cutting the shell to size was easy enough. Many sites recommend a Dremel instead of a X-acto knife, but I found that the plastic used in the reproduction shell was soft enough to make cutting through it fairly easy. At one point the knife snapped in half, pinging a razor-sharp chunk of blade across my desk, but fortunately the half-blade was better suited to slicing up the plastic than the pointed tip had been.
The first time I put the GBA together I didn’t bother soldering the wire. A few online guides suggest that this is optional if you don’t mind a dimmer display. Having used other modded GBAs I thought that the dim screen was disappointing, so I decided to open it back up and solder it. This was the part that concerned me: I don’t mind ruining a cheap reproduction shell, but I really didn’t want to damage a GBA motherboard. I haven’t done any soldering in years and never anything as fine as required here. Surprisingly this part of the mod also went smoothly and I got the wire soldered on my first attempt.
The electrical tape comes in handy in a few places:
- Taping around the edges of the screen to eliminate light bleed;
- Insulating the area where the cartridge slot pins poke through the back of the motherboard;
- Insulating the soldered wire from other components around it.
With all of this done I loosely screwed the GBA together and gave it a try. It worked!
I screwed it together properly. It didn’t work.
I unscrewed it a little. It worked!
Hmm. If I squeezed the bottom of the GBA the screen switched off. Looking closely, it seemed that the screen was still working but the backlight was off. Time to unscrew it fully and figure out what’s going on.
Some diagnosis later - disassembling everything I’d just put together, including removing the soldered wire - I found that there was a tiny rip in the ribbon cable that connects the screen to the adapter. This cable is part of the screen and cannot be replaced. Any damage to the cable means the screen is dead. I have no idea where the rip came from, but now I needed a new screen. Remember that I said the screen was the most expensive component in the mod?
Another $78, another week, and finally a new screen arrived. In the meantime I’d re-soldered the adapter cable’s wire; of course it detached again whilst I was working on the GBA so I had to solder it a third time. Each solder was messier than the last, but I somehow avoided damaging the motherboard. This time there were no rips and I got the GBA assembled. I didn’t bother taping up the screen with electrical tape as I’d subsequently heard that it doesn’t look that great.
The last piece was the Famicom-style screen lens. The lens came with glue already in place. All I had to do was peel the backing off the lens, ensure the screen and lens were clean, and stick it down. After checking both surfaces and sticking the lens down I switched the GBA on to discover that there was now some sticky goop between the lens and the screen. I have no idea how that got there. One quick complete disassembly, clean and reassembly later and I had a fully modded GBA.
Lessons learned along the way:
- The SNES shell is very gray, and I think a white shell might look better with the multicolor buttons and Famicom lens;
- An X-acto knife is good enough to mod the shell, but beware breaking blades;
- You absolutely must solder the adapter wire for the best display;
- Soldering the wire isn’t as difficult as you’d think;
- You’re not treating the screen’s ribbon cable carefully enough, even if you take into account this lesson;
- The light bleed around the screen edges is annoying, but not annoying enough that I want to open it up again (RetroModding sell a “backlight bleeding protector” which might work better than electrical tape);
- The tools to do the mod weren’t as expensive as I’d expected;
- Screens and adapter cables can be ~20% cheaper on eBay and AliExpress if you don’t mind waiting for delivery from China;
- You can probably buy a pre-modded GBA for around the price of the individual components;
- You can definitely buy a pre-modded GBA for considerably less than the price of the individual components if you damage your first screen and have to buy another.
The most important lesson I learned was this:
- I should stick to software.
Here’s the finished product:
I finally figured out how to get the mGBA debug server working:
- Build your GBA ROM.
- Open the ROM with mGBA.
- Open the mGBA debug server UI (in macOS, open Tools -> Start GDB server…).
- Using the default options (port 2345 and address 0.0.0.0), hit “Start”.
- Open up a terminal and run
/opt/devkitpro/devkitARM/bin/arm-none-eabi-gdbto start GDB.
- In GDB, enter
target remote localhost:2345to connect to mGBA.
file <path_to_elf>, replacing
<path_to_elf>with the full path to the
.elffile that should have been created alongside your ROM.
- When asked if you want to change the file, enter
yand hit Return.
Now you should be connected to mGBA, with symbols loaded, ready to start debugging.
PypeBros kindly created a title screen for Professor Sinister, so here’s a new release:
In addition to the title screen, this latest version includes some bugfixes, tweaks to the music and improved audio quality.
Get in touch if you have a way of getting the ROM onto one of those cheap reproduction carts that folks sell on Etsy. I’d love to have a physical copy of this on a dedicated cart.