How it works
BelmontUI ships no assets. Everything on the tab is found at run time and copied from the game’s own menus, so it looks, sounds and navigates like the native Settings pages.
- Finding the menus
- Tab placement: always last
- Belmont Minimap coexistence
- Cloning the native widgets
- The mod list and sub-menus
- Where saves happen
- What breaks on game updates, and how it fails
Finding the menus
A watcher component checks every 30 frames, about half a second at 60 fps. The game rebuilds its menus for each scene, so the work repeats per scene.
- In game, where a
GameMenuManagerexists, the tab goes into the pause menu’s top bar. - On the title screen, where there is no pause menu, the tab goes into each Settings screen, next to CONTROLS, GAMEPLAY and the others.
Every lookup is by component type, never by object name.
Tab placement: always last
The MODS SETTINGS tab is appended to the bar, never inserted in front of another mod’s tab. On every poll BelmontUI checks three things and fixes only its own tab:
- it is the last entry of the bar’s tab array;
- its toggle is the last sibling in the toggle row;
- its toggle is the last one in the toggle group.
If another mod added a tab after it, BelmontUI moves its own tab back to the end and logs [ui] tab moved to the end of '<bar>' (after '<tab>') at debug level. If another mod rewrote the tab array without it, it is put back: [ui] Mods Settings tab put back into '<bar>' (another mod rewrote its tabs).
The other toggles on the bar are narrowed by n/(n+1) so one more fits.
Belmont Minimap coexistence
Belmont Minimap (local.belmont.minimap) adds a MODS tab only to a pause-menu bar that still has exactly the five native tabs. So when Minimap is installed, BelmontUI leaves the bar alone until Minimap’s TabContent_MODS tab is there, then adds its own after it. If Minimap’s tab never comes, BelmontUI gives up waiting 20 seconds after the bar initialised with the menu closed.
Whether Minimap is installed is decided on the first poll, not at load: BelmontUI loads before Minimap, so the plugin list is incomplete during its own Load().
Cloning the native widgets
- The page is a copy of the Settings screen’s GAMEPLAY tab, or any settings tab when that is missing. Its scroll area and navigation group stay; its rows and settings logic go. The title becomes “MODS SETTINGS” and the long note is cleared. On the pause menu, the content moves into a shell copied from the last native pause-menu page, so it shows and hides like its neighbours.
- The top-bar toggle is a copy of the bar’s last visible toggle, relabelled. On every poll it is matched to a native toggle (sizes, layout, text settings, images) while both are off, so it stays identical.
- The rows are copies of native rows: the checkbox row for toggles, the slider row for sliders, the list row for choices and keys. The game’s binding component is stripped and the Doozy control is driven against your
ConfigEntry. Localizers are stripped so your texts stay. Controller navigation uses the same selectable the native page uses for that kind of row. - Buttons are a copy of the System page’s framed button, placed where other rows have their control. Before the pause menu has been opened once there is nothing to copy, so on the title screen a button row shows a checkbox that works the same.
- The mod list: a copy of the checkbox row per registered mod, with the title as its label, the summary as a smaller copy of the label, and a copied System-tab button captioned Settings. See the mod list and sub-menus.
- Sounds: value changes play the native row’s change sound; opening, closing and reset play the Settings screen’s sounds.
The mod list and sub-menus
On every rebuild, BelmontUI builds all rows at once: one list row per registered mod, sorted by title (case-insensitive), then each mod’s rows followed by a Back row. The list and the sub-menus only switch which rows are active, so moving between them doesn’t rebuild anything. The log gets [ui] Mods Settings: <n> mod(s) [<titles>], <m> rows in all. With no mod registered, the page shows one line: “No mod has registered settings.”
- The summary starts just after the mod’s name and runs up to the button. Its position is fixed the first frame the list row is drawn, because a name measured before it is shown comes out too wide.
- Opening. Pressing a list row’s Settings button opens the sub-menu on the next frame. The page title becomes the mod’s title and the first row is selected. The Settings screen’s open sound plays, and the log gets
[ui] '<title>' opened (...). - Back. B, Esc, Backspace or the Back row close the sub-menu. The mod’s config files are saved, its list row is selected again, the close sound plays, and the log gets
[ui] '<title>' closed (...). - The game’s back actions. While a sub-menu is open, every enabled input action whose name contains Close, Cancel or Back is switched off, in every enabled action map. Without that, B would also close the pause menu or the Settings screen. They are listed once in the log as
[ui] sub-menu: the game's back actions are off while it is open: [...]. They come back once the key that closed the sub-menu is released, and at once when the tab loses focus or the menu closes. - Leaving the tab. If the tab loses focus or the menu closes while a sub-menu is open, the sub-menu closes silently and saves, and the tab opens on the list next time.
Where saves happen
- A control writes its
ConfigEntryimmediately when it changes. - Config files are written to disk when the player leaves a mod’s sub-menu (that mod’s files), when the tab loses focus, when the menu closes, and when
ResetAll()orSave()is called. - On the title screen’s Settings screen, the tab reports no pending changes, so the game’s apply / discard flow never asks about it.
Each mod’s values go to its own BepInEx\config\<guid>.cfg. BelmontUI has no config file.
What breaks on game updates, and how it fails
The tab depends on the game’s menu components: the Settings tab view and its rows, the pause menu’s tab view, and the Doozy UI controls. A game update that renames or restructures them can stop the tab from being built.
BelmontUI fails safe:
- Every step is guarded. A failure logs
[ui] Mods Settings tab not added to '<bar>': <reason>and leaves the game’s menu as it was. You lose the tab, not the menu. - A single row that fails logs
[ui] <guid>: row '<label>' not added: ...and the other rows still show. - Mods keep working either way; their settings stay editable in their
.cfgfiles. - Because the public API uses only plain .NET types, a rebuilt BelmontUI after a game update does not force consumer mods to rebuild.