UI & the book menu
What the game’s UI actually is
Section titled “What the game’s UI actually is”No middleware. No Noesis/XAML/Scaleform/HTML strings. The entire UI is the
engine’s own entity-component system: menus are *.entity.ot assets under
GameUis/, Book_Menu/, Ui/ composed of widget components and driven by C++
controller components.
(Dear ImGui 1.91.7 is compiled in, but only for the hash-gated dev overlay — see Dev/debug mode. Irrelevant here.)
Widget components, generic and reusable from data: oCEntityCpntGameUi
(root/canvas), WindowUi, LabelUi, PictureUi, ButtonUi, GaugeUi, EditUi,
InputUi, ScrollPanelUi, NavigableZoneUi / NavigableListenerUi (gamepad and
mouse focus), oe::UiLayouterEntityCpnt, oCEntityCpnt3dText.
The main menu is the “Book”
Section titled “The main menu is the “Book””oCDtEntityCpnt3DBookController(Settings)— the 3D book.oCDtEntityCpnt3DBookTabController(Settings)— one per physical tab; its settings deserializer reads just 3 sub-object/picker refs, so tab→page wiring is pure data.oCDtEntityCpntBookMenuUiController(Settings)— orchestrator; ~8 picker refs + 2 struct readers, version-gated on class hash0x1768bccf. No inline page list — pages hang off tabs and pickers.- Page interface
oIDtEntityCpntBookPageUiController; concrete pages underGameUis/All_Book_Pages/*.entity.ot(Challenges, Compendium, HeroMeta, PlayBookPage, SelectHero, Social, Recap, Credits, BookOptions, TutoCompendium…). - Reusable blocks:
oe::dt::ButtonListUiControllerEntityCpnt,MultiChoiceOptionUiControllerEntityCpnt.
Cross-entity refs are path strings, not GUIDs — pages are wired as
GameUis\All_Book_Pages\Play_Book_Page.entity.ot plus [Game Ui] <page>\Game Ui
picker strings. [Named Event Sender] / [Named Event Listener] components exist
in page data, so buttons → named events → the loader’s existing R.on("gameplay:*")
is an interactivity bridge needing no new C++ hook. A **-prefixed label renders
literally, so literal labels need no text-bank key. Only the
EntitySettings/...gen file exists per entity — the bare .entity.ot path is
virtual.
The five tabs are a compiled-in constant
Section titled “The five tabs are a compiled-in constant”| Class | Hash | Size | Ctor | Vftable |
|---|---|---|---|---|
oCDtEntityCpnt3DBookController |
0x1781bd6c |
0x230 |
FUN_140307b10 |
0x140f23368 (30 slots) |
oCDtEntityCpnt3DBookTabController |
0x18ababa6 |
— | FUN_140309a60 |
descriptor DAT_141470f20 |
oCDtEntityCpntBookMenuUiController |
— | 0x328 |
FUN_1403e0580 |
— |
The settings deserializer builds the tab picker array with
_eh_vector_constructor_iterator_(count=5, size=0x40) — count 5 is a compiled-in
constant. BookController_ResolveSettings runs a fixed loop of 5, type-checking
entries as oCEntityCpntEntitySpawner → runtime +0xf8..+0x118;
BookController_ResolveTabs loops the same 5 → runtime +0x120..+0x148.
Clicks on tabs are not 3D picking and not ButtonUi: Main_Book_Menu has a
Nav_Zone with 5 navigable elements plus 5 [Navigable Listener Ui] components —
the hit areas live on the 2D UI layer, as pure data.
Button press dispatch
Section titled “Button press dispatch”The per-frame task oCEntityCpntButtonUi - Button input update
(UiButton_InputPoll) iterates live ButtonUi components; each pressed button that
passes its visibility/enable/focus gates goes through a raw input latch and then
UiButton_PressCommit with the widget desc from cpnt+0x268 — the single
choke point for every ButtonUi click. PressCommit runs the widget press state
machine (+0x32c state, +0x328 mode) then vcalls slot 2 on *(widget+0x570),
the owning controller’s oINavigableListener.
Widget offsets pinned from press captures: +0x280 = widget name,
+0x1e8 = rendered label.
hook_ui.cpp post-detours PressCommit (gated by RSMM_ENABLE_UI_HOOK, surfaced
in the desktop loader-flags panel) and emits R.on("ui:press"). Because the page
controller doesn’t know cloned buttons, *(widget+0x570) may be null — the hook
skips the native commit for listenerless widgets and only emits the event.
Building the mod menu
Section titled “Building the mod menu”- Page and tab are data — clone an existing book page entity and a tab mesh
controller entity, register both via
UsedRscList, repoint the picker refs. - Labels and art — text-bank append plus texture overrides.
- Actions — the loader hook on button press →
R.on("ui:press")→ Lua. - Mod ops — Lua toggles Lua-mods live; asset enable/disable/uninstall writes an intent file the host CLI consumes (the game runs under Proton, the CLI lives on the host, so it can’t shell out in process).
Pieces that were built (removed 2026-09-23 unless noted)
Section titled “Pieces that were built (removed 2026-09-23 unless noted)”engine/entity_strings.py(kept) — lstr surgery (scan/replace length-prefixed strings in section payloads; byte-stable, reversible, typo-guarded). The generic clone-and-retarget primitive for all entity work.engine/mod_menu.py+rsmm menu build|remove— repurposesTuto_Page_1as an “RSMM Mods” page: label keys swapped toRSMM_Menu_*and appended to the text bank in all languages with the generated mod list. Emitted as a normal override mod; reads pristine bytes from.rsmm.bak, so rebuild-after-apply is idempotent. The base page and bank are cloned from the user’s install at build time — no game bytes in the repo.engine/entity_inspect.py+rsmm menu inspect— structural entity summary and diff.- Intent protocol —
rsmm._internal.intent_write(op, mod_id)(allowlisted enable/disable/uninstall, strict id shape) appends JSONL to<cooking>/.rsmm_intents.jsonl; Lua getsR.mods.list()/R.mods.request(); the host getsrsmm intents list|apply|clear, which re-validates (the file is user-writable, so untrusted), applies last-intent-per-mod, and re-runsrsmm apply.rsmm watchauto-consumes. - Component-append primitive (
engine/entity_append.py, kept) — entity cooked layout is section 0 = directory (u32 count+ count × u32 class index), sections 1..count = one self-framed component record each (first u32 = class index, 16-byte instance GUID after the first inner END), last section = trailer.cooked.emit(cooked.parse(x))is byte-identical on the 188 KB book scene, so appending = bump count, append class index, insert record before the trailer — the entity-file analog of the versiondef vector append proven for items.
What failed, and why
Section titled “What failed, and why”- Social-page donor — scrapped. The clone rendered its text over every page,
cut and mis-scaled: its native
Dt Social Book Pagecontroller (which owns categories, sub-pages and button spawning) doesn’t participate in the slot show/hide flow. - Appended 6th bookmark — inert. All 5 page-tab nodes carry the same position and all 5 bookmark entities are byte-identical except GUID and material; the visible vertical spread is computed at runtime by array index. A 6th bookmark either never spawns or lands exactly under the Tuto one. The scene append itself loaded crash-free, so the append primitive is in-game safe — it’s the index-driven controller that has to change.
What works
Section titled “What works”Buttons appended into the Tuto frame page. Tuto_Compendim_Page decodes to 27
sections including two oCUIGameButtonDesc records (Prev_Button / Next_Button
arrow icons — which is why presses reported name="OPTION_BUTTON"). Both records
are self-framed and therefore cloneable:
- WindowUi record: name lstr, desc link by name,
[Game Ui] <page>\Game Uiparent picker,[NavigableZone Ui] … \Page Nave Zonepicker (nav registration), anchor floats, GUID at the standard record offset. - ButtonDesc record: position as f32 fractions, textures, flip/rotation, hover colors. No GUID.
build_tab_assets clones the page’s own Next pair three times as
RSMM Btn Next/Prev/Toggle with descs RSMM_Next_Mod / RSMM_Prev_Mod /
RSMM_Toggle_Mod, appending all six components to the page — same-file clone, so
parent and nav pickers resolve unchanged. No foreign controllers, no new assets, no
Main_Book_Menu override.
The end-to-end loop is proven in-game: the page renders, clicks select mods,
queueing writes an intent, rsmm intents apply flips the manifest, rebuilds the
page and re-applies.
- A true 6th tab — loader detour on
BookController_ResolveSettings/Tabs(symbols and patterns are in place) to trigger and position an appended tab spawner, plus a 6th Nav element inMain_Book_Menu’s Nav_Zone for the click. - Structural entity edits — whole-file clone plus GUID/text swap is proven; adding new rows to a versioned record is not. Fallback: a fixed row count with pagination in Lua.
- Whether the BookMenu controller tolerates a page swap on one of its 7 path-string page refs — likely yes, since the refs are plain paths, and it would give the mod page a whole tab without touching the 5-slot array.
