Files
openroller/docs/re_gc_android_runtime.md

9.8 KiB

Groove Coaster Android Runtime Reverse

This is a clean-room interoperability record for the offline Android package gc2offlinev4.xapk. It documents facts used to validate the GC stage loader and Vectorail runtime. Addresses below are ELF virtual addresses in lib/arm64-v8a/libtune.so; the Ghidra project uses an additional 0x100000 image-base offset.

Provenance

XAPK     c6c394f7a1cc65331edc98aac94014668f9d5277ce17262fecf9aeeb1a2a3003
APK      62c9e739dc9b8c1bcbcb4b5234d78f154b20d1a930329b9c21a3c9236b92a0dc
libtune  689b2e4c0bc4479a3f309944b5070796878c66c0dfdbd283c11ec0bbeeea9efa

Stage and audio ZIPs use the password returned by mtxc::ObbFile::getZipPassword: eiprblFFv69R83J5.

The Android ac_10pt8tion_{easy,normal,hard}{,_ext}.dat payloads are binary identical to the corresponding files in GC/data/stage. For example both copies of ac_10pt8tion_easy.dat have SHA-256 86a75c86b91bbbe25cae78bcb51d4f7842b3fc4946e4b091f2ec5ba084497765. The Android executable is therefore a valid independent specification for the arcade files consumed by Vectorail.

Stage Selection And Containers

TuneAppMain::LoadStageData (0x9da24) loads six chart ids per song. The first three are serialized mobile ids. The other three are synthesized by prefixing those ids with ac_. GameScene::makeFilenameStageDat (0xc4bac) then appends .dat or _ext.dat.

The sampled offline package contains one-note placeholder_bgm mobile charts, while its ac_ charts contain the complete arcade route, camera, notes and background scene. GameScene::LoadStageData (0xc607c) performs this order:

  1. Load the selected main DAT from <stage-pack>.zip.
  2. Call TuneGameData::LoadGameData (0x933a0).
  3. Call TuneGameData::LoadBGData (0x93aa4) on the same bytes.
  4. Load _ext.dat only for an extra/arcade difficulty when arrange mode is off.
  5. Build runtime data and load stage resources.

For every selected ac_ chart, LoadExtData starts at byte 6 of the matching _ext.dat, replaces the four timing lists, reads another array of 99-byte notes, and links each ext note to a same-time main note. If both effective types are FLICK, the main runtime type becomes 0x10 (dual flick).

The corpus contains 2927 valid sidecars with 18965 ext notes. Every sidecar parses with the layout described by docs/stage_ext.pat, and all ext note records are FLICK entries. One unusual main chart is itself named SW_marianne_hard_ext.dat, so its sidecar is SW_marianne_hard_ext_ext.dat. File discovery must check whether removing the suffix names an existing main chart instead of excluding every _ext.dat.

Parsed Stage Sections

The parser now follows every section consumed by Android LoadGameData and LoadBGData:

Header Runtime data
0 stage config, BPM and four timing tables
1 route draw-distance keys
2 route points
3 99-byte note records
4 59-byte camera records
5 44-byte TuneBGEffectData / FlowItem keys
6 12-byte visualizer keys
7 background texture names and 20-byte image keys
8 background color keys
9 model/shader names and animated stage objects

After the variable-length object stream, Android records the current read position and treats it as the base of four relative extension offsets. The third extension contains signed object parent indices. Header word 11 points inside this table on current charts; it is not the object-stream end. The parser now derives the base from the consumed object stream exactly as Android does, which also fixes the small mobile DAT revision.

Timing, Route And Long Elements

TuneTimingData::GetTime selects the latest timing key at or before the note. Mode 1 is absolute milliseconds, mode 3 is next-note spacing, and other modes multiply the authored value by 60000 / BPM.

TuneGameData::GetWayPosition (0x953bc) linearly interpolates route points by timestamp and clamps before/after the route. WaySplitCheck (0x95c4c) clips the polyline to the requested time range, emits samples at a fixed world distance, carries the unused distance across authored segments, and stores the true interpolated timestamp for each sample.

Android spacing is 0.20 for HOLD/SLIDE/DUAL, 0.15 for SCRATCH and 0.40 for BEAT. Arcade game471.exe uses 0.55 for BEAT; Vectorail intentionally keeps the arcade value when playing arcade DATs.

LoadTuneMarkDataOne (0x967d8) confirms the wire-field translation and compatibility remaps. Runtime types 7/8 become 1, 11 becomes 10, 12/14 become 9, and 13 becomes 4. Marker effects are forced to 35 for type 10, 32 for type 9, 37 for type 15 and 11 for raw type 13. Duration types are 3, 4, 5, 10 and 15. MERRY type 6 expands to count targets separated by the authored beat spacing.

Camera

TuneGameData::GetCameraData (0x95588) independently confirms the seven anchor modes, the two interpolation paths and the orthographic/perspective blend documented in re_gc_camera.md. Android uses right-handed OpenGL look-at/projection matrices. The arcade executable uses the corresponding D3D left-handed path; Vectorail converts the evaluated eye/target/up state to its OpenGL renderer and uses the arcade fixed 75 degree FOV.

No camera wire fields are discarded, including non-finite values in the three ac_comet_* intros.

Background Runtime

DrawBGColor (0xb3fc8) selects the active color key, optionally cross-fades to the next key, and applies BPM-derived HSV brightness modulation when its rhythm-reactive flag is set. DrawBGImage (0xb49b0) supports tiled atlas images, a built-in centered image and external texture entries. DrawBGVisualizer (0xb6118) selects timed visualizer keys and calls the 8572-byte procedural DrawVisualizer path. The conventional FFT routine at 0x104ec0 is not used to drive this stage visualizer.

The 44-byte table previously called particles is TuneBGEffectData. GameScene::ExecFlowItem (0xc0610) uses it to spawn and update authored flowing background elements. Shape 1 spawns a random screen point, shape 2 a screen-space grid and shape 3 an eight-point ring. Repeat and lifetime are scaled by the active beat before points are unprojected to route depth. It is separate from hit-effect particles.

Audio

Stage audio is stored as encrypted *_bgm.ogg.zip and *_shot.ogg.zip files. MtxSoundBuffer::LoadData (0x10a614) rewrites .m4a resource names to .ogg. The sampled Android 10pt8tion pair is Vorbis, 44.1 kHz stereo, and both streams are 129.621859 seconds. The corresponding arcade BGM WAV is PCM s16le, 44.1 kHz stereo, with the same duration.

GameScene::ExecGameStage (0xaf8ac) maintains a 60 Hz logical count, reads MtxSoundSource::GetPlaySecTime (0x10af3c) from BGM, and replaces the logical count when they differ by more than two frames. BGM and SHOT are prepared and started together. If either source is no longer playing, both are stopped, repositioned and restarted; SHOT is also periodically aligned to the BGM time.

Vectorail now derives gameplay time from consumed BGM source bytes instead of wall time. Its two SDL streams start together, retain their source PCM and are cleared/requeued at the BGM position when their source clocks differ by more than two frames. Corpus verification found 114 of 1674 unique arcade pairs with different PCM frame counts; exhausted SHOT tails remain silent when the BGM position is already beyond the shorter stream.

Verification Status

opencoaster-stage-verify currently validates all 2970 main DATs recursively, including the nested stage/sound/ac_dontfight_ex.dat and the chart whose actual name ends in _ext. It compares the retained wire model to the runtime route, all camera fields, note type/effect/color/timing/distance translation and clip dimensions. It also validates resolved BGM/SHOT containers and PCM formats.

The final aggregate result is 2970/2970 main charts, 2927 loaded sidecars, 18965 ext notes and 18948 linked dual-flicks. Section coverage is 16869 FlowItem keys, 54152 visualizer keys, 79 background image keys, 417451 background color keys and 1171999 stage objects. Audio resolution succeeds for 2918 charts; 2768 have paired BGM/SHOT and 150 intentionally resolve only BGM. Gameplay coverage includes 193 MERRY records expanded to 676 timed targets and 6364 SLIDE HOLD records.

Implemented in the player:

  • route timing and draw windows;
  • camera modes, interpolation and projection;
  • note heads, directional overlays, long paths, BEAT samples and MERRY layout;
  • ext timing tables and dual-flick pairing, including both direction vectors;
  • stage model transforms, visibility, colors and one-level parents;
  • exact background color cross-fade and BPM brightness;
  • timed background image selection and external DDS loading;
  • timed visualizer execution for types 1 through 7;
  • FlowItem spawning, screen layouts, route-depth unprojection and motion;
  • MERRY target timing and desktop SLIDE HOLD duration judging;
  • BGM/SHOT playback, hit SE, source-position game clock and pair resync.

Remaining asset/input fidelity gaps:

  • the seven visualizer types execute as GLSL procedural equivalents; the original Android vertex generators have not been copied constant-for-constant;
  • FlowItem timing/layout/motion executes, but original texture selectors 34/35 are represented by colored billboards;
  • mode-3 background loading is implemented, but stage_back10 is referenced by the corpus and absent from the supplied game dump; built-in modes 1/2 still need their original atlas resources;
  • dual flick and directional SLIDE HOLD use desktop multi-input/hold semantics rather than Android touch lines;
  • Android touch-line gesture semantics (the desktop player maps controls to keyboard/gamepad inputs).

This distinction is intentional: a green parser/runtime translation result does not claim pixel-identical procedural meshes or unavailable textures.