GUI: widgets, sprite sheets and text¶
Verified against: SLUS_212.15 (NTSC-U, SHA1 e9cb2cc49aa046b9e494313dce2f5038ed17b2f4). Runtime claims (sprite
colours, draw-order keys) were read from the running game in PCSX2 2.9.94 over PINE and say so. The disc-side checks
(2026-10-04) walked every chunk container of the NTSC-U WAD and are reported as counts and layouts only.
Purpose¶
How the front end and the HUD put 2D things on the screen: the widget classes, the screen flow that chains menu
screens, the sprite sheets (chunk 0x4C, "Particle Page") every 2D image comes from, how a sprite reaches a
RenderWare PTank, how text is laid out and drawn as one sprite per character, where the UI strings come from, and the
order in which it is all drawn. Start-up and the front end says which screens run when; the screen
geometry (GUI coordinates, the overlay camera) is on Graphics; what the in-game HUD shows
and where is on The in-game HUD.
In short: menus are C++ widgets, not data. A screen's layout, items and transitions are in its constructor and
Init; the data it uses is a sprite sheet resource (named by a hash), global UI strings set from Lua, and markup tags
inside those strings. Every image, glyph and icon is a rectangle of one texture in a sprite sheet, drawn as a PTank
particle.
Original structure¶
The GUI is c:/Warriors/Source/GUI/ (0x001a1f10-0x002176b8, Source map), with the
profile-manager screens in GUI/ProfileManagementGUI/ and the Rumble-mode screens in GUI/RumbleModeGUI/. The sprite
sheets belong to Graphics/ParticlePage.cpp (0x00181b68-0x00182820) and the resource manager
(Graphics/ResourceMgr.cpp). Names are ours unless they are class strings passed to the allocator.
| Address | Name | Role | Evidence |
|---|---|---|---|
0x001a8e30 |
Widget::Widget |
base constructor of every widget | confirmed (code) |
0x001a1bf8 |
BaseWidget::BaseWidget |
a sprite widget (0x100 bytes) |
confirmed (code) |
0x001ccf88 |
TextWidget::TextWidget |
a text widget (0xd0 bytes); 0x001cd1e0 sets its text |
confirmed (code) |
0x001b9600 |
TextWidget_Layout(out, widget, draw, colour, ...) |
measures and draws marked-up text | confirmed (code) |
0x001d3ef0 |
OptionGrid::OptionGrid |
a grid of selectable items (vtable 0x0053b768) |
confirmed (code) |
0x001d4110, 0x001d4230 |
OptionGrid setup, add item |
at most 50 items | confirmed (code) |
0x001cea70 |
UsageInfo::UsageInfo |
the button legend line (0x2d0 bytes); 0x001cec28 sets its text |
confirmed (code) |
0x001b8f98 |
markup text widget (class MessageHUD) |
marked-up text in a box; setup 0x001b9090 |
confirmed (code) |
0x001a0fd0 |
Bar |
a two-sprite meter (PM_Light, the Rumble gang screen) | confirmed (code) |
0x001e1338 |
ScrollingMenu |
the Rumble screens' scrolling lists | confirmed (code) |
0x0017ae38 |
colour table set-up | fills the colour table at 0x005fd260 |
confirmed (code) |
0x001c7e80 |
ScreenFlowController::ScreenFlowController |
stack of screens (SFC_States, SFC_SharedData) |
confirmed (code) |
0x001c8010, 0x001c80e8, 0x001c82e8, 0x001c81e8, 0x001c83c8 |
add transition, push, pop, unwind, update | confirmed (code) | |
0x001c6b78... |
message box (0x005e5840) |
timed messages and two-choice dialogs | confirmed (code); file unnamed |
0x001e95c0 |
MenuInput_Dispatch |
pad and stick to menu commands (Front end) | confirmed (code) |
0x0019ee70 / 0x0019eea0 |
GlobalString_Get(id) / _Set(id, text) |
the UI string table | confirmed (code) |
0x00181b20 |
chunk 0x4C onLoaded |
links a sprite sheet to its texture dictionary | confirmed (code) |
0x00182820 |
chunk 0x4D onLoaded |
the table of all sprite sheets | confirmed (code) |
0x00181e38 |
Page_Rect(instanceData, i) |
rectangle i of a sheet |
confirmed (code) |
0x0018aec0 |
ResourceMgr_CreateInstance(...) |
a sprite batch over a sheet | confirmed (code) |
0x001972b0 |
Instance_SetResident(inst, on) |
loads the sheet, creates the PTank | confirmed (code) |
0x00182de0 |
Instance_AddSprite(inst, sprite) |
appends one sprite to the batch | confirmed (code) |
0x00197168 |
Instance_Render(inst, viewport) |
draws the batch's PTank | confirmed (code) |
0x00185d20 |
ResourceMgr_RenderOverlay |
the 2D pass (Draw order) | confirmed (code) |
0x001c3de0, 0x001c4d00, 0x001c4768 |
radar: set up, add a blip, set a blip's icon | The radar | confirmed (code) |
0x001b4168 |
HUD_RadarAddHuman |
the blip a human gets from its class | confirmed (code) |
0x00179808, 0x00179958, 0x00179c30 |
Font_Size(scale), Font_Measure, Font_Draw |
text as sprites | confirmed (code); file inferred (Graphics/, before the stub 0x0017b1c0) |
Data¶
Sprite sheet: chunk 0x4C "Particle Page"¶
A sprite sheet is a resource of two chunks: a 0x2A RenderWare texture dictionary holding exactly one texture,
then a 0x4C chunk listing rectangles of that texture. Little-endian, 16-byte aligned:
struct ParticlePage { // chunk 0x4C, size = align16(0x14 + 16 * count)
uint32_t placeholder; // +0x00 0x00421798 in every page (a pointer from the build); not read
uint32_t count; // +0x04 number of rectangles, 1..371 seen
int32_t firstGlyph; // +0x08 rectangle of character 0 when the sheet is used as a font; -1 otherwise
uint32_t rects; // +0x0c 0 in the file; set on load to the address of +0x14
uint32_t texDictionary; // +0x10 0 in the file; set on load to the dictionary read just before
struct { float u0, v0, u1, v1; } rect[count]; // +0x14 texture coordinates, top-left and bottom-right
// zero padding to a multiple of 16
};
Evidence: confirmed (code) for +0x04 (the count, 0x001828b0), +0x08 (0x00179958, 0x00179c30), +0x0c
and +0x10 (0x00181b20) and the 16-byte rectangles (0x00181e38). The onLoaded handler pops the 0x4C chunk and
the 0x0B dictionary before it, stores the dictionary at +0x10 and &rect[0] at +0x0c, and pushes the chunk back
as 0x4C. Disc check (corroboration): all 1,335 0x4C chunks have exactly that size, zeros at +0x0c and
+0x10, a 0x2A chunk with one texture just before them, and every coordinate in [0, 1] with u1 >= u0 and
v1 >= v0. firstGlyph is -1 in 1,328 pages; the 7 others are fonts or carry a font (big_font 0, part_page0
94, part_page1 20 among them).
The rectangles are inset by a quarter texel. Disc check (2026-10-06, the 1,547 rectangles of the 576 sheet-table
records with a texture): every u0 is a quarter texel past a texel edge and every u1 a quarter texel short of one,
and v0, v1 the same distance in texture coordinates (a quarter texel of the width: an eighth of a texel down a
512 × 256 texture). So the sprite is the whole texels from floor(u0 × width) to ceil(u1 × width), and a radar
icon listed 12.5 texels wide is 13; the inset keeps bilinear filtering from reaching the neighbouring sprite
(inferred). Coney's reference images cut sprites this way (graphics::rectTexels, src/graphics/reference_sprites.h).
How a sprite refers to its texture: by sheet and rectangle index only. The sheet's texture is the first (only)
texture of its dictionary (instance + 0x14 = the dictionary's texture list head − 8, 0x00181b68); its raster's
width (+0x0c) and height (+0x10) turn a rectangle into pixels (0x00181e18, 0x00181e28).
Sprite sheet table: chunk 0x4D "Particle Page Header"¶
One chunk in warriors.glr: u32 count, then count × {u32 size, u32 nameHash}. The onLoaded handler
(0x00182820) keeps the chunk at ResourceManager + 0x9040, the count at +0x9048 and the records at +0x9044.
0x001828c0(rm, i) returns record i; 0x00181e50(rm, hash) returns the size for a name hash (or, for a sheet not in
the table, the size of the WAD file named "%u" of the hash). Confirmed (code). The name hash is the CRC-32 of the
sheet's name, the same number that names its WAD file in the decimal block
(WAD contents).
Disc check (corroboration): 576 records; size is the resource's size in bytes. Names matched so far: 0
part_page0, 1 part_page1, 3 menu_system (the menu sprites), 7 part_fire, 8 lighting, 10
hud_minigames, 13 big_font, 51 legal_screen, 530 part_fog_00 and 531 part_fog_01
(the 3D fog's wisps).
Resource instances (sprite batches)¶
The resource manager has 255 instance slots of 0x78 bytes at ResourceManager + 0x189c; an instance is one PTank
(a batch of sprites) over one sheet. ResourceMgr_CreateInstance (0x0018aec0) takes the next free slot after the
last one used (round robin; 255 = none free) and fills it (0x001828e0). Confirmed (code):
| Offset | Meaning |
|---|---|
+0x00 |
sheet name hash |
+0x04 |
the PTank atomic (once resident) |
+0x08 |
the atomic's frame |
+0x0c |
the sheet's page data (once resident) |
+0x10 / +0x11 |
in use / resident |
+0x18 |
capacity: most sprites per frame |
+0x1c |
sprites added this frame |
+0x20 |
the float passed first at creation ("depth": 8,000 to 11,000 seen) |
+0x28 |
PTank data flags: 0x10000082 plus 0x05 (format 0), 0x25 (format 1) or 0x08 (format 2) |
+0x30, +0x34 |
source and destination blend: 5 and 6 (source alpha, inverse source alpha) |
+0x38 |
format: 0 position and size, 1 adds a 2D rotation, 2 a full matrix |
+0x3c |
where the atomic lives: 0 drawn directly by its owner, 1 the 3D overlay world (ResourceManager + 0x9038), 2 the 2D overlay world (+0x9034) |
+0x42 |
viewport, 0xff = all |
+0x44 / +0x48 |
the PTank's position array and its stride (16) |
+0x4c / +0x50 |
colour array and stride (4) |
+0x54 / +0x58 |
texture-rectangle array and stride (16) |
+0x6c / +0x70 |
size array and stride (8) |
+0x74 |
the largest sprite count seen |
The array rows are confirmed (code) at the accessors 0x00182d80-0x00182db8 and confirmed (runtime), PCSX2
2.9.94: the slots sit at ResourceManager + 0x189c (0x01fd009c on a retail boot), and on the main menu slot 3
(part_page0) has capacity 1,024, slot 6 (big_font) 2,048, both with format 0 and +0x3c = 2. The arrays keep
the last frame's sprites after the count at +0x1c is reset, which is how they were read.
The PTank flag names, inferred from RenderWare 3.7's rpPTANKDFLAG* values: 0x10000000 array, 0x80 two texture
coordinates per sprite, 0x02 colour, 0x01 position, 0x04 size, 0x20 2D rotation, 0x08 matrix.
Instances created at start-up by the resource manager (0x00184918), in slot order 0-12 (the slot numbers assume the
first slot used is 0, inferred): part_page1, part_page1, part_page0, part_page0, part_page1, part_page0,
big_font (slot 6), lighting, lighting, part_page1, part_page1, hud_minigames, part_fire. Text code
refers to fonts by slot (Text).
Sprite record¶
What Instance_AddSprite (0x00182de0) copies into the PTank. Confirmed (code):
| Offset | Size | Used when | Meaning |
|---|---|---|---|
+0x00 |
64 | format 2 | a 4 × 4 matrix |
+0x40 |
16 | format 0, 1 | position (x, y, z, 1) in overlay-camera space |
+0x50 |
8 | format 0, 1 | size (w, h) |
+0x60 |
4 | format 1 | rotation |
+0x64 |
16 | always | texture rectangle u0, v0, u1, v1 (a sheet rectangle) |
+0x74 |
4 | always | colour, RwRGBA (R in the low byte) |
A sprite past the capacity is dropped (the call returns 0). An instance that is not in use or not resident ignores sprites.
Sprite colours¶
Sprite colours are RenderWare's 0-255, with 255 full intensity and opaque, not the GS's 128. Confirmed
(runtime), PCSX2 2.9.94, from the colour arrays of the batches on the main menu: the big_font text is (178, 178,
178, 255) for the selected item and (170, 43, 43, 255) for the others, each preceded by its drop shadow (0, 0,
0, 128); the Quick Rumble background picture is (93, 106, 49, 254). The legal screen draws with (255, 255, 255,
255) (confirmed (code) at 0x0015a0b8, Graphics). The shadow's alpha × 128 / 255 is
therefore a half-transparent shadow (128 of 255 for a fully opaque widget), not a conversion to the GS range. On
PCSX2's screen the grey and the red text come out at the same 70 % of these values, as does the legal picture's white
at full colour, which fits one scale for all three (the cause of the 70 % is open,
Graphics).
The colour table¶
The menus take their colours from a table of RwRGBA at 0x005fd260, 8 bytes apart, filled once by 0x0017ae38.
Confirmed (code); the three menu colours also confirmed (runtime), Sprite colours:
| Address | RGBA | Address | RGBA |
|---|---|---|---|
0x005fd260 |
black | 0x005fd2c8 |
(192, 96, 0, 255) |
0x005fd268 |
white | 0x005fd2d0 |
(0, 128, 128, 128) |
0x005fd270 |
(127, 127, 127) | 0x005fd2d8 |
(128, 128, 0, 255) |
0x005fd278-0x005fd2a0 |
red, green, blue, cyan, magenta, yellow | 0x005fd2e0-0x005fd2f8 |
(32, 0, 0, 48), (4, 4, 4, 140), (0, 0, 32, 64), (10, 5, 40, 60) |
0x005fd2a8-0x005fd2c0 |
(255, 128, 0), (128, 0, 0), (0, 0, 128), (0, 128, 0), alpha 255 | 0x005fd300 / 0x005fd308 |
(0, 0, 0, 0) / (36, 75, 130, 255) |
0x005fd310 |
(178, 178, 178, 255): usage lines, Rumble titles, item text base | 0x005fd318 |
(178, 178, 178, 255): the selected grid item |
0x005fd320 |
(80, 80, 80, 255): message-box and Game Type items | 0x005fd328 |
(170, 43, 43, 255): the front end's red (titles, items, the logo's tint, bar fill) |
Widgets¶
Every screen element derives from one base (0x001a8e30). A widget object has its vtable at +0x00, a second
interface vtable at +0x6c, and, for a menu screen, a screen-flow state at +0x70 (its own vtable at +0x88, its
transition map at +0x78). Confirmed (code) at the constructors 0x002077b8, 0x00209bc0, 0x001d3ef0.
Widget vtable slots (GCC 2 layout: a slot is an 8-byte {s16 delta, fn} pair, the call adds the delta at
vtable + slot to the object and calls the function at vtable + slot + 4; offsets from the vtable start), from
the screens read here; confirmed (code) for the calls, names inferred:
| Slot | Role |
|---|---|
+0x30 |
Update: input and animation; a screen writes its result code at +0x74 |
+0x38 |
Render: add the widget's sprites |
+0x48 |
is visible |
+0x60 |
destructor |
+0x68 |
Shutdown: release children and resources; in BaseWidget and TextWidget this slot is the setup with a rectangle, size, colour and flags, and BaseWidget's shutdown is +0x70 |
+0x70 |
the widget's rectangle |
+0x88 |
is active |
+0x90 |
take focus (OptionGrid: 0x001d4d28) |
+0x98 |
drop focus |
+0xa8 |
Init: create children, once (+0x0c marks it done) |
+0xb0 |
how much of the text to show (0-1; text reveal) |
Screen-flow state vtable (at widget +0x88): +0x10 Enter(flow), +0x18 Update() → result, +0x20 Exit().
For PM_Greet (0x0053e050): Enter (0x00207d48) resets the pad handlers and calls Init; Update
(0x00207dd0) sets the result to "stay" (-0x100), calls the widget's Update and Render and returns the result;
Exit (0x00207da0) calls Shutdown. Confirmed (code).
The widget classes¶
The classes the menus are built from. Confirmed (code) at the addresses given; the screens that use them are on Front end.
BaseWidget (0x001a1bf8, vtable 0x005394b8), one sprite. Setup (slot +0x68, 0x001a1db8): (size, depth,
widget, position, colour, visible, active, spriteWord, instance, anchor, aspectFix, ...).
- Sprite word (
+0xc8): sheet-table record in the high half, rectangle in the low half (0x30000ismenu_systemrectangle 0). Instance+0xc4; -1 makes its own at the given depth. - Size:
0x001a2120(size, 0, widget, 0)sets width = height =sizein overlay units (not GUI units: 1.1 is the screen's height, 1.595 its width in the default mode, Graphics); each update keeps the height and sets the width to height × the rectangle's pixel aspect ((u1 − u0) × texW / ((v1 − v0) × texH)), times*0x0050b208× 0.80357 more whenaspectFix(+0xe0) is set. - Anchor (
+0xc0): 0 the centre is at the position, 1 the left edge (the sprite moves right by half its width), 2 the right edge; applied inRender(0x001a2690) only. - Render: colour
+0xb4; an optional timed fade, alpha = byte+0xf8× (end+0xf4− now) / duration+0xf0; an optional shadow record (+0xec, none after setup) at the position + (0.0025, 0.004), without the anchor shift, alpha × 0.502.
TextWidget (0x001ccf88, 0xd0 bytes, vtable 0x0053ae78), one line without markup, used by grid items and the
PM titles. Setup (slot +0x68, 0x001cd060): (widget, position, metrics, colour, visible, active, mode3D, flags,
fontSlot). Fields: text +0x04 (set by 0x001cd1e0, which also measures it), visible / active +0x50 / +0x54,
colour +0x5c, position +0x60, Font_Draw flags +0x70 (4 proportional, 1 right, 2 centred), metrics +0x78
(Font_Size of the given scale), font slot +0xa0 as given, measured width +0xb0, reveal +0xc0 (1.0), shadow
byte +0xc4 = 0x80. Render (0x001cd288) is one Font_Draw at the position: the pen starts at x and the glyphs
are centred on y. Confirmed (runtime): text placed at y 0.81 has its capitals centred at 0.811.
The markup text widget (0x001b8f98, vtable 0x0053a248, layout 0x001b9600), used by UsageInfo, the
hint texts and message boxes. Setup 0x001b9090(sizeScale, fontScale, widget, position, colour, visible, active, ?,
fontSlot): the font slot is 6 if 6 is passed, otherwise 3 (part_page0; the constructor's default is 3); base
size +0x1d0 (0x001b9288); shadow byte +0x18c = 0x80; box right limit +0x184 = 10,000; proportional
+0x180 = 1; alignment +0x17c (0x001b9478): 0 left, 1 right, 2 centred.
UsageInfo (0x001cea70, 0x2d0 bytes): a markup text widget at +0x40, size 1.0, colour 0x005fd310, font slot
3. 0x001ceb40(widget, position, leftAlign) sets it up, left-aligned when leftAlign ≠ 0, centred otherwise
(the PM screens pass 1, the Rumble screens 0); its first text is string 0x1a, then the owner's (0x001cec28, at
most 0x95 bytes).
OptionGrid (0x001d3ef0, vtable 0x0053b768: update 0x001d4f20, render 0x001d52c8, is ready (slot +0x88) 0x001d4418,
focus 0x001d4d28, unfocus 0x001d4db8; input interface 0x0053b740, handler 0x001d4c40):
- Setup
0x001d4110(y, grid, rows, a2, owner): position (0, 0, y, 1); up to 5 items-per-row counts at+0xb0fromrows; the owner (+0x80) sees every command first; the HUD player 0's input record (+0x50); centre x+0x90= 0.5; move cue+0x94= 4; wrap+0x98= 1;+0x9c= 0 (left and right walk all items); row gap+0xa0= 0; selected+0xa4= 0. Owners then set+0x8c(left x),+0x88= 1 (left-packed) and their own cue. - Add item
0x001d4230(fontScale, grid, text, hasSeparator, code, flags, enabled, colour, fontSlot, separatorAlphaMode), at most 50: anOptionGridTextWidget(0x200bytes,0x001d5350, vtable0x0053b8c8, setup0x001d5450) in anOptionGridItem(0x60bytes,0x001d3bb0) holding the code at+0x54(read by0x001d43f0; the selected index by0x001d43e8). The text is drawn in0x005fd310's alpha with the colour below; withhasSeparatora second text," : "(0x00555f98), in font slot 3 and the given colour, follows it. The item's box is its width (plus the separator's) byh + lineGap= 6h / 7 ofFont_Size(fontScale). - Layout (each update): rows in order; with
+0x88= 1 items are packed left to right from+0x8cwith no gap (0x001b5e60), otherwise each row is centred on+0x90(0x001b5dd8); the row y starts at+0x28and moves by the item height plus+0xa0. A grid of fewer than 3 items does not wrap. - Colour (item render
0x001d5700):0x005fd318(grey 178) when enabled and focused, otherwise the item's colour (+0x1f4); alpha from0x005fd310; the separator in the item's colour, alpha 255 or the text's byseparatorAlphaMode. No highlight sprite and no size change: selection is colour only. - Input: nothing until 20 ms after the last command or focus (
+0x84); the d-pad by the auto-repeating query, or the plain one after a refused move (+0xc4); buttons by the button pass (mask0xffff0fff). The owner's handler runs first; if it returns 0: up / down (0x001d4628/0x001d4790, only with two or more rows) keep the column (clamped to the row's length), wrap from the first row to the last and back, skip items that are not selectable, and play0xewhen they land on the same item; left / right (0x001d48e0/0x001d4a30) walk all items in order with wrap (+0x9c= 0) or stay in the row (+0x9cset), skipping unselectable ones; a move (0x001d4b88(grid, i, 1)) unfocuses the old item, focuses the new one and plays cue+0x94(0x0010fc30(*0x0050aa84, cue));0x001d4b88(grid, i, 0)selects without a sound (defaults). Accept and back are the owner's. - Focus (
0x001d4d28) clears the input record (0x00146000), marks the grid focused, focuses the selected item and stamps+0x84.
Bar (0x001a0fd0(width, height, 0.5, widget, position, backColour, spriteWord, 1, 0), 0x70 bytes): a meter of
two sprites from the given sheet rectangle, width and height in overlay units, left edge at the position (inferred
from the runtime: PM_Light's bar starts at x 0); fill colour +0x28, fill fraction +0x38; drawn by
0x001a1138(0.25, bar, 0, 0).
ScrollingMenu (0x001e1338, vtable 0x0053bfc8), the Rumble lists: Front end.
The pause header: CircledTextHeader¶
The big header at the top of the story pause menu (the mission's title after one to three circled characters, the level
name under it) is the class we call CircledTextHeader (constructor 0x001e7f50, vtable 0x0053ca00), a subclass
of IconTextHeader (0x001e77a0, function index). It has one instance, the pause
menu's top header at 0x006340c0, built by PauseMenu_StaticInit (0x001dfc80, the only caller of the constructor)
with the text "Current Objectives" (0x108); the two other pause headers (0x00634c70, 0x00634fe0) are plain
IconTextHeaders. Confirmed (code). The front-end screens that pass the allocator tag CircledTextHeader (mission
select 0x001bc5a8 and 0x001bd838, game stats 0x002155a8) allocate 0x600 bytes and construct a
CircledText (0x001d0aa8) in them, a different and smaller class: the tag is only a name.
Confirmed (code) at those call sites.
Parts (offsets from the object; confirmed (code) at 0x001e7f50, 0x001e7898, 0x001e8368):
| Offset | Part | Set up as |
|---|---|---|
+0x20 / +0x30 |
position (x at +0x20, y at +0x28) and box (width at +0x30) |
the caller's |
+0x360 |
text offset (x at +0x360, y at +0x368) |
the caller's |
+0x50 |
the child (the pause menu passes its current-objectives checklist) | placed under the header (Layout) |
+0x70 |
the label, a markup text (IconTextHeader's) |
size 1.0, grey 0x005fd310, font slot 3, the constructor's text |
+0x260 |
rule A, a BaseWidget (IconTextHeader's) |
the caller's sprite word, size 0.1, depth 8,000, grey 0x005fd310 |
+0x390 |
rule B, a BaseWidget |
the same sprite word, depth 8,000, grey 0x005fd310 |
+0x490 + 0x100 × i, i < 3 |
three circles, BaseWidgets |
part_page0 rectangle 84 (sprite word 0x54), depth 8,500, black (0x005fd260), size 0.082 × the circle scale |
+0x790 + 0xd0 × i, i < 3 |
a one-character TextWidget in each circle |
font slot 4, Font_Size(1.561) |
+0xa00 |
the title, a TextWidget |
font slot 4, Font_Size(2.23), grey 0x005fd310 |
+0xad0 |
the subtitle, a TextWidget |
font slot 4, Font_Size(1.561), grey 0x005fd310 |
+0x370 |
the title's characters (the text after the :) |
|
+0xba0 |
the number of circles in use |
The pause menu sets it up (0x001dbee0) at position (0.5, 0.22) (16:9: (0.5, 0.18)), box width 0.5, text offset
(0.01, −0.01) and sprite word 10 (part_page0 rectangle 10) for both rules. Confirmed (code).
The text (CircledTextHeader_SetTitle, 0x001e8ee0): the string is split at its first :. Every character before
it gets a circle of its own, in order; the : itself (when it is not the first character) adds one more circle whose
character is @ + the checkpoint (game state +0x33a, so checkpoint 1 shows A, 2 B); what follows the : is the
title. Nothing bounds the count at three. The last circle is then coloured red 0x005fd328 (170, 43, 43). The
subtitle is always the current level's name (the level record at game state +0x14d4 + 0x84 × +0x56dc, name at
+0x49). Confirmed (code). The pause menu passes the game state's mission title (+0x134), or the Rumble mode's title
in a Rumble level, after colouring all three circles with the level's colour (Pause); so a
mission title of the form <number>:<title> shows the number in a circle of the level's colour and the checkpoint
letter in a red one (runtime, level99 checkpoint 2: a circled 1 and B, Pause).
That the mission titles have that form is inferred from this code and that screenshot.
Colours. SetCircleColour (0x001e85e8) and SetLetterColour (0x001e8658) colour all three circles or
characters. Every Update sets each used character back to opaque black, so a letter colour set from outside lasts
until the next update only (confirmed (code); in effect the characters are always black, inferred).
Drawing (Render 0x001e8d00; the pause menu calls RenderColoured, slot +0xd0, 0x001e8dd0, with grey
0x005fd320 or 0x005fd310 and the menu's fade alpha): the label in the given colour with shadow byte 0x80, the
child, then for each used circle the circle and its character (no shadow), rule B, the title and the subtitle (shadow
0x80). RenderColoured changes only the alpha of the circles, characters, rule, title and subtitle. Rule A is
updated but never drawn: neither Render nor IconTextHeader's draws +0x260. The label, rule A and the child are
hidden on the pause grid (IconTextHeader_SetVisible(0), 0x001e7ea8). Confirmed (code).
Layout¶
CircledTextHeader_Update (0x001e86c8) places every part each update from the position (X, Y), the box width W, the
text offset (ox, oy) and the factors below, in GUI coordinates (HUD: coordinates). Confirmed
(code):
- rule A: centre (X + 0.035, Y + 0.105), size 0.57 × 0.007 (device-converted);
- rule B: centre (X + 0.035, (Y − 0.07) × a), the same size;
- with
hrule A's height (+0x2f4, 0.007) andtthe title's line height (TextWidget+0xb4:h + lineGapofFont_Size(2.23), Text, 0.091 in 4:3),y0= Y − h / 2 + t + oy − 0.045 − 0.151; - the subtitle (
Font_Size(1.7)) at (0.257, (y0+ 0.075) × b); - the circles from x = 0.272 (an absolute x, not relative to X), each step 0.051: the character at (x,
y0× d),Font_Size(1.7), and its circle centred at (x + the character's width / 2,y0× c), size 0.082 × s square; - the title (
Font_Size(2.23)) after the last circle at (x,y0× d), or at x 0.255 when there is no circle; - the label at (X − W / 2 + ox − 0.005, (Y + h / 2 − its height / 2 + oy + 0.1) × e);
- the child at (the label's x + 0.005, (Y + h / 2 + 0.02 + 0.1 + g) × f), with g = −0.015 (−0.025 with flag
0x02alone;0x001e7898).
The factors per video mode (CircledTextHeader_ApplyVideoMode, 0x001e81c0, run at the top of every update; device
flags as on Graphics):
| Flags | a 0x0050f2fc |
b 0x0050f300 |
c 0x0050f304 |
d 0x0050f308 |
e 0x0050f30c |
f 0x0050f310 |
s 0x0050f2dc |
|---|---|---|---|---|---|---|---|
default, and progressive 4:3 (0x20) |
0.9 | 0.95 | 0.78 | 0.68 | 1.0 | 1.0 | 1.2 (static) |
0x04 without 0x02 or 0x08 (16:9) |
0.6 | 0.67 | 0.1 | 0 | 1.0 | 1.0 | 1.2 (static) |
0x02 without 0x04 |
1.49 | 1.63 | 2.4 | 2.3 | 1.1 | 1.1 | 0.84 |
0x02 with 0x04 |
1.15 | 1.25 | 1.4 | 1.3 | 1.1 | 1.1 | 0.84 |
s is written only in the 0x02 modes and never restored, so after such a mode the circles would stay at 0.84
(inferred). The function also writes 0x0050f314 (0.082, or 0.05 with 0x02), which the update does not read.
Worked example (inferred, computed from the constants above): on the pause grid in the default mode (X, Y = 0.5,
0.22), y0 ≈ 0.102, so the title and the circle characters sit at y ≈ 0.069 and the circles' centres at 0.079, rule B
at 0.135, the subtitle at (0.257, 0.168) and rule A (not drawn) at 0.325. In the 16:9 row the factor d is 0, which
would put the title's centre on y 0; whether the original shows that is an open question (no 16:9 screenshot yet).
The markup text widget's fields¶
MessageHUD (0x001b8f98, about 0x1e4 bytes), confirmed (code) at the functions of
GUI/MessageHUD.cpp:
| Offset | Type | Field | Meaning |
|---|---|---|---|
+0x04 |
u32 | changed | set by every text change |
+0x0c |
u32 | created | |
+0x20 |
vec4 | anchor | (x, 0, y, 1) |
+0x60 |
char* | ownedText | a copy the widget frees on shutdown |
+0x68, +0x6c |
char* | text, originalText | the text being laid out, and the one set |
+0x70 |
BaseWidget | icon | shown instead of text when its sprite word is set |
+0x170 |
u32 | endTime | <DISPLAYTIME> end, game ms; -1 never, 0 none |
+0x174 |
u32 | fadeTime | the prompt fade during a <FREEZE> |
+0x178 |
u32 | frozen | a <FREEZE> is running |
+0x17c |
u8 | alignment | Font_Draw flags |
+0x180 |
u32 | proportional | |
+0x184 |
f32 | maxWidth | box right limit (10,000) |
+0x188 |
u32 | allowCR2 | enables <CR2> and <CRM> |
+0x18c |
u8 | shadow | 0x80 |
+0x190 |
s32 | bgFontInstance | the <BGFONT> batch, -1 none |
+0x194 |
u32 | pendingSound | played once on the next draw |
+0x198 |
s32 | glyphBase | <MONEYFONT>'s base, -1 none |
+0x19c |
u32 | fontSlot | 3 or 6 |
+0x1a0 |
u32 | paged | draw only the line window |
+0x1a4, +0x1a8, +0x1ac |
u32 | firstLine, lastLine, lineCount | the line window of wrapped text |
+0x1b0, +0x1b1 |
u8 | alpha, alphaOverride | the fade's alpha; a fixed alpha |
+0x1b4 |
u32 | useAlphaOverride | |
+0x1b8 |
u32 | wordWrap | wrap at +0x1d4 |
+0x1bc |
u32 | drawn | drawn this frame |
+0x1c0 |
f32[4] | metrics | Font_Size record |
+0x1d0 |
f32 | baseSize | |
+0x1d4 |
f32 | wrapWidth | from <AUTOINDENT f> or set |
+0x1d8 |
u32 | colour | |
+0x1dc, +0x1e0 |
u32, u8 | stickTimer, stickGlyph | the animated stick glyphs (<SDD>...) |
Markup tags¶
Text strings carry tags in angle brackets. The table at 0x0050d718 holds 66 tag strings of 61 bytes each; the
layout code (0x001b9600) compares the text after a < with each in turn and acts by index. Confirmed (code):
| Index | Tag | Effect |
|---|---|---|
| 0 | <COLOR rrggbbaa> |
colour (hex); alpha multiplied by the widget's fade |
| 1 | <SIZE f> |
font size × f |
| 2 | <PULSE ms> |
colour swings linearly between × 1.5 and × 0.5 (clamped to 255), reversing every period: a triangle wave |
| 3 | <SOUND name> |
plays the sound (CRC-32 of the name) once, on the first frame shown |
| 4 | <FREEZE ms> |
sets the display end and freezes the game timer for ms (0x00145ea8(gameTimer, ms, 2000)) |
| 5 | <DISPLAYTIME ms> |
the text disappears after ms; in its last 1,000 ms its alpha is the remaining ms × 0.255 |
| 6 | <BOLD> |
no effect: the layout has no case for it (confirmed (code) at 0x001b9600) |
| 7 | <BIGFONT> |
font slot 6 (big_font) |
| 8 | <MONEYFONT> |
the icon ("money") font: until </MONEYFONT>, every character is drawn from another rectangle base (below, MONEYFONT) |
| 9 | <BGFONT> |
glyphs on a background (creates an instance over sheet 0, depth 8,000) |
| 10, 11 | <MONEYPLUS>, <MONEYMINUS> |
the icon characters = and < |
| 12-16 | <CENTER>, <CCENTER>, <RIGHT>, <RRIGHT>, <LEFT> |
alignment: CENTER (flags 6) and RIGHT (5) shift the whole line by its measured width; CCENTER and RRIGHT pass the same flags to each Font_Draw run, aligning each run about the pen; LEFT is 4 |
| 17-20 | <CR>, <CR2>, <CR3 f>, <CRM> |
new line; CR2 only when the widget's +0x188 is set and *(s16)(W_GameState + 0x224) < 2 (single player), CR3 adds f, CRM only when +0x188 and +0x1b8 are set |
| 21 | <AUTOINDENT f> |
not handled by the layout; at the start of a text it sets the wrap width (+0x1d4) to f (MessageHUD_ReadAutoIndent, 0x001bb390) |
| 22-32 | <FIST> ... <BGCIRCLE> |
HUD icons, each a single character of the current font (:, >, ?, ;, @, A, B, C, E, F, y) |
| 33-49 | <S>, <O>, <T>, <ST>, <X>, <START>, <SELECT>, <R1>, <R2>, <R3>, <L1>, <L2>, <L3>, <DU>, <DD>, <DL>, <DR> |
button glyphs: characters 0x9f, 0x9d, 0x96, n, 0x9e, 0x97, 0x93, 0x9c, 0x94, 0x92, 0xa0, 0x95, 0x91, 0x9b, 0x99, 0x9a, 0x98 |
| 50, 51 | <LAS>, <RAS> |
(no character set) |
| 52-55 | <SDD>, <SDL>, <SDR>, <SDU> |
animated stick glyphs: alternate between two characters every 500 ms |
| 56-65 | </COLOR>, </SIZE>, </PULSE>, </BOLD>, </BIGFONT>, </MONEYFONT>, </BGFONT>, </CENTER>, </RIGHT>, </LEFT> |
restore: </COLOR> the widget's colour, </SIZE> Font_Size(1.0) (not the base size), </BIGFONT> the saved font |
A glyph tag is replaced by its one-character string ("%c") and drawn like text, so the button icons are ordinary
characters of the font sheet. The HUD icon tags 22-32 are, in order: <FIST>, <RARREST>, <RDEAD>, <BOBJ>,
<ROBJ>, <YOBJ>, <RFLASH>, <RSPRAY>, <RPEDRAT>, <RCALLGANG>, <BGCIRCLE> (the table at 0x0050d718, 61
bytes a name). Confirmed (code).
<MONEYFONT>¶
Confirmed (code) at 0x001b9600 (MessageHUD_Layout), 0x00179958 (Font_Measure), 0x00179c30 (Font_Draw),
0x00181e38 (Page_Rect):
<MONEYFONT>sets the widget's glyph base (+0x198):0xd0100when the current font slot is 6 (big_font, inside<BIGFONT>or a slot-6 widget), else0xbwhen the widget's own slot (+0x19c) is 3 (part_page0); otherwise nothing.</MONEYFONT>sets it back to -1. Every run and glyph between the two is measured and drawn with that base.- With a base,
Font_MeasureandFont_Drawuse rectangle base + (c −'0') in slot 3 and base + (c − 1) in slot 6, instead offirstGlyph + c;Page_Rectkeeps only the low 16 bits, so0xd0100is rectangle 256. - So in
part_page0the money font starts at rectangle 11 for'0':<MONEYPLUS>=is 24,<MONEYMINUS><23, and<ST>, the charactern, is rectangle 73: the triangle button (the same rectangle the scripts pass toHUDTurnOnActionCycleAnimas the triangle, HUD; inferred-corroborated).<ST>appears only inside<MONEYFONT>; without it, it is the letter n. The icon tags<BOBJ>;→ 22,<ROBJ>@→ 27,<YOBJ>A→ 28 likewise. - In
big_fontthe animated stick tags give characters 2-6 (each alternates its own character with 2 every period0x0050e6e4:<SDD>3,<SDL>4,<SDR>5,<SDU>6), drawn as rectangles 257-261: the stick pictures. HUD string 4 (revive) uses<SIZE 1.5><BIGFONT><MONEYFONT><SDL>.... GSTRING.ACTIONBUTTON(config_strings_*.lua) is<MONEYFONT><ST></MONEYFONT>plus a space, and is concatenated in front of the action-prompt strings. Disc check: counts only.- Quirk:
Font_Draw's own alignment measure is taken with base -1 (only<CCENTER>/<RRIGHT>runs use it).
Behaviour¶
The screen flow¶
ScreenFlowController (GUI/ScreenFlowController.cpp) is a stack of screen-flow states (a list, SFC_States) plus
shared data (SFC_SharedData). Confirmed (code):
- Add transition (
0x001c8010(flow, state, code, next)): instate's map,code→next. - Update (
0x001c83c8): call the top state'sUpdate. Result-0x100: stay.-0xff: pop (0x001c82e8:Exitthe top, remove it,Enterthe new top). Any other value: look it up in the top state's map; no entry means stay; a target already on the stack is unwound to (0x001c81e8:Exitevery state in the stack, remove those above the target,Enterthe target); otherwise push it (0x001c80e8:Exitthe old top, push,Enterthe new one). Returns true when the stack is empty.
So a screen never draws while it is covered: covered screens are exited and re-entered.
A frame of 2D¶
- Widgets'
UpdateandRenderrun in the mode's update (for the menus, insidePM_Controllerand the HUD).RendercallsInstance_AddSpritefor each sprite: a sprite widget adds one sprite from its sheet (plus a black drop shadow offset by (0.0025, 0.004) at its alpha × 128/255, see Graphics); a text widget adds one sprite per character (Text). - In the overlay pass the overlay world is rendered through the overlay camera; each PTank atomic in it has the
render callback
0x00197000, which does not draw but queues the instance with a sort key. ResourceMgr_RenderOverlay(0x00185d20) then sets Z test and Z write off and culling off, sorts the queue and draws each instance (0x00197168: blend states from+0x30/+0x34, the PTank's sprite count, render), with the overlay camera of its viewport when the instance is bound to one, and finally empties every instance (0x00185cc8: sprite count 0).
An instance with +0x3c = 0 is not in a world: its owner draws it directly (0x00197168, as the legal screen does).
Draw order¶
The queue holds {instance, key} pairs (ResourceManager + 0xca4, pointers at +0x149c, count at +0x1898) and is
sorted with the C library's qsort and the comparator 0x00184890: ascending key, so the smallest key is drawn
first and the largest ends on top (Z test is off). Confirmed (code). The key, set by 0x00197000: for an instance in
the 2D overlay world (+0x3c = 2) a float read from the atomic at +0x28; otherwise the squared distance from the
player camera to the atomic minus the square of a radius. Confirmed (code) for the reads. The 2D key is the depth
given at creation (+0x20): confirmed (runtime), PCSX2 2.9.94, reading the queue (ResourceManager + 0xca4,
{instance, key} pairs, still in memory after the pass empties the count) and the instances:
| Screen | Queued instances (queue order) and keys | Drawn (ascending) |
|---|---|---|
| main menu | part_page0 10,000; big_font 9,000 |
text, then the button glyphs |
| Quick Rumble menus | slot 60 (a one-sprite sheet, CRC 0x349348bd: the background picture) 8,000; part_page0 10,000; big_font 9,000 |
background, text, glyphs |
| in a fight | part_page1 -1.21e8; lighting -1.00e8; part_page1 -1.21e8; part_page0 10,000; big_font 9,000 |
the 3D-overlay batches (+0x3c = 1, distance keys) first, then text, then glyphs |
Each 2D key equals the float at the instance's +0x20. The keys of instances in the 3D overlay world are negative:
-120,999,792 for the two part_page1 batches (depth 11,000) and -99,999,792 for lighting (depth 10,000), which
is the squared camera distance (208, the same for all three) minus the square of the creation depth, so the radius
the key subtracts is the depth (inferred from the numbers). They always come before the 2D ones.
menu_system (8,500) was not on these screens.
Text¶
Fonts are sprite sheets. A text widget draws through a font slot (an instance index: 6 is big_font); the
sheet's firstGlyph gives the rectangle of character 0, so character c (a byte) is rectangle firstGlyph + c.
big_font has 262 rectangles and firstGlyph 0, so its rectangles are indexed by the character code directly; the
button glyphs above (0x91-0xa0) are characters of the same sheet. Confirmed (code) at 0x00179958 and
0x00179c30; the disc check gives the counts. Two call sites pass an explicit base instead of firstGlyph and shift
the character: font slot 6 by -1, slot 3 by -'0' (digits); where they are used is not traced.
Size. Font_Size(scale) (0x00179808) gives {w, h, spacing, lineGap} in GUI units:
w = scale / 30
h = w * (screenW / screenH) * 0.75 * 1.3333 # = w * 640/448 in 4:3
spacing = 0.003
lineGap = -h / 7
Advance. For each character, confirmed (code) at 0x00179958:
- a space (
0x20) or0xac:(w + spacing) × 0.56, halved in proportional mode; - otherwise, in proportional mode (flag
0x04): the glyph's pixel size iswPx = (u1 - u0) × texW + 0.5,hPx = (v1 - v0) × texH + 0.5, its drawn width iswPx × w / hPx(heighth, aspect kept), and the advance is that width plusspacing; - in fixed-width mode every glyph is
wwide.
Drawing (Font_Draw, 0x00179c30): measure the string; right-aligned (flag 0x01) moves the start left by the
width plus half a glyph, centred (flag 0x02) by half the width. Each glyph becomes a sprite centred at
(pen + (width + spacing) / 2, baseline) with size (width, h), converted to overlay-camera space by device slots
+0x90 and +0x98, with the glyph's rectangle and the colour. With a shadow alpha, a black copy offset by (0.0025,
0.004) and alpha shadow × colourAlpha / 255 is added first. A reveal fraction below 1 shows only the top part of each
glyph (both the sprite and its rectangle are cut).
Layout (0x001b9600) runs twice for a visible widget, once to measure and once to draw. It splits the text at
<, draws the runs between tags with the current font, size, colour and alignment, applies tags, and moves to a new
line on <CR> by h + lineGap plus the extra of <CR3 f>. The widget's box is grown to the widest line; with
<CENTER> each line is centred on the box. Confirmed (code). CRM is the soft break MessageHUD_WordWrap
(0x001bac20) inserts when it wraps.
Strings¶
UI text comes from Lua, not from the string-table chunks. config_preload2.lua runs config_strings_<lang>.lua
for the current language (en, de, fr, it, es; WAD entries 3,774-3,778) and passes each entry of its tables
to a binding: GSTRING.HUD to CfgHUDMessage(id, text) (0x0035e5d0 → 0x0019eea0), and likewise CRIME
(CfgCrimeMessage), TSTRING (CfgTutorialMessage), COMMAND (CfgWarriorCommand) and ANNOUNCE
(CfgAnnounceMessage). Confirmed (code) for CfgHUDMessage: it copies the text into the Level Dynamic & LUA Pool
heap and stores the pointer at 0x00600048 + id × 4; GlobalString_Get(id) (0x0019ee70) returns it, or an empty
string. The other tables and the script side are inferred from the scripts' string constants.
The text a level shows (objectives, hints, prompts) never reaches a Cfg* table: global.lua builds GSTRING's
other keys and LABEL, and each level script its own LEVEL<n> (some from level<n>_strings_<lang>.lua), once per
language, keeping the one for GetLanguage(); scripts then pass the strings themselves (LEVEL34.MS_C1_5) to
bindings such as HUDSetObjective. Inferred from the scripts' code. The keys are listed in
Text labels.
The chunk types 0x0F-0x13 (English to German string tables) and StringTable/StringTableCache.cpp are used by
other screens (credits, Rumble mode); they do not occur in the WAD (WAD
contents).
The string cache (StringTable/StringTableCache.cpp, our name StringTableCache) interns strings: one copy of
each distinct text, so callers can keep the returned pointer. StringTableCache_Intern(cache, text) (0x00386d08)
hashes the text (h = h × 5 + c over its bytes, signed), looks it up in an STL hash set (buckets at +0x08-+0x0c,
element count +0x14), and on a miss copies it into the Level Dynamic & LUA Pool (tag char, align 16), inserts
the copy and adds its length + 1 to the byte total +0x18; it returns the stored copy, or 0 for a null text. The
destructor (0x00386b30) frees every copy from the same pool, clears the set and frees its buckets. Callers: the
credits list (0x001a94e8, 0x001a8fb8) and the Rumble character data (0x001f1fa8, and 0x001fdfe0, which can
first copy the text and change its case through 0x00435e70) into the cache at 0x0050f4f0. Confirmed (code) at
the cited addresses.
The radar¶
GUI/RadarHUD.cpp (0x001c41a0-0x001c6878). The HUD object 0x00600840 holds two radars, one per player, at
+0x15d0 and +0x3f10; every radar binding calls the same function on both. A radar keeps 128 blip slots of
0x40 bytes from +0x920 (0x001c3de0). The list of every icon and blip type is
Radar icons and blips. Confirmed (code) unless marked.
A blip is a particle system of type hud_radar_dot (Particles), made by 0x001e99f8 with two
words: a colour and a sprite word. Blip type 9 alone is a sprite widget (BaseWidget, 0x001a1bf8) instead. The
radar's add function 0x001c4d00(radar, handle, colour, type, layer) frees the slot the handle already has on that
layer, takes a free one and records, at slot +0x10 onwards, the handle, the colour, the type (+0x2c) and the
particle's handle (+0x24). An object may have two layers (0 and 1), each its own slot (found by
0x001c5188(radar, handle, layer)).
Icons are rectangles of part_page0. The radar makes its own sprite batches at start (0x001c3de0) with
PTank_Create(depth, 0x45, ...): the sprite word's high half (0) is the record of the sprite-sheet table
(chunk 0x4D) the batch draws, record 0 being part_page0.
A blip's sprite word is batch << 16 | rect; hud_radar_dot's initialiser (0x003e5bf8) keeps the batch and sets
the rectangle to 0x45 (69, the plain dot). HUDSetRadarItemTexture sends message 0x11 with the icon id, and the
dot's handler (0x003e5f20 → 0x0039bb18) replaces the low half:
So an icon id is a rectangle index of part_page0 (371 rectangles, a 512 × 256 texture); the icons scripts and
code use are 7-34 texels a side (disc check). HUD_RadarSetBlipIcon (0x001b2ca0) draws icon 22 at 0.7 and tints icons
29-31 0x63db4bff (green; colours here are 0xRRGGBBAA, as HUDAddRadarObject packs them at 0x001b40f8).
| Radar field | Batch | Blip types |
|---|---|---|
+0x4c |
16 sprites, depth 9,000 | 10 (mission objective) |
+0x50, +0x54, +0x58 |
32 sprites each, depths 5,000-7,000 | +0x58 type 2, +0x54 type 3, +0x50 every other |
+0x5c, +0x60 |
96 sprites each, depths 1,000, 2,000 | 6, 7, 8, by layer |
+0x48 |
16 sprites, depth 10,000 | not traced |
A new blip of type 7, 9, 10 or 12 stays visible while the radar's +0x08 is set and +0x0c clear; any other is
sent message 0x2a, which sets flag 0x04 of the dot (+0x54; hidden, inferred) until message 0x29 clears it.
0x001b2990 adds nothing for type 5, and for type 6 adds two layers with icons 352 and 359.
Humans. HUD_RadarAddHuman (0x001b4168) picks the blip from the character class byte (CfgChar +0x11a,
AI): class 1 (police) type 8 with icon 356 on both layers; class 0 or 3 type 9 (icon 362, grey
0x787878ff) for the player (brain type 0) and type 7 (icon 365) for any other; class 4 none; any other type 6. A
human with byte +0x19d set counts as class 2 unless a Warrior. Human_Init adds type 5 (nothing) for everyone. The
AI re-marks humans that turn on the player as type 6 (0x002b0608, 0x002c2e80, 0x002d6e10, 0x002d79a8), and a
dealer greeting the player adds type 2, 4 or 3 with icon 29, 31 or 30 at 0.8 (0x002c7ee0, dealer types 0, 1, 2).
Colours and icons by blip type, as the adding code passes them (colours 0xRRGGBBAA; white leaves the icon's
own colours). Confirmed (code) at 0x001b2990, 0x001b2ee8, 0x001b4168, 0x001b40f8, 0x001b2e38,
0x001b2e68, 0x001b3258, 0x002c7ee0, 0x001b2ca0:
| Type | Added by | Colour | Icon, size factor |
|---|---|---|---|
| 1 | HUDAddRadarObject (and the secondary objective) |
the script's {r, g, b}, alpha ff |
69 (the dot) until a script sets one |
| 2, 4, 3 | a dealer of type 0, 1, 2 (DealerGoal_AddRadarIcon, at the greeting) |
white at the add, then 0x63db4bff (green) |
29, 31, 30 at 0.8 |
| 5 | civilians | nothing is added | |
| 6 | hostile humans (classes 2, 5, 6; the AI) | white (the grey 0x808080ff passed is ignored) |
352 on layer 0, 359 on layer 1, both at 0.8 |
| 7 | a Warrior other than the player | white | 365 at 0.8 |
| 8 | police (class 1) | white, then blip mode 0 (alpha 0x80) |
356 on both layers at 0.8 |
| 9 | the player | slot 0x787878ff; the widget draws in the widget colour 0x005fd310 (runtime: (178, 178, 178, 255)) |
362 |
| 10 | HUDAddRadarMissionObjective |
white, flashing (alpha toggled every 4 updates for 100), then solid | 69 until a script sets 27, 28 or 22 |
Every icon set through HUD_RadarSetBlipIcon (0x001b2ca0, which HUDSetRadarItemTexture reaches through
0x001b4038) with icon 29, 30 or 31 is tinted 0x63db4bff, whoever sets it, and icon 22 is forced to 0.7. So
the flash dealer's blip is the green icon 29 in the +0x58 batch whether the dealer goal or level99_lesson2.lua sets
it. 0x001b4038 then sets the icon lock (0x001b2bf0) from the script's third argument.
A dot's size on screen. hud_radar_dot starts with size 0.7 (+0xc0; 0x003e5bf8) and every icon set multiplies
it by the call's factor (Radar_SetSlotIcon / Radar_SetBlipIcon, both +0xbc and +0xc0), so the factors
compound when an icon is set twice. The particle draw (ParticleTask_Draw 0x0039b020, dots have flag
0x400000) makes the sprite, in overlay units,
width = 2 × size × (u1 − u0)
height = 2 × size × (v1 − v0) × (texH / texW) × 0.925 (sheet +0x1c = texH / texW, 0.5 for part_page0)
for the icon's rectangle (u0, v0, u1, v1) of part_page0 (512 × 256), centred on the blip position. The size and
colour blend from the previous update's values (+0xbc, +0xb0) to the current ones (+0xc0, +0xb4) over the
update interval (0x00338240, 0x0039ba68); a new dot starts at size 0 and colour 0xffffff00, so it fades and grows
in over its first interval (inferred from the fields; not timed). Confirmed (code); confirmed (runtime): a type-7
blip (icon 365, 11 × 11 texels) had size 0.56 (0.7 × 0.8), colour white, and drew about 11 px of 640, as
2 × 0.56 × 11/512 = 0.024 units × 441 px.
Blip modes (0x001b32e0(hud, handle, mode)), sent with message 0x19 to the dot (0x003e5d00): mode 0 plain;
1 and 2 icon 352; 3 and 4 icon 353 at 0.8 on layer 0 with a ring (icon 32 or 33 at 0.75) on layer 1, flashing. The
dot's own states: 0 dim (alpha 0x80), 1 full, 2 pulsing size (× 1.3), 10 growing, 13 blinking, 14 cycling size
0, 0.6, 1.2, 99 opaque white. HUDSetRadarObjectFlash sets these states (inferred from the handler).
To render an icon as a reference thumbnail: load part_page0 (WAD file named by the decimal CRC-32 of
part_page0; its 0x2A dictionary and 0x4C rectangles), cut rectangle n and scale it to fit 64 × 64. Coney's
sheet reader (Coney's implementation) already does the first two steps.
Radar blip slots¶
A radar's fields and its 128 slots of 0x40 bytes (from radar +0x920). Confirmed (code) at 0x001c4d00,
0x001c4ff8, 0x001c5210, 0x001c41a0; meanings marked inferred where the code only shows the use.
| Radar offset | Meaning |
|---|---|
+0x00 / +0x04 |
created / on (HUDTurnOnRadar) |
+0x08 / +0x0c |
shown (Radar_Show) / hidden (Radar_HideAllBlips) |
+0x14 |
enemy blips (types 5, 6, 8) may show when marked (inferred) |
+0x18 |
enemy marks never time out (inferred) |
+0x20, +0x24, +0x28 |
zoom, fast and rest radii (HUD) |
+0x38-+0x40 |
disc colour state, old state, change time |
+0x48-+0x60 |
the sprite batches (table above); +0x48 holds icons 25 and 26 |
+0x70 / +0x80 |
the player's position / matrix, copied by Radar_Update |
+0x90 |
which player |
+0x2920 / +0x2930 |
zoom scale / the disc centre |
| Slot offset | Type | Meaning |
|---|---|---|
+0x00 |
vec4 | fixed position (in-use 2) |
+0x10 |
handle | the tracked object |
+0x14 |
u32 | colour (0xRRGGBBAA) |
+0x18 |
u32 | icon last set (0x45 at creation) |
+0x20 |
u32 | the batch the dot was made in |
+0x24 |
handle | the hud_radar_dot |
+0x28 |
ptr | the BaseWidget of a type-9 blip |
+0x2c |
s16 | blip type |
+0x2e |
s16 | mark timer, updates (300 per mark) |
+0x30 |
u8 | shown: always for types 7, 9, 10, 12; set by a mark for the others |
+0x31 |
u8 | in use: 1 follows its object, 2 a fixed point |
+0x32 / +0x33 |
u8 | objective flash countdown (100) / phase |
+0x34 |
u32 | icon lock (0x001c4640) |
The METRICS1 file¶
The one "font metrics" WAD entry is cn12.met (entry 3,743), next to cn12.bmp (entry 3,742, the 256 × 128
24-bit bitmap). It is text: the line METRICS1, a line naming cn12.bmp and mcn12.bmp, a number, then 95 lines
code x0 y0 x1 y1 # 'c' for the characters 32-126: pixel rectangles of a 12-point bitmap font. No code in the PS2
executable refers to it (no .met, .bmp or cn12 string), so the game does not use it; probably a leftover of a
debug or PC tool (inferred).
Function index¶
Every function of the GUI files this page covers, by source file in address order, with the name it has in Ghidra (ours). Rows link to the section that describes the behaviour where there is one. The files and ranges are from the Source map.
GUI/BaseWidget.cpp¶
0x001a1bf8-0x001a29c0: BaseWidget (vtable 0x005394b8, 0x100 bytes; a second copy of its slots at
0x0053a0d4-0x0053a10c belongs to a subclass). Slot numbers are the delta offsets of the GCC 2 vtable
(Widgets).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001a1bf8, 0x001a1c80 |
BaseWidget_Construct, BaseWidget_Reset |
vtable, then hidden, inactive, sprite word 0, colour 0, size 0, anchor 0, instance -1, depth 10,000 (+0xe4) |
confirmed (code) |
0x001a1c30 |
BaseWidget_Destroy |
slot +0x60: releases; frees when the flag's bit 1 is set |
confirmed (code) |
0x001a1db8 |
BaseWidget_Setup |
slot +0x68, The widget classes |
confirmed (code) |
0x001a1f10 |
BaseWidget_Release |
slot +0x70: an owned batch instance (+0xc4, +0x0c = 0) is freed, as is the shadow record +0xec |
confirmed (code) |
0x001a1fc8 |
BaseWidget_EnableShadow(on) |
slot +0x78: allocates the 0x80-byte shadow sprite record (ParticleContainer) or frees it |
confirmed (code) |
0x001a2060 |
BaseWidget_SetPosition(pos, convert) |
slot +0x08: +0x30; converted through the device (slot +0x90) into the overlay position +0x80 |
confirmed (code) |
0x001a2118, 0x001a2120 |
BaseWidget_SetAnchor, BaseWidget_SetSize(w, h, widget, convert) |
anchor +0xc0; size +0x90/+0x94 (h = w when h ≤ 0) |
confirmed (code) |
0x001a2190 |
BaseWidget_SetRect |
overrides the texture rectangle +0xa4-+0xb0 |
confirmed (code) |
0x001a21b8, 0x001a21e8 |
BaseWidget_Update, BaseWidget_UpdateSize(mode) |
slot +0x30 calls slot +0x98 with 1: mode 0 sets the width from the height, 1 the height from the width, by the rectangle's pixel aspect (Size); anchor offset +0xd0 |
confirmed (code) |
0x001a2538, 0x001a25b8, 0x001a25c0 |
BaseWidget_StartFade(widget, ms), _CancelFade, _IsFadeDone |
the timed fade: duration +0xf0, end +0xf4; when done it hides the widget and returns 1 |
confirmed (code) |
0x001a2638, 0x001a2648 |
BaseWidget_SetAlpha, BaseWidget_SetColour (slot +0x20) |
alpha +0xf8 and +0xb7; colour +0xb4 |
confirmed (code) |
0x001a2660, 0x001a2690 |
BaseWidget_Render (slot +0x38), BaseWidget_RenderAlpha(alpha) (slot +0xa0) |
The widget classes | confirmed (code) |
0x001a2910, 0x001a2918 |
BaseWidget_SetSpriteWord, BaseWidget_SetInstance(widget, inst) |
+0xc8; -1 makes the widget its own batch over the sprite word's sheet (ResourceMgr_CreateInstance, depth +0xe4, flags +0xfc) |
confirmed (code) |
After GUI/ChecklistMessageHUD.cpp (no path string): the widget base¶
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001a8e30 |
Widget_Construct |
the base of every widget (vtable 0x00539978): +0x04 = 1, +0x08 = 1, +0x0c = +0x10 = 0, +0x2c = 1.0 (Widgets) |
confirmed (code) |
0x001a8e78 |
Widget_Destroy |
slot +0x60; frees when the flag's bit 1 is set |
confirmed (code) |
0x001a8ea8 |
Widget_IsBatchResident(widget, inst) |
slot +0x80 of many widget vtables: whether a batch instance is resident (byte +0x11) |
confirmed (code) |
GUI/GridContainer.cpp¶
0x001ab620-0x001acb90: a grid of child widgets with d-pad navigation, used by the Rumble arena screen
(0x001eabc0). GridContainer (0x001abad0; vtable 0x00539b68, input interface 0x00539b40 at +0x7c) holds
GridContainerItems (0x70 bytes, vtable 0x00539c90: a child at +0x60, its index +0x64) in a vector at
+0x84-+0x8c. Fields: position +0x20, size +0x30, focused +0x40, input record +0x60 (HUD player 0's),
owner +0x90 (its handler sees every command first), last command time +0x94, spacing +0xa0 (x gap +0xa0, y
gap +0xa8), vertical wrap +0xb0, horizontal wrap +0xb4, rows +0xb8 and columns +0xb9 (bytes), capacity
+0xbc = rows × columns, selected +0xc0, owns children +0xc4, last move refused +0xc8. Each update lays the
items in cells of width (W − gx (columns + 1)) / columns and height (H − gy (rows + 1)) / rows; the pen x advances
by a cell plus gx for every item and is not reset at a new row (so only one-row grids lay out as a grid); the row
is index / columns. Slots are the delta-word offsets (vtable entry − vtable − 4), as on this page's
widget slot table. Confirmed (code).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001ab620 |
GridContainerItem_Construct(child, index) |
widget base, vtable 0x00539c90, scale +0x50 1.0, SetChild, initialised |
confirmed (code) |
0x001ab6a0 |
GridContainerItem_Destroy |
slot +0x60 |
confirmed (code) |
0x001ab6c8 |
GridContainerItem_SetChild |
slot +0xc0: child, index, and the child's rectangle size into +0x30 |
confirmed (code) |
0x001ab718 / 0x001ab758 |
GridContainerItem_SetPosition / _SetSize |
own position +0x20 / size +0x30, passed on to the child (its slots +0x08 / +0x10) |
confirmed (code) |
0x001ab798 |
GridContainerItem_GetRect |
slot +0x70: +0x30 with the height × the reveal (slot +0xb0) |
confirmed (code) |
0x001ab7f0 / 0x001ab838 |
GridContainerItem_Focus / _Unfocus |
slots +0x90 / +0x98: the child's, and +0x40 |
confirmed (code) |
0x001ab8e0 |
GridContainerItem_IsActive |
slot +0x88: a child, initialised, child active |
confirmed (code) |
0x001ab930 |
GridContainerItem_Update |
slot +0x30: child position, size and update; recentres itself vertically on the child's height; visible = reveal > 0 |
confirmed (code) |
0x001aba58 |
GridContainerItem_Render |
slot +0x38: the child's render when active and visible |
confirmed (code) |
0x001abad0 |
GridContainer_Construct |
widget base, the two vtables, empty vector | confirmed (code) |
0x001abb70 |
GridContainer_Destroy |
slot +0x60: frees the vector storage |
confirmed (code) |
0x001abbf8 |
GridContainer_Shutdown |
slot +0x68: unfocus, delete the items (and their children's shutdown and delete when +0xc4), empty the vector |
confirmed (code) |
0x001abd30 |
GridContainer_Setup(pos, size, spacing, rows, columns, owner, ownsChildren) |
slot +0xc0: the fields above, selection 0, wraps off, initialised |
confirmed (code) |
0x001abe10 |
GridContainer_AddItem(child, index) |
slot +0xc8: below capacity, a new item appended |
confirmed (code) |
0x001abee8 |
GridContainer_IsActive |
slot +0x88: initialised and every item active |
confirmed (code) |
0x001abf60 |
GridContainer_IsSelectable(i) |
slot +0xe0: item i has a child whose slot +0x58 test is true |
confirmed (code) |
0x001ac000 / 0x001ac0e8 |
GridContainer_MoveUp / _MoveDown |
slots +0xe8 / +0xf0: step by the column count to the next selectable item (over the capacity, with +0xb0 wrap, else stay); Select(i, 1) when it changed |
confirmed (code) |
0x001ac1d8 / 0x001ac2f8 |
GridContainer_MoveLeft / _MoveRight |
slots +0xf8 / +0x100: step by 1 (wrap to the other end with +0xb4, else stay); no move: cue 0xe and +0xc8 = 1 |
confirmed (code) |
0x001ac400 |
GridContainer_Select(i, sound) |
slot +0x108: i clamped to the last item; when selectable, unfocus the old, focus i, cue 4 if sound; clears +0xc8 |
confirmed (code) |
0x001ac4e8 |
GridContainer_GetSelectedChild |
slot +0xd8 |
confirmed (code) |
0x001ac528 |
GridContainer_OnCommand(cmd) |
interface slot +0x08: the owner's handler first; else 0 up, 1 down, 2 left, 3 right; stamps +0x94; returns 1 |
confirmed (code) |
0x001ac620 / 0x001ac6b0 |
GridContainer_Focus / _Unfocus |
slots +0x90 / +0x98: clears the input record (0x00146000), +0x40, the selected item's focus, stamps +0x94 |
confirmed (code) |
0x001ac7d0 |
GridContainer_GetRect |
slot +0x70 |
confirmed (code) |
0x001ac828 |
GridContainer_Update |
slot +0x30: when focused and 20 ms after the last command, the d-pad (mask 0xf000) by the repeating query 0x001e93c0, or the plain one 0x001e9518 after a refused move, then buttons (0x001e9468, mask 0xffff0fff), input cleared; lays out and updates the items; recentres; visible = reveal > 0 |
confirmed (code) |
0x001acb90 |
GridContainer_Render |
slot +0x38: every item's render when active and visible |
confirmed (code) |
GUI/MessageHUD.cpp¶
0x001b8f98-0x001bb4a0: the markup text widget (class MessageHUD, vtable 0x0053a248; its path string is
passed by MessageHUD_Shutdown), the base of UsageInfo, the hint texts, the counter texts and message boxes. Its
fields are in The markup text widget's fields; the tags in Markup tags.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001b8f98 |
MessageHUD_Construct |
the markup text widget (vtable 0x0053a248): widget base, box BaseWidget at +0x70, proportional +0x180 = 1, font slot +0x19c = 3, active +0x54 = 1, +0x50 = 1.0 |
confirmed (code) |
0x001b9040 |
MessageHUD_Destroy |
slot +0x60: the box's destructor, then the widget base's |
confirmed (code) |
0x001b9090 |
MessageHUD_Setup |
slot +0xc0 (The widget classes): box sprite (depth 11,000), right limit +0x184 = 10,000, reveal 1.0, shadow +0x18c = 0x80, base size 1.0, font slot 6 or 3, Font_Size metrics +0x1c0, colour +0x1d8 |
confirmed (code) |
0x001b9288 |
MessageHUD_SetBaseSize |
+0x1d0 = size |
confirmed (code) |
0x001b9290 |
MessageHUD_Shutdown |
slot +0x68: when created, releases the icon sprite (+0x70), frees the owned text copy (+0x60), releases the <BGFONT> instance (+0x190), hides the widget and turns word wrap (+0x1b8) off |
confirmed (code) |
0x001b9370 |
MessageHUD_SetPosition |
slot +0x08: the anchor (+0x20), and moves the icon sprite with it |
confirmed (code) |
0x001b93a0 |
MessageHUD_GetIconSprite |
the icon sprite's word (+0x138); non-zero means the widget shows a sprite instead of text |
confirmed (code) |
0x001b93a8 |
MessageHUD_Measure |
word-wraps into a scratch copy when +0x1b8 is set, then runs the layout in measure mode; returns the box (x0, y0, x1, y1) |
confirmed (code) |
0x001b9478 |
MessageHUD_SetAlignment |
byte +0x17c, passed to Font_Draw as its alignment flags |
confirmed (code) |
0x001b9480 |
MessageHUD_SetMetrics |
the Font_Size record (+0x1c0) and the proportional flag (+0x180) |
confirmed (code) |
0x001b94d0, 0x001b94d8 |
MessageHUD_SetColour, MessageHUD_GetColour |
the base colour +0x1d8 |
confirmed (code) |
0x001b94e0 |
MessageHUD_GetScaledMetrics |
the four metrics times the base size (+0x1d0) |
confirmed (code) |
0x001b9548 |
MessageHUD_Update |
slot +0x30: updates the icon sprite when there is no text; visible (slot +0x40) while the reveal (slot +0xb0) is above 0 |
confirmed (code) |
0x001b9600 |
MessageHUD_Layout |
the markup interpreter (Text, Markup tags). Measure mode makes one pass, draw mode two (the first fixes each line's width for <RIGHT> and <CENTER>). Tags 6 (<BOLD>) and 21 (<AUTOINDENT>) have no case here: <BOLD> does nothing and <AUTOINDENT f> is read by MessageHUD_ReadAutoIndent. While a <FREEZE> is running, text after the prompt line fades over 334 ms. The box's right edge is capped at +0x184 × reveal |
confirmed (code) |
0x001bac20 |
MessageHUD_WordWrap |
splits the text at spaces, measures each line with its tags stripped, and inserts <CRM> before the word that takes the line past the wrap width (+0x1d4); the line count goes to +0x1ac |
confirmed (code) |
0x001baee0 |
MessageHUD_ClipToPage |
keeps only the wrapped lines from +0x1a4 to +0x1a8 (split at <CRM>): a scrolling page of a long text |
confirmed (code) |
0x001bafd0, 0x001bb000 |
MessageHUD_PageUp, MessageHUD_PageDown |
move that line window by one line; false at the top or at the last line (+0x1ac) |
confirmed (code) |
0x001bb038 |
MessageHUD_Draw |
when visible, active and not expired: draws the icon, or copies the text, wraps it, clips it to the page (when +0x1a0 is set), plays a pending <SOUND> (+0x194) and lays it out in draw mode; sets +0x1bc (drawn this frame) |
confirmed (code) |
0x001bb1c0 |
MessageHUD_Render |
slot +0x38: MessageHUD_Draw in the widget's colour |
confirmed (code) |
0x001bb1e0 |
MessageHUD_SetIcon |
drops the text and shows a sprite of the given word in its own batch; clears the end time and the freeze | confirmed (code) |
0x001bb250 |
MarkupText_SetText |
points +0x68 and +0x6c at the text (not copied), clears the icon, the end time, the freeze and the stick-glyph timer (+0x1dc) |
confirmed (code) |
0x001bb2b0 |
MessageHUD_ClearText |
clears the icon, the owned text, the end time and the freeze (The HUD's frame) | confirmed (code) |
0x001bb2f0 |
MarkupText_IsExpired |
true once the <DISPLAYTIME> end (+0x170, never for -1) has passed, or a <FREEZE> (+0x178) is over |
confirmed (code) |
0x001bb390 |
MessageHUD_ReadAutoIndent |
the wrap width (+0x1d4): the number after a leading <AUTOINDENT f>, or the width given |
confirmed (code) |
0x001bb440 |
MessageHUD_SetDisplayTime |
end time = now + ms (-1 leaves it), freeze off | confirmed (code) |
0x001bb4a0 |
Hud_GreyLeadingZeros |
rewrites a digit string with its leading zeros in HUD colour tag 5 and the rest in tag 0: the grey zeros of the score (Score and money) | confirmed (code) |
After GUI/MissionSelectHUD.cpp (no path string): MultiLineTextWidget and the HUD's bars¶
A translation unit without a path string runs from 0x001c16a8 to its static-init stub 0x001c3dc0. Besides the
HUD pieces in HUD, it holds MultiLineTextWidget (vtable 0x0053a550,
class string of the PM and Rumble screens' allocations): plain text (no markup) wrapped into at most 10 lines, used by
the captions, the error screen and the profile and Rumble dialogs.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001c16a8, 0x001c16f0 |
MultiLineText_Construct, MultiLineText_Destroy |
constructor and destructor (slot +0x60) |
confirmed (code) |
0x001c1718 |
MultiLineText_Shutdown |
slot +0x68: no lines, not created |
confirmed (code) |
0x001c1738 |
MultiLineText_Setup |
slot +0x90 (scale, widget, position, colour, flags, fontSlot): metrics Font_Size(scale) (+0xa0), colour +0xb4, Font_Draw flags +0xb8, font slot +0xbc, shadow 0x80 (+0xc0), wrap width 10,000 (+0x9c) |
confirmed (code) |
0x001c1800 |
MultiLineText_SetScale |
slot +0x98: new metrics, relaid out on the next update |
confirmed (code) |
0x001c1870 |
MultiLineText_WordLength |
the length of the word at a position (to a space, the end or a <) |
confirmed (code) |
0x001c18c0 |
MultiLineText_SetText |
slot +0xa0 (width, widget, text, force): text +0x40 (not copied), wrap width +0x9c |
confirmed (code) |
0x001c1900 |
MultiLineText_Layout |
greedy word wrap: words are added while the line stays within +0x9c (a word wider than the width gets a line of its own); <CR> forces a break; at most 10 lines, each a (start, end) span at +0x48; box width +0x30 = the widest line, height +0x38 = lines × (h + lineGap) |
confirmed (code) |
0x001c1ae0 |
MultiLineText_IsLoaded |
slot +0x88: created and its font batch resident |
confirmed (code) |
0x001c1b10 |
MultiLineText_Update |
slot +0x30: relayout when the text, scale or width changed |
confirmed (code) |
0x001c1b58 |
MultiLineText_Render |
slot +0x38: one Font_Draw per line at +0x20's x; the block is centred on y (+0x44 = 0) or ends at y (+0x44 ≠ 0) |
confirmed (code) |
GUI/RadarHUD.cpp¶
0x001c41a0-0x001c6878: the two radars (HUD +0x15d0 and +0x3f10, The radar); a radar is a
plain struct, not a widget, with 128 blip slots of 0x40 bytes from +0x920 (layout in
Radar blip slots). The map disc is drawn by Radar_Render (HUD).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001c3de0 |
Radar_Setup |
from HUD_InitLevel: the radar's sprite batches and map texture (The radar); the first function after the stub 0x001c3dc0, so the start of RadarHUD.cpp (inferred) |
confirmed (code) |
0x001c41a0 |
Radar_Shutdown |
from HUD_ShutdownLevel: resets all 128 slots (frees type-9 widgets, handle to none, colour, type, flags 0), releases the seven sprite batches +0x48-+0x60; +0x10 = 1, created +0x00 = 0 |
confirmed (code) |
0x001c42e0 |
Radar_IsCreated |
returns +0x00 |
confirmed (code) |
0x001c42e8 |
Radar_HideAllBlips |
when created: hidden +0x0c = 1 and every used slot's dot (0x001e9b38) or widget (vtable +0x40, 0) hidden |
confirmed (code) |
0x001c4388, 0x001c4448 |
Radar_Show, Radar_Hide |
only while the radar is on (+0x04): show or hide every used slot's dot or widget; show also clears +0x0c and sets shown +0x08, hide clears +0x08 |
confirmed (code) |
0x001c4500 |
Radar_SetTintState |
the disc colour state (HUD) | confirmed (code) |
0x001c4528, 0x001c45a0 |
Radar_SetBlipMode, Radar_SendSlotMode |
find the handle's slot on a layer, then send its dot message 0x19 with the mode (Blip modes) |
confirmed (code) |
0x001c4640 |
Radar_SetBlipIconLock |
layer 0 slot +0x34 = the value; while it is set Radar_SetSlotIcon and Radar_SetBlipIcon do nothing (its caller HUD_RadarSetBlipIconLock, 0x001b2bf0, is the flag of HUDSetRadarItemTexture) |
confirmed (code) |
0x001c4690 |
Radar_SetSlotIcon(scale, radar, slot, icon) |
unless locked: the dot's two size floats (+0xbc, +0xc0) × scale, message 0x11 with the icon (the dot's rectangle), slot +0x18 = icon |
confirmed (code) |
0x001c4768 |
Radar_SetBlipIcon(scale, radar, handle, icon, layer) |
the same by handle; a type-9 slot's widget gets the sprite word and the size instead | confirmed (code) |
0x001c48c0, 0x001c4960, 0x001c49d0 |
Radar_SetBlipColour, Radar_SetSlotColour, Radar_ChangeBlipColour |
the dot's colour (0x001e9ac0) and slot +0x14, by handle and layer, by slot, or by handle on layer 0 |
confirmed (code) |
0x001c4a40 |
Radar_SetBlipType(radar, handle, type) |
changing to type 9 makes the slot's BaseWidget (size 1, depth 20,000, colour 0x005fd310, sprite 0x16a = 362, batch 2); leaving type 9 frees it; slot +0x2c = type |
confirmed (code) |
0x001c4be0 |
Radar_MarkBlip |
an enemy seen by the scanner: re-sends the slot's colour on a first mark, mark timer +0x2e = 300 updates, marked +0x30 = 1 |
confirmed (code) |
0x001c4c88 |
Radar_IsObjectiveBlip |
layer 0 slot type is 10 | confirmed (code) |
0x001c4d00 |
Radar_AddBlip |
The radar; also records the batch at +0x20, icon 0x45 at +0x18, flash counter +0x32 = 100 and phase +0x33 = 2; types 7, 9, 10 and 12 get +0x30 = 1 (always shown) |
confirmed (code) |
0x001c4ff8 |
Radar_FreeSlot |
frees the widget, kills the dot (vtable +0x4c) in task phase 0, resets every field |
confirmed (code) |
0x001c5108 |
Radar_RemoveHandle |
frees the handle's slot when found | confirmed (code) |
0x001c5158 |
Radar_FindFreeSlot |
first slot whose in-use byte +0x31 is 0, or -1 |
confirmed (code) |
0x001c5188 |
Radar_FindSlot(radar, handle, layer) |
the (layer + 1)-th slot holding the handle, or -1 |
confirmed (code) |
0x001c5210 |
Radar_Update(radar, playerMatrix) |
per frame from HUD_Update; below the table |
confirmed (code) |
0x001c60b0 |
Radar_Render |
the map disc (HUD) | confirmed (code) |
0x001c6730 |
Radar_MoveSlotToBatch48 |
recreates a slot's dot in batch +0x48, keeping its size and icon; Radar_Update does it for icons 25 and 26 (so batch +0x48, depth 10,000, holds those two) |
confirmed (code) |
0x001c6878 |
Radar_GetBlipPosition(out, radar, handle) |
the layer-0 dot's world position (its vtable +0xac); the instruction arrow uses it |
confirmed (code) |
Radar_Update (0x001c5210), each frame while the radar is created and on, confirmed (code):
- The player's position and matrix are copied to
+0x70and+0x80. - A slot in use with
+0x31= 1 follows its object (position from the handle's vtable+0xac; a dead handle frees the slot);+0x31= 2 is a fixed point (slot+0x00). - Placement: the offset from the player in x and y is turned by the camera; at distance
d< zoom (+0x20) the blip sits at offset × 0.12 / zoom from the disc centre (+0x2930,+0x2938), otherwise on the edge at 0.9 × 0.12 along its direction (0x0050e9bc). Screen x adds, screen y subtracts the offset's second component. - Objectives (type 10): when the object is more than 1 m (
0x0050e9ac) above or below the player and within 20 m (0x0050e9b8, |dx| + |dy|), icons 27 and 28 are re-coloured and drawn at 0.9 (0x0050e9b0), icon 22 at 1.2 (0x0050e9b4), icons 74 and 75 left alone; the first time in a story level with hints on (W_GameState + 0x56e2, not an Armies of the Night level), hint0x16is queued (game flag0x10000000). A new objective blip flashes for 100 updates (alpha toggled every 4) before it is drawn solid. - Enemies (types 5, 6, 8): inside the zoom they show only while the radar is shown, not hidden, its
+0x14is set and the slot is marked (+0x30). A police blip (type 8) within 0.9 × zoom of a story player whose brain is type 1, while marked or+0x18is set, queues hint 8 once (flag0x200; not in levels 80 and 99). The mark timer+0x2ecounts down each update; at 0 the mark is cleared and the dot hidden unless+0x18is set. - The player arrow (type 9): a
BaseWidget(depth 20,000, on top of the blips) at the blip position (the disc centre), turned by the player's heading minus the camera's (Quat_Headingof the human's rotation and of radar+0x80, into widget+0xa0, radians). Confirmed (runtime): with the player turned 79° to the left of the camera's view (heading 154° against 233°, clockwise from +y), the arrow pointed left; facing along the view it points up. So a positive difference turns the arrow clockwise on screen. With two players and two views player 0's icon0x16bbecomes0x169or0x15f, player 1's0x16a0x168or0x15e(byW_GameState + 0x90); icons 25, 26 and those four are not turned. Its size is set to 0.026 (0x0050e9cc) and then, the same frame, to 0.035 for icons 25 and 26 and 0.03 otherwise (BaseWidget_SetSize(s, s, widget, 1); with mode 1 the height follows the width by the rectangle's aspect, Widgets; inferred thatsends as the width in overlay units). - Icons 25 and 26 are moved to batch
+0x48when not already there (0x001c6730).
GUI/ScreenFlowController.cpp¶
0x001c7e80-0x001c8710: the screen stack the profile manager and the Rumble menu run on; its methods are reached
through an interface vtable (0x0053a9d8, reused by RM_Controller and PM_Controller).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001c7e80 |
ScreenFlowController_Construct |
allocates the state stack (SFC_States, a list) at +0 and the shared-data map (SFC_SharedData) at +4 |
confirmed (code) |
0x001c8010 |
ScreenFlowController_AddTransition |
(flow, state, code, next): state's map (state +8) entry code → next (Screen flow) |
confirmed (code) |
0x001c80c8 |
ScreenFlowController_PushState |
the vtable entry for push | confirmed (code) |
0x001c80e8 |
ScreenFlowController_Push |
Exit the old top, push, Enter(flow) the new one |
confirmed (code) |
0x001c81e8 |
ScreenFlowController_UnwindTo |
Exit every state, erase those above the target, Enter the target |
confirmed (code) |
0x001c82e8 |
ScreenFlowController_Pop |
Exit and free the top, Enter the new top |
confirmed (code) |
0x001c83c8 |
ScreenFlowController_Update |
the top's Update: -0x100 stay, -0xff pop, else follow the map (unwind or push); true when empty |
confirmed (code) |
0x001c8590 |
ScreenFlowController_Destroy |
frees the stack and the shared-data map | confirmed (code) |
0x001c8628 |
ScreenFlowController_GetTop |
the top state, 0 when empty | confirmed (code) |
0x001c8658 / 0x001c8710 |
ScreenFlowController_SetShared / _GetShared |
shared-data map: set key → value; get (0 when absent) |
confirmed (code) |
GUI/SubTitle.cpp¶
The caption system, the object at 0x00619570 (HUD +0x18d30), 0x001ca898-0x001cb190
(Movies has the format and drawing); the anchor 0x001cafa0 is its release. After it, the
caption pager at HUD +0x18e60.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001ca898 |
Captions_Construct |
text widget (class 0x001c16a8) at +0x60, kind +0x50 = 3, position (0.5, 0.8) |
confirmed (code) |
0x001ca908 |
Captions_SetPosition |
stores the position, adjusts it to the screen (Camera_AdjustScreenPoint), resets |
confirmed (code) |
0x001ca950 |
Captions_Draw |
Movies | confirmed (code) |
0x001cab90 |
Captions_OnSubtitlesChunk |
chunk 0x51 handler: the chunk into 0x0050ea74 |
confirmed (code) |
0x001cabc0, 0x001cacc0, 0x001cad38 |
Captions_Init, _SelectLanguage, _SelectScene |
Movies | confirmed (code) |
0x001cac78 |
Captions_Reset(captions, keep) |
clears the current caption (+0x14, +0x1c, the name +0x24, +0x44, +0x48) |
confirmed (code) |
0x001cae58 |
Captions_IsFreezing |
returns +0x58 (a kind-2 caption, Boot) |
confirmed (code) |
0x001cae60 |
Captions_WaitTitleCard |
Scenes | confirmed (code) |
0x001cafa0 |
Captions_Release |
from WorldLevel_Release: frees the Subtitles chunk, resets, releases the text widget |
confirmed (code) |
0x001cb000, 0x001cb008 |
Captions_SetHoldFlag, Captions_GetHoldFlag |
+0x4c (scene caption event 6) |
confirmed (code) |
0x001cb010, 0x001cb190 |
Captions_SetKind, Captions_Next |
Movies | confirmed (code) |
0x001cb2a8 |
CaptionPager_Init |
once: screen effect (1.0, type 1, ScreenFx_Queue), W_GameState + 0x438 = 1, Captions_Init, player 1's record bound |
confirmed (code) |
0x001cb340 |
CaptionPager_OnCommand |
command 1 shows the next caption (Captions_Next) and stamps the time +0x70 |
confirmed (code) |
0x001cb398 |
CaptionPager_Update |
250 ms after the last step: the pad pass with mask 0xf000, then clears the input record |
confirmed (code) |
0x001cb418 |
CaptionPager_Render |
checks readiness only; draws nothing | confirmed (code) |
GUI/TextEntryPad.cpp¶
The name keyboard of PM_Create and the Rumble gang name (Front end); vtable 0x0053acc8,
input interface 0x0053aca0; parts allocated with the file's tag: an OptionGrid (+0x70), a UsageInfo (+0x74)
and a TextWidget (+0x98); the name buffer at +0x7d, its limit +0x7c = 25 bytes (PM_Create checks 8 itself).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001cc1a0, 0x001cc210 |
TextEntryPad_Construct, TextEntryPad_Destroy |
widget base and vtables | confirmed (code) |
0x001cc240 |
TextEntryPad_Shutdown |
shuts down and frees the grid, the text and the usage line | confirmed (code) |
0x001cc2f0 |
TextEntryPad_Setup(pad, pos, ?, ?, onDone, owner, rumble) |
name text (size 1.2, font 3; size 2.0, font 6 for the profile style) at y 0x0050eb04 / 0x0050eb08; usage line (string 0x1a centred, or 0x1f left-aligned); grid at y 0x0050eb0c / 0x0050eb10, rows of 12, cue 7, row gap 0.013, one item per character of string 0x97 (a space as size 0.94 code 0x20), then 0x99 (OK) and 0x9a (DEL); colour 0x005fd320 (0x005fd328 profile style) |
confirmed (code) |
0x001cc998 |
TextEntryPad_SetText |
the buffer and the shown name | confirmed (code) |
0x001cca08 |
TextEntryPad_IsReady |
grid and usage line ready | confirmed (code) |
0x001cca80 |
TextEntryPad_OnCommand |
the owner's handler (+0x78) first; then on accept: OK with a name that is not all spaces plays 0xb and calls onDone(name), otherwise 0xe; DEL removes the last character (0xc); a character is appended (an _ item types a space, 0xa) or refused at the limit (0xe), and at the limit the cursor jumps to OK; commands 0 and 1 on items 8 and 32 also jump to OK (inferred: the grid's edges) |
confirmed (code) |
0x001ccd08, 0x001ccd90 |
TextEntryPad_Update, TextEntryPad_Render |
grid, text and usage line | confirmed (code) |
After GUI/TextEntryPad.cpp (no path string): TextWidget¶
TextWidget (The widget classes), vtable 0x0053ae78.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001ccf88, 0x001ccfc0 |
TextWidget_Construct, TextWidget_Destroy |
vtable, reset; delete frees when bit 0 | confirmed (code) |
0x001ccff0 |
TextWidget_Reset |
empty text, invisible, inactive, shadow +0xc4 = 0x80, reveal +0xc0 = 1.0, set-up +0x58 = 0 |
confirmed (code) |
0x001cd060 |
TextWidget_Setup |
The widget classes; set-up +0x58 = 1 |
confirmed (code) |
0x001cd180, 0x001cd188 |
TextWidget_IsSetUp, TextWidget_Release |
read and clear +0x58 |
confirmed (code) |
0x001cd190 |
TextWidget_SetPosition |
+0x60 |
confirmed (code) |
0x001cd1b0, 0x001cd1b8, 0x001cd1c0 |
TextWidget_IsActive, _SetActive, _SetVisible |
+0x54, +0x54, +0x50 |
confirmed (code) |
0x001cd1c8, 0x001cd1d8 |
TextWidget_SetColour, TextWidget_SetAlpha |
colour +0x5c; its alpha byte +0x5f |
confirmed (code) |
0x001cd1e0 |
TextWidget_SetText |
copies the text to +0x04, line height +0xb4 = metrics +0x7c + +0x84, width +0xb0 by Font_Measure |
confirmed (code) |
0x001cd280 |
TextWidget_Update |
empty | confirmed (code) |
0x001cd288 |
TextWidget_Render |
visible, active and not empty: Font_Draw at +0x60; with mode3D (+0x74) the text is placed in 3D at the widget's world position (0x0017a1b0, font 5) |
confirmed (code) |
UsageInfo (GUI/TutorialHUD.cpp)¶
The button legend line (The widget classes): vtable 0x0053b0f0, 0x2d0 bytes, a markup text
widget at +0x40, a 0x95-byte text buffer at +0x230, its rectangle at +0x30. It sits in TutorialHUD.cpp's TU
(before its stub 0x001cfea0; inferred). Slots are offsets from the vtable start minus 4 (GCC 2 {delta, fn}).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001cea70 |
UsageInfo_Construct |
widget base, the markup text widget | confirmed (code) |
0x001ceab0, 0x001ceb00 |
UsageInfo_Destroy, UsageInfo_Shutdown |
slots +0x60, +0x68; shutdown clears the text and the created flag +0x0c |
confirmed (code) |
0x001ceb40 |
UsageInfo_Setup(info, pos, leftAlign) |
once: text at pos, size 1.0, colour 0x005fd310, font slot 3, alignment 0 (left) when leftAlign, else 2 (centred); first text string 0x1a; base size 1.0; stores the measured rectangle |
confirmed (code) |
0x001cec28 |
UsageInfo_SetText |
copies at most 0x95 bytes and sets them; nil is ignored |
confirmed (code) |
0x001cec78 |
UsageInfo_SetColour |
slot +0x20: the text colour (0x001b94d0) |
confirmed (code) |
0x001cec98 |
UsageInfo_SetAlpha(info, a) |
text +0x1b1 = a, +0x1b4 = 1 (alpha override); the menus' fades use it |
confirmed (code) |
0x001cecb0 |
UsageInfo_SetPosition |
slot +0x08 |
confirmed (code) |
0x001cecd0 |
UsageInfo_GetRect |
slot +0x70: (x, 0, height, w) of the measured text |
confirmed (code) |
0x001ced38 |
UsageInfo_IsCreated |
slot +0x88: +0x0c |
confirmed (code) |
0x001ced40 / 0x001ced88 |
UsageInfo_Update / UsageInfo_Render |
slots +0x30 / +0x38: update the text when ready; draw it when visible and ready |
confirmed (code) |
CircledText (no path string)¶
0x001d0aa8-0x001d16e8, in the TU that ends at the stub 0x001d2de0 with the GameMenu base. A widget class
(vtable 0x0053b390, 0x5fc bytes; our name) drawing a text after up to two circled characters: a back sprite
(+0x140), two circle sprites (+0x250 + 0x100 × i) each with a one-character TextWidget (+0x450 + 0xd0 ×
i), and the main TextWidget (+0x60). The mission-select and game-stats screens build it in 0x600-byte blocks
they allocate with the tag CircledTextHeader, which is only a name: the class of that name is the pause menu's
header (The pause header), confirmed (code) at the constructor calls 0x001bc5a8,
0x001bd838 and 0x002155a8. Fields: circle width/height +0x5f0/+0x5f4,
circle count +0x5f8, text offset +0x30, circle offset +0x240, colours +0x130 (normal, 0x005fd320) and +0x134
(focused, 0x005fd318), reveal +0x50.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001d0aa8 / 0x001d0b70 |
CircledText_Construct / _Destroy |
the parts above; slot +0x60 |
confirmed (code) |
0x001d0c68 |
CircledText_Shutdown |
slot +0x68: releases every part |
confirmed (code) |
0x001d0d18 |
CircledText_Setup |
slot +0xc0 (circleW, circleH, owner, widget, pos, textOff, circleOff, metrics, backSprite, circleSprite, backInst, circleInst), once: text font flags 4; back sprite size 1.0, depth 8,400, grey (128, 128, 128); circles size 0.1, depth 8,500, red (160, 16, 16), sized circleW × circleH; spacing divisor 0x0050ec64 = 3 (2 with device flag 0x02); character y offset 0x0050ec68 = circleH × 0.1; reveal 1.0 |
confirmed (code) |
0x001d0fe8, 0x001d1008 |
CircledText_SetText, _SetCircled |
slots +0xc8, +0xd0: the main text; one character of the string per circle, count = its length |
confirmed (code) |
0x001d1098, 0x001d1108, 0x001d1178 |
_SetCircleColour, _SetCharColour, _SetBackColour |
slots +0xd8, +0xe0, +0xe8 |
confirmed (code) |
0x001d1198 |
CircledText_IsReady |
slot +0x88: created and the circle batch (+0x204) resident |
confirmed (code) |
0x001d11c8 / 0x001d11f8 |
CircledText_SetReveal / _GetRect |
slots +0xa8 / +0x70: reveal clamped to 0-1; the rectangle with its height × the reveal |
confirmed (code) |
0x001d1250 |
CircledText_Update |
slot +0x30: the back sprite over the rectangle, its texture rectangle cut by the reveal; circles left to right from the left edge + circleOff, each step circleW / 3, their height × the reveal; the main text after the last circle; visible while the reveal is above 0 |
confirmed (code) |
0x001d16e8 |
CircledText_Render |
slot +0x38: back sprite, text (focused colour when slot +0xa0 is true), each circle and its character (recoloured to +0x134 unless its colour is the colour-table entry 0) |
confirmed (code) |
OptionGridItem (GUI/OptionGrid.cpp)¶
0x001d3bb0-0x001d3f78, after the stub 0x001d3b90, so the start of OptionGrid.cpp's TU (inferred). The grid
item (vtable 0x0053b818, 0x60 bytes; OptionGrid) wraps one widget (+0x50) and a code
(+0x54); +0x40 is focused, +0x20 the position, +0x30 the widget's rectangle.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001d3bb0 / 0x001d3c18 |
OptionGridItem_Construct / _Destroy |
from OptionGrid_AddItem (0x001d4230): widget, code, created; slot +0x60 |
confirmed (code) |
0x001d3c40 / 0x001d3c90 |
OptionGridItem_SetWidget / _SetPosition |
keep the widget and copy its rectangle; set the position and forward it (widget slot +0x08) |
confirmed (code) |
0x001d3cd0 / 0x001d3d18 |
OptionGridItem_Focus / _Unfocus |
slots +0x90 / +0x98: forward and set +0x40 |
confirmed (code) |
0x001d3d60 / 0x001d3d98 |
OptionGridItem_SetColour / _GetColour |
slots +0x20 / +0x28: forward; without a widget the colour is 0x005fd310 |
confirmed (code) |
0x001d3dd8 |
OptionGridItem_GetCode |
+0x54, read by 0x001d43f0 |
confirmed (code) |
0x001d3de0, 0x001d3e30, 0x001d3e90 |
OptionGridItem_IsReady, _Update, _Render |
slots +0x88, +0x30, +0x38: forward to the widget when it is ready |
confirmed (code) |
0x001d3ef0 / 0x001d3f78 |
OptionGrid_Construct / _Destroy |
vtable 0x0053b768, input interface 0x0053b740 at +0x6c; the item array +0x74 freed on destruction |
confirmed (code) |
GUI/OptionGrid.cpp¶
0x001d4000-0x001d5808: OptionGrid (vtable 0x0053b768, input interface 0x0053b740) holding up to 50
OptionGridItems (0x60 bytes) that each wrap an OptionGridTextWidget (vtable 0x0053b8c8, 0x200 bytes).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001d4000 |
OptionGrid_Shutdown |
Slot +0x68: when created (+0x0c), unfocuses, frees every item's text widget and OptionGridItem and empties the vector (+0x74..+0x78). | confirmed (code) |
0x001d4110 |
OptionGrid_Setup |
Position (0,0,y,1), up to 5 per-row counts at +0xb0, owner +0x80, HUD pad 0 at +0x50, centre x 0.5, move cue 4, wrap on, row gap 0, selection 0. | confirmed (code) |
0x001d4200 |
OptionGrid_SetPad |
Input record +0x50 = HUD_GetVirtualPad(hud, player). |
confirmed (code) |
0x001d4230 |
OptionGrid_AddItem |
At most 50 items: allocates and sets up an OptionGridTextWidget, wraps it in an OptionGridItem with the item code, appends it. | confirmed (code) |
0x001d43e8, 0x001d43f0 |
OptionGrid_GetSelected, OptionGrid_GetItemCode |
Selected index +0xa4; item i's code (OptionGridItem +0x54). | confirmed (code) |
0x001d4418 |
OptionGrid_IsReady |
Slot +0x88: 1 once every item is ready (item slot +0x88), latched in +0x10. gui.md's "active" name for this address is this readiness check. | confirmed (code) |
0x001d44b8 |
OptionGrid_IsSelectable |
Item i exists and its text widget is active (slot +0x58). | confirmed (code) |
0x001d4530, 0x001d4570 |
OptionGrid_GetColumn, OptionGrid_ClampColumn |
Column of the selection within its row; a column clamped to a row's length - 1. | confirmed (code) |
0x001d4590, 0x001d45c8, 0x001d45f8 |
OptionGrid_GetRowOf, OptionGrid_GetRowLast, OptionGrid_GetRowFirst |
Row of item i, and a row's last and first item index, from the per-row counts. | confirmed (code) |
0x001d4628, 0x001d4790 |
OptionGrid_MoveUp, OptionGrid_MoveDown |
With 2+ rows, the same column in the row above/below, wrapping, skipping unselectable items recursively; cue 0xe and +0xc4 set when the move lands on the same item. | confirmed (code) |
0x001d48e0, 0x001d4a30 |
OptionGrid_MoveLeft, OptionGrid_MoveRight |
Previous/next selectable item: across all items (wrap when +0x98) when +0x9c = 0, else within the row; cue 0xe when nothing else is selectable. | confirmed (code) |
0x001d4b88 |
OptionGrid_Select |
Unfocus the old item, focus item i, play cue +0x94 when asked. | confirmed (code) |
0x001d4c40 |
OptionGrid_OnCommand |
Input interface slot +0x08: owner's handler (+0x80) first; if it returns 0, commands 0-3 move; stamps +0x84 with the game time. | confirmed (code) |
0x001d4d28, 0x001d4db8 |
OptionGrid_Focus, OptionGrid_Unfocus |
Slots +0x90 / +0x98: clear the input record, focused +0x40 = 1 / 0, (un)focus the selected item, stamp +0x84. | confirmed (code) |
0x001d4e48 |
OptionGrid_SetAlpha |
Alpha of one item (all when -1; the others 255) through the item colour slots +0x28/+0x20. | confirmed (code) |
0x001d4f20 |
OptionGrid_Update |
Slot +0x30: when ready, focused and 20 ms after the last command, a d-pad pass (mask 0xf000; auto-repeat, or plain after a refused move) and a button pass (0xffff0fff); then lays rows out left-packed or centred from y +0x28, row step item height + gap +0xa0; total height +0x38; fewer than 3 items never wrap. | confirmed (code) |
0x001d52c8 |
OptionGrid_Render |
Slot +0x38: when visible and ready, renders every item. | confirmed (code) |
0x001d5350, 0x001d5398, 0x001d53f8 |
OptionGridTextWidget_Construct, _Destroy, _Shutdown |
Text widget +0x50 and separator text widget +0x120; destructor slot +0x60; shutdown slot +0x68 releases both. | confirmed (code) |
0x001d5450 |
OptionGridTextWidget_Setup |
Slot +0xa8: text in 0x005fd310; with a separator, a " : " text in the item colour after it; box width covers both. |
confirmed (code) |
0x001d55e8 |
OptionGridTextWidget_IsCreated |
Created (+0x0c) != 0; was undefined code, created by this pass. | confirmed (code) |
0x001d55f8, 0x001d56a0 |
OptionGridTextWidget_SetPosition, _Update |
Slot +0x08 moves text and separator (at x + text width); slot +0x30 updates both. | confirmed (code) |
0x001d5700 |
OptionGridTextWidget_Render |
Slot +0x38: 0x005fd318 when active and focused, else the item colour +0x1f4 with alpha +0x1f3; separator alpha 255 or the text's by +0x1f8. |
confirmed (code) |
After the Rumble result screen: GUI/ScrollingMenu.cpp start, file inferred¶
0x001e09e8-0x001e0a98: the first two ScrollingMenuItem methods (vtable 0x0053c190), before the file's path
string appears.
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001e09e8 |
ScrollingMenuItem_Construct |
From 0x001e1830: index +0x60, scale 1.0, alpha 255. |
confirmed (code) |
0x001e0a58 |
ScrollingMenuItem_SetIndex |
Slot +0xf0: index +0x60, refresh (slot +0xa8). | confirmed (code) |
GUI/ScrollingMenu.cpp¶
0x001e0a98-0x001e33c8: the vertical list used by the Tutorial page and Rumble screens. Each entry wraps a widget in
a ScrollingMenuItem (vtable 0x0053c190, 0x90 bytes; constructor 0x001e09e8 in the previous chunk) that can show
a second "description" widget under it once focused. ScrollingTextWidget (vtable 0x0053c2c8) is the usual entry
widget; it has no path string of its own and is placed here by address (inferred).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001e0a98 |
ScrollingMenuItem_ReleaseDescription |
slot +0x108: shuts down and frees the description widget +0x64 |
confirmed (code) |
0x001e0af0 |
ScrollingMenuItem_Destroy |
slot +0x60 |
confirmed (code) |
0x001e0b18 |
ScrollingMenuItem_SetPosition |
slot +0x128: +0x20, moves the main widget +0x60 (its slot +0x08) |
confirmed (code) |
0x001e0b58 |
ScrollingMenuItem_SetReveal |
slot +0xa8: reveal clamped 0-1 to the main widget; the description gets the share past the main widget's part (+0x68 / height), 0.99 or more becomes 1 |
confirmed (code) |
0x001e0c90 |
ScrollingMenuItem_GetRect |
slot +0x70: main rectangle plus the shown description's height, minus 2 / screen height, plus +0x7c; height times the reveal |
confirmed (code) |
0x001e0e48, 0x001e0eb0 |
ScrollingMenuItem_Focus, _Unfocus |
slots +0x90/+0x98: focus stamps the time +0x74 and sets +0x40; unfocus clears both widgets' focus |
confirmed (code) |
0x001e0f18 |
ScrollingMenuItem_IsReady |
slot +0x88: both widgets ready |
confirmed (code) |
0x001e0f98 |
ScrollingMenuItem_Update |
slot +0x30: description placed under the main widget (x plus half or full width by +0x78, y offset +0x7c); while focused and +0x6c ms (550) after focus, its reveal grows by +0x70 a frame (cue 0xd as it starts), otherwise shrinks; alpha byte +0x80 |
confirmed (code) |
0x001e12a0 |
ScrollingMenuItem_Render |
slot +0x38: main widget, then description, when visible and ready |
confirmed (code) |
0x001e1338 |
ScrollingMenu_Construct |
0x110 bytes, vtable 0x0053bfc8, input interface 0x0053bfa0 at +0x7c; code-to-item map +0x84 (header 0x18 bytes, tag STL), item vector +0x94-+0xa0 |
confirmed (code) |
0x001e1458 |
ScrollingMenu_Destroy |
slot +0x60: frees vector and map |
confirmed (code) |
0x001e1550 |
ScrollingMenu_Shutdown |
slot +0x68: clears the map; with +0xd4 (owns widgets) frees each item's widgets; frees the items |
confirmed (code) |
0x001e16f0 |
ScrollingMenu_Setup |
slot +0xc0 (pos, owner, owns): input = HUD virtual pad 0, owner +0xcc, delay +0xc4 = 550, step +0xc8 = 0.25, max visible +0xb0 = -1, clip height +0xb8 = 10000 (none) |
confirmed (code) |
0x001e1800 |
ScrollingMenu_SetPlayer |
slot +0x198: input +0x60 = HUD_GetVirtualPad(hud, n) |
confirmed (code) |
0x001e1830 |
ScrollingMenu_AddItem |
slot +0xc8 (code, widget): new item with delay, step and +0xd8/+0xdc; map[code] = item; appended; count +0xac |
confirmed (code) |
0x001e1a38 |
ScrollingMenu_SetItemDescription |
slot +0xd0: item slot +0x100 with the widget, reveal 0 |
confirmed (code) |
0x001e1be0, 0x001e1c40 |
ScrollingMenu_GetRect, _IsReady |
slots +0x70/+0x88: rectangle +0x30 scaled by reveal +0x50; all items ready |
confirmed (code) |
0x001e1cb8, 0x001e1d70 |
ScrollingMenu_MoveUp, _MoveDown |
slots +0x1a8/+0x1b0: at an end cue 0xe and auto-repeat off (+0x104 = 1); else Select(i -/+ 1), starting scroll state 1 or 2 when the cursor passes the middle row (+0x100 adjusts for even counts) |
confirmed (code) |
0x001e1e48 |
ScrollingMenu_Select |
slot +0x1b8: +0x104 = 0, unfocuses the old item (releases its description when +0xe4), +0xa8 = i, focuses it, cue 4 |
confirmed (code) |
0x001e1f28, 0x001e1f68, 0x001e1fa8 |
ScrollingMenu_GetSelectedWidget, _HasDescription, _GetItemWidget |
slots +0x110/+0x118/+0x120 |
confirmed (code) |
0x001e1fe0 |
ScrollingMenu_OnCommand |
interface slot +0x08: the owner +0xcc sees the command first; else 0 = up, 1 = down; stamps +0xd0; returns 1 |
confirmed (code) |
0x001e2090, 0x001e2130 |
ScrollingMenu_Focus, _Unfocus |
slots +0x90/+0x98: clears the input record (0x00146000); focuses the selected item unless +0xe8 |
confirmed (code) |
0x001e21c0 |
ScrollingMenu_SetPosition |
slot +0x08: +0x20 |
confirmed (code) |
0x001e21d8 |
ScrollingMenu_Update |
slot +0x30: input only when focused, not scrolling, 100 ms after +0xd0 and the item's description not focused (repeat pass 0x001e93c0, or plain 0x001e9518 when +0x104, mask 0xf000; button pass 0x001e9468); scroll states +0x80 (1 up, 2 down, 3 reveal top, 4 hide top) step +0xc8; stacks the window (+0xa4 first, +0xac count, at most +0xb0) from +0x20; clip to +0xb8 sets fit count +0xb4 and a partial reveal of the last item |
confirmed (code) |
0x001e2e40 |
ScrollingMenu_Render |
slot +0x38: the visible items, one more in state 3 |
confirmed (code) |
0x001e2f40, 0x001e2f98 |
ScrollingTextWidget_Construct, _Destroy |
0x270 bytes, vtable 0x0053c2c8; MessageHUD text +0x70 |
confirmed (code); file inferred |
0x001e2fe8 |
ScrollingTextWidget_Setup |
slot +0xc0: position +0x20, size +0x30, offset +0x60; text width = size x, colour 0x005fd310; focus colour +0x260 = 0x005fd310 |
confirmed (code) |
0x001e3070 |
ScrollingTextWidget_SetText |
slot +0xc8 (wrapWidth, text, always): the wrap width goes to MessageHUD_ReadAutoIndent (+0x1d4, unless the text starts with <AUTOINDENT f>), when it is above 0 or the last argument is set; height +0x38 = text height |
confirmed (code) |
0x001e3140, 0x001e31e0 |
ScrollingTextWidget_SetMetrics, _Shutdown |
slots +0xd0/+0x68: MessageHUD_SetMetrics wrap box; shuts the text down |
confirmed (code) |
0x001e3248 |
ScrollingTextWidget_GetRect |
slot +0x70: +0x30, height times reveal |
confirmed (code) |
0x001e32a0 |
ScrollingTextWidget_Update |
slot +0x30: text at x - width / 2 + offset; crop +0x1f4 = max(0, height - text height); visible while reveal > 0 |
confirmed (code) |
0x001e33c8 |
ScrollingTextWidget_Render |
slot +0x38: focus colour +0x260 when focused, else 0x005fd320 |
confirmed (code) |
After GUI/ControlMenuHUD.cpp (no path string): text boxes, headers, menu input¶
0x001e6e98-0x001e9b88, between the pause Stats screen (Pause) and the Rumble
screens: the boxed text base of the HUD's scroll-in texts, the three header classes, the menu input passes
(Front end) and particle helpers. No path strings; grouping by address (inferred).
| Address | Name | What it does | Evidence |
|---|---|---|---|
0x001e6e98, 0x001e6ef0 |
BoxedText_Construct, _Destroy |
vtable 0x0053c7e8: MessageHUD base, backdrop BaseWidget +0x210, icon +0x310 |
confirmed (code) |
0x001e6f50 |
BoxedText_SetupBox |
(pos, style 0-7): margins +0x418-+0x430 (0.02, 0.06, 0.005, -0.2, -0.027, 0.05, adjusted by style); backdrop +0x204 (part_page0 rect 78, (0,0,0,143), depth 8000) and icon +0x208 (rect 27, depth 11000) per style: default both, 0 icon with shadow, 4 backdrop only |
confirmed (code) |
0x001e72c0, 0x001e7300 |
BoxedText_Shutdown, _SetVisible |
slots +0x68/+0x50; the icon only shows with a sprite (+0x3d8 != -1) |
confirmed (code) |
0x001e7330, 0x001e7338, 0x001e7358 |
BoxedText_SetIconSize, _SetIconSprite, _GetIconSprite |
+0x410; icon sprite word |
confirmed (code) |
0x001e7360, 0x001e7380 |
BoxedText_SetBackdropSprite, _SetBackdropColour |
backdrop sprite word and colour | confirmed (code) |
0x001e73a0, 0x001e7408 |
BoxedText_SetPosition, _IsReady |
slots +0x08/+0x88 |
confirmed (code) |
0x001e7468 |
BoxedText_Update |
slot +0x30: backdrop fitted round the measured text plus margins; its alpha 0x0050f2c4 = 0 except style 4; icon left, right or centred by style |
confirmed (code) |
0x001e7750 |
BoxedText_Render |
slot +0x38: backdrop, icon, text |
confirmed (code) |
0x001e77a0, 0x001e7808, 0x001e7868 |
IconTextHeader_Construct, _Destroy, _Shutdown |
vtable 0x0053cae0: HeaderBase, MessageHUD +0x70, sprite +0x260, text +0x60 |
confirmed (code) |
0x001e7898, 0x001e7a00 |
IconTextHeader_Setup, _SetupInBatch |
slots +0xc0/+0xc8: child +0x50; sprite size 0.1, depth 8000, 0x005fd310; text size 1, font 3; y gap 0x0050f2e0 = -0.015 (-0.025 in mode 2); the second takes a sprite batch |
confirmed (code) |
0x001e7b70 |
IconTextHeader_Update |
slot +0x30: sprite at pos + (0, 0x0050f2ec) sized (0x0050f2e4, 0x0050f2e8); text left-aligned, centred vertically; child below |
confirmed (code) |
0x001e7d78, 0x001e7df0 |
IconTextHeader_Render, _RenderColoured |
slots +0x38/+0xd0: grey 0x005fd310 shadow 0x80, or a given colour |
confirmed (code) |
0x001e7e58, 0x001e7e78, 0x001e7ea8 |
IconTextHeader_SetText, _ShowChild, _SetVisible |
text +0x70; child slot +0x40; visible flags |
confirmed (code) |
0x001e7ef8 |
IconTextHeader_IsReady |
slot +0xb8: sprite batch +0x324 and batches 3, 6 |
confirmed (code) |
0x001e7f50, 0x001e8020, 0x001e8120 |
CircledTextHeader_Construct, _Destroy, _Shutdown |
vtable 0x0053ca00, the pause menu's top header only (The pause header): rule B +0x390, 3 circles +0x490, 3 letters +0x790, title +0xa00, subtitle +0xad0 |
confirmed (code) |
0x001e81c0 |
CircledTextHeader_ApplyVideoMode |
the layout factors per video mode (Layout) | confirmed (code) |
0x001e8368 |
CircledTextHeader_Setup |
slot +0xa8: title size 2.23, subtitle 1.561, font slot 4; circles sprite 0x54 (rect 84), 0x005fd260, depth 8500, size 0.082 x scale |
confirmed (code) |
0x001e85e8, 0x001e8658 |
CircledTextHeader_SetCircleColour, _SetLetterColour |
the three circles or letters | confirmed (code) |
0x001e86c8 |
CircledTextHeader_Update |
slot +0x30: used circles spaced 0x0050f324, black letters, title after, subtitle under, child |
confirmed (code) |
0x001e8d00, 0x001e8dd0 |
CircledTextHeader_Render, _RenderColoured |
slots +0x38/+0xd0 |
confirmed (code) |
0x001e8ee0 |
CircledTextHeader_SetTitle |
characters before : become letters; a : adds a circle with '@' + game state +0x33a; rest is the title; last circle 0x005fd328; subtitle = level name of game state +0x56dc (table at +0x151d, 0x84 each) |
confirmed (code) |
0x001e9088 |
CircledTextHeader_IsReady |
slot +0xb8: base and circle batches |
confirmed (code) |
0x001e90f8, 0x001e9130 |
SimpleHeader_Shutdown, _Setup |
allocator tag SimpleHeader, vtables 0x0053c8e8/0x0053c968; Rumble titles: TextWidget font slot 6 (big_font), 0x005fd310, pos +0xf0, created +0xdc |
confirmed (code) |
0x001e91e8, 0x001e9250 |
SimpleHeader_Update, _Render |
measured size into +0x100; text when created |
confirmed (code) |
0x001e9298, 0x001e92d0 |
HeaderBase_Construct, _Destroy |
vtable 0x0053cbc0, child +0x50 |
confirmed (code) |
0x001e92f8, 0x001e9330, 0x001e9368 |
HeaderBase_Render, _SetColour, _PlaceChild |
renders the child; colour via child slot +0x78; moves and updates it |
confirmed (code) |
0x001e93c0 |
MenuInput_PadRepeatPass |
auto-repeating pressed bits of pad +0x19 (0x005dd810, 0x50 each) & mask; plain +0x08 = 0 |
confirmed (code) |
0x001e9468 |
MenuInput_ButtonPass |
released bits & mask | confirmed (code) |
0x001e9518 |
MenuInput_PadPlainPass |
pressed bits & mask; plain +0x08 = 1 (a held stick does not repeat) |
confirmed (code) |
0x001e95c0 |
MenuInput_Dispatch |
handler +0x1c slot +0x08: back 5 (0x30), accept 4 (0x40) after 110 ms; directions 0-3 from bits 0x1000/0x4000/0x8000/0x2000 or stick past 0.5, gap 110 ms or 400 ms while held (+0x04); stick +0x0c/+0x10, time +0x18 |
confirmed (code) |
0x001e99f8 |
HudEffect_Spawn |
named particle effect via the task system 0x00512c7c (radar blips) |
confirmed (code) |
0x001e9aa8, 0x001e9ac0 |
HudEffect_GetColour, _SetColour |
+0xb0 (and +0xb4) |
confirmed (code) |
0x001e9ae8, 0x001e9b38 |
HudEffect_SendShow, _SendHide |
task messages 0x29 and 0x2a |
confirmed (code); meaning inferred |
0x001e9b88 |
HudEffect_IsValid |
not null | confirmed (code) |
Coney's implementation¶
- Sprite sheets (
src/graphics/particle_page.h):parseParticlePagereads a0x4Cchunk's count,firstGlyphand rectangles as laid out above, refusing data shorter than its header or count; the placeholder and the two load-time words are ignored, zero padding after the rectangles is allowed, the coordinates are not checked (the original does not either). Agraphics::SpriteSheetis the rectangles plus a shared reference to the texture. -
The
0x4Chandler (src/platform/sprite_sheets.h,onParticlePageLoaded): pops the page and the0x0Bdictionary before it and pushes aSpriteSheetObjectback under0x4C, owning the dictionary and bound to its first texture. A dictionary with no texture fails the load.loadSpriteSheetResourceloads a sheet by resource name from the WAD file named by the name's decimal CRC-32; the legal screen uses it (Front end). -
The sheet table (
graphics::parseSpriteSheetTable,SpriteSheetTable): the0x4Dchunk's records, withrecord(i)(0x001828c0) andsizeOf(hash)(0x00181e50, without the original's fallback to the WAD file's size, which a caller can ask the WAD for). ItsonLoadedhandler (0x00182820) pushes the table back under0x4D; Coney's choice: with no resource manager to keep it, whoever loadswarriors.glrtakes it off the stack.addSpriteSheetHandlersregisters both handlers.
Disc check (NTSC-U, 2026-10-04, counts only): coney_tests "[disc][sprite_sheets]" with CONEY_DISC set finds
1,335 0x4C chunks in 814 entries; every one follows a 0x2A chunk, has exactly the size align16(0x14 + 16 ×
count), loads through the handlers, has a dictionary of exactly one texture and rectangles inside [0, 1] the right
way round; 1,328 have firstGlyph -1. The table in warriors.glr has 576 records; every name hash names a WAD file
whose size equals the record's size, and records 0, 1, 3, 7, 8, 10, 13 and 51 are the CRC-32s of part_page0,
part_page1, menu_system, part_fire, lighting, hud_minigames, big_font and legal_screen, as above.
- Sprite batches (
src/graphics/sprite_batch.h,SpriteBatch): a sheet, a capacity and a depth;addSprite(0x00182de0) appends a sprite record of format 0 (position in overlay-camera space, size, texture rectangle, colour) and drops it past the capacity, returning false;render(0x00197168) projects each sprite through the overlay camera (Graphics), centred on its position, and draws the batch as one call with the sheet's texture and source-alpha blending. The largest count seen is kept as at+0x74. - The 2D pass (
OverlayPass): batches queued for the frame, drawn in ascending key (the depth by default), then every batch and the queue emptied (0x00185d20, the comparator0x00184890,0x00185cc8). Coney's choices: batches with equal keys keep the order they were queued in (the original'sqsortpromises no order); batches are queued by their owner, where the original collects them by rendering its overlay world; only format 0 exists. -
coney --disc <disc> --view-sheet <sheet>lays a sheet's rectangles out in a grid and draws them as sprites through a batch and the 2D pass (Running Coney):menu_system(6 rectangles over a 512 × 256 texture) andbig_font(262 glyphs) show correctly on the NTSC-U disc. -
UI strings (
src/gui/global_strings.h,src/scripting/config_strings.h), loaded as the game does: in a game run, by the legal screen's preloads in the script system (Scripts); for--view-textand the strings disc check, by a short load that runs the game's own bytecode,enum_preload.luaand thenconfig_preload2.lua, in its Lua 4.0 virtual machine (src/scripting/lua_vm.h, Front end) withGetLanguage(the language field's value, 0 English to 4 German),GetPlatformanddoFileprovided.doFilerunsconfig_strings_<code>.lua, and the script's loops pass every entry ofGSTRING.HUD,GSTRING.CRIME,TSTRING,GSTRING.COMMANDandGSTRING.ANNOUNCEtoCfgHUDMessage(0x0035e5d0),CfgCrimeMessage,CfgTutorialMessage,CfgWarriorCommandandCfgAnnounceMessage, which fillGlobalStrings;get(id)(0x0019ee70) returns a HUD string or an empty one,set(0x0019eea0) stores one. The id is the table key the script gives, explicit in the files (GSTRING.HUD[n] = ...), not the order of the entries. The other ~1,800 binding calls ofconfig_preload2.lua(character, weapon and sound configuration) are skipped as no-ops. Coney's choices: every table is a map, so any id works (the original's HUD array at0x00600048has a size not yet known);doFile("config_strings_en")adds the.luathe WAD entry has, as the original does;GetPlatformreturns 1, as on the PS2 (below).
Disc check (NTSC-U, 2026-10-04, counts only): coney_tests "[disc][strings]" with CONEY_DISC set loads all five
languages. HUD strings: English, Spanish and German 388, French 386, Italian 387, with ids 0-388 (id 372 is unset in
English); every language has 15 crime, 27 tutorial, 8 command and 5 announce strings and a usage line 0x1f. A load
runs about 42,600 Lua instructions and skips 1,796 binding calls.
- Fonts (
src/graphics/font.h,Font): a sprite sheet with a first glyph, charactercbeing rectanglefirstGlyph + c.fontMetrics(scale)isFont_Size(0x00179808);measureisFont_Measure(0x00179958): proportional or fixed widths and the 0.56 gap of a space or0xacas Text gives them;drawisFont_Draw(0x00179c30): right and centred alignment, one sprite per glyph centred at (pen + (width + spacing) / 2, y) of size (width, h) through device slots+0x90and+0x98, and the black shadow at (0.0025, 0.004) with alpha shadow × colour alpha / 255. Coney's choices: a character past the sheet's last rectangle takes no room and draws nothing; a space draws nothing; the shadow comes just before each glyph (the page's "added first", read per glyph); the reveal fraction is not implemented yet. - Markup (
src/gui/markup.h): the tag table by index, with the characters of the glyph tags;parseMarkupsplits a text at<...>. A tag name compares exactly; a<with no>after it is text; a name not in the table is skipped and counted. All 66 names are known, the HUD icon tags 22-32 with their characters;stickCharactergives an animated stick tag's character for the text's time (its own first, Coney's choice). - Layout (
src/gui/text_layout.h,layoutText,TextWidget_Layout0x001b9600): measures each line's runs, grows the box to the widest line, places each line by its alignment and draws each run withFont::drawinto sprites tagged with their font slot;addTextSpriteshands them to each slot's batch. It implementsCOLOR(alpha × the widget's fade),SIZE,PULSE,DISPLAYTIME(hidden after, fading over the last second),BIGFONT(slot 6),MONEYPLUS/MINUS,MONEYFONT(MONEYFONT: agraphics::GlyphBase, its base 256 or 11 chosen at the tag and its origin by each run's slot, so base + (c − 1) inbig_fontand base + (c −0) otherwise; it goes toFont::measureandFont::draw, and the alignment's measure inFont::drawis taken without it, as the original's), the animated stick tags, the alignments,CR,CR2,CR3 f,CRM, every glyph tag and the closing tags;SOUNDnames and the largestFREEZEare reported for the audio layer and the timer;BOLD,BGFONTandAUTOINDENThave no effect yet. Lines break at theCRtags and, for a multi-line widget given a wrap width (the message box,RM_No2ndController's 0.6), before a word that would pass it; how the original breaks is not researched, so Coney breaks only at spaces. Coney's choices: a closing tag restores the value before its opening tag; a line takes the alignment in effect at its first character, centring and right-aligning inside the box (CCENTERandRRIGHTact asCENTERandRIGHT);CR2andCRMalways break;PULSE msscales the colour by 1 + 0.5 × sin(2π t / ms); the style's y is the first line's centre line. The text font: a text starts in slot 2, apart_page0instance, and<BIGFONT>switches to slot 6,big_font(Coney's choice, from the data: inpart_page0, first glyph 94, characters0x91-0xa0are exactly the button pictures of the tag table, while inbig_fontthey are empty but for a triangle at0x9c;big_font's rectangles 256-261 hold a circle and five d-pad pictures. Inferred from viewing the sheets with--view-sheet). - The screen flow (
src/gui/screen_flow_controller.h,ScreenFlowController,ScreenFlowState), as above: per-screen transitions (0x001c8010), push (0x001c80e8), pop (0x001c82e8), unwind (0x001c81e8) and update (0x001c83c8) with the results "stay" (-0x100) and "back" (-0xff); a code without a transition is ignored; the flow is done when its stack is empty. Coney's choice:exitis called only on an entered screen, so unwinding exits the top screen alone (the covered ones were exited when they were covered). - Widgets (
src/gui/widget.hand beside it):Widget(0x001a8e30) withinitonce (untilshutdown),updatewith the frame's game time and the player's pad,renderinto batches, and visibility.BaseWidget(0x001a1bf8, its sprite0x001a2690): one rectangle of a sheet as one sprite, after the black shadow at (0.0025, 0.004) with alpha × 128 / 255. The classes of The widget classes, as written there:BaseWidget(setup0x001a1db8): size in overlay units with the width from the rectangle's shape (or given), the aspect fix, the anchor (centre, left edge, right edge) and an optional shadow at the unshifted position with alpha × 0.502.TextWidget(0x001ccf88, setup0x001cd060, text0x001cd1e0) stands for both text widgets: a text throughlayoutTextplaced left at x, centred on it or ending at it, in the font slot given, shadow 0x80, with a fade and its own time for<PULSE>and<DISPLAYTIME>.OptionGrid(setup0x001d4110, add item0x001d4230up to 50, focus0x001d4d28, handler0x001d4c40): row counts, left-packed or centred rows, the" : "separator,kSelectedGreyfor the focused selection, the moves, wrap only from 3 items, cue+0x94and0xe, and the d-pad's plain query after a refused move, and a fade over every item (Coney's, for the pause menu's fade out).ScrollingMenu(0x001e1338,src/gui/scrolling_menu.h): no wrap, cue 4 and0xe, 100 ms between moves, a window around the cursor (Front end).UsageInfo(0x001ceb40, text0x001cec28): size 1.0,kMenuGrey, left-aligned or centred.Bar(0x001a0fd0) and the message box (src/gui/message_box.h, Front end). The colour table issrc/gui/colour_table.h. Coney's choices: a grid's row pitch adds 0.0036 to 6h / 7 so rows land 0.0505 apart at size 1.15, as measured (Front end); items past the row counts get a row each; part_page0 is Coney's font slot 2 (the original's 3). The screens that use them are on Front end. - The
METRICS1file is not read: no code uses it (above), and the fonts' metrics come from their sheets' rectangles. coney --disc <disc> --view-text <font> <text or @id>lays a text out and draws it through a batch per font and the 2D pass (Running Coney). On the NTSC-U disc,@0x1fshows the usage line as cross "ok" and triangle "back", and a test text showsbig_fontheadings, colours, sizes, alignment and every button picture correctly.
Disc check (NTSC-U, 2026-10-04, counts only): coney_tests "[disc][sprite_sheets]" finds 7 fonts among the 1,335
sheets (4 with a rectangle for all 256 bytes), all loading as fonts; "[disc][text]" loads big_font (262
rectangles over 512 × 256) and part_page0 (371, first glyph 94), finds a rectangle for all 30 glyph tags' characters
in both, and lays out all 388 English HUD strings: 10,682 sprites on 400 lines, 32 tags skipped (the tags with no
effect yet), no string empty. Rerun 2026-10-11 with <MONEYFONT> built: the swap prompts 0xc-0xe draw
part_page0 rectangle 73 (the triangle) once each, the revive prompt 4 one big_font stick picture (256-261), and
the rumble prompt 0x25 in part_page0 one cross.
TODO for research, found while implementing:
- Sprite colours (answered, 2026-10-04): 0-255 with 255 opaque (Sprite colours), as Coney takes them; nothing to change.
- Draw order (answered): the 2D key is the creation depth (Draw order), as Coney's
OverlayPassdefaults to; nothing to change. Note that instances of the 3D overlay world, when they come, sort before every 2D batch because their keys are negative. - Names for the batch functions: the
@origtags call0x00184890ResourceMgr_CompareOverlayKeysand0x00185cc8ResourceMgr_EmptyInstancesuntil the research database names them. - Names for the sheet functions: the
@origtags call themChunkLoaded_ParticlePage(0x00181b20),ChunkLoaded_ParticlePageHeader(0x00182820),ResourceMgr_SheetRecord(0x001828c0) andResourceMgr_SheetSize(0x00181e50) until the research database names them. GetPlatformon the PS2 (answered, 2026-10-04): it returns 1 (0x00357998), so the language files take theirelsebranch: "PRESS THE START BUTTON" for0x76, triangle as "back" in0x1f(confirmed (code); the wording confirmed (runtime) in PCSX2). Coney now returns 1 as well (Scripts). The original question: what does the binding return? Each language file has about twenty strings in anif Platform == 2 then ... else ... end; the two branches differ mainly in naming triangle or circle as "back" (0x1famong them). Coney returns 0 (the triangle branch, which matches Front end's reading). Which platform is 2, and which branch does the NTSC-U game show?- Who runs
config_preload2.lua, and when? (answered): the legal screen'sEnter(0x00161218), afterenum_preload.luaandconfig_preload.luaand beforeconfig_preload3.lua, all in one Lua state (Scripts); Coney's order matches for the two it runs. Originally: Coney runs it afterenum_preload.luato get the strings; the original's call site is not on this page. doFile(answered): it always adds.lua(0x003579a0formats"%s.lua",0x00579058) and runs the result as a WAD name (script system slot+0x34), as Coney does. Originally: does the binding add.luato a name without an extension (as Coney does), or look the name up some other way?- The HUD string array at
0x00600048: its size, and whatGlobalString_Getdoes with an id past it. - The font a text widget starts in (answered, The widget classes): the light
TextWidgetdraws in the slot its owner passes (the PM items and titles pass 6,big_font); the markup widget starts in slot 3 (part_page0, the same sheet as Coney's slot 2) unless 6 is passed. Originally: which instance slot does aTextWidgetdraw with before any<BIGFONT>? Coney takes slot 2 (part_page0), the sheet whose characters0x91-0xa0are the button pictures (above). Still open: what the explicit base of the twoFont_Measure/Font_Drawcall sites (slot 6 with -1, slot 3 with-'0') draws. <MONEYFONT>(answered and built, MONEYFONT).- HUD icon tags 23-31 (answered and built, the tag table).
- Layout details (answered in the tag table:
CCENTER/RRIGHT,CRM, the closing tags andPULSE's triangle swing; and the y is the first line's centre, confirmed (runtime) for the light widget).<BOLD>and<AUTOINDENT>answered too (the tag table). The stick tags' characters are answered (MONEYFONT); still open: which of the two shows first; whether the shadow is drawn per glyph (Coney) or under the whole string first. - Names: the
@origtags call0x00179808,0x00179958and0x00179c30Font_Size,Font_MeasureandFont_Draw, and0x001b9600TextWidget_Layout, all with file(unknown). - Widget geometry (answered, The widget classes): a sprite widget's anchor is chosen per
widget (0 centre, 1 left edge, 2 right edge; the PM logo uses 1) and its size is in overlay units; the
OptionGrid's rows, packing, spacing and colours are listed there. - Names: the
@origtags call0x001a2690BaseWidget_AddSprite,0x001cd1e0TextWidget_SetText,0x001cec28UsageInfo_SetText,0x001d4110OptionGrid_Setup,0x001d4230OptionGrid_AddItem,0x001d4d28OptionGrid_TakeFocusand the screen-flow functionsScreenFlowController_AddTransition,_Push,_Pop,_Unwindand_Update, until the research database names them. - Names: the
@origtags call0x0019ee70GlobalString_Get,0x0019eea0GlobalString_Setand0x0035e5d0CfgHUDMessagewith file(unknown)until the research database names them.
What the implementer still needs:
- The other instance formats (1, a 2D rotation; 2, a full matrix), the resource manager's 255 instance slots and the worlds an instance can live in (3D overlay, 2D overlay), where the original's pass finds its batches.
- The widget base's other slots (the rectangle, active, the text reveal) and
BaseWidget's timed fade. - Text widgets around the layout: the reveal fraction,
<SOUND>played once,<FREEZE>on the game timer, and the open tags above.
Open questions¶
- Blip type 12: who uses it (radar batch
+0x48answered: icons 25 and 26,0x001c6730). - What icons 22 and 355 show, and the dealer type 2's role.
- The 2D sort key (answered): the creation depth; see Draw order.
- The sheet
0x349348bdbehind the Quick Rumble menus (sheet-table record 12, inferred; Front end): its resource name (not one of the names tried). Coney loads it by its WAD file name,882067645; on the NTSC-U disc it has 7 rectangles over a 512 × 512 texture, rectangle 5 the gang collage. firstGlyphofpart_page0(94) andpart_page1(20): which text uses them, and the two explicit-base call sites inFont_Measure/Font_Draw.<MONEYFONT>(answered, MONEYFONT): a rectangle base for the glyphs, 11 inpart_page0and 256 inbig_font(Page_Rectkeeps the low 16 bits of0xd0100).- The message box (answered for the layout and input: Front end): its file.
OptionGrid(answered, The widget classes): the owner reads the code itself (0x001d43f0(grid, 0x001d43e8(grid))). Still open: setup'sa2(+0xa8) and the item flags argument (4).- The widget base's fields (
+0x0cinitialised,+0x40,+0x50the input record,+0x54-+0x68) and the second interface at+0x6c.