forked from tsuki/openroller
Initial public source release
Split reusable rendering and format support into vectorail-core and vectorail-gc.
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
# 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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```sh
|
||||
./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:
|
||||
|
||||
```sh
|
||||
./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` now accumulates those deltas through a selected label
|
||||
and advances `play()` entry frames to their following `stop()` frame. This is
|
||||
why hidden templates and transition masks no longer appear together.
|
||||
|
||||
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:
|
||||
|
||||
```text
|
||||
-5 -4 -3 -2 -1 0 0 1 2 3 4 5
|
||||
```
|
||||
|
||||
`FUN_00447170` supplies the stable slot geometry. Its twelve vertical pairs
|
||||
are:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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`.
|
||||
|
||||
The remaining visual work is outside this recovered static scene snapshot:
|
||||
the executable-owned player/status HUD in the blank upper band, continuous
|
||||
timeline interpolation, and the exact transition timing between task states.
|
||||
|
||||
## Common and navigator layers
|
||||
|
||||
The select task is not visually self-contained. Two additional original
|
||||
movies are composed with it:
|
||||
|
||||
```text
|
||||
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`:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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.
|
||||
Reference in New Issue
Block a user