Documentation on Wii Controller Hacks
Hello! This guide is documentation of this thread: https://gbatemp.net/threads/new-classic-controller-hacks.659837/
Many thanks to people like Vague Rant, crediar, ThomasLin and anyone else that has done hacks similar to these. This would not have existed without their efforts.
This guide is currently being worked on. My apologies if there are any formatting errors. It is also currently a bit incomplete and needs more.
Things you will need:
- Dolphin Emulator on a computer (open Dolphin and select Options -> Configuration -> Interface -> Enable Debugging UI)
- PPC Assembly knowledge
- The game you want to hack
- CodeWrite (Assembler) https://github.com/TheGag96/CodeWrite
Identifying Symbols
Before booting up the game in Dolphin, right click it and go to Properties > Filesystem
Skim through the Data Partition looking for any files with the extension .elf or .map These may be unstripped binaries or function name maps, respectively; however, .map is also a pretty generic extension, so it might literally be maps in the video game sense and not the symbol maps we're looking for. If you find .elf or .map files, you can extract them (right click -> Extract file) to anywhere on your PC. Dolphin can load .elf files directly and .map files can be opened with Symbols > Load Other Map File...
(There is a list of known Wii games with debug symbols at Retro Reversing.)
Assuming that a symbol map was NOT leftover on the disc:
1. Load game you want to hack and pause. From the dolphin menu, choose Symbols -> Generate Symbols From -> Signature Database
This feature is NOT foolproof. There are many functions dolphin wont identify, and many may be incorrectly identified.
2. Using the Symbols search field on the left side of the Code tab, search for the symbol "KPADRead" or "KPADiRead"
Usually, Dolphin is unable to find the symbol KPADRead. We can use 2 other symbols to our advantage here.
3. Search for the symbol "select_1obj_continue" and click on that. Then look in the bottom left callers section. That unidentified symbol is read_kpad_dpd. Right click it and rename it to read_kpad_dpd.
Ensure to use Symbols -> Save Symbol Map throughout this process so you do not lose any progress.
4. Inside of the symbol read_kpad_dpd, look at the Callers section again. Click on one of those callers, and rename the symbol you are in to KPADRead.
After renaming the symbol to KPADRead, you should currently be looking at a section of the disassembler which should read something like this:
The 2 bl calls above read_kpad_dpd are 2 other relevant KPAD functions. The one in the middle should be read_kpad_acc and the one at the top should be read_kpad_stick. Go inside both of those unnamed ones and rename it to the corresponding one. Example:
Good work so far! You have made good progress. Next I will cover if dolphin did NOT find select_1obj_continue
If dolphin was unable to find the symbol select_1obj_continue:
1. Use the symbols search tab again and find a symbol named "WPADProbe"
2. Set a breakpoint on the very start of that symbol. Keep running in dolphin until the Callstack section in code returns something like this:
* --- [LR = XXXXXXXX] (The callstack wont return XXXXXXXX. I just put that there since the addresses for KPAD stuff is always different for each game.)
3. Once you find that call, the area will most likely be white meaning dolphin did not find the symbol. Look for the 3 bl calls to further confirm this is KPADRead. And of course, rename the symbol to KPADRead when you have confirmed.
Find where the buttons are read by KPAD
Some games (Older SDK) read buttons directly in KPADRead, while some newer SDK games read buttons in a separate function read_kpad_button.
1. Going from the three b1 calls (read_kpad_stick, read_kpad_acc, read_kpad_dpd), scroll up until you find another bl.
2. Scroll three more instructions past that bl and check what the instruction looks like.
Or like this:
The exact registers may not be the same, but the instructions (stw vs. lwz) and the offsets (0x0068 vs. 0x0060) should be persistent
If you have the first: this is an older SDK game that reads buttons directly from KPADRead
Scroll up some more until you reach an instruction that looks like this:
andi. r0, r7, 0x9FFF
Congratulations! Make note of the address that is at. That is where you will be injecting buttons into the Wii Remote.
If you have the second one, this is a newer SDK game that reads buttons inside read_kpad_button
Right click on the bl instruction and select Follow Branch
Right click > Rename Symbol > read_kpad_button
Look ahead one instruction to find the following:
andi. r0, r6, 0x9FFF
Congrats! Make sure to leave note of the address this is at.
The above example may not have the ideal button mapping for the game you're hacking; notably, that example is for a game with the vertical Wiimote layout; the D-Pad directions will be all wrong if you're doing a horizontal (NES-style) game, so you will need to edit those and whatever else needs fixing
Next, open the CodeWrite app (linked at the start of this thread) and put your assembly button injector into the ASM field, the address you identified before with the
If your gecko code works, congratulations! You just made a classic controller hack!
1. Return to
2. locate the
3. Scroll all the way to the bottom of
- That unrecognized function is
Dolphin is very good at incorrectly recognizing
The first one is almost always the real
for extra confidence, check if the function right above it has been identified by Dolphin as
5. Make a note of the address where
Depending on the game, you might want different implementations here: left stick, right stick, always on, toggled with a button; the example above supports four players, uses the right stick and the pointer is disabled after a few seconds of no movement
If you're using the example pointer routine, you must copy the address where you found
for example,
5. Back in CodeWrite, insert this routine at the address you found in
6. Test the code in dolphin or on a Wii. If it works, you are done!
IMPORTANT: From what i've seen, the IR assembly doesnt always work on Dolphin. If that is the case, try the code on a Real Wii.
This is still a work in progress thread, and will be updated soon for more implementations, such as:
- Tilting
- Modifying existing Wii Remote Controls or adding Nunchuk support to games
- Whatever else i forgot to add
And of course, give feedback if the thread is difficult to follow or anything
Many thanks to people like Vague Rant, crediar, ThomasLin and anyone else that has done hacks similar to these. This would not have existed without their efforts.
This guide is currently being worked on. My apologies if there are any formatting errors. It is also currently a bit incomplete and needs more.
Things you will need:
- Dolphin Emulator on a computer (open Dolphin and select Options -> Configuration -> Interface -> Enable Debugging UI)
- PPC Assembly knowledge
- The game you want to hack
- CodeWrite (Assembler) https://github.com/TheGag96/CodeWrite
Identifying Symbols
Before booting up the game in Dolphin, right click it and go to Properties > Filesystem
Skim through the Data Partition looking for any files with the extension .elf or .map These may be unstripped binaries or function name maps, respectively; however, .map is also a pretty generic extension, so it might literally be maps in the video game sense and not the symbol maps we're looking for. If you find .elf or .map files, you can extract them (right click -> Extract file) to anywhere on your PC. Dolphin can load .elf files directly and .map files can be opened with Symbols > Load Other Map File...
(There is a list of known Wii games with debug symbols at Retro Reversing.)
Assuming that a symbol map was NOT leftover on the disc:
1. Load game you want to hack and pause. From the dolphin menu, choose Symbols -> Generate Symbols From -> Signature Database
This feature is NOT foolproof. There are many functions dolphin wont identify, and many may be incorrectly identified.
2. Using the Symbols search field on the left side of the Code tab, search for the symbol "KPADRead" or "KPADiRead"
Usually, Dolphin is unable to find the symbol KPADRead. We can use 2 other symbols to our advantage here.
3. Search for the symbol "select_1obj_continue" and click on that. Then look in the bottom left callers section. That unidentified symbol is read_kpad_dpd. Right click it and rename it to read_kpad_dpd.
Ensure to use Symbols -> Save Symbol Map throughout this process so you do not lose any progress.
4. Inside of the symbol read_kpad_dpd, look at the Callers section again. Click on one of those callers, and rename the symbol you are in to KPADRead.
After renaming the symbol to KPADRead, you should currently be looking at a section of the disassembler which should read something like this:
Code:
8016fb58 mr r3, r31 KPADRead
8016fb5c mr r4, r19 KPADRead
8016fb60 bl ->0x8016E410 --> zz_8016e410_
8016fb64 mr r3, r31 KPADRead
8016fb68 mr r4, r19 KPADRead
8016fb6c bl ->0x8016C920 --> zz_8016c920_
8016fb70 mr r3, r31 KPADRead
8016fb74 mr r4, r19 KPADRead
8016fb78 bl ->0x8016DBF0 --> read_kpad_dpd
Code:
8016fb58 mr r3, r31 KPADRead
8016fb5c mr r4, r19 KPADRead
8016fb60 bl ->0x8016E410 --> read_kpad_stick
8016fb64 mr r3, r31 KPADRead
8016fb68 mr r4, r19 KPADRead
8016fb6c bl ->0x8016C920 --> read_kpad_acc
8016fb70 mr r3, r31 KPADRead
8016fb74 mr r4, r19 KPADRead
8016fb78 bl ->0x8016DBF0 --> read_kpad_dpd
If dolphin was unable to find the symbol select_1obj_continue:
1. Use the symbols search tab again and find a symbol named "WPADProbe"
2. Set a breakpoint on the very start of that symbol. Keep running in dolphin until the Callstack section in code returns something like this:
* --- [LR = XXXXXXXX] (The callstack wont return XXXXXXXX. I just put that there since the addresses for KPAD stuff is always different for each game.)
3. Once you find that call, the area will most likely be white meaning dolphin did not find the symbol. Look for the 3 bl calls to further confirm this is KPADRead. And of course, rename the symbol to KPADRead when you have confirmed.
Find where the buttons are read by KPAD
Some games (Older SDK) read buttons directly in KPADRead, while some newer SDK games read buttons in a separate function read_kpad_button.
1. Going from the three b1 calls (read_kpad_stick, read_kpad_acc, read_kpad_dpd), scroll up until you find another bl.
2. Scroll three more instructions past that bl and check what the instruction looks like.
Code:
stw r0, 0x0068 (r31)
Code:
lwz r8, 0x0060 (r21)
If you have the first: this is an older SDK game that reads buttons directly from KPADRead
Scroll up some more until you reach an instruction that looks like this:
andi. r0, r7, 0x9FFF
Congratulations! Make note of the address that is at. That is where you will be injecting buttons into the Wii Remote.
If you have the second one, this is a newer SDK game that reads buttons inside read_kpad_button
Right click on the bl instruction and select Follow Branch
Right click > Rename Symbol > read_kpad_button
Look ahead one instruction to find the following:
andi. r0, r6, 0x9FFF
Congrats! Make sure to leave note of the address this is at.
Old SDK:
New SDK:
Code:
; KPADRead
; r4 holds extType
; r7 holds wiimote bitfield
; r8 holds wiimote+nunchuk bitfield
; r9 holds classic bitfield
CLASSIC:
cmpwi r4, 0x2
bne- RETURN
CLASSIC_HOME:
andi. r0, r9, 0x800
beq- CLASSIC_UP
ori r7, r7, 0x8000 ; home
CLASSIC_UP:
andi. r0, r9, 0x1
beq- CLASSIC_DOWN
ori r7, r7, 0x8 ; up
CLASSIC_DOWN:
andi. r0, r9, 0x4000
beq- CLASSIC_LEFT
ori r7, r7, 0x4 ; down
CLASSIC_LEFT:
andi. r0, r9, 0x2
beq- CLASSIC_RIGHT
ori r7, r7, 0x1 ; left
CLASSIC_RIGHT:
andi. r0, r9, 0x8000
beq- CLASSIC_A
ori r7, r7, 0x2 ; right
CLASSIC_A:
andi. r0, r9, 0x10
beq- CLASSIC_B
ori r7, r7, 0x800 ; a
CLASSIC_B:
andi. r0, r9, 0x40
beq- CLASSIC_X
ori r7, r7, 0x400 ; b
CLASSIC_X:
andi. r0, r9, 0x8
beq- CLASSIC_Y
ori r7, r7, 0x100 ; 2
CLASSIC_Y:
andi. r0, r9, 0x20
beq- CLASSIC_L
ori r7, r7, 0x200 ; 1
CLASSIC_L:
andi. r0, r9, 0x2000
beq- CLASSIC_R
ori r7, r7, 0x800 ; a
CLASSIC_R:
andi. r0, r9, 0x200
beq- CLASSIC_ZL
ori r7, r7, 0x400 ; b
CLASSIC_ZL:
andi. r0, r9, 0x80
beq- CLASSIC_ZR
ori r7, r7, 0x800 ; a
CLASSIC_ZR:
andi. r0, r9, 0x4
beq- CLASSIC_PLUS
ori r7, r7, 0x400 ; b
CLASSIC_PLUS:
andi. r0, r9, 0x400
beq- CLASSIC_MINUS
ori r7, r7, 0x10 ; plus
CLASSIC_MINUS:
andi. r0, r9, 0x1000
beq- RETURN
ori r7, r7, 0x1000 ; minus
RETURN:
andi. r0, r7, 0x9FFF
New SDK:
Code:
; read_kpad_button
; r4 holds extType
; r6 holds wiimote bitfield
; r7 holds wiimote+nunchuk bitfield
; r8 holds classic bitfield
CLASSIC:
cmpwi r4, 0x2
bne- RETURN
CLASSIC_HOME:
andi. r0, r8, 0x800
beq- CLASSIC_UP
ori r6, r6, 0x8000 ; home
CLASSIC_UP:
andi. r0, r8, 0x1
beq- CLASSIC_DOWN
ori r6, r6, 0x8 ; up
CLASSIC_DOWN:
andi. r0, r8, 0x4000
beq- CLASSIC_LEFT
ori r6, r6, 0x4 ; down
CLASSIC_LEFT:
andi. r0, r8, 0x2
beq- CLASSIC_RIGHT
ori r6, r6, 0x1 ; left
CLASSIC_RIGHT:
andi. r0, r8, 0x8000
beq- CLASSIC_A
ori r6, r6, 0x2 ; right
CLASSIC_A:
andi. r0, r8, 0x10
beq- CLASSIC_B
ori r6, r6, 0x800 ; a
CLASSIC_B:
andi. r0, r8, 0x40
beq- CLASSIC_X
ori r6, r6, 0x400 ; b
CLASSIC_X:
andi. r0, r8, 0x8
beq- CLASSIC_Y
ori r6, r6, 0x100 ; 2
CLASSIC_Y:
andi. r0, r8, 0x20
beq- CLASSIC_L
ori r6, r6, 0x200 ; 1
CLASSIC_L:
andi. r0, r8, 0x2000
beq- CLASSIC_R
ori r6, r6, 0x800 ; a
CLASSIC_R:
andi. r0, r8, 0x200
beq- CLASSIC_ZL
ori r6, r6, 0x400 ; b
CLASSIC_ZL:
andi. r0, r8, 0x80
beq- CLASSIC_ZR
ori r6, r6, 0x800 ; a
CLASSIC_ZR:
andi. r0, r8, 0x4
beq- CLASSIC_PLUS
ori r6, r6, 0x400 ; b
CLASSIC_PLUS:
andi. r0, r8, 0x400
beq- CLASSIC_MINUS
ori r6, r6, 0x10 ; plus
CLASSIC_MINUS:
andi. r0, r8, 0x1000
beq- RETURN
ori r6, r6, 0x1000 ; minus
RETURN:
andi. r0, r6, 0x9FFF
The above example may not have the ideal button mapping for the game you're hacking; notably, that example is for a game with the vertical Wiimote layout; the D-Pad directions will be all wrong if you're doing a horizontal (NES-style) game, so you will need to edit those and whatever else needs fixing
Next, open the CodeWrite app (linked at the start of this thread) and put your assembly button injector into the ASM field, the address you identified before with the
andi. instruction into the Insertion Address field, and click the right arrow at the bottom of the window to generate a Gecko Code. Copy the resulting Gecko code and add it to Tools > Cheats Manager > Gecko Codes in DolphinIf your gecko code works, congratulations! You just made a classic controller hack!
Adding Features
IR Pointer support:1. Return to
read_kpad_dpd, which you identified earlier2. locate the
calc_dpd_variable function; Dolphin will not have recognized this for you so it will have an autogenerated name3. Scroll all the way to the bottom of
read_kpad_dpd and look for the function call near the end, looking like this:
Code:
8016e078 mr r3, r31 read_kpad_dpd
8016e078 extsb r4, r30 read_kpad_dpd
8016e078 bl ->0x8016D610 --> zz_8016d610_
calc_dpd_variable.- right click > Follow Branch
- right click > Rename Symbol >
calc_dpd_variable
- the start of this function should look pretty much, if not exactly like this:
Code:8016d610 stwu sp, -0x0020 (sp) calc_dpd_variable 8016d614 mflr r0 calc_dpd_variable 8016d618 stw r0, 0x0024 (sp) calc_dpd_variable - that third instruction is an ideal location to insert pointer emulation; make a note of that address
- the start of this function should look pretty much, if not exactly like this:
SCGetAspectRatio functionDolphin is very good at incorrectly recognizing
SCGetAspectRatio. use the symbol search to look for SCGetWpadSpeakerVolume; you will probably get 8 or more results, most of which are wrongThe first one is almost always the real
SCGetAspectRatio, which Dolphin has mis-identifiedfor extra confidence, check if the function right above it has been identified by Dolphin as
MyNandCallback and the function right below it has been identified as SCGetDisplayOffsetH; SCGetAspectRatio is usually sandwiched between those two functions which Dolphin often does correctly identify5. Make a note of the address where
SCGetAspectRatio begins
Code:
; calc_dpd_variable
stw r0, 0x24(sp)
; player using real pointer? (gravity already valid)
cmpwi r4, 0x0
bne- NO_CLASSIC
; check for Classic Controller
lbz r0, 0x5C(r3)
cmpwi r0, 0x2
bne- NO_CLASSIC
; magic
bl GRAB
MAGIC:
GETASPECT: .int 0x00000000 ; PUT THE ADDRESS FOR SCGetAspectRatio HERE!
ASPECT: .float 1.3333333333
MULTIPLIER: .float 0.015 ; pointer speed
TIMER: .hword 0
.hword 0
.hword 0
.hword 0
DEADZONE: .float 0.1
MAX_RANGE: .float 1.0
GRAB:
mflr r5
stw r5, 0x0C(sp) ; save magic pointer
; handle four separate timers for multiplayer
slwi r7, r25, 0x1 ; player index
addi r7, r7, TIMER-MAGIC
STICK_WAKE:
lfs f0, 0x74(r3)
fabs f0, f0
lfs f1, 0x78(r3)
fabs f1, f1
fadd f0, f0, f1
lfs f2, DEADZONE-MAGIC(r5)
fcmpo cr0, f0, f2
blt- CHECK_TIMER
li r6, 300
b DELTA
; check pointer sleep timer
CHECK_TIMER:
lhax r6, r5, r7
cmplwi r6, 0x0
ble- NO_CLASSIC
subi r6, r6, 0x1
DELTA:
sthx r6, r5, r7
li r6, 0x2
stb r6, 0x5E(r3)
lwz r12, GETASPECT-MAGIC(r5) ; SCGetAspectRatio()
mtlr r12
blrl
cmpwi r3, 0x1
mr r3, r31 ; restore input pointer
lwz r5, 0x0C(sp) ; restore magic pointer
lfs f2, ASPECT-MAGIC(r5)
bne- FULLSCREEN
fmuls f2, f2, f2 ; widescreen
FULLSCREEN:
lfs f3, MULTIPLIER-MAGIC(r5)
; pointer X axis
lfs f0, 0x20(r3)
lfs f1, 0x74(r3) ; right stick X
fdiv f1, f1, f2 ; divide by aspect ratio
bl RANGE_CHECK
stfs f0, 0x20(r3)
; pointer Y axis
lfs f0, 0x24(r3)
lfs f1, 0x78(r3) ; right stick Y
fneg f1, f1
bl RANGE_CHECK
stfs f0, 0x24(r3)
RETURN:
lwz r0, 0x24(sp)
mtlr r0
addi sp, sp, 0x20
blr
RANGE_CHECK:
fmadd f0, f1, f3, f0
lfs f1, MAX_RANGE-MAGIC(r5)
fcmpu cr0, f0, f1
blt CHECK_NEGATIVE
fmr f0, f1
b IN_RANGE
CHECK_NEGATIVE:
fneg f1, f1
fcmpu cr0, f0, f1
bgt IN_RANGE
fmr f0, f1
IN_RANGE:
blr
NO_CLASSIC:
Depending on the game, you might want different implementations here: left stick, right stick, always on, toggled with a button; the example above supports four players, uses the right stick and the pointer is disabled after a few seconds of no movement
If you're using the example pointer routine, you must copy the address where you found
SCGetAspectRatio into the following line:
Code:
GETASPECT: .int 0x00000000 ; PUT THE ADDRESS FOR SCGetAspectRatio HERE!
SCGetAspectRatio was at 80124550, so the line looks like this for me:
Code:
GETASPECT: .int 0x80124550 ; PUT THE ADDRESS FOR SCGetAspectRatio HERE!
5. Back in CodeWrite, insert this routine at the address you found in
calc_dpd_variable; I told you to make a note of it6. Test the code in dolphin or on a Wii. If it works, you are done!
IMPORTANT: From what i've seen, the IR assembly doesnt always work on Dolphin. If that is the case, try the code on a Real Wii.
This is still a work in progress thread, and will be updated soon for more implementations, such as:
- Tilting
- Modifying existing Wii Remote Controls or adding Nunchuk support to games
- Whatever else i forgot to add
And of course, give feedback if the thread is difficult to follow or anything





