This is a technical note regarding Hicolour mode trick for Game Boy Color and it’s realization in GBC game “Crystalis” in particular.
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:
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
ROM:424D loc_424D: ; CODE XREF: sub_4243+3j
ROM:424D ld b, $0C ; !! load bank number for title screen
ROM:4250
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
ROM:4269 loc_4269: ; CODE XREF: sub_4243+1Fj
ROM:4269 ld b, $0C ; !! load bank number for title screen
ROM:426C
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
SKIP:
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
COPYLOOP:
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.