Files
openroller/docs/re_gc_menu_exe.md
T

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::vftable at 0x006fbcdc;
  • CDifficultyTask::vftable at 0x006fb898.

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:

  • PLC3 creates/replaces a definition at a depth or updates its transform;
  • RMOV removes a depth;
  • TRN2 carries the 2D affine transform;
  • COLT carries RGBA multiplication, including authored visibility fades;
  • ASRC contains the exported play();, 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 at 426 + 16;
  • (9 - count) * 0.5 * 52, with the row anchored at 433 + 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.

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.