176 lines
14 KiB
Markdown
176 lines
14 KiB
Markdown
# Built-in cheats (`cheats`, CHEATS)
|
||
|
||
`mods/cheats/CheatsMod` – one groove.exe hook, no data changes.
|
||
|
||
## How the game does it
|
||
|
||
Main menu frame function 0x54B460 (state 2, table 0x579050): whenever a pad bit is held
|
||
(0x5FCF08 ≠ 0) it calls **FUN_0041a160(0)**, the sequence matcher:
|
||
|
||
```
|
||
for i in 0..5:
|
||
if newlyPressed(0x5FCF30) & seq[i][pos[i]]: pos[i] at 0x58483C + 4i
|
||
pos[i]++
|
||
if seq[i][pos[i]] == 0: sequence complete
|
||
if cheatFlags(0x584710) & (1<<i): clear bit (toggle off, no sound)
|
||
else: set bit, play sound (FUN_00546170(0,-127,8))
|
||
i == 2: next mode 0x584700 = 0x0A, 0x584638 = 4 (go to the credits)
|
||
pos[i] = 0
|
||
else pos[i] = 0 any other key resets this cheat
|
||
```
|
||
Sequence table **0x571FBC**, 6 × 9 dwords (8 steps + 0). Menu key bits (FUN_0041a220, raw DIK
|
||
state array 0x58226C + 4·DIK): ↑ 0x10, ↓ 0x40, ← 0x80, → 0x20; Return 0x4000, Space 0x8000,
|
||
C 0x2000, A 0x400, S 0x800, Esc 0x1000, Tab 0x08, backslash 0x01. Typing a sequence works
|
||
with arrows alone; no extra key has to be held (the "hold" is just that the matcher only runs while
|
||
some key is down). The flag word is never saved or reset (only bit 2 is cleared after the
|
||
credits), so cheats stay on until the game quits.
|
||
|
||
## Decoded cheat table
|
||
|
||
| Bit | Sequence | Read at | Effect | Option |
|
||
|---|---|---|---|---|
|
||
| 0 | ← → → → ↑ → → ↑ | 0x54F5C9, VM opcode 0xC0 "hurt player" (FUN_0054f5c0) | energy (0x584E70) is not decreased → no damage, no lost lives | `health` (default on) |
|
||
| 1 | ↓ ← → ↓ ↓ ← → ↓ | strat property 0xEA (0x5524F4), read by the Llama strat at chapter start | property 0xFA (0x584E60, pink idol counter, chapter collectible) = 50 | `collectibles` |
|
||
| 2 | ← ← ← ← → → → → | FUN_0041a160, 0x54A5F6 | plays the credits (CREDITS.dat) right away, then back to the main menu, bit cleared | – (one-shot, not a setting) |
|
||
| 3 | → → ↓ ↓ ↓ ↑ ↓ ↓ | 0x54D6F9 (chapter finished) | chapter flags `|= 3` (collectible + gold coin, KSV +0x08) | `rewards` |
|
||
| 4 | ↑ ↓ → ← ← ← ← ↓ | strat property 0xB2 (0x550EE1) | property 0xC0 (0x584FF4, purple fruit counter) = 500 instead of 0 | `fruit` |
|
||
| 5 | ↑ ← ↓ → ↓ ↓ ↑ ↑ | strat property 0x97 (0x55100C) | property 0xE8 (0x6DA304, charge bar) = 165 instead of 50 | `charge` |
|
||
|
||
Llama strat (t1l1m001 STPC 0x429536, same in all 23 level WADs) – VM short opcodes: 0x07 p =
|
||
push property p, 0x14 n = pop, skip n words if zero, 0x16 n = skip n words, 0x40 p = pop into
|
||
property p, 0x44 n = push n, long opcode 0x45 = push next word:
|
||
```
|
||
if prop[0x97]: prop[0xE8] = 0xA5000 else prop[0xE8] = 0x32000 ; 165 / 50 (20.12)
|
||
if prop[0xB2]: prop[0xC0] = 0x1F4000 else prop[0xC0] = 0 ; 500 / 0
|
||
if prop[0xEA]: prop[0xFA] = 0x32000 ; 50
|
||
```
|
||
Strat property switch: FUN_00550e60 (get, 0x10A cases, table 0x552684), FUN_005509f0 (address
|
||
for store, index table 0x550D64 → 0x550C78).
|
||
|
||
## Patch
|
||
|
||
`call FUN_0041c050` at 0x41D3EA in WinMain (right after the command line is parsed, before
|
||
DirectX init) → cave: `or dword [0x584710], mask` (+ `mov byte [0x5834D0], 1` for
|
||
diagnostics) → `jmp 0x41C050`. The cave writes only game globals, so it needs no data block.
|
||
|
||
## Hidden debug features found
|
||
|
||
| Feature | State | In the mod |
|
||
|---|---|---|
|
||
| `-display_diagnostics` (DAT_005834D0) | works: writes `<COMPUTER>.dispdiag` (DDraw/D3D enumeration) into the game folder; FUN_0040f4f0 opens **every display mode of every DirectDraw device × D3D device** (FUN_00410550/FUN_00410700 = mode switch, FUN_00411000), logs caps and texture formats, closes it again, then returns and the game starts normally. On Windows with two monitors both screens flashed black for 30+ s (tester, 2026-11); invisible under Wine | option `diagnostics`, label "Troubleshooting: write display diagnostics", off by default, warning in its description (tested: ELIASPC.dispdiag, 60 KB) |
|
||
| `c:\kingdom_dbg.txt` | dead: FUN_0040d2c0 (called from FUN_0043c220) only creates the file with "Logfile %s started now/today (write this code!)"; the 43 debug prints (FUN_00414600) go to OutputDebugStringA (0x6DC3E8), never to the file | no |
|
||
| FrameAdvanceDelay (FUN_0043cb40) | slide show `In\001.bmp…` + `In\In.wav` at start, `Out\…` at exit, delay from registry `FrameAdvanceDelay` (default 1.0 s); folders are empty | no (data, not a cheat) |
|
||
| F4 in game | requests state 0x0C (world map) – the normal "back to map" | no |
|
||
| F7 in game | toggles 0x584708 → start row of the strat debug print (0x6D9DB8 = 0x20 / 0x48); nothing visible in retail strats | no |
|
||
| F6 | screenshot `capture%d.tga` | no |
|
||
| `CheatTeleport`, `CheatCharge` strats | present in most level WADs; gated by property 258, see "Developer switch (property 258)" | no |
|
||
|
||
Level select / unlock-all is the save editor's job (`--unlock`), not duplicated here.
|
||
|
||
## Verified in game (Wine 6, Xvfb :8, Village 1-1)
|
||
|
||
* All cheats on: chapter start shows the full charge bar, pink idol 50, purple fruit 500
|
||
(`img/mods/cheats_hud.jpg`, original on the left).
|
||
* Infinite health: running off the cliff into the sea three times – original loses an energy
|
||
segment per fall (HUD shows the red segment), with the cheat the energy HUD never appears, no
|
||
loss (`img/mods/cheats_fall.jpg`).
|
||
* `rewards` (bit 3) decoded only, not played through to a chapter end.
|
||
* Diagnostics: file written, game runs normally.
|
||
|
||
## Frog moonjump (PS second controller)
|
||
|
||
Question from speedrunners: is the frog moonjump (PS: needs a controller in port 2) reachable on PC?
|
||
Answer: **the logic is in the PC data, but its input can never be set on PC without patching.**
|
||
|
||
Strat side (all 34 level WADs disassembled, 2026-11-08). Property 88 `padAux` (DAT_005FCF58) is read
|
||
only by Llama (23 WADs), LlamaChase (t1l2m003), KuscoBunny (t1l7m004, t1l8m001, t1l8m002), KuscoFrog
|
||
(t1l4m003, t1l8m003) and CheatTeleport (26 WADs, also `padAuxPressed`). Everything except the frog's
|
||
air handler is additionally gated by property 258 (0x102), which the exe getter hard-wires to 0
|
||
(jump table 0x552684[0x102] → 0x55231B `push 0`); CheatTeleport removes itself when it is 0.
|
||
|
||
KuscoFrog (t1l4m003 unit 44), airborne handler L_136c – **no prop-258 check**:
|
||
```
|
||
if (self.padAux & 0.5) { ; bit 0x800
|
||
g22 = 0.055 ; vertical speed upward, every frame while the bit is set
|
||
v31 = 1.0
|
||
}
|
||
self.y = max(self.y + g22, v44)
|
||
```
|
||
Ground handler L_1d6c (take-off): `(padPressed & 4.0 /*jump*/) | ((padAux & 0.5) && prop258)` → the
|
||
aux bit alone cannot start a jump, it only keeps the frog rising once it is in the air (= moonjump:
|
||
jump normally, then hold the aux button). Llama/Bunny/LlamaChase have the same code (Llama: g22 = 0.0698)
|
||
but behind `prop258`, so they never moon-jump.
|
||
|
||
Exe side: 0x5FCF58 is referenced exactly 3× in groove.exe (all 4-byte immediates scanned): 0x425B93
|
||
`mov dword [0x5FCF58], 0` (end of the pad poll FUN_004256C0, only if pad count 0x5FCF6C > 1, i.e. a
|
||
DirectInput joystick is present), 0x425C1E (edge detect → 0x5FCF60 `padAuxPressed`), 0x551403 (strat
|
||
getter, prop 0x58). It lives in .bss → always 0. No keyboard key, joystick (1st or 2nd), debug key or
|
||
demo (.dem) playback writes it. Under Wine without a joystick: pad count 1, padAux 0 (read in game).
|
||
|
||
Verified in game (apptest :25, t1l4m003 Jungle day ch. 3, teleported onto PotionPadFrog → FrogPotion
|
||
→ frog): `set {int}0x5FCF58 = 0x800` via the winedbg gdb proxy. Frog on the ground: nothing happens.
|
||
Frog jumps (X): it keeps rising – y 13460 → 41617 (~2 s) → 59842 (~4 s), far above the level; clearing
|
||
the bit makes it fall back. Same write as the llama: normal jump, no effect (prop 258 = 0).
|
||
`img/mods/frog_moonjump.jpg` (top: normal frog jump, bottom: bit 0x800 set).
|
||
|
||
To enable on PC a patch is required, e.g. make the pad poll map a key to padAux bit 0x800. Which PS
|
||
button of pad 2 feeds bit 0x800 is not known (no PS binary analysed) – hypothesis only.
|
||
|
||
## Developer switch (property 258)
|
||
|
||
Strat property 258 (0x102) is a hard-wired developer flag: getter FUN_00550e60, jump table
|
||
0x552684[0x102] → 0x55231B `mov eax,[esp+14h]; push 0; push eax; call 0x54BBD0` (the `push 0` byte is
|
||
at **0x552320**; the same handler also serves the unread ids 0x75, 0x9E, 0xC1, 0xEE). It is read
|
||
101× in the 34 level WADs (all units disassembled; `pushProp` only, no player/parent/... variants).
|
||
Every use is a logical test (`and`, `not`, `jz`), so any non-zero value enables it.
|
||
|
||
Keys (in-game `padHeld` 0x5FCF20, measured with gdb while holding the key): A 0x400, S 0x800
|
||
(camera left/right), Return 0x1000, Z 0x2000 (action), X 0x4000 (jump), Space 0x8000 (power),
|
||
P 0x08 (pause). No keyboard key gives 0x100/0x200. `padAux` (prop 88, 0x5FCF58) and
|
||
`padAuxPressed` (prop 90, 0x5FCF60 = edge of padAux, computed every frame at 0x425C1E) are never
|
||
set on PC (see above). Strat constants are 20.12: `0.25` = 0x400, `0.5` = 0x800, `0.75` = 0xC00,
|
||
`10.0` = 0xA000, `256r` = raw 0x100.
|
||
|
||
| Reader strat | Levels | Gate | Bits / keys | Effect | Status |
|
||
|---|---|---|---|---|---|
|
||
| CheatTeleport (+CheatCharge, same unit) | placed in 22 levels: 1-1..1-4, 2-1, 2-2, 4-1..4-3, 5-1..5-3, 6-1..6-3, 7-2, 7-4, 8-1..8-5 (code also in 3-1..3-4, not placed) | entry `if (!prop258) remove` at level start | hold **A+S** (padHeld 0xC00) | teleports the llama to the next node of the strat's own path (hand-placed warp list, 1–18 nodes per level, often next to checkpoints, doors, puzzles; wraps around); sets `g0 = 14` | verified 1-1 (4 nodes, cycled twice) |
|
||
| CheatTeleport | same | prop258 | padAuxPressed 0x400 | +1 heart (if alive), +5 purple fruit, hudDigit = charge | verified (fruit 0 → 5, HUD shows it) |
|
||
| CheatTeleport | same | prop258 (+ prop 0 == 0) | padAux 0x100 held | +1 coin per frame (prop 190, coins of this level) | verified (HUD 307 coins after 0.5 s) |
|
||
| CheatTeleport | same | prop258 (+ prop 0 == 0) | padAux 0x200 held | +1 pink idol per frame, max 99 (prop 250) | verified (0 → 65 → 99) |
|
||
| CheatTeleport | same | prop258 | hold Space+Z (0xA000) | only `print` debug lists (player/camera state dump: "gFreezePlayer", "gCamAng", "teleported to location (" …) that the PC VM skips → nothing visible | static |
|
||
| CheatTeleport | same | prop258 **and prop 0 ≠ 0** | padAux 0x100 held = debug menu; then padPressed Space/Z = previous/next music track (spawns unit 48 = track display, `musicPlay`), X = lose heart, Return = +1 idol, A = +1 coin (+10 charge every 5), S = full charge, 0x100 = **`endLevel(g101,g102,g103)`** (finish level), 0x200 = +1 counterC; combo becomes 0xF00 | unreachable even with prop258 = 1 (prop 0 is also hard-wired 0, handler 0x552192) | static |
|
||
| Llama | 23 levels (all with a llama) | prop258 | padAux 0x800 held | moonjump/fly: starts a jump on the ground without pressing X (no cooldown check) and sets vertical speed 0.0698/frame every frame in the air → rises until released | verified 1-1: y 8.25 → 20.6 in ~2 s without touching a key |
|
||
| LlamaChase | 2-3 (night chase) | prop258 | padAux 0x800 | same moonjump; also allows jumping anywhere (normally only on floor material 11) | static (chase intro did not start under load) |
|
||
| KuscoBunny | 7-4, 8-1, 8-2 (potion) | prop258 | padAux 0x800 | rabbit jumps/rises with speed 0.045 while held | static |
|
||
| KuscoFrog | 4-3, 8-3 (potion) | prop258 only for take-off | padAux 0x800 | air rise has **no** gate (moonjump agent, verified); with prop258 the bit also starts the jump | static |
|
||
| KuscoAndPachaTogetherOnATree (river log) | 3-1..3-4 | prop258 | hold **Space** (0x8000) | damage handling skipped (invincible), +1 heart every frame, collision flag 0x40000 off (passes through banks), horizontal movement ×8, no current/steering term | verified 3-1: with Space the log shot through the river bank out of the level |
|
||
| RiverWhirlpool | 3-2 (36×) | prop258 | hold Space | whirlpool does not pull the log | static |
|
||
| CondorManDoor | 5-2 | prop258 | padHeld 0x100 (no PC key) | a hit always takes the "straight hit" branch (`v16 = 1`, later `otherPiece()` on impact; hypothesis: the door breaks) instead of needing the angle within 96 | static |
|
||
| EndGameTriggerBox | 8-6 (finale, 15×) | prop258 | hold Space+Z during the box's wait time | `endLevel(1,1,1)` at once (hypothesis: skips the rest of the finale) | static |
|
||
|
||
How it was verified (apptest `devflag-a`, :25, German exe): `set {char}0x552320 = 1` via the winedbg
|
||
gdb proxy at the main menu (CheatTeleport removes itself at level start, so the patch must be in
|
||
before the level loads), then `set {int}0x5FCF58 = 0x100/0x200/0x400/0x800` one at a time.
|
||
`img/mods/devflag_teleport.jpg` (1-1 start → three A+S warps), `devflag_aux_moonjump.jpg` (before /
|
||
after padAux 0x400+0x100: fruit 5 and 307 coins on the HUD / bit 0x800: llama flying above the
|
||
village), `devflag_river.jpg` (3-1 log normal / after Space boost in the bank / outside the level).
|
||
Note: the exe run by LlamaMod relocates the per-level array 0x584F1C to 0x6DB940 (seen at 0x550BC2),
|
||
so `levelRecord`/coins are not at the address of `re/groove.exe`.
|
||
|
||
Property 0 is a second hard-wired 0 read by strats (131×: Camera, CameraSide, CheatTeleport, birds,
|
||
butterflies, ladybirds, moths, Bunny, Panther, Yzma…). Ambient critters with a parameter set remove
|
||
themselves when it is non-zero, the camera uses alternative heights/distances when it is 0, and
|
||
CheatTeleport switches its combo to 0xF00 – **hypothesis**: a platform/hardware flag (PS button
|
||
layout or low-memory mode), not a debug flag. Other constant getter cases: 0x5E/0x60 = 84,
|
||
0x5F/0x61 = 123, 0x82/0x83 = 42 (stick thresholds), and 0, 0x13, 0x15, 0x47, 0x75, 0x76, 0x77,
|
||
0x98, 0x9E, 0xC1, 0xD8, 0xED, 0xEE, 0x102 = 0; of the zero ids only 0 and 0x102 are read by any strat.
|
||
No exe code writes padAux, and no other dev key handler was found beyond the table above (F4, F6, F7).
|
||
|
||
Suggested "Developer mode" option for the cheats mod (not implemented): patch byte 0x552320 to 1
|
||
(or point jump-table entry 0x552A8C at a cave that pushes 0x1000, leaving the shared handler alone),
|
||
and map keys to padAux in the pad poll: at the end of FUN_004256C0 (0x425B88, where padAux is only
|
||
cleared when >1 pad) jump to a cave that builds padAux from the DIK state array 0x58226C + 4·DIK,
|
||
e.g. F1 → 0x800 (moonjump), F2 → 0x400 (heart + fruit), F3 → 0x200 (idols), F5 → 0x100 (coins).
|
||
A+S teleport and Space (river) work with the normal keys once prop 258 is 1. Note that F4/F6/F7
|
||
are taken; the frog's ungated moonjump would also react to the 0x800 key.
|