Background Layer System & VRAM Management
Klonoa: Empire of Dreams uses the GBA's four hardware background layers in Mode 0. The graphics engine manages a pipeline of compressed ROM data → decompression → DMA to VRAM, with runtime tilemap streaming for smooth scrolling. This page documents the subsystem as understood from decompilation so far.
Each of the three gameplay BG layers is assigned its own 16 KB character base block (charblock) in VRAM. This prevents tile data from different layers from conflicting.
| Layer | Charblock | VRAM Address | Screenblock | Tilemap VRAM |
|---|---|---|---|---|
| BG0 (far) | 0 | 0x06000000 | 28 | 0x0600E000 |
| BG1 (mid) | 1 | 0x06004000 | 29 | 0x0600E800 |
| BG2 (near) | 2 | 0x06008000 | 30 | 0x0600F000 |
The title screen uses all four charblocks (0–3), loading shared
tilesets for the HUD, item icons, and UI elements. During gameplay, charblock 3
(0x0600C000) holds additional shared graphics loaded by
SetupSceneGfx.
Three parallel pointer tables store compressed background data for every level in the game. Each table is indexed by a combined vision/world/layer formula.
| Table | Address | Entries | Indexing | Content |
|---|---|---|---|---|
| BG Tile Table | 0x08189034 | 162 | (vision−1)×27 + world×3 + layer | Compressed 4bpp tile charblock data |
| BG Tilemap Table | 0x081892BC | 162 | (vision−1)×27 + world×3 + layer | Compressed screenblock tilemap data |
| BG Palette Table | 0x08188F5C | 54 | (vision−1)×9 + world | Compressed 512-byte RGB555 palette (shared by all 3 layers) |
There are 6 visions × 9 worlds × 3 layers = 162 tile/tilemap entries and 6 × 9 = 54 palette entries (one palette is shared across all three layers of each scene).
A lookup table at 0x08057ACC maps (levelIdx, sublevel)
to an entry index used by the tile/tilemap loaders. Seven additional sub-tables
define per-layer properties:
| Address | Name | Content |
|---|---|---|
| 0x08057ACC | Layer Config Lookup | Entry index + charblock config per level |
| 0x080576D4 | Scroll Flags | Scroll mode flags per layer |
| 0x08057714 | Layer Width | Tilemap width per layer config |
| 0x08057794 | Layer Height | Tilemap height per layer config |
| 0x080578D4 | Charblock Index | Which charblock base to use |
| 0x08057914 | Screenblock Index | Which screenblock base to use |
| 0x08057894 | Tile BPP | Tile color depth (4bpp/8bpp) |
All background assets use the game's DecompressData pipeline
(at 0x08043AF4). Each compressed blob has a 4-byte header
followed by the compressed payload:
struct CompressedAsset {
u32 header; // bit 31: Huffman flag, bits 0-30: decompressed size
u8 payload[]; // compressed data
};
Two compression paths:
After decompression, the first 4 bytes of the result are a
sub-header that encodes the BGCNT register
template (screen size, screenblock base) and a DMA size hint. The
remaining bytes are the actual tile/tilemap data.
struct DecompressedBGAsset {
u16 bgcnt; // BGCNT register template (CBB always 0, filled at runtime)
u16 dmaHint; // approximate DMA size / 256
u8 data[]; // tile charblock or screenblock tilemap data
};
The bgcnt field follows the GBA hardware register format
but with CBB = 0 always. The game's InitLevelBG
overrides the CBB field at runtime based on which charblock is assigned
to each layer.
| Bits | Field | Values Seen |
|---|---|---|
| 1–0 | Priority | 0–3 |
| 3–2 | Char Base Block | Always 0 (overridden at runtime) |
| 7 | Color Mode | 0 = 4bpp (16 colors), 1 = 8bpp (256 colors) |
| 12–8 | Screen Base Block | Typically 4 |
| 15–14 | Screen Size | 0 = 256×256, 1 = 512×256, 2 = 256×512, 3 = 512×512 |
Level background setup flows through a clear pipeline of functions. Here is the call sequence from level entry to first frame:
InitLevelBG() @ 0x08001158 — master level BG init
├─ Disables VBlank interrupt
├─ Reads level/world from gSceneControl
├─ For each of 3 layers:
│ ├─ Reads tile pointer from BG_TILE_TABLE[index]
│ ├─ AllocAndDecompress(tilePtr)
│ ├─ DMA tiles → VRAM charblock (CB0/CB1/CB2)
│ ├─ Reads tilemap pointer from BG_TILEMAP_TABLE[index]
│ ├─ AllocAndDecompress(tilemapPtr)
│ └─ DMA tilemap → IWRAM screen buffer
├─ Reads palette from BG_PALETTE_TABLE[palIndex]
├─ Sets REG_BG0CNT, REG_BG1CNT, REG_BG2CNT
├─ DMAs screen buffers → VRAM screenbases
└─ Re-enables VBlank
DispatchLevelLayerSetup() @ 0x0804C798 — scripted BG dispatch
└─ For each sublevel:
├─ SetupLevelLayerConfig(level, sub) @ 0x0804B920
│ └─ Configures gBGLayerState from lookup tables
├─ LoadBGTileData(level, sub) @ 0x0804B464
│ └─ DecompressAndDmaCopy(romPtr, vramDest, size)
└─ LoadBGTilemapData(level, sub) @ 0x0804B4B0
└─ AllocAndDecompress + DMA rows to screenblock
VBlankDmaTransfer() @ 0x08000CE2 — called every VBlank
├─ DMA gScreenBufferA → VRAM 0x0600E000
├─ DMA gScreenBufferB → VRAM 0x0600E800
├─ DMA gScreenBufferC → VRAM 0x0600F000
└─ DMA OAM buffer → hardware OAM
ScrollBGLayer(direction, delta) @ 0x08001CD0 — tilemap streaming
├─ Updates camera position in gBGLayerState
└─ Streams new tilemap rows/columns into screen buffers
0x03003430)Three 28-byte entries (one per gameplay BG layer) control VRAM destinations, scroll positions, and tilemap dimensions.
struct BGLayerState { // 28 (0x1C) bytes per entry
u32 tileVramDest; // +0x00 VRAM charblock address for tile DMA
u32 tmapVramDest; // +0x04 VRAM screenbase for tilemap DMA
u16 scrollX; // +0x08 horizontal scroll (subpixel)
u16 scrollY; // +0x0A vertical scroll (subpixel)
u16 field_0C; // +0x0C
u16 field_0E; // +0x0E
u16 mapWidth; // +0x10 tilemap width in tiles
u16 mapHeight; // +0x12 tilemap height in tiles
u16 flags; // +0x14
u16 dmaTileCount; // +0x16 tile rows for DMA transfer
u8 dmaRowSize; // +0x18 bytes per DMA row
};
// gBGLayerState[0] = { .tileVramDest = 0x06000000, .tmapVramDest = 0x0600E000 }
// gBGLayerState[1] = { .tileVramDest = 0x06004000, .tmapVramDest = 0x0600E800 }
// gBGLayerState[2] = { .tileVramDest = 0x06008000, .tmapVramDest = 0x0600F000 }
0x03004790)struct DecompBufferCtrl {
u8 *buf0; // +0x00 tile data A / tilemap layer 0
u8 *buf1; // +0x04 tile data B / tilemap layer 1
u8 *buf2; // +0x08 tile data C
u8 *buf3; // +0x0C tile data D
u8 *buf4; // +0x10
u8 *buf5; // +0x14 tilemap layer 2
};
Three IWRAM buffers (each 2 KB) act as staging areas for tilemap
data. During VBlank, VBlankDmaTransfer DMAs them to the
corresponding VRAM screenbases. This double-buffering prevents tearing.
| Buffer | IWRAM Address | VRAM Destination | Layer |
|---|---|---|---|
| Screen Buffer A | 0x03000900 | 0x0600E000 | BG0 |
| Screen Buffer B | 0x03001100 | 0x0600E800 | BG1 |
| Screen Buffer C | 0x03001900 | 0x0600F000 | BG2 |
Each tilemap cell is a standard GBA 16-bit screenblock entry:
// 16-bit tilemap entry (GBA BG screenblock format)
u16 entry;
u16 tileId = entry & 0x3FF; // bits 0-9: tile index (0-1023)
u16 hflip = (entry >> 10) & 1; // bit 10: horizontal flip
u16 vflip = (entry >> 11) & 1; // bit 11: vertical flip
u16 palBank = (entry >> 12) & 0xF; // bits 12-15: palette bank (0-15)
Tile IDs are relative to the layer's assigned charblock. Each 4bpp tile is 32 bytes (8×8 pixels, 4 bits per pixel). A single charblock holds up to 512 tiles.
SetupSceneGfx (0x0804886C) loads several
hardcoded tilesets into VRAM that are shared across all levels. These
provide the HUD, collectible icons, and UI elements that overlay the
per-level backgrounds.
| ROM Address | Description | VRAM Destination |
|---|---|---|
| 0x082F4D3C | Shared tiles → charblock 0 | 0x06000000 |
| 0x082F518C | Shared tiles → charblock 1 | 0x06004000 |
| 0x082F5D0C | Shared tiles → charblock 2 | 0x06008000 |
| 0x082F7D64 | Shared tiles → charblock 3 | 0x0600C000 |
| 0x08366214 | Large shared tileset (8 × 4 KB blocks) | Via VRAM cursor |
| 0x08367468 | Small shared tileset (3 × 512-byte blocks) | Via VRAM cursor |
When a level loads its per-layer tile data (InitLevelBG),
it overwrites the charblock regions with level-specific tiles. The
per-level tilemaps may reference both the level tiles (at low tile IDs)
and the shared tiles (at higher tile IDs that weren't overwritten).
Beyond the BG layer system, the game uses a graphics command stream
for scripted scene transitions, cutscenes, and dynamic effects. A byte stream
stored in ROM (accessed via gStreamPtr at 0x03004D84)
encodes a sequence of commands that are dispatched each frame.
The stream processor reads command bytes and dispatches to handler functions. Each command consumes 2–7 bytes from the stream (command byte + arguments). Three dynamically allocated buffers support the stream:
gDecompBuffer) —
holds the decompressed command stream datagGfxBufferPtr, 32 bytes) —
per-scene palette/flag stategBuffer_52A4, 0x480 bytes) —
temporary workspace for stream processingLoadAndDecompressStream(streamIdx) // decompress from ROM, set gStreamPtr
AllocAndClearGfxBuffer() // alloc 32-byte GFX state
AllocAndClearBuffer_52A4() // alloc 0x480-byte scratch
// ... frame-by-frame command dispatch ...
ShutdownGfxSubsystem() // on scene exit:
├─ ShutdownGfxStream() // frees stream entries + stream buffer
├─ FreeSoundStruct()
├─ FreeBuffer_52A4()
├─ FreeGfxBuffer()
├─ FreeDecompStreamBuffer()
└─ StopAllMusicPlayers()
| Address | Name | Bytes | Description |
|---|---|---|---|
| 0x0804C0BE | StreamCmd_ResetEntries | 2 | Frees all active stream entries, resets video state |
| 0x0804C1FC | StreamCmd_DmaSpriteData | 4 | DMAs sprite tile data from stream buffer to OBJ VRAM |
| 0x0804C6A8 | StreamCmd_EnableMosaic | 3 | Sets mosaic on BG2/BG3, reads 4-bit mosaic level |
| 0x0804C776 | StreamCmd_SetRenderMode | 2 | Modifies render mode flags in level state struct |
| 0x0804C798 | DispatchLevelLayerSetup | var | Triggers full per-level BG layer setup pipeline |
| 0x0804C7FC | StreamCmd_SetBGScroll | 7 | Sets layer scroll X/Y from stream (subpixel «4) |
| 0x0804E76E | StreamCmd_StopMusic | 2 | Halts all music players |
| 0x0804E7D2 | StreamCmd_StopSound | 2 | Stops sound effects |
| 0x0804E7E6 | StreamCmd_Nop3 | 3 | No-op (advances stream by 3 bytes) |
| 0x0804E7FA | StreamCmd_StopMusicAndDisableIRQ | 2 | Stops music and disables VBlank/HBlank interrupts |
| 0x0804E42A | StreamCmd_ClearRenderMode | 2 | Clears render mode bits in level state |
| 0x0804E5C6 | StreamCmd_ToggleLayerFlag | 2 | Toggles bit 5 of layer state byte |
| 0x0804E5F0 | StreamCmd_SetBlendMode | 4 | Sets REG_BLDCNT from blend mode lookup table |
| 0x0804E708 | StreamCmd_SetWindowRegs | 6 | Writes REG_WININ and REG_WINOUT from stream |
| 0x0804E73A | StreamCmd_EnableScrollMode | 2 | Sets scroll mode bits in level state |
| 0x0804E814 | StreamCmd_DisableVBlank | 2 | Disables VBlank IRQ and interrupt handlers |
| 0x0804E8FE | StreamCmd_DisableVBlankAndStopMusic | 2 | Disables VBlank + stops all music |
| 0x0804E974 | StreamCmd_SetMusicParams | 4 | Sets music tempo/volume from stream data |
| 0x0804E9DC | StreamCmd_ConfigureBlend | var | Configures blend mode, coefficients, and alpha |
| 0x0804EA94 | StreamCmd_RunScript | var | Runs an inline script sub-stream |
| 0x0804C24C | StreamCmd_ConfigureSprite | var | Configures OAM sprite parameters from stream |
The stream command system uses a dispatch table indexed by the command opcode. Commands range from simple 2-byte operations (advance stream, call one function) to complex variable-length scripts that configure multiple hardware registers and manage sprite/palette state. A total of 22 stream commands have been identified so far.
| Address | Name | Module | Description |
|---|---|---|---|
| 0x08001158 | InitLevelBG | engine | Master level BG init: decompresses all tile/tilemap/palette data, DMAs to VRAM, configures BGCNT registers |
| 0x08001CD0 | ScrollBGLayer | engine | Runtime tilemap streaming during BG scrolling |
| 0x08000CE2 | VBlankDmaTransfer | engine | VBlank handler: DMAs screen buffers and OAM to VRAM |
| 0x08003904 | ResetVideoRegisters | engine | Full video init for level entry |
| 0x08003D58 | ClearVideoState | engine | Zeroes OAM shadow buffer |
| 0x08003DC0 | SetupOAMSprite | code_0 | Configures a single OAM shadow entry |
| 0x08043AF4 | DecompressData | code_3 | Processes sub-header, Huffman+LZ77 or LZ77-only decompression |
| 0x08043B34 | DecompressAndCopyToPalette | code_3 | Decompress and DMA to palette/OBJ VRAM |
| 0x0804886C | SetupSceneGfx | gfx | Title/intro scene setup: loads shared tilesets into all 4 charblocks |
| 0x0804832C | UpdateBGScrollRegisters | gfx | Writes scroll positions to hardware registers |
| 0x08048498 | UpdateBGTileAnimation | gfx | Animates tile data (palette cycling, frame updates) |
| 0x08049726 | LoadBGPalette | gfx | Loads background palette from ROM table |
| 0x0804B424 | DecompressAndDmaCopy | gfx | Decompress ROM data and DMA to VRAM destination |
| 0x0804B464 | LoadBGTileData | gfx | Per-level tile data loader via lookup table |
| 0x0804B4B0 | LoadBGTilemapData | gfx | Per-level tilemap loader with multi-quadrant handling |
| 0x0804B920 | SetupLevelLayerConfig | gfx | Configures BG layer struct from lookup tables |
| 0x0804BBD4 | SetupWorldMapBG | gfx | World map / dialog background init |
| 0x0804C798 | DispatchLevelLayerSetup | gfx | Scripted dispatch calling all per-level BG setup functions |
| 0x0804BAB4 | FinalizeLevelLayerSetup | gfx | Loads level BG palette from ROM table to palette RAM |
| 0x0804BAD4 | LoadAndDecompressStream | gfx | Decompresses ROM stream data, sets global stream pointer |
| 0x0804BAFC | FreeDecompStreamBuffer | gfx | Frees the decompressed stream buffer |
| 0x0804BB12 | ClearScreenBufferB | gfx | DMA-fills screenblock buffer B with 0xF000 (blank tilemap) |
| 0x0804BB3C | AllocAndClearGfxBuffer | gfx | Allocates and zero-fills the 32-byte GFX state buffer |
| 0x0804BB74 | FreeGfxBuffer | gfx | Frees the GFX state buffer |
| 0x0804BB88 | AllocAndClearBuffer_52A4 | gfx | Allocates and zero-fills the 0x480-byte scratch buffer |
| 0x0804BF7C | ShutdownGfxSubsystem | gfx | Tears down all graphics on scene exit (free chain + stop music) |
| 0x0804BFD0 | InitGfxStreamState | gfx | Allocates stream buffer, clears OAM, resets VRAM cursors |
| 0x0804C050 | ResetGfxStreamEntries | gfx | Frees all active stream entries, resets video state |
| 0x0804C0EC | LoadGfxStreamEntry | gfx | Loads/decompresses a stream entry from ROM table |
| 0x0804C1C0 | DmaSpriteToObjVram | gfx | DMAs sprite tile data from stream buffer to OBJ VRAM |
| 0x0804C218 | SetSpriteTableFromIndex | gfx | Sets sprite table pointer from ROM lookup at 0x08189E84 |
| 0x0804E42A | StreamCmd_ClearRenderMode | gfx | Clears render mode bits in level state |
| 0x0804E814 | StreamCmd_DisableVBlank | gfx | Disables VBlank IRQ + interrupt handlers |
| 0x0804E8FE | StreamCmd_DisableVBlankAndStopMusic | gfx | Disables VBlank + stops all music |
| 0x08050134 | StopSoundEffects | m4a | Stops sound effect playback channels |
| 0x080500FC | StopAllMusicPlayers | m4a | Iterates ROM_MUSIC_TABLE to halt all 4 music players |
| 0x0804BF7C | ShutdownGfxSubsystem | gfx | Tears down all graphics on scene exit (free chain + stop music) |
| 0x0804BE08 | SetupGfxCallbacks | gfx | Initializes VBlank/HBlank callback function pointers |
| 0x08048768 | FadeOutController | gfx | Manages BG scroll fade-out + callback transition |
| 0x0804BDB4 | InitLevelStateDefaults | gfx | Sets default map dimensions, scroll, window registers |
| 0x0804BD8A | ClearScreenBufferB_Alt | gfx | Alternate screenblock fill (non-word-aligned variant) |
| 0x0804E5F0 | StreamCmd_SetBlendMode | gfx | Sets REG_BLDCNT from blend mode lookup table |
| 0x08000F70 | UpdateFadeEffect | engine | Applies brightness fade using REG_BLDY |
| 0x08000FA2 | HBlankScrollUpdate | engine | Writes BG scroll from scanline-indexed tables |
| 0x0800111E | WaitVBlankAndClearMosaic | engine | Waits for VBlank, clears REG_MOSAIC |
| 0x0800113E | AcknowledgeInterrupt | engine | Writes REG_IE to REG_IF (interrupt acknowledge) |
| 0x08003DA0 | ClearOamEntries6Plus | engine | Zeroes OAM shadow entries 6–91 |
| 0x0800043C | Abs | system | Absolute value of signed integer |
| 0x08000448 | StrCmp | system | Byte-by-byte string comparison |
| 0x08000460 | StrCpy | system | Null-terminated string copy |
| 0x08000948 | FixedMul8 | system | 8.8 fixed-point signed multiply (s16×s16 » 8) |
The graphics subsystem uses three dynamically allocated buffers, each with a matching alloc/free pair:
| Buffer | Global | Size | Alloc Function | Free Function | Purpose |
|---|---|---|---|---|---|
| Decomp Stream | gDecompBuffer | Variable | LoadAndDecompressStream | FreeDecompStreamBuffer | Holds decompressed graphics command stream data |
| GFX State | gGfxBufferPtr | 32 bytes | AllocAndClearGfxBuffer | FreeGfxBuffer | Per-scene graphics state (palette indices, flags) |
| Scratch Buffer | gBuffer_52A4 | 0x480 bytes | AllocAndClearBuffer_52A4 | FreeBuffer_52A4 | Temporary workspace for graphics stream processing |
All three use the same pattern: allocate via thunk_FUN_080001e0,
DMA3-fill with zeros using fixed-source mode, then store the pointer in an
IWRAM global. The free functions simply call thunk_FUN_0800020c
on the stored pointer.
Functions in this subsystem that have been fully decompiled to matching C:
| Function | Status | Notes |
|---|---|---|
| DecompressData | Matched | Two-stage (Huffman+LZ77) or single-stage decompression |
| DecompressAndDmaCopy | Matched | Decompress + DMA to VRAM + free |
| DecompressAndCopyToPalette | Matched | Decompress + 16-bit DMA to palette |
| AllocAndDecompress | Matched | Alloc buffer + decompress into it |
| FreeDecompStreamBuffer | Matched | Simple free wrapper |
| FreeBuffer_52A4 | Matched | Simple free wrapper |
| FinalizeLevelLayerSetup | Matched | Palette table lookup (asm barrier technique) |
| LoadAndDecompressStream | Matched | Stream table lookup (asm barrier technique) |
| DisableInterruptsForGfxSetup | Matched | Sound shutdown (late variable init technique) |
| IsSelectButtonPressed | Matched | Input check (asm barrier technique) |
| MPlayStop | Matched | Music player stop (no special technique needed) |
| ShutdownGfxSubsystem | Matched | Tears down graphics on scene exit (free chain) |
| StreamCmd_ResetEntries | Matched | Stream command: reset all active entries |
| StreamCmd_EnableMosaic | Matched | Stream command: enable BG2/BG3 mosaic |
| StreamCmd_StopMusic | Matched | Stream command: halt all music |
| StreamCmd_StopMusicAndDisableIRQ | Matched | Stream command: stop music + disable IRQ |
| StreamCmd_DisableVBlank | Matched | Stream command: disable VBlank interrupt |
| UpdateFadeEffect | Matched | Applies brightness fade via REG_BLDY |
| TransitionToWorldMap | Matched | World map transition with callback state setup |
| GetEntityLookupData | Matched | Entity table lookup (shift-pair interleaving technique) |
| FixedMul8 | Matched | 8.8 fixed-point multiply (register asm("r1") for forced copy) |
| InitSceneState | Matched | Sound scene init via voice table lookup |
| m4aSongNumStop | Matched | Stop song if currently playing |
| m4aMPlayCommand | Matched | Stop player for matching song |
| SetupGfxCallbacks | Matched | World map VBlank/callback initialization |
| FadeOutController | Matched | Screen fade-out with scene transition switch |
| MPlayMain_SetAndProcess | Matched | Store to gSoundInfo + call MidiProcessEvent (u32 return type trick) |
| FreeAllDecompBuffers | Matched | Free 6 decomp buffers + collision map, stop 4 music channels |
| SoundCmd_Dispatch | Matched | Read command byte, dispatch via ROM_SOUND_CMD_TABLE |
| StreamCmd_StopSoundAndSync | Matched | Stop sound, sync freq to BG scroll state |
| StreamCmd_SetSoundFreqs | Matched | Read two u16 frequencies from stream |
Functions that are understood but blocked by agbcc code generation differences:
| Function | Blocker |
|---|---|
| LoadBGTileData | Register allocation: compiler uses push {lr} instead of push {r4,r5,lr} |
| LoadBGTilemapData | Register allocation around table lookup |
| FreeGfxBuffer | Hand-written bx lr leaf |
| AllocAndClearGfxBuffer | DMA3 with stack-allocated source: register allocation around sp |
| AllocAndClearBuffer_52A4 | Same DMA3 stack-source pattern |
| ClearScreenBufferB | Non-word-aligned + bx lr leaf |
| StreamCmd_SetBGScroll | Register allocation mismatch across ReadUnalignedU16 calls |
| StopAllMusicPlayers | Unusual u16 widening pattern in loop init |
| StreamCmd_StopSound | Non-word-aligned function start |
| StreamCmd_ClearRenderMode | Non-word-aligned bx lr leaf |
| StreamCmd_SetRenderMode | Non-word-aligned bx lr leaf |
| StreamCmd_Nop3 | Non-word-aligned bx lr leaf |
| StreamCmd_DisableVBlankAndStopMusic | Non-word-aligned function start |
| InitGfxStreamState | DMA3 with stack-allocated source |
| ResetGfxStreamEntries | Loop structure / pointer arithmetic mismatch |
| Abs, StrCmp, StrCpy | Hand-written bx lr utility leaves (system module) |
| HBlankScrollUpdate, AcknowledgeInterrupt | Non-word-aligned bx lr engine leaves |
| SetPaletteAnimEntry | Register allocation: compiler swaps r0/r1 for parameter copy |