9.4 KiB
Menu reverse notes: game471.exe as source of truth
This work deliberately does not derive menu layout or behavior from web
screenshots. The authoritative inputs are GC/game_patched.exe and the
matching files in GC/data/2d_boost and GC/data/task_cfg.
Task flow
The global state machine at FUN_00651440 constructs these tasks in order:
| state | task | constructor | scheduler id |
|---|---|---|---|
0x10 |
CSelectMusicTask |
FUN_005aa900 |
0x260 |
0x11 |
CDifficultyTask |
FUN_005bc750 |
0x261 |
0x12 |
CGameMainTask |
gameplay constructor | — |
This proves that selecting a song and selecting its difficulty are separate screens. OpenRoller's earlier combined selector was structurally wrong.
The main vtables recovered from RTTI are:
CSelectMusicTask::vftableat0x006fbcdc;CDifficultyTask::vftableat0x006fb898.
Their render callbacks are FUN_005aca40 and FUN_005be2d0 respectively.
Scene files chosen by the executable
FUN_00446a10 chooses the language-specific select-music task config. The
English configs resolve to:
data/task_cfg/selectmusic_eng.cfg
data/2d_boost/selectmusic2_eng.rvb
data/2d_boost/selectmusic2_eng.mtx
data/task_cfg/difficulty_eng.cfg
data/2d_boost/selectmode2_eng.rvb
data/2d_boost/selectmode2_eng.mtx
The .rvb MOVI header identifies both scenes as 720x1280 @ 60 fps. Its two
dimension fields are stored height-first. This ordering is confirmed by the
root placements recovered from TIME: imc_title=(360,112),
imc_focus=(341,695), and imc_navi=(238,1136). The UI is authored directly
for the cabinet's portrait display.
The RVB top-level child layout is now parsed by gc::ParseRvbScene. For the
English music scene it is:
PREP @ 0x000033 size 0x0056cf (455 action/path bindings)
REPO @ 0x005702 size 0x0017b0
DEFN @ 0x006eb2 size 0x03046e
EXPG @ 0x037320 size 0x000054
TIME @ 0x037374 size 0x00177a
PREP exposes the real animation hierarchy, including
/imc_focus/imc_fd_jacket_anim, the four nodes below /imc_focus/imc_diff,
the eight /imc_sort/imc_sort* nodes, tg_decision, and the focus in/out
actions. The companion difficulty scene exposes its own /imc_slmode
hierarchy.
Use the local probe to inspect all bindings:
./build/openroller-rvb-probe GC/data/2d_boost/selectmusic2_eng.rvb
./build/openroller-rvb-probe GC/data/2d_boost/selectmode2_eng.rvb
The probe also accepts exact MovieClip states, for example:
./build/openroller-rvb-probe --state /=jf_slmusic_start \
--state /imc_focus=jf_focus_start \
GC/data/2d_boost/selectmusic2_eng.rvb
Recovered RVB/MTX runtime
Every animation object is a recursive node with a four-byte tag, total size,
local-data size, local data, and child nodes. DEFN supplies named MOVC and
SHAP definitions; TIME contains labeled FRAM records. Frames are display
list deltas rather than complete scenes:
PLC3creates/replaces a definition at a depth or updates its transform;RMOVremoves a depth;TRN2carries the 2D affine transform;COLTcarries RGBA multiplication, including authored visibility fades;ASRCcontains the exportedplay();,stop();, and target actions.
gc::BuildRvbSnapshot accumulates those deltas through a selected label. Its
snapshot state can either advance a play() entry to the following stop()
or request a relative animation frame globally or per MovieClip path. Per-path
evaluation is required here because the selected transition must advance while
unrelated child loops remain at their authored steady frames.
MTX starts with MTX\0; each payload is a DDS whose first dword was replaced
by the container. Restoring DDS yields a standard DDS. RVB ImageN maps to
MTX texture N-1; all 115 music resources and all 210 difficulty resources
match their declared dimensions.
Coordinates recovered from render code
The select-music render callback walks exactly twelve neighboring song slots.
The associated catalog offsets stored at 0x006e0708 are:
-5 -4 -3 -2 -1 0 0 1 2 3 4 5
FUN_00447170 supplies the stable slot geometry. Its twelve vertical pairs
are:
175/189 228/242 281/295 334/348 387/401 440/454
701/715 754/768 807/821 860/874 913/927 966/980
The fixed components in FUN_005aca40 include positions (39,497),
(251,467), (262,500), (262,522), and (330,594). These are floats read
directly from the executable's .rdata, not measurements from a screenshot.
CDifficultyTask passes sprite centres to FUN_005b33a0, which subtracts half
of the scaled source rectangle. The selected song fragments therefore resolve
to these destination rectangles:
jacket source (1,1,196,196) -> (105,167,98,98)
title source (0,197,374,34) -> (209,178,374,34)
source source (0,232,374,24) -> (220,214,374,24)
artist source (198,180,314,16) -> (220,239,314,16)
The difficulty callback centers variable-length groups using the executable's actual formulas and spacings:
(7 - count) * 0.5 * 68, with the row anchored at426 + 16;(9 - count) * 0.5 * 52, with the row anchored at433 + 16.
FUN_00447170 also supplies the transition timing used by the executable. Song
slot changes interpolate linearly over 0.125 seconds. The list enters/exits
over 0.375 seconds and shifts by one viewport width using exponent 4 easing.
The row plates retain their full dimensions; their opacity and the title scale
use the slot values 0.7, 0.8, 0.9, and 1.0. The desktop selector now
combines these executable-owned values with frame-based RVB entry, exit, sort,
focus, difficulty-change, and decision states. Confirmation is a distinct
jf_decision/jf_mode_decision phase before the task exit timeline; collapsing
both phases is why an immediate screen switch does not resemble the cabinet.
Common and navigator layers
The select task is not visually self-contained. Two additional original movies are composed with it:
data/2d_boost/common_eng.rvb/.mtx
data/2d_boost/navigator/navi_001_yume.rvb/.mtx
common_eng supplies the network/player icons and the three bottom controller
prompts. The select-music controller uses the jf_ctrl_3 layout and the
jf_ctrl_tx02, jf_ctrl_tx05, and jf_ctrl_tx08 label states (Select,
Change song order, Confirm).
The navigator is itself a 720x1280 MovieClip scene, not a single positioned
DDS. Its recovered opening state draws the bottom backing plate at
(0,1000)..(720,1232), Yume at (416,830)..(720,1280), and a separate mouth
layer. Rendering only navigator/001_yume/base.dds was therefore structurally
incorrect.
Dynamically linked carousel rows
The twelve list rows do not live in the root select-music timeline. The task
creates twelve instances of each exported linkage symbol below and attaches
them to imc_scroll_dds:
EXPG UNIQUE_71 -> mc_music_link
EXPG UNIQUE_74 -> mc_index_link
gc::BuildRvbSymbolSnapshot resolves those EXPG symbols back to their DEFN
MovieClips. mc_music_link contains the 520x34 row plate and its three score
cells. FUN_00447170 places it at x=8 and the twelve y positions listed above;
FUN_00447620 supplies row opacity (0,.7,.8,.9,1,0,0,1,.9,.8,.7,0), not a
geometric scale. The title atlas fragments do use the same values as scale.
mc_index_link is the corresponding 356x36 category plate. Entries returned
by FUN_005aa7c0 below 50000 select mc_music_link; pseudo entries at or above
50000 select mc_index_link and therefore consume a normal carousel slot.
For the Genre sort, FUN_005b40f0 loads
data/2d_boost/menu/s_j[_eng].dds. FUN_005b3fc0 selects one of its 256x32
rows and FUN_005aca40 draws it at the index clip position plus (53,2):
with the static x=88 row position this gives label x=141. The English rows are
beginner, Anime & Pops, VOCALOID, Touhou, Rhythm Game, Game, Variety, Original.
Sort-tab indirection
The integer stored in DAT_007f3134 is not the left-to-right tab number. The
eight internal sort kinds map through the executable byte table 34621857;
internal kind 0 (Genre and s_j_eng.dds) consequently drives visual frame
jf_sort3_ini. The visual tab order remains New, Monthly Theme Music, Genre,
Difficulty, Score Average, Title, Favorite, At random. Each imc_sortN child
also receives its independent jf_sortN_on/off availability frame. The small
40x18 NEW marker at (44,181) is a separate root child present in both
jf_sort3_ini and the stable jf_sort3 frame; it is not part of imc_sort1.
Executable-owned menu background
The background is a separate 3D task, not part of any RVB and not a guessed
flat colour. FUN_00577fb0 first emits a full-screen four-vertex strip with
the exact D3D colours:
top: ARGB FF30309B
bottom: ARGB FFE57386
It then draws two locally loaded TUMO resources. The loader table begins at
the literal data/model/menu_obj_05.tumo; its second 0x40-byte entry is
data/model/obj_sphere06.tumo. The latter is the 288-segment wire sphere seen
behind the list. Its recovered camera is LH, FOV 60 degrees, aspect 720/1280,
near/far 0.1/1000, looking along +Z. The sphere is translated to z=100, scaled
by 5, rotated equally about XYZ at time*0.125, and given the small authored
two-frequency vertical drift.
Finally FUN_005b73a0 draws data/2d_boost/menu/balloon.dds as a 12x4 grid
of 64px cells at y=1000. This reconstructs source rectangle (0,0,768,256);
the last 48 pixels are clipped by the 720px cabinet viewport. This is the
dark controller backing visible behind the common HUD and navigator.