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:
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
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:
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
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.
Code:
stw    r0, 0x0068 (r31)
Or like this:
Code:
lwz    r8, 0x0060 (r21)
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.

Old 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 Dolphin

If 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 earlier
2. locate the calc_dpd_variable function; Dolphin will not have recognized this for you so it will have an autogenerated name
3. 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_
- That unrecognized function is 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
4. Locate the SDK SCGetAspectRatio function
Dolphin 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 wrong
The first one is almost always the real SCGetAspectRatio, which Dolphin has mis-identified
for 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 identify

5. 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!
for example, 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 it
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.

Tilting Support


1. Find the symbol read_kpad_acc we identified earlier
2. Scroll up outside of it to find another unidentified symbol, this is calc_acc_vertical
3. Go to the very top of it, and find the third instruction, which should look like this:
Code:
stw    r0, 0x0044 (sp)
Make note of this address.
Code:
; calc_acc_vertical
  stw r0, 0x44(sp)

; check if this is a classic controller
  lbz r0, 0x5C(r3)
  cmpwi r0, 0x2
  bne- RETURN

; magic
  bl GRAB
MAGIC:
    RANGE:  .float   0.5
GRAB:
  mflr r5

  lfs f0, RANGE-MAGIC(r5)

; load in right stick, constrain and write
  lfs f1, 0x74(r3)
  fmuls f0, f0, f1
  stfs f0, 0x58(r3)

; restore lr and stack then get outta here
  lwz r0, 0x44(sp)
  mtlr r0
  addi sp, sp, 0x40
  blr

RETURN:

If the code works to allow Tilting with the right classic controller stick, congrats! If not, the game most likely does not use calc_acc symbols (older SDK) or for some reason stops reading accelerometer data when an extension is connected.

Accelerometer Implementation
The general idea is that inside read_kpad_acc() you need to force the game to load some specific accelerometer data instead of the real data whenever a certain button is pressed.

My usual trick for doing this is to map various accelerometer functions as buttons 0x80, 0x40 and 0x20 on the Wii Remote in the button injector. These are all unused bits on the Wiimote, i.e. technically possible button values which simply don't exist on the Wiimote. Then inside read_kpad_acc(), I'll write a routine which checks if the user pressed one of those fake buttons and load fake data in before calc_acc() (the function which processes that data) can run. calc_acc() is pretty easy to find because it always runs three times in a row, once for each axis. Before each function call will be an lfs f1, 0xXXX(r30) which is reading in the real accelerometer data, so that's the ideal place to insert some code to replace the real data.
Code:
    CLASSIC_ZL:
      andi. r0, r8, 0x80
      beq- CLASSIC_ZR
      ori r6, r6, 0x80     ; fake button for shaking
Example of how it would be implemented in your button injector.

So obviously it varies from title to title, but here's an example from a recent hack, Rygar. I'll add some comments for good places to do some code insertions. This is specifically the USA version, EUR is different again.


Code:
801B7374 lwz    r6, 0x000C (r3)     read_kpad_acc   ; this is what i use for complex games
801B7378 addi   r4, r30, 12         read_kpad_acc
801B737C lwz    r5, 0x0010 (r3)     read_kpad_acc
801B7380 lwz    r0, 0x0014 (r3)     read_kpad_acc
801B7384 stfs   f4, 0x04AC (r3)     read_kpad_acc   ; the Z axis gets written here
801B7388 mr     r3, r30             read_kpad_acc
801B738C lfs    f1, 0x04A4 (r30)    read_kpad_acc   ; this reads in the X axis
801B7390 stw    r6, 0x0014 (sp)     read_kpad_acc
801B7394 stw    r5, 0x0018 (sp)     read_kpad_acc
801B7398 stw    r0, 0x001C (sp)     read_kpad_acc
801B739C bl     ->0x801B6EA8        --> calc_acc
801B73A0 lfs    f1, 0x04A8 (r30)    read_kpad_acc   ; this reads in the Y axis (good for simple games)
801B73A4 mr     r3, r30             read_kpad_acc
801B73A8 addi   r4, r30, 16         read_kpad_acc
801B73AC bl     ->0x801B6EA8        --> calc_acc
801B73B0 lfs    f1, 0x04AC (r30)    read_kpad_acc   ; this reads in the Z axis (")
801B73B4 mr     r3, r30             read_kpad_acc
801B73B8 addi   r4, r30, 20         read_kpad_acc
801B73BC bl     ->0x801B6EA8        --> calc_acc
For games with this rough format (probably about 2008 onward?), this gives you a couple of options. I mostly inject at the first instruction because it means I definitely have r0, r4, r5 and r6 available to do GPR stuff and f0, f1, f2 and f3 to do FPR stuff.

The value that's currently in f4 is the Z axis about to be written out (see comment), so you can just load some other value into f4 to replace the real accelerometer data. To replace the X and Y axes, you'll need to write to 0x???? (r30) (offset varies by SDK version but if you find something like the above, you can see the offsets the game is using). In this case, you need to write to 0x04A4 (r30) for the X axis and 0x04A8 (r30) for the Y axis.

If the game you're looking at only needs you to work with a single axis (in particular Y or Z), you can use the points where the Y and Z axes get read in from 0x04A8 (r30) and 0x04AC (r30). In this case, r0, r3 and r4 are definitely free and f1 needs to be replaced with fake data.

This is still a work in progress thread, and will be updated soon for more implementations, such as:
- 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 👍
 
Last edited by awesomeee,
Removing "Please connect a nunchuk" screens

For fixing Nunchuk errors, usually I will load the game up in Dolphin and let it run until it reaches some kind of Nunchuk error screen. Then in the Memory tab I will switch the search type to ASCII and search for the text that shows up in the Nunchuk error (e.g. "Please connect the Nunchuk to the Wii Remote" type of thing). If it is able to locate the error text, I can then set a read breakpoint (check the Read only button, then right click where the error text is located in memory and Toggle Breakpoint) on that text and find the code where the text is loaded.

From there, you want to use the Callstack in the Code tab to trace your way backward through the code trying to find where the actual Nunchuk check occurred which caused the game to display the error. I mostly do this part via trial and error, I'll go back one or two calls and set an execute breakpoint there (click in the left column of the disassembler in the Code tab) then switch to the Nunchuk in Dolphin settings and see whether that code is still running.

What you want to find is the point where different code paths are taken depending on which controller is connected. The extension types important to us are 0 (none), 1 (Nunchuk) and 2 (Classic Controller). So eventually you should reach a point where there's code which only runs if a specific value is not equal to 1. The extension check will generally look something like cmpwi rX, 0x1, but it's important to note that checking if something equals 1 is very generic code that's used all over the place, so finding a cmpwi rX, 0x1 doesn't mean you've found the Nunchuk check.

If you suspect you have found the Nunchuk check, set a breakpoint on that instruction and try letting the game run with no extension, Nunchuk and Classic Controller. If the register that gets checked matches the values I mentioned above based on which controller you have connected, you can be reasonably sure that it is checking the extension type. If that check fails (is not equal to 1), it means the current connected controller is not a Nunchuk. At that point, you can change this check to also accept Classic Controller, usually looking something like this in assembly which you can paste into CodeWrite:
Code:
  cmpwi r0, 0x1
  beq- RETURN
  cmpwi r0, 0x2

RETURN:
Some games may use cmplwi instead. Also the register number varies
This isn't doing anything too complex: instead of just checking if the value is 1, this checks if it's 1, then checks if it's 2. That way, whatever behavior the game was going to run if the extension type was 1 (like skipping over running the Nunchuk error) will also run if the extension type is 2. A lot of the exact process for how the Nunchuk is checked for varies wildly from game to game, but the extension type values are part of the SDK, so they're something reliable to look for.

Redirecting the Classic Left Stick into the Nunchuk Stick
  • Inside the button injector assembly, add an instruction li r4, 1to make the game skip writing out the Classic Controller button values. You can see examples of this in any of my recent hacks in the Technical Notes section.
    • These would otherwise write to 0x60(r3). That's where the Classic Controller handles its buttons, but also where the Nunchuk handles its analog stick, so if we're pretending to be a Nunchuk, we can't have CC buttons stored there.
  • Locate where read_kpad_stick() sets the address to write the Left Stick (or Right Stick if you're emulating the Nunchuk stick there) data in KPADStatus.
    • This part is pretty easy, especially because you're already tracking down those bctrl instructions. Here's the section you're interested in (example uses Left Stick):
      Code:
      802a07c4 mr        r12, r31
      802a07c8 addi    r3, r30, 12
      802a07cc lha    r4, 0x002C (r4)
      802a07d0 lha    r5, 0x002E (r29)
      802a07d4 lwz    r6, -0x3934 (r13)
      802a07d8 lwz    r7, -0x3930 (r13)
      802a07dc mtctr    r12
      802a07e0 bctrl
    • The specific address you want to adjust is the one with the addi r3, r30, 12, so 802a07c8 in this example.
  • Modify this with an 04 Gecko codetype, replacing the instruction with an mr r3, r30. The code will look like this:
    Code:
    042A07C8 7FC3F378
    The 04 codetype replaces a single word (4 bytes) at a single address. You take the address you want to patch and replace 80 with 04 then put the assembled replacement (In this case, the mr instruction) on the right side.
If that did not work, your game most likely needs code to read the nunchuk stick even though one isnt available.
  1. Configure Dolphin to emulate a Wiimote and Nunchuk and boot into your game

  2. Locate KPADStatus struct (the final controller report from KPADRead)
    • This is fairly easy to find if you've got any of the KPAD functions. Just set a breakpoint on any of the KPAD functions read_kpad_acc, read_kpad_button read_kpad_stick read_kpad_dpd, and when the breakpoint fires, r3 in the Registers tab you can right click on it and view in code. This is theKPADStatus struct. But go in memory instead since that is what we will be using.
  3. Set a read breakpoint on the value at the offset 0x60 from r3, which is the Nunchuk Stick's X axis (left/right).
    • It's useful to hold a direction on your emulated stick before doing this so that you can keep following the same value through memory; it's easier to track a meaningful number than a 0.
  4. When the game reads that value in and writes it to somewhere else in memory (KPADRead()usually does this 3 to 6 times, then the game's own code might do it one or two more before actually processing the data), set a read breakpoint on each of those new addresses and run again until they get hit.
    • Weed out (disable) any breakpoints that are no longer holding the Nunchuk Stick data, sometimes they'll be overwritten with something else and in that case there's no longer any reason for you to keep tracking them, any hits will just be false positives that waste your time.
    • When the data is just being "moved around", it's usually done by loading to and from the general purpose registers (r0, r3-r31). These registers don't "understand" what floating point numbers are, they're just treating these values as generic hex bytes, so you can be reasonably sure that it isn't being meaningfully processed there.
  5. Locate where the stick data actually starts being read, to be used by the game for movement or whatever else the stick does in this game.
    • Continuing on from the previous step of just setting read breakpoints until something interesting happens: if you start seeing the stick data being loaded in via lfs (load floating-point single) instructions, you're almost certain to be on the right track. The floating point registers are used to process the values in terms of what the numbers actually represent. Make a note of anywhere you see floating-point operations going on.
  6. Switch the extension controller to Classic Controller in Dolphin.
    • At this point, you should probably hit Save on your breakpoints, then Clear. You don't strictly need all of those old breakpoints any more and it will get confusing fast having so many around. If you make a mistake and need them back, you can hit Load to get roughly back to where you were.
  7. Breakpoint the location you identified in step 5. If the game no longer hits this address, you've identified where the behavior is different between the Nunchuk and Classic Controller.

    Now, you will need to write an assembly to tell the game to read the Nunchuk stick even though one isnt available.
    • This will look different for every game, so anything beyond this point won't be particularly useful unless you're hacking the same game as me. Here's the code from the example I'm using currently:
      Code:
      ; classic is nunchuk
      ; 80224770 for USA
        extsb r6, r4
        mulli r6, r6, 0xF00
        add r6, r3, r6
        lbz r0, 0xBC(r6)
        cmpwi r0, 0x2
        bne- RETURN
        li r0, 0x1
        stb r0, 0xBC(r6)
      
      RETURN:
        li r0, 0x0
        lis r6, 0x8032
      For this game, it was necessary to generate a pointer to the controller data (that's what the first three instructions are doing), then read in the extension value. Classic Controller is 2, so if the value is not 2 we just return (restoring the registers we clobbered with our extension check), otherwise we replace the extension byte with 1 (Nunchuk) and write it back to the controller data so that any future checks will believe we have a Nunchuk connected.
  8. If the Left Stick now handles Nunchuk interactions, you're done.

Needs some more changes and info, this is mostly copied and pasted from the original guide
 
Last edited by awesomeee,

Site & Scene News

New Hot Discussed User Submitted