User interface: Scaleform, menus and the debug menu

How the game builds and draws its interface: the UI manager, the generic view host and the UI render list, the Scaleform movies and the node API that drives them, menus and the HUD as GameDB data, the Army screen's 3D stage and camera, the title, save-select and loading screens, and Monolith's developer debug menu with the cheat backend and console behind it.

The interface is a set of Scaleform GFx movies that the engine drives entirely from code and data: the movies hold artwork and timelines, while the GameDB holds menu layers, element placement, HUD states and cameras. This chapter follows the stack from the UI manager down to a single movie clip, then covers the Army screen's 3D stage, the title and loading screens and the developer debug menu that shipped without its controller.

The UI stack

The UI manager

The UI manager is [base+0x26FFB08] (vt_0x1F7A878, a class with three virtual methods; see Engine core). 0x1889674 stores the object in that slot and creates its members; CInterfaceMgr::Init (0x17804C4) later calls 0x188BAE4, which builds the views (below).

Offset Meaning
+0x30 the HUD; its +0x108 is the combat widget holding the ammo count (+0x408) and the reticle state (byte +0x414, written by 0x114C588)
+0x50 the menu system: the layer vector at +0x500 .. +0x508 (see Engine core), the Interface/Menu/MenuManager record at +0x518, the store header at +0x800 (see Saves, the store and the network layer)
+0x88 the object that owns the debug-menu open byte +0x7AE (below)
+0xB0 the player's visual component, used by the Skills screen (see Items)
+0xF0 / +0xF8 the top menu layer's record and its layer state
+0x220 the UI viewport as an int32 rectangle {left, top, right, bottom}; view initialisation takes its width and height from it
+0x248 / +0x250 the pending message-box completion {function, context}; the box's close callback 0x17DA030 calls function(result, context)
+0x360 the views, one per Interface/Shared.UIViews entry; the first three are cached at +0x378, +0x380 and +0x388
+0x430 a GameModes record (the HUD builder reads its UseMercenaryWidget)

The top menu layer is what menu predicates test: the evaluator 0x276E74 compares each record of a predicate's IncludedTopMenuLayers / ExcludedTopMenuLayers with [UIManager+0xF0] and each record of IncludedTopMenuLayerStates / ExcludedTopMenuLayerStates with [UIManager+0xF8].

There is no UI thread in a running game. The UI manager's constructor also creates a thread object named UIThread (0x1889460 -> 0x193856C; vt_0x22F94B8 with the shared thread methods, vf8 the name, vf9 the stub 0x19385BC, vf10 a 0x4000-byte stack), but it is never started: the shared thread start 0x529500 has 30 call sites, and each starts another object (the named threads of Threads and the frame, a Bink worker, a queue worker and two event workers on static objects). The object sits at UIManager+0x08. The UI's own console commands live in the source file CUIThread.cpp. The UI's per-frame work shows on the main thread instead: the HUD's CUIMgr::Render (0x1AB28C) runs inside the main scene's render submission, and captured movie frames reach the render thread through the main thread's command ring (below).

Views: the generic host and the factory

A view is a CUIBaseView (vt_0x1F7CD48, 0x60 bytes). The factory 0x1889250(&view, 0, &record, &index) allocates one and calls the constructor 0x18CAA38(view, record, index). The host is generic: it builds any record of Interface/GFX/UI.

View offset Meaning
+0x48 its layer (below)
+0x50 u8, set to 1 at construction
+0x54 u32 view index
+0x58 the Interface/GFX/UI record

Interface/GFX/UI has five records; a view record names a flash layer and the bundles it needs:

View ActiveFlashLayer RequiredBundle Built
NavMarkers _Structuresuiflash primary 1 (interface\flash\navmarkers.gfx) interface\bundles\gfx\ingame.bndl, ingame_uifx, ingame_navmarkers, pc_alternatbuttonstyles yes (index 0)
Player primary 2 (interface\flash\sp.gfx) the same four, plus five additional bundles (worlduidata, ingame_tex, store_icon ...) yes (index 1)
MessageBox primary 4 (interface\flash\messageboxes.gfx) interface\bundles\gfx\ingame.bndl yes (index 2)
PreLoad primary 3 (interface\flash\preload.gfx, the only layer with BackgroundAlpha 1) none no
Debug primary 0 (interface\flash\widgets\debugmenu.gfx) interface\bundles\gfx\debug.bndl no

0x188BAF0 walks Interface/Shared.UIViews, which ships as [NavMarkers, Player, MessageBox], and builds one view per entry into UIManager+0x360. PreLoad and Debug are never built.

Layer setup 0x18CACF4(layer, record, index) reads ActiveFlashLayer and refuses a layer whose _Structuresuiflash.File is empty; it stores the index at layer+0x30 and the flash record at layer+0x48. The view's methods then do the rest:

The engine advances only the views it owns in UIManager+0x360; a view built outside that list draws nothing until its vf2 is called.

The movie wrapper and the UI render list

The wrapper's +0x18 is the GFx movie and +0x38 its render ID. Its vf4 0x47D78F0 is one UI frame:

  1. movie->vf24 Advance(step, 2, 0), then movie->vf25 Capture(1), which returns the captured frame.
  2. 0x115226C posts a 0x28-byte command on the main thread's render command ring 0x272E8D8 (see Threads and the frame): {+0x00 thunk 0x65086C, +0x08 the UI render list, +0x10 method 0x650888, +0x18 u32 render ID, +0x20 frame}. On the render thread 0x650888 locks the list and stores the frame into the entry with that ID.
  3. 0x251E00(ring, 0), then 0x2AA3A8 runs the wrapper's callback queue at +0x3920.

The UI render list is a static object at 0x272EB20:

Offset Meaning
+0x10 / +0x18 vector of 0x18-byte entries, kept sorted by mode
+0x28 lock
Entry offset Meaning
+0x00 the render target (vt_0x22C6720)
+0x08 u32 mode bit
+0x0C u32 render ID
+0x10 the last captured frame

0x1C8DAF4(list, handle, mode) registers a movie: it takes the next ID from the counter at 0x27966A0, inserts an entry before the first one with a higher mode and returns the ID. Every movie has its own entry, so two movies of the same mode do not collide. In a loaded game the list holds NavMarkers (mode 1), the Player HUD (mode 2), MessageBox (mode 4) and two more movies with modes 8 and 0x10. The last captured frame stays on screen until the next capture. Scaleform draws the list through its own D3D11 renderer (see Rendering).

Message boxes

Interface/MessageBox holds 173 box definitions. A request is queued on the message-box manager at [CGameClientShell+0x890] (the request layout is in Saves, the store and the network layer); the caller's completion function goes into UIManager+0x248 / +0x250 and receives the button result.

Scaleform movies

Where they ship

The base game's movies are sections of three bundles in UI_Assets.arch06 (2,269 containers scanned, 19 movies found):

A running game holds 18 resident movies (see The executable), which matches the 17 of ingame plus the font movie. The patch archives also carry 56 loose copies of eight of them (24 distinct versions). The archive search order (see Archives and file formats) serves these copies:

Movie Library symbol Base copy: bytes / SWF version / exports Served copy
componentlibrary.gfx CompLibrary 183,424 / 8 / 197 Patch_DLC4: 204,515 / 8 / 246
sphud.gfx (none) 177,433 / 14 / 133 Patch_DLC4: 191,900 / 14 / 141
hierarchy.gfx HIELIB 47,651 / 17 / 31 Patch_09: 60,114 / 15 / 34
upgrades.gfx UPGRADESLIB 96,435 / 17 / 39 Patch_DLC5: 112,457 / 15 / 48
minimap.gfx MM-LIB 25,070 / 17 / 21 Patch_DLC4: 25,505 / 17 / 22
zonemap.gfx MAPLIB 10,747 / 17 / 15 Patch_DLC4: 13,765 / 17 / 15
messageboxes.gfx (none) 11,734 / 8 / 7 Patch_09: 17,906 / 8 / 10
wbid.gfx WBIDLib 17,176 / 8 / 16 Patch_09: 17,040 / 8 / 16
skills.gfx, swords.gfx, progression.gfx, strugglesequence.gfx, mainmenu.gfx SKILLSLIB, SWORDSLIB, PROGRESSLIB, StruggleLib, MainMenuLib base only
debugmenu.gfx (none) 27,142 / 9 / 33 (12 DBG- symbols, 21 textures) base only

All 18 interface movies, and the eight served patch copies, are uncompressed (GFX), have a 1280 x 720 stage (25,600 x 14,400 twips) and a single root frame, and run at 60 frames per second (30 for the minimap, the message boxes, navmarkers.gfx and sp.gfx). Fonts are imported from interface\fonts\gfxfontlib.swf ($HydraHUD); the message boxes import the component library, and the zone map imports MM-LIB from the minimap. No movie embeds a picture: all 231 images of the 18 base movies are Scaleform external images (DefineExternalImage2) named by their .tex path and resolved by the engine's texture system. Symbol names carry the movie's prefix: LIB- (component library), SPH- (HUD), HIE- (Army screen), MAP- (zone map), UPG- (upgrades), MM- (minimap), WBID-, DBG- (debug menu).

What the movies contain

The movies are ActionScript 2 skins with almost no code. A widget's timeline has the frame labels out, off, in and on; the off and on frames run stop(); if (this._FrameEventHandler) this._FrameEventHandler(_root.EFrameEvents_Off) (or _On), and the root defines EFrameEvents_None, _On, _Off, _FX and _Sound (0 to 4). Code selects a widget with gotoAndPlay("in") and deselects it with gotoAndPlay("out"); the widget animates itself and reports the end of the animation through the handler. Layout, lists, counts and text all come from code and the GameDB.

Driving a movie from code: the node API

The engine wraps every Scaleform value it touches in a 0xF8-byte node (vt_0x1DAB888, constructor 0x1C309C): +0x28 the value's object interface, +0x30 the value type ((type & 0x8F) == 0xA is a display object), +0x38 the object. Callers receive nodes through output handles that the callee constructs in place (0x1C2F80, the node at handle+0x10); a handle holds pointers to itself, so it cannot be moved once constructed, and its destructor 0x1C68FC releases the node.

Call Address Arguments
_root wrapper vf42 0x4679C4 (wrapper, out)
GetVariable 0x467A20 ([wrapper+0x18], value, "path"): resolves _root, not clips attached at run time
GetMember node vf83 0x1C2EE4 (node, out, "name")
GetMember by path node vf84 0x11C063C (node, out, "a.b.c")
createEmptyMovieClip node vf80 0x7B1EE4 (node, out, "name"): creates the clip; returns an empty handle when the name exists
attachMovie node vf76 0x474434 (node, out, "symbol", "instance"), both char*; refuses an existing instance name (0x47482C)
removeMovieClip node vf86 0xEBDAC (node)
Invoke without arguments node vf85 0x11C10B4 (node, "method"); the general invoke is 0x4676C0
play / stop node vf121 0xEBDE4 / vf122 0xEBE0C (node)
gotoAndPlay / gotoAndStop by label vf110 0x46F30C / vf112 0x471BE8 (node, "label")
gotoAndPlay / gotoAndStop by frame vf111 0x7DE75C / vf113 0x16D9B8 (node, frame)
setText vf103 0x471BFC (char*), vf102 0x471C84 (wchar_t*) (node, text)
setHTMLText vf106 0x11C156C (node, char*)
set the 2D matrix 0x128592C ([node+0x28], [node+0x38], matrix): rows {a, b, 0, tx}, {c, d, 0, ty}, translation in pixels (multiplied by 20.0 into twips)

Interface elements are found by name through Interface/Elements (0x32A774): an element record names its library symbol (SymbolName), an effect list and an Interface/ElementAttribute placement.

The attach-node lifetime rule

An attached clip lives exactly as long as the node attachMovie returned. attachMovie allocates the node, attaches the symbol (0x115BB34), then 0x4744EC sets the owner byte node+0x58 to 1 and registers an onUnload listener at node+0xA0 whose callback is 0x7941C4. The node destructor (vf0 0x1C3158 -> 0x1C318C) releases the listener and, while +0x58 is set, invokes removeMovieClip on the clip. If the clip is unloaded some other way (removed, or its parent removed), 0x7941C4 clears +0x58 first, so a clip is never removed twice. Lookup nodes (GetMember vf83 / vf84, _root) never set the owner byte.

So releasing an attach handle removes the clip, and a lookup handle for the same clip is a different node whose release leaves the clip alone. Code that keeps attached clips on screen must hold their attach handles (and release children before parents).

A menu is data: layers, trees of nodes, widgets, elements and their placements, all under Interface/.

Layer objects are instances of per-class engine types: the mode-select layer is vt_0x1F92AF0 (see Saves, the store and the network layer), the Army screen layer vt_0x1F7B588 (its job vt_0x1F7B748 at +0x9C8, run 0x189C4F8) and the captain detail layer vt_0x1F7CB10 (job vt_0x1F7CC18, run 0x18CA348).

The HUD

The HUD is the Player view. Interface/ElementSetup::Setup (108 fields) configures it: HUDBase -> Interface/HUD/Widget/HUDManager, five event managers (centre, left, right ...), the item widget, the mercenary widget, DebugHUDElements -> Interface/HUD/Widget/FXTest, the bar effects and fill rates, toast events and minimap thresholds.

The HUD builder 0x18C3E80 creates the widgets by name: combat (CUIHUDCombat), photo mode, combo, the event managers, the standard, keyboard, quick-time, centre and top prompt groups, combat hints, the pausing tutorial, pinned challenge, character and quest-start presentations, quest failure, brand notification, the crosshair (CUIHUDCrosshair), skill points, score and timer. Last it tests GameModes.UseMercenaryWidget on the record at [UIManager+0x430]: set, it builds the mercenary summon bar (CUIHUDMercenaryWidget, the Desolation of Mordor bodyguard slots); clear, the item widget.

HUD content is driven by states: Interface/HUD/Widget (28), Interface/HUD/WidgetList (20), Interface/HUD/State (44) and Interface/HUD/StateConfig (38). Each menu layer names the HUD state it wants (the Army screen: Interface/HUD/State/Hierarchy). Button prompts are data as well (see Items); the ammo counter and the reticle colour are written into the widget at [[UIManager+0x30]+0x108] by combat code (see Combat).

The Army screen

The hierarchy movie and the 3D stage

The Army screen is a 3D stage in a dedicated menu world with the hierarchy.gfx overlay on top. The movie draws only the 2D panels: its 22 HIE- symbols are the enemy overview, the trait card parts (HIE-TraitHeader, -TraitGroup, -TraitContent, -TraitDivider), dialogs (upgrade, target, cancel), quest start and complete panels, assault and defence groups, contract overview and the player icon. No symbol describes rows or seats: the captains, the seats and the fort are world objects. (The instance names Row0 - Row9 belong to the component library's control-bindings screen, and OverlordTree / WarchiefTree to the zone map's fort highlight.)

Faction/HierarchyMenu/Setup::Default (114 fields) configures the screen: the stage bundle bundles\hierarchymenu.bndl, the menu world collision prefabs\ui\worlduimenuws_collision.bndl, the henchmen, power-struggle and blood-brother link effects, the fidget bundle bundles\animationcharacters\menu\fidget.bndl (at most 20 fidgets at once), one camera state per mode (army, mercenary, orders, garrison and contract assignment, store, map, pause ...), the character model manager and 38 tutorials.

Each army's stage is chosen by its Faction/Definition.WorldUIFaction -> Faction/World/Faction (10 records): Instance names the stage rig (prefabs\ui\wuim_regionfaction_ws.bndl for five regions, wuim_minasfaction_ws for Minas Ithil, wuim_desert_dlc4_ws and wuim_dlc4merc_faction_ws for the Desolation of Mordor), FortPicker the fort pieces, and Member / GarrisonMember / VendettaMember the seat grids. The section is requested by the region's stage object in the menu world (vt_0x1FA4008, named Hier.Map.Region_*) when that object activates: its vf16 0x1A34E78 maps the record Region to the region's army, resolves the world section and requests it into the object's own group (0x4B420C), then links the object into the active-stage list 0x2675860. The activation runs when the menu world is rebuilt during a load, not on a visit to the Army screen.

Stage holders and the world-UI owner

The world-UI owner [base+0x26FFB60] (vt_0x1F97950; see Engine core) keeps the stage holders in a map at +0x2B8 keyed by army record: 0x50-byte entries with the holder at +0x48, looked up by 0x455EDC(owner, army). The armies of one region share one holder. A holder (vt_0x1FA2FB8):

Offset Meaning
+0x158 the replay event
+0x160 the holder's frame {position, rotation}, from which grid seats are placed
+0x198 / +0x1A0, +0x1A8 / +0x1B0 the stage characters: arrays of 0x28-byte entries, object at +0x20
+0x1B8 the summary container (its +0x9F50 is the army)
+0x1C0 the stage manager
+0x1E8 the fort (vt_0x1FA2698); +0x274 requests a fort build
+0x275 rebuild every stage character
+0x276 rebuild the rows

The holder tick 0x16EE24 (from the owner's per-frame Update, vf18 0x16F4A0) runs the fort reconciler 0x2EC464, which diffs the wanted fort pieces against the built ones; then, when +0x275 is set, prunes relationships (0x1B5E13C) and rebuilds every stage character from the summaries (0x1D7A918), writing the word +0x275 = 0x100 so that the row rebuild 0x1A284D8 follows on the next pass. The engine sets +0x275 itself when it sets a holder up for an army (0x1A29168, from 0x19C3760 and 0x19C3F60). With the menu closed an army has no stage objects; they are built fresh from the summaries when the screen opens.

Stage characters and member summaries

A captain on the stage is a CWorldUIFactionMember (vt_0x1F733C0, 0xB58 bytes, placement constructor 0x1822164), created through the object server by class name. It is built from the army's member summary, not from the FactionMember.

Stage character offset Meaning
+0xAC position of the animated root (the hip), on top of the seat
+0x110 the character model (vt_0x1F6F950)
+0x1A8, +0xAF8 the cast seat position
+0x668 the mount, for a mounted captain
+0x720 the previous look, kept while a new one is set up
+0x728 -> its private look (0x40 bytes, constructor 0x62DCB4) -> a private summary copy
+0x740 the preview states (Faction/HierarchyMenu/PreviewTypeStates)
+0x760 the model object
+0xB18 its world-UI type record (Faction/World/Character::FactionMember, FactionMember_WorldMap ...), not the member
+0xB3C set when the preview states must be rebuilt

A member summary (vt_0x1DB1070, 0x1A8 bytes) is one member's frozen description:

Offset Meaning
+0x30 a copy of the member's inventory (slot items, colours at +0xC8, overlays at +0xE0)
+0x40 a copy of the appearance (piece for piece like the live appearance object)
+0x48 the faction
+0x58 .. +0x60 a copy of the active traits
+0x70 a copy of the markers
+0x88 the key {u64 FM+0x178 army, u32 FM+0x174 pool index, u32 0}
+0x100 location
+0x120 level
+0x130 combat tree (FM+0x188)
+0x138 / +0x140 / +0x148 / +0x150 class, role, personality, tribe
+0x168 / +0x178 row, rarity
+0x188 the flag word (FM+0x508)
+0x190 bit 0: the name is known (FM+0x510 bit 0)

There are three generations of copies. The army context keeps one summary per member ([ctx+0x30], lookup 0x1B5C0F8(FS, &key) -> 0x16FC5C, a pure read); the open stage draws from its own container of copies (0x16FC1C(owner, army), none shared with the army's); and each stage character draws from a third, private copy behind +0x728. The engine's refills: 0x169168(summary, member) re-reads the whole member into a summary (appearance from 0x75D2FC, items from 0x75D314, class, tree, role, personality, tribe, level, flags, the trait list rebuilt from FM+0x198 and the markers from FM+0x1B0); its sources are the member's mock-member snapshot when one exists (see The Nemesis system). 0x62BF54 refills a container copy (or appends one for an unseated member).

The per-captain redraw is 0x19C395C(owner, &key), which the engine calls after a Training Order is applied (0x1C51630): holder 0x455EDC -> summary 0x1B5C0F8 -> stage character 0x1A28FAC(holder, key) -> UI_CWorldUICharacter 0x627F40(character, summary, 0, 0). Every step returns quietly when the army is not on a stage. 0x627F40 chooses the look (0x2EB2DC, below), and 0x62EEE4 swaps the new look in, rebinds the model and, only when the look test 0x170564 reports a change, tears down the selection and model objects, rebuilds them and sets +0xB3C. The look test does not compare the death bits (flag bits 2 and 21), so a change of state alone does not rebuild a captain's preview pose.

Under modification: a value written into an army summary reaches the open stage only when the screen is entered again, because the stage draws from its own copies; a stage character's private copy is deep (it owns its item instances), so refilling it in place frees items the model has bound.

Every frame while the screen is open, the owner's tick runs the character model manager (HierarchyCharacterModelMgr, ConfigSwapsPerFrame) -> 0x628BF8 -> the stage character's vf63 0x3680B0 (its summary) -> 0x30DE28, which walks the summary's trait list for a MountAITemplate.

Seats, placement and animation

A fort's composition is data: Faction/World/Faction.FortPicker -> Faction/World/Fort/Picker -> fort setups -> piece entries {faction predicate, Army-screen predicate, pieces} -> pieces {world sections, position, rotation, fixed seats}. The upper rows (Overlord, inner and outer warchiefs, the first captain row) sit on fixed seats (WorldUIFixedFactionMemberPos: a row and column, a position and a rotation), which any piece can register (0x629EEC -> 0x62A17C). The other rows are a grid (_StructuresWorldUI.FactionMember: RowGap, ColumnGap) placed from the holder's frame (0x1D79DF0). A seat is dropped onto the stage's collision by a ray, 0x62AA6C(seat, offset, out): on a hit the height becomes the hit point plus the offset, on a miss the seat keeps its data position. The stage character (its vf11) calls it through 0x62AA48 with a zero offset, so the character's animated root, the hip, is placed on the seat itself.

Stage idles are chosen by data and played by clip name. A preview state's HierarchyPreviewTypeAnimData.RootAnimChoice / RootInAnimChoice is a choice group; 0x42E72C takes the first child whose predicates pass (0x42E984 -> 0x42EAFC; inputs: the combat tree's CharacterType, the role, the personality and whether the name is known), 0x42E82C collects its Animation records, 0x42F794 picks one at random and 0x42F1D0 plays it by name (0x4C8A0C -> 0x302540: the name's hash looked up in the skeleton's animation set). A name the character's set lacks plays the bind pose. Fidgets fire only for tags the current choice carries.

The detail view plays the effects of Faction/World/Interactable/FX/States::FM_Army_Detail: its entries choose, by species predicate, an InteractableFXData whose FX names a ClientFX sequence (Hierarchy_Detail, Hierarchy_DetailOlag, Hierarchy_RiderDetail); 0x2ECF5C reads the link on every update.

Unknown captains: the silhouette

A captain whose name the player does not know is drawn as a generic model. The look chooser 0x2EB2DC takes, in order: the decapitation picker (DecapModelPicker, 0x2EB258 -> 0x2EB14C) when the summary's flag word has bits 17 and 21 set; else, when the stage character's type record has a DefaultModel and 0x170744(summary, flag) is false, that default model (0x2EA718); else the captain's own model (summary +0x138), built by 0x2EBA60 from the appearance copy.

0x170744 returns true when the member (found from the key at summary+0x88) has killed the player (FM+0x4C4 non-zero: such a captain is always shown); with the flag argument set, when a further predicate passes; otherwise it returns summary+0x190 bit 0, the copy of the name-known bit (see The Nemesis system for intel levels).

Faction/World/Character DefaultModel KilledByDecapitation
FactionMember, _Assignment, _GarrisonMember, _ModelCam, _Quest, _Stash, _WorldMap, WorldMap Character/Models/HierarchyUnknown Head_Stick
FactionMember_Portrait PortraitUnknown Head_Stick
GraveyardVendor HierarchyUnknown none
FactionMember_StoreFront, Talion, Mount and the other nine none none

The Army camera

The Army screen has two camera branches.

An orbit key (_StructuresUIOrbitPointParams) holds fov, OrbitRadius and Roll as plain floats of the key, and CameraOffset, CameraOffsetRange and CameraAdjust as shared float-pool pairs (type 7; see GameDB). CameraOffsetRange is the (yaw, pitch) freedom of the key in degrees: the overview's two keys 340 and 341 carry (24, 12) at radius 1,025 and (16, 10) at radius 5,250, both with fov 25. A key also has a node offset (orbit and focal offsets with position and rotation tracking masks) and a DepthOfField link to Client/DepthOfFieldSettings (near and far in-focus and blurred distances, blur intensities). The reader 0x527A18 copies all of it into the camera when the state starts, so a key edited while its state runs takes effect the next time the state starts. WorldMapCameraMode is 4 on every Army camera state except two: CS_Store_ProductDisplay_Cursor (5) and CS_Mercenary_Cursor (6).

The title screen, save select and loading screens

The title screen and its message of the day

The title screen's root layer is Interface/Menu/MenuLayer/FrontEndRoot::FrontEnd (class CUIFrontEndRootLayer): content symbol LIB-MenuList, the selection tree FrontEndRoot, a build version widget, the new-game layer list NewGameSetup_Full, and a message-of-the-day node Interface/Menu/MenuTreeNode/StoreMOTD/Default (cooldown colour, size and a 10-second time). The message's text comes from the online service's broadcast feed, not from the localization tables. Interface/Shared names a front-end world, worlds\reference\frontend\frontend.world06c, which does not ship, and a background bundle with two images cross-faded every 10 seconds.

The skin choice is a tree node too: Interface/Menu/MenuTreeNode/Skin::Skin_DarkTalion names the character model PlayerSkin_DarkTalion_Main and stores its unlock, notification and viewed state in Profile/GlobalOptions.

Save select

The save-select screen is the mode-select layer Interface/Menu/MenuLayer/GameModeSelect::ModeSelect. Its row state, availability test, the Continue / New Game / Delete actions and the confirmation box are in Saves, the store and the network layer; a new game pushes the SingleOption::Difficulty layer.

Loading screens and Production Notes

A loading screen is an Interface/LoadingScreen record (27): Allowed, Platform, a GameData predicate, completed / incomplete quest gates, required game modes and world, a fast-travel state, background slides with cycle and fade times, a bundle, an optional movie, portrait settings, valid tips and enter / exit audio hooks.

CUILoadingScreen::CUILoadingScreen (0x18D2024) looks up the elements LoadingBG, LoadingBGCaption and LoadingProgressBar. Then, only when the byte 0x2A2637D is set (one reader, no writer in the executable), it finds the element ProductionNotes (symbol LIB-ProdNotes in the component library), gets its child Field and writes Monolith's notes into it: Debug/Shared::Debug.DebugMOTD (a "Project Timeline" header) followed by every DebugMOTDDates entry (DisplayString plus DateString through the month-name formatter 0x18D2608), separated by <br>. The five shipped dates run from the fourth DLC's release to the fifth DLC's bug lockdown (May to June 2018). In the retail game the byte is never set, so the notes never appear.

The Monolith debug menu

What shipped

The controller that built and ran Monolith's developer menu was compiled out; everything around it shipped.

Piece Where Content
The movie debugmenu.gfx in interface\bundles\debug.embb (UI_Assets; the bundle also holds the controller glyph textures and interface\textures\debug\debugsheet.tex) title "Monolith / Debug Menu" in the $Console font; 12 widgets: DBG-MenuRoot, -MenuButton, -ItemsWithDesc, -ConsoleCommands, -CheatEntry, -LevelList, -LevelEntry, -ScriptHookEntry, -ScriptHookPrompt, -ProdNotes, -Prompts, -Loading
The view Interface/GFX/UI::Debug the movie and debug.bndl
The root Debug/Shared::Debug the command set Profile/CommandSets/DebugMenu, enter / exit sounds, the Production Notes text
Console pages Debug/ConvarShortcut 34 pages (QA, AAA, HDR, Camera, Combat, Inventory, Presentation, AI info, nav mesh, nodes, markup and world review, game speed, frame stepping ...), 530 items, each a title and a console macro
Cheats Interface/Cheat (14) and Interface/Cheat/Order (9 shown) God, Health, Clip, RemoveAI, SafeZone, Invisible, quest unlock / complete / teleport, CrimeRemove, AllAchievements, DSDebugCam ...
Captain actions Debug/FactionMember::Default 20 actions (level up / down, decapitate, set as follower / spy / dead / prisoner / revenge target, get intel, swap positions, eject, hire mercenary ...), each a faction event
Script hooks Debug/DebugScript (135), Debug/LevelScriptType (14), Debug/MetadataType (94) names and parent links
Layout Interface/ElementAttribute::DebugMenu_Root, _Active, _Inactive, _SubLayer, _PromptBar; buttons DebugMenu_BaseBtn, _LevelListBtn, _ConsoleCommand, _ShowcaseBtn, _ScriptHookEntry; MenuTransition::Debug header at the top right (1.0, 0.0), the active page at (0.05, 0.12), the prompt bar at (0.5, 0.98); inactive pages turn 60 degrees about Y, so pages flip like cards
Levels WorldInfo.InDebugMenu 11 worlds; six *_debug worlds exist as records

The widgets are the self-animating skins described above (DBG-MenuButton: out 0, off 9, in 10, on 14). DBG-ConsoleCommands is a page list with an 18-row sub-menu and a grey command preview; DBG-ItemsWithDesc a list with a description panel; DBG-CheatEntry a row with a checkbox.

The shell that still runs

The open / close logic is intact behind the byte 0x2A2636D, which has four readers and no writer:

All four act on [UIManager+0x88] and do nothing while the byte is clear. SetDebugMenuShown pushes Profile/CommandSets/DebugMenu on the input stack (0x1170D44; DisableAllOtherCommands with 26 menu commands enabled, so gameplay input is blocked), plays EnterMenuSFX and sets the open byte [[UIManager]+0x88]+0x7AE; hiding pops the set (0x1170D34) and plays ExitMenuSFX. The Army screen action pushes the set and sets the byte without a sound. While the byte is set, IsDebugMenuOpen 0x381EEC makes the renderer 0x382044 call 0x381E68, which draws a full-screen black quad at alpha 0xDD over the world and the HUD, and the gameplay-input test 0x17B91EC fails.

The input data is live too: Interface/Shared.DebugCommandSet (DebugMenuActivate_Hydra) is pushed at every game start (0x1780A04), and Profile/Commands binds DebugMenuActivate1-4 (held together: Home or 9, End or 0, Left and Right; on a pad L3, Start and D-pad left and right). The input registration 0x3123CC gives the debug commands their IDs (DebugMenuActivate1-4 0xC8-0xCB, spectator and God shortcuts 0xCC / 0xCD, AI info next / last 0xCE / 0xCF, debug spawn modes 0xD0-0xDB, captain list / save / load 0xDC-0xDF, the debug camera 0xBC-0xC7).

What was stripped

  1. The view is never built: Debug is not in Interface/Shared.UIViews, so debug.bndl and the movie are never loaded.
  2. No code names the menu's records: DebugMenu_, DBG-, Debug_Menu, ScriptHook, ProdNotes and Showcase occur nowhere in the executable as strings (controls in the same scan: NavMarkers 12, MessageBox 25, HierarchyLayer 4, FrontEndRoot 5), and the component, attribute and view records have no inbound RecordLinks.
  3. The page data has no reader: the fields ConvarShortcut.Key / Title / String / ShowInDebugMenu / CloseMenuOnCommand, Interface/Cheat.CheatConsoleString, WorldInfo.InDebugMenu, Debug/FactionMember.Actions and DebugScript.Parent have no accessor, while Debug/Shared.DebugMenuCommandSet on the same record does (0x7B5A0C).
  4. Nothing opens it from input: the open functions have no caller besides the script binding, the console registry and the Army screen action, and the DebugMenuActivate commands are referenced only by their registrar.
  5. Most page commands are not in the retail console: the 530 items expand to 1,711 console commands; 85 name a live command, 18 a handler compiled down to ret 0, and 1,608 a name registered nowhere.

The only shipped script that touches the menu is the Bright Lord finale, which prints "Finale Part 4 Triggered" and calls System:ShowDebugMenu(false), the call the menu's Script Hook page made after firing a script.

The cheat backend

The cheat manager is [GameManager+0x6CA8]: a vector of 0x28-byte entries (+0x10 id, +0x18 the Interface/Cheat record, +0x20 toggleable, +0x21 on), built by 0x180EF9C, with per-id allowed flags (0x65B0E4). The retail game registers one entry, God. The names table 0x22F55A8 reads GOD, HEALTH, CLIP, REMOVEAI, SLOWMO (ids 0 to 4).

The Interface/Cheat console strings (Cheat_God ...) are not registered in the retail console. The captain actions are faction events fired by CreateAndProcessFactionEvent 0x1B5B610(FS, event, member, other) on the Army screen's highlighted member, [[base+0x26FFB10]+0xA78] (see The Nemesis system).

The console it drives

ExecuteCommand is the engine interface's vf29 (0x1180D8C): it hands the line to 0x1182FA4, which looks the name up in the console registry (Find, see The executable) and runs the command synchronously on the calling thread, splitting ;-separated commands. A name value line whose name is not registered creates a new settings variable (0x1182F10) instead of failing. Of the registered commands, 98 of 449 have handlers compiled down to ret 0.

The console's output goes nowhere. Command output and the engine log both end in the sink 0x6AAC14, which under the lock 0x27C45A0 calls the function pointer [base+0x27C45C8] if set, then writes to a stream if the byte 0x27C45D1 is set. In the retail game the pointer is null (written only by its static initialiser 0x585A0 and the exit path 0x1196AF4) and the byte has one reader and no writer.

The debug camera these pages drive is described in Characters, the player and the camera.

Tutorials

Tutorials are data. Four families of records drive them:

Family Records What a record holds
Interface/Tutorial/Pausing 116 a modal pane set that pauses play: HUDState (Interface/HUD/State.PausingTutorial), HeaderString, DismissString, Panes (348 _StructuresTutorialPausingPaneData structures in all: BodyString, image, or video, an Interface/Movies record), its own Bundle (interface\bundles\kraken\tutorials\*.bndl), the input CommandSet that holds play (Profile/CommandSets.TutorialModal_A), InputLockout (0.5 s), RequireAllPressed, HoldAndPress, InputHoldDuration and enter / exit sounds
Interface/Tutorial/Gameplay 120 an in-play hint: DisplayString, duration, sounds
Interface/Tutorial/Menu 82 a menu tutorial: a Setup (screen position) and OnEnter, OnExitBegin, OnExit steps (253 _StructuresTutorial_MenuStep)
Faction/HierarchyTutorial 62 an Army-screen tutorial: a predicate, ExitMenuOnFinished and FirstState, a chain of Faction/HierarchyTutorial/States (84)

A menu step applies or removes an input command set (HierarchyTutorial_A, HierarchyTutorial_LStickX ...), enables or blocks mouse movement and fast travel, advances on the common input, and may change the menu around it through three record kinds: WidgetData (18: Show_ / Hide_ a widget such as the fort status header or the hierarchy overview), TreeData (24: restrict a menu tree to some nodes, ChallengesRestrict, GemSocketsRestrict ..., or clear the restriction) and MenuSelectionData (8: lists of Interface/Menu/MenuSelection/Predicate records to disable, for example every order on a captain during the Bruz ambush tutorials). Layer data adds per-screen settings (ShowMemberNavMarker / HideMemberNavMarker on the Army screen, SelectMtnPass on the quest log).

An Army-screen state names the menu tutorial to show, an optional map presentation, a preset or job member or the mercenary entry point to select, the view, a camera transition to wait for (with a command set to apply meanwhile), PopupDelay (1.0 s in PlayerDiedFirstTime_State01), DelayNextState and Next, and closing conditions (CloseOnPositionSelected, CloseOnNewEquipmentHighlighted). The tutorials' predicates live in Faction/HierarchyTutorial/Preadicates (83 records, 73 fields; the category's name carries the misspelling). TutorialViwed links a Combat/Predicates/Bool record (has this tutorial been seen) and AnyTutorialsViwed, AllTutorialsViwed and NotTutorialsViwed test other tutorials; the rest test the Army screen's mode and state (normal, replay, interrogation, commanding, orders, assault setup; overview or details), the selected captain's known state (known, location known, full info, presumed dead, branded, decapitated, henchman ...), the active game mode, inventory and new-item counts, skill points and store counts.

From script. TutorialPausingShow / TutorialPausingHide and TutorialGameplayShow / TutorialGameplayHide are methods of the CCharacter script class (0x1BB2108 resolves the Interface/Tutorial/Pausing record and calls the character's vtable slot 99); the player raises OnTutorialPausingDismissed / OnTutorialGameplayDismissed. System:GetHierarchyTutorialViewed (0x1B86BA4) checks a Faction/HierarchyTutorial record against the viewed list reached through the player ([CGameClientShell]+0x888, with its byte +0x3880; the test is 0x30C46C). The HUD classes are CUIHUDPausingTutorial and CUIHUDGameplayTutorial; the Army-screen tutorials are run by the hierarchy menu's tutorial manager (HierarchyMenuTutorialMgr.cpp), and the console names a HierarchyMenu.ResetTutorials command.

Open questions

Shadow of War (Steam), game version 1.21. An unofficial fan research project, not affiliated with or endorsed by Warner Bros. Games, Monolith Productions or Middle-earth Enterprises. Middle-earth: Shadow of War belongs to its owners.