This folder holds what ties The Broken Seal's image together: the cartridge header and entry, the RAM layout, the code resident in IWRAM, the BIOS calls, the far-call tables between the banks of the main image, the overlays, the resource directory and the flash save library.
Nearly all of it is assembly: handwritten code that no C compiler would produce, each file showing why in its header, and the tables the linker lays out.
See also: Runtime, which stands on this folder, and Scenes, the overlays' main users.
| File | What it holds |
|---|---|
| rom_head.s, entry.s | the cartridge header and the entry that sets up the CPU modes and stacks |
| ewram.s, iwram.s, iwram_hi.s | the main image's RAM variables, in their order in EWRAM and IWRAM (the memory map below) |
| runtime.s, runtime_irq_table.s, runtime_mix*.s, runtime_mul_q16_ip.s | the code copied to IWRAM: the interrupt dispatcher, its handler table, the sound mixer and the fixed-point multiply |
| iwram_entry.s | the stubs that call the IWRAM code from the ROM |
| cpu_set.s, sound_get_jump_list.s | BIOS calls |
| heapfree.s | Runtime_ReleaseHeapBlock and MemFree |
| updfar.s | three ARM routines that share one literal pool |
| syspal.s, memory/*.png | the window palette and the basic sprite palettes, built from the PNGs |
| overlay.s | the far-call stub macro: 8 bytes that load a routine's address and jump to it |
| far_call/*.S | each bank's far-call table: one 8-byte jump per export, in a fixed order (stub.inc) |
| overlay/ | the overlays: overlays.s places each one's packed stream as a directory resource, overlay.ld links each at 0x2008000, and the ovl_*.s hold the RAM variables of the overlays that have them |
| overlay.inc | the stub tables every overlay and far-call bank opens with |
| resource/directory.s | the resource directory: one entry per picture, table or overlay, each defining its ResourceId_ name |
| resource/*.s, resource/metadata_get_record.c, resource/colconv*.tsv | the resource decoders, the colour conversion tables and the animation metadata lookup |
| save/ | the flash save library: the chip drivers for Atmel, Macronix and SST flash (FLASH_C, compiled with pret's old_agbcc), and the save record signature (savedata.c) |
The unit headers are in games/tbs/include/unit/system/, the far-call prototypes in games/tbs/include/farcall/. Link order, overlays included, is in games/tbs/objects.tsv; games/tbs/overlays.tsv gives each overlay's .bss header. games/tbs/main.ld places the main image's sections, and games/tbs/include/ram_buffer.h names the fixed RAM addresses the code uses as constants.
| Prefix | What it acts on |
|---|---|
| System_ | the cartridge entry |
| Iwram | the resident IWRAM code and its entry stubs (IwramIrqMain, IwramHalt, IwramMulQ16) |
| Bios_ | the BIOS calls |
| Resource_, ResourceId_ | the resource decoders and the directory's ids |
| Flash_, EraseFlash, ProgramFlash | the flash library, named as pret names it |
| Runtime_, Sound_ | things the engine names elsewhere |
main.ld does not let the main image's code flow from one object to the next. It places each bank at an address of its own, written per edition (. = ABSOLUTE(...) before each rom_main_NN), and leaves the space up to that address empty. Each bank then opens with its far-call table: the first row of every rom_main_NN place in objects.tsv is a far_call/*.S file. A function that another bank calls is reached through its 8-byte entry in that table, never at its own address.
Distance does not explain this. A Thumb bl reaches 4 MB either way, and the whole main image, from 0x08000000 to the last bank's table below 0x08190000, is within reach of every bank. What the arrangement does buy is independence: another bank knows only where a bank starts and the place of each export in its table, so a bank can grow or shrink inside its gap without moving a single address that other code holds. The gaps are of different sizes in each edition, as each edition's code grew differently, German most. XXX: the bytes show the fixed starts and the tables, not how Camelot built the game; linking each bank on its own against the others' tables is the likeliest reason, but not a proven one.
The stub itself (overlay_veneer in overlay.s) is ldr r4, =target; bx r4. It can use r4 freely because every game C file is compiled with -fcall-used-r4 (the Makefile's CFLAGS_tbs), so no caller expects r4 to survive a call. bx also switches to ARM state when the target is ARM code, which is why the IWRAM routines can be reached through the same stubs.
An overlay runs from EWRAM at 0x2008000, 96 MB below the ROM, out of any bl's reach; every call it makes into the main image has to load an address into a register. It does so through its import table, the last of its three objects (import/<place>.s): the overlay's code makes a short bl to a local stub, and the stub's word holds the main-image address. The overlay is linked on its own at 0x2008000 (overlay/overlay.ld), after the main image and against its symbols (-R in the Makefile's overlay rule), so the import table is where its calls into the main image are recorded.
The stub's word is not the function but its far-call entry: Vault imports Engine_TaskWait, whose stub goes to WaitFramesFar, which goes to WaitFrames. So an overlay holds only the places of far-call entries, which move only when a table changes, and the two hops are the price. The rows an overlay may import, and their order, are fixed in overlay.inc: Camelot's four divide routines first, then the far-call entries in the order of the main image. Each import table sets import.NAME for the stubs its overlay calls and includes that list, so every overlay that imports a function keeps it in the same relative order. Several local names may lead to one target (ArcTan2, Math_Atan2 and CalculateFacingAngle all reach ArcTan2Far), because different overlays' code calls the same routine by different names.
The other direction is the entry table at the overlay's start (entry/<place>.s): six veneers that the field object calls as gOverlayArea.initialize(), gOverlayArea.events() and so on (struct SceneEntries, include/field_runtime.h). The engine and an overlay meet only at those two tables, each of known order.
An overlay never calls another overlay, because only one is ever loaded: all of them are linked to the same 32 KB at 0x2008000. Code that several scenes need is therefore copied into each of them, which is what games/tbs/src/scene/common/ holds.
When an overlay is loaded, Resource_LoadCode (runtime/runtime/system.c) decompresses it into the overlay area and then runs Resource_PatchThumbBranch over it, which turns each bl pair from a stored position back into a displacement from the instruction. The packer, tools/ags-overlays, does the reverse: in the stream, every call to the same routine has the same bytes, wherever it is made from. XXX: that this was done so that the overlays compress better under LZ is an inference from what the filter does; nothing in the ROM says so.
A far-call table is a list of places. Inserting a row moves every entry after it by 8 bytes and the bank's own code after the table by 8 bytes too. In this tree that is a relink, since callers use the ...Far symbols, so the build still works; but:
A resource id is a row number: resource in directory.s defines ResourceId_NAME as the row's place. Code that names a resource by its symbol follows a renumbering, but numbers travel without symbols in several places, and those silently point at the wrong row:
The directory ends with 25 unused rows (unused 25), where a row can be added without renumbering any other. XXX: whether they were left for that is not known.
Some places in memory are fixed: code reaches them as numbers, not through the linker. include/ram_buffer.h, include/iwram_call.h and include/cartboot.h list them, each once, with FIXED_ADDRESS(type, address, linked): the matching build compiles the number, and make modern compiles the linked name instead (FIXED_LINKED_ in include/types.h). These are the invariants, and what follows from each:
The addresses are tbs-en's, from its link map (out/tbs-en/tbs-en.map); German moves a few IWRAM variables (ram_buffer.h).
EWRAM**, 256 KB from 0x02000000 (main.ld), laid out by ewram.s:
| Address | Size | What | Named in |
|---|---|---|---|
| 0x02000000 | 32 KB | the main image's variables: the save buffer, the game flags (gGameFlagBytes), the game state (gGameState), the sound driver's work and the flash library's | ewram.s |
| 0x02008000 | 32 KB | the overlay area, gOverlayArea: the loaded scene or screen overlay | ewram.s, overlay/overlay.ld |
| 0x02010000 | 32 KB | gMapCellBuffer; battle effects also use it for particles | Ram_MapCellBuffer, ram_buffer.h |
| 0x02018000 | 16 KB | gActorSpriteSlots | Ram_ActorSpriteSlots |
| 0x0201c000 | 16 KB | gDecodeBuffer | Ram_DecodeBuffer |
| 0x02020000 | 32 KB | gMapBlocks, the map's blocks (games/tbs/src/field/field/map_layers.c) | Ram_MapBlocks |
| 0x02028000 | 16 KB | gMapLayerData | ewram.s |
| 0x0202c000 | 16 KB | gMapCollision | Ram_MapCollision |
| 0x02030000 | 32 KB | gEwramHeap, the EWRAM heap; heap.c allocates from it up to 0x02040000 | ewram.s, games/tbs/src/runtime/runtime/heap.c |
| 0x02038000 | 32 KB, to the end | gBgTileBuffer | Ram_BgTileBuffer |
IWRAM**, 32 KB from 0x03000000 (main.ld):
| Address | What | Named in |
|---|---|---|
| 0x03000000 | the resident runtime, 0x1400 bytes copied from the ROM: the interrupt dispatcher IwramIrqMain, the handler table gIrqHandlers, the fixed-point multiply and the mixer's helpers | main.ld (.iwram_runtime), runtime*.s |
| 0x03001400 | the main image's IWRAM variables, among them the heap slots gWorkSlot (0x03001e50) | iwram.s, Ram_WorkSlot |
| 0x03002000 | gIwramHeap, the IWRAM heap, which heap.c allocates from below Ram_IwramHeapEnd | iwram.s |
| 0x03007000 | the sound mixer, SoundDriver_Mixer, 0x3a4 bytes copied from the ROM by Audio_Initialize | Ram_SoundMixer, main.ld (.iwram_mixer), games/tbs/src/sound/audio.c |
| 0x03007800 | Ram_IwramHeapEnd: the end of the IWRAM heap, where gCartridgeResetMarker sits | Ram_IwramHeapEnd, iwram_hi.s |
| 0x03007a00 | Ram_FrameWaitStackTop: the stack WaitFrames runs on | Ram_FrameWaitStackTop, iwram_hi.s |
| 0x03007f00, 0x03007fa0 | the system-mode and IRQ-mode stack tops System_Entry sets | entry.s |
| 0x03007ffc | gIrqVector, where the BIOS jumps on an interrupt | entry.s, iwram_hi.s |
VRAM**, from 0x06000000 (games/tbs/include/system/io_reg.h):
The names here follow what each piece does in the link: the far-call tables are named by the bank they open, the stubs by their target with Far added, and the overlays by their directory number. IwramIrqMain and the other resident routines are named from their code. The fixed addresses come from the literals in the ROM's code and are checked by building: the matching build compiles them, make modern uses the linked names.
Still unknown:
Source: src/system