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:
- Load the selected main DAT from
<stage-pack>.zip. - Call
TuneGameData::LoadGameData(0x933a0). - Call
TuneGameData::LoadBGData(0x93aa4) on the same bytes. - Load
_ext.datonly for an extra/arcade difficulty when arrange mode is off. - 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_back10is 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.