The resource archive¶
Almost the entire interface ships as data, not code. A single archive of roughly 4.1 MiB holds the strings, bitmaps, colours, fonts, view geometry, screen definitions and event wiring - and Chikuma reads it the way the firmware reads it, with one parser, rather than hand-building screens. This is the single fact that makes the UI faithful: screens build themselves from records, so they cannot drift from the originals by an author's taste.
Reading and re-packing the archive is lossless. Chikuma's tooling decodes and re-encodes it byte-for-byte: of 7,845 records in the main archive, all but two round-trip exactly, and the two that do not (a couple of record types with no traced consumer) are carried verbatim. Every codec is verified per record, not per section, so a single wrong field fails the check.
The container format¶
The archive is a container (format version 3) that Chikuma parses in chikuma/ui/archive/. Its shape,
top to bottom:
header { version, payload offset, section count }
sections a table of { fourcc, record count, flag, directory offset }
directories per section, a table of { id, payload offset, length }
payloads the record bodies
One reader serves the archive and every language blob, because they all share this format and one id
space. A record is addressed by a numeric resource id; a separate name table maps human names
(Extras_Screen, MainMenu_List_Music) to those ids, and it is needed because navigation is expressed
in names, not numbers.
The record types¶
The sections divide the interface into kinds. The important ones:
- SCST - the controller class a screen uses, by name (for example
TSilverCntlr). - ITEM - menu row tables. Each ITEM id is a list of rows.
- SORC - the sources a row's dynamic content binds to: triples of value, id and kind, where one kind is a static value and the others are a publish/subscribe binding. A row's label lives here, not in ITEM.
- CEVT / SEVT / AEVT / TEVT - the event bindings, all four one format: a list of entries mapping an event name to an action name and, optionally, a navigator verb with its arguments. All of them re-encode byte-exact.
- COLR - colours.
- FONT / FSTR - font ids and names.
- IMAG / BMap - image references and the bitmaps themselves. Every bitmap decodes to PNG with a JSON sidecar and re-encodes byte-exact, across the several pixel formats the archive uses (palette, BGRA, RGB565, and alpha masks).
- Str - the UTF-8 strings.
- View / SLyt / VSlt and the rest of the record-list family - the view geometry and screen layout, fixed-size records per view class, decoding to objects that carry a frame rectangle, a resource id, a visibility flag and a per-class tail.
A screen builds itself¶
Entering a menu does not construct a new screen tree. A shared list view is re-pointed at a different row table - the target screen's slot record names which view and which ITEM id - and the rows are read from the archive. Dozens of screens share one list view this way.
Behaviour is data too. The chain, when a row is highlighted or pressed, runs:
row event -> CEVT entry (keyed by the row's own list instance)
-> an action name and/or a navigator verb with arguments
-> the controller picks an event by the device's state
-> SEVT entry (keyed by the current layout)
-> navigator verb (switch layout / push screen) with a target NAME
-> the name table turns that name into a resource id
So a binding reads, in the archive's own vocabulary, like this (names shown, numbers omitted):
<the Music row>, chosen -> HandleMusicSelected
<the Extras row>, chosen -> navigator.PushScreen Extras_Screen Extras_Screen_WorldClock
<the Extras row>, delayed-select -> navigator.SwitchLayout MainMenus_Main_Screen_Extras
This is why "the preview pane follows the highlight" is not code in a component: it is the screen
switching to another of its own layouts through a delayed-select binding. The full wiring is written
up in reports/ui_menu_wiring.md.
How the blobs are cut, and localization¶
archive_blob.bin and names_blob.bin are cut from the decrypted image at fixed offsets. The straight
cut is the oracle; the re-packing tools are diffed against it, and re-packing all of the containers
reproduces them byte-identical.
Localization is an override run keyed by resource id, which is exactly how the firmware does it. There is no English blob - English is the archive itself. Each of the twenty other languages is a small container of just two sections (the strings, and a date/time format table) laid out after the archive. A language overrides a string by carrying the same id and fourcc with different text; the ids it leaves alone (font family names, and the entire Legal screen, for instance) fall back to English by design. Lookup asks the selected language first and falls back to the archive - there is no search chain.
Because the whole thing round-trips, the strings can be exported (Chikuma's tooling writes them out as an Xcode String Catalog keyed by resource name), edited, and re-applied, and the archive still re-packs byte-identical. That is what makes editing the interface's text a data operation rather than a code change.
What is not decoded, and what is open¶
Two record types (an alias table and the date/time format table) have no traced consumer and are carried raw rather than decoded. A read-and-repack path is complete and lossless; an external builder that authors brand-new screens from scratch exists only in part (there is an append-only overlay and id ledger, not a full builder). Roughly a sixth of the view-body bytes are read by nothing that has been traced, but none are unexplained, so a new screen only needs the named fields its classes actually read.