Skip to content

Integrating 3DS analog controls cheats - #38

Merged
beebono merged 2 commits into
beebono:mainfrom
bmpriest:analog-controls
Nov 1, 2026
Merged

beebono merged 2 commits into
beebono:mainfrom
bmpriest:analog-controls

Conversation

@bmpriest

@bmpriest bmpriest commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

What is it?

The 3DS has a number of cheats that enable analog stick, circle pad pro, ZR/ZL and even gyroscope functionality. They do this by utilizing some bits in the RTC register and then the Action Reply code reads from it and patches in game functions. The one-sentence version: a CPAD patch is three mechanisms stacked — a transport, a relay and a game hook — and an emulator only needs the hook.

see: https://github.com/shocoman/Analog-Controls-for-NDS-Games-on-3DS and https://gbatemp.net/threads/circle-pad-patches-for-super-mario-64-ds-and-other-games-in-twilightmenu-with-twpatcher-and-rtcom.623267/#post-10026852

DSperate doesn't need the full Action Reply codes, so an intermediate repo (https://github.com/bmpriest/ds-emu-dac) serves to convert the codes into compatible codes for the emulator.

What changes in DSperate

For convenience, DSperate uses a separate usrcheat.dat for the analog cheats. usrcheat-analog.zip These cheats as well as control specifics live in an Analog menu where specific cheats can be enabled/disabled, as well as some stick settings like deadzones, invert Y, and sensitivity.

There's also various bits to disable gracefully when turning off the analog cheats, making them exclusive in cases where there are more than one, preventing save states from getting weird, and tests.

What works

I have tested DSperate with a NextUI compatible pak and played a decent bit of Super Mario 64 DS, Metroid Prime Hunters, Star Fox Command, and Animal Crossing on my RG35XXH.

What it needs

Because these cheats map analog functionality to the sticks, there needs to thought given to current touch screen functionality. If the stylus is mapped to the right stick in Metroid Prime Hunters, for example, using the right stick will move the DS stylus as well as moving the player's view.

I'd recommend either a shortcut to disable the analog cheats temporarily, or an additional hotkey to enable stylus support: for example, L2 plus Right Stick for stylus input.

shocoman's Circle Pad patches let a DS game read a 3DS stick: an ARM7/ARM11
relay copies the stick into guest RAM at 0x027FFDF0, and an ARM9 hook in the
game reads it. An emulator has no relay and needs none, so the frontend
captures both sticks and the triggers and the core writes that block every
frame, once a stripped CPAD code is enabled.

pad.analog (off, left, right, dual) picks the sticks, with
pad.analog_deadzone, pad.analog_invert_y and a sensitivity per stick. Stick Y
is flipped by default, as SDL's up is negative and a 3DS's is positive. ZL and
ZR come with two sticks, past 3/4 travel, and not under a hotkey chord.

The settings are a pause-menu page of their own, after Controls, opening with a
link to the game's analog cheats; the Cheats page lists the rest.

Hooks expect one of two arrangements, which collide at +6, so
reads_extended_analog looks for how the enabled code takes the nub;
pad.analog_layout can overrule it. A code counts as analog by its CPAD name.
paths.analog_cheats names a second database for these patches, merged for an
exact ROM match only, because a hook patches fixed addresses and best_entry's
first-revision fallback would install it over the wrong code.

The DSIN log goes to version 3 to carry the sticks; v1 and v2 still replay.
Cheat persistence enforces exclusive groups, since two schemes over the same
addresses cannot run at once, and every way out of the menu applies cheat
changes: Reset and Load used to run a new code with the channel still off.
A game picked from the list now gets its .ini's pad settings; other keys in
it wait for the next start.
An analog code patches branch sites and installs a hook body, and a disabled
code stopped running but left all of that in guest RAM. Worse, a scheme's
install guard tests only the hook's first word, which two schemes for one game
can share, so switching took the old body for its own and a new branch landed
inside it.

The engine now journals the bytes an analog code writes and restores them when
it is disabled, if each byte still holds what the code wrote. Only analog codes
are journaled: other cheats write game data
the game goes on changing, and putting an old value back would be wrong.

A save state holds guest RAM with the hook in it, so the journal travels in an
optional CHJR chunk, keyed by a hash of each code's words so a revised code
does not inherit its predecessor's hook. The next run() keeps what enabled
codes own and restores the rest, which needs a VBlank hook that still runs when
the code list is empty. Reader::peek lets the chunk be absent: older states and
cheat-free ones load as before, and the frontend's own chunks follow it.

Also adds docs/techniques/07, which describes the whole feature.
@beebono
beebono merged commit 34969ea into beebono:main Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants