GBC Hicolour notes

This is a technical note regarding Hicolour mode trick for Game Boy Color and it’s realization in GBC game “Crystalis” in particular.
Original title.png
The feature of this mode is using on one screen more colors, than standard 8 background GBC palettes can offer. Usually ~2000 colors per screen can be achieved. Hicolour technique is used at game’s title and gameover screens, the problem was to translate it, as all other pieces were already translated.


After a short debugging in BGB, the organization of these 2 screens becomes obvious:
ROM bank 59 (16XXXX) is given away for these two screens data storage and rendering code. The background map is generated automatically and just maps sequentially whole VRAM tiles from $8800. We don’t need to change this.
Tiles are packed with LZSS and stored $464C-$53E4 (in GBC’s address space). The LZSS scheme is terribly written - code sometimes does unnecessary things, scheme itself is not optimal:
lzss scheme
In the same time, there are at least 2 whole banks left in ROM free: I used 0x30000 in ROM (bank C) for title screen data and 34000 in ROM (bank D) for gameover. So strategy is to get unpacked data (BGB has “save memory dump” feature), place it in bank C, then jump to code hack for plain data copy from this bank and return to original code after LZSS unpack procedure.
Details of this are a bit tricky: original 59th bank is a switchable bank, so when we change it to our free bank, the code will continue from this offset, but in new bank, which is undesirable. Best way is to make some code hack in “hardwired” first bank, but it’s stuffed with code with no free space. Fortunately we have A LOT free space in bank C, so we will just place code in the same offset at necessary offset in new bank. Original bank 59 also has some free space near 0x7F00, so we jump there from original code:

	sub_4243:                               ; CODE XREF: sub_894+8p
	ROM:4243                                         ; ROM:08D3p ...
	ROM:4244                 cp      $95 ; 'РҐ'
	ROM:4246                 jr      z, loc_424D     ; title screen
	ROM:4248                 ld      b, $0D          ; !!load bank number for gameover screen
	ROM:424B                 jr      loc_4250        ; write to VRAM tiles
	ROM:424D ; ---------------------------------------------------------------------------
	ROM:424D loc_424D:                               ; CODE XREF: sub_4243+3j
	ROM:424D                 ld      b, $0C   	 ; !! load bank number for title screen
	ROM:4250 loc_4250:                               ; CODE XREF: sub_4243+8j
	ROM:4250                 ld      a, 0            ; !!load flag, that we copy VRAM
	ROM:4253                 call    $7F00           ; !!jump to our code hack routine
	ROM:4256                 ld      a, 2
	ROM:4258                 ld      [loc_FF70], a
	ROM:425A                 ld      [loc_C160], a
	ROM:425D                 ld      a, [loc_C109]
	ROM:4260                 cp      $95 ; 'РҐ'
	ROM:4262                 jr      z, loc_4269     ; that's our pal source
	ROM:4264                 ld      b, $0D          ; !!load bank number for gameover screen
	ROM:4267                 jr      loc_426C        ; load dst buffer to D000
	ROM:4269 ; ---------------------------------------------------------------------------
	ROM:4269 loc_4269:                               ; CODE XREF: sub_4243+1Fj
	ROM:4269                 ld      b, $0C   	 ; !! load bank number for title screen
	ROM:426C loc_426C:                               ; CODE XREF: sub_4243+24j
	ROM:426C                 ld      a, 1            ; !!load flag, that we copy palettes
	ROM:426F                 call    $7F00           ; !!jump to our code hack routine

code hack in RO59: @7F00-8000:

	;b has bank number, a is a VRAM/Palette copy flag
	or a ;test if a is zero
	;we're in VRAM copy
	ld hl, 4000
	ld de, 8800
	jr z, SKIP
	;we're in Palette copy
	ld hl, 5000
	ld de, D000
	ld a, b; copy bank number
	ld	(C104), a; to return from interrupt
	ld	(2000), a; switch to bank and we're in empty bank
	-----code in empty bank (7f??)
	ld bc, 800; 800 words to copy
	ldi     a, [hl]
	ld      [de], a
	inc     de
	ldi     a, [hl]
	ld      [de], a
	inc     de
	dec     bc
	ld a,b
	or c; to raise zero flag for 16bit counter
	jr      nz, COPYLOOP
	ld 59
	ld (C104), a; to return from interrupt
	ld (2000), a; goto bank59
	--------we're back in bank 59 some nops later and block copied
	ret; return back to our original code

That takes care of actual tiles editing in translated ROM. Now, for palettes.


Palettes manipulation is the core of Hicolour technique. It uses GBC’s hardware ability to access palettes memory during HBlank, which theoretically means that every scanline can have it’s own set out of 8 palettes. Practically, Z80 is too slow for that, but each 2 scanlines can have totally new 8 palettes. There’s also an GBC interleaving factor comes in, which shift palettes sets between left and right parts of screen. Additionally, developer was given only one bank for code and data, so picture is only 128x128, though 160x144 also possible and can be found in some demos at GBC dev ring. In details, the process of palettes update looks like this: Palettes are copied from RAM D000 - DFFF: each color is 16 bits, so 0x800 colors. each scanline is written 4 palettes, 4 colors each (10 colors), (0x80 scanlines total) Screen is divided on 16x2 blocks, which share one palette, whole screen contains (128x128)/(16x2) = 512 of such blocks, each pal stored as 8 bytes, so all palettes are stored in 512x8 = 0x1000 bytes and 0x10 tiles in height (i.e. 0x80 scanlines) Meaning, each 2 scanlines use common 8 palettes, next 2 scanlines have updated next 8 palettes. And tiles are just set of indexes of this “block” palette, so it will be uneasy to see anything recognizable even in unpacked tiles.

Palettes data is also compressed with LZSS and for title screen it’s stored $53E5-$60B2. On the analogy with tiles, I’ve just placed unpacked palettes at offset 0x1000 from the beginning of free bank (right after tiles) and code already modified to copy these unpacked as well. Now game can show title screen, even if I fill original data with zeroes. It’s also possible to edit tiles and palettes with hands to get some results. But it’s obviously easier to convert data to some editable .tga file, which then will be edited in any editor, like GraphicsGale, getting excellent visual result.


The problem is that GBC development scene is already dead. Dev ring had several tools specifically for our task, but almost all of the url’s are 404 for about 15 years.
Thankfully, TGA2GBC by Jeff Frohwein is still available for download and placed here just in case. This tool uses exactly the same scheme, used by Crystalis developer (I suspect, he used exactly this tool, as game was released a year later, than TGA2GBC). The tool is compiled for DOS, so you can either recompile it with modern allegro library (the sourcecode is attached), or try to launch it with additional tools. I personally had luck only with FreeDOS, launched under VirtualBox with ftp server started for files exchange with host machine.
Good, now we can convert from .tga to tiles and palettes, ready to be inserted in ROM. The second part of task is to get actual .tga from game’s data. Generally, you can take screenshot from title screen with sprites switched off, crop it carefully and edit. But it’s not guaranteed to get exact color values, which were used by the game, plus I needed to confirm all my theoretic investigation with Hicolour mode, so I’ve written second tool gbc2Tga, which does the job. The rest is easy - edit .tga, convert to tiles and palettes, paste back to ROM and enjoy result.
The same scheme works for game over screen. The code above is also adjusted for game over data raw copy. The final result looks pretty good:
Title changed GO changed