Help with ASM hacking in Mega Man 9?

Started by darkeye14, February 04, 2013, 02:29:01 AM

Previous topic - Next topic

darkeye14

EDIT: After changing the "e" in the blr-only code to a capital "E", that particular code worked! However, no other asm code works still...

If I modify all the codes to use capital letters instead of lowercase, I end up with this crash dump, which is different from the one before:
[spoiler][/spoiler]

Yesterday, Delroth and I finished a "fast weapons switch" code for Mega Man 9. Essentially, this allows players to cycle through their weapons with L and R, like in Mega Man 10. In addition, pressing both L+R will change Mega Man's weapon to the Mega Buster, exactly as in Mega Man 10 as well.

We initially wrote this code by finding addresses in Dolphin and writing the logic in PowerPC assembly using those addresses. The code (which works perfectly on Dolphin) is:
Weapon Swap L [delroth]
28319E0A DDFF2000
C0000000 0000000E
3ca00030 60a54e6e
88c50000 2c060001
4d810020 3c608031
60633ffe a0e30000
3c608031 60633fa4
88830000 38840001
2c04000b 40820008
38800000 39000001
7d082030 7d083839
4182ffe4 98830000
3c608031 60634090
38800000 b0830000
38630002 98830000
60c60080 98c50000
4e800020 00000000
00313f99 000000FF
E0000000 80008000

Weapon Swap R [delroth]
28319E0A DDFF0200
C0000000 0000000E
3ca00030 60a54e6e
88c50000 2c060001
4d810020 3c608031
60633ffe a0e30000
3c608031 60633fa4
88830000 2c040000
40820008 3880000b
3884ffff 39000001
7d082030 7d083839
4182ffe4 98830000
3c608031 60634090
38800000 b0830000
38630002 98830000
60c60080 98c50000
4e800020 00000000
00313f99 000000FF
E0000000 80008000

Weapon Clear [delroth]
28319E0A DDFF0000
C0000000 00000003
3c600030 60634e6e
88830000 70840001
98830000 4e800020
E0000000 80008000

L + R = Mega Buster [darkeye]
28319E0A DDFF2200
00313FA4 00000000
02314090 00000000
00314092 00000000
00313f99 000000FF
E0000000 80008000


However, when I tried to run that on an actual Wii using Gecko, this happens the instant it tries to load MM9:
[spoiler][/spoiler]

We then tried to pare down the code so it just changed Mega Man's weapon to Rush Jet with no other frills. Since that can be easily implemented in pure Gecko, I wrote a corresponding Gecko code and tested that as well:
Change to Rush Jet with L (ASM) [delroth]
28319E0A DDFF2000
C0000000 00000003
3c680031 60633FA4
3880000a 98830000
4e800020 00000000
E0000000 80008000

Change to Rush Jet with L (No ASM) [darkeye]
28319E0A DDFF2000
00313FA4 0000000A
E0000000 80008000


Both codes work fine on Dolphin, and the code with no ASM also works perfectly on actual hardware. However, the code with ASM causes the game to crash when you press L; no error message is displayed, and the graphics and sound just instantly stop.

Since I had heard Dolphin had trouble emulating Gecko, I initially chalked this up to inconsistencies between Dolphin and real hardware. However, delroth suggested I feed Gecko a code that only contained a blr:
Only blr [delroth]
28319E0A DDFF2000
C0000000 00000001
4e800020 00000000
E0000000 80008000


Surprisingly, that also causes the Wii to crash when I press L. At this point, it seems like using an ASM at all in my codes is causing the Wii to crash. Can anyone here help me figure out what the cause of this is? Thank you.

Oh yeah, and here's a list of addresses in MM9 and what they do, in case this will help anyone out:
[spoiler]00313FA4 = what weapon Mega Man has equipped
Buster = 0 (0x0)
Magma = 1 (0x1)
Hornet = 2 (0x2)
B. Hole = 3 (0x3)
Jewel = 4 (0x4)
Tornado = 5 (0x5)
Splash = 6 (0x6)
Plug = 7 (0x7)
Concrete = 8 (0x8)
R. Coil = 9 (0x9)
R. Jet = 10 (0xA)

Number of shots on screen: 00314090
Number of Rush on screen: 00314091
Number of Hornets on screen: 00314092

Mega Man covered in ink flag: 00313F99
0xFF = no ink
0x0E = ink
This is needed so that fast weapon switching clears the ink that the octopuses cover you with in Splash Woman's stage

0x00313ffe: a bitmask that tells you which weapons you have
Structured in this manner: 0000 0JRC PSTJ BHMD, where
J = Rush Jet
R = Rush Coil
C = Concrete Shot
P = Plug Ball
S = Laser Trident
T = Tornado Blow
J = Jewel Satellite
B = Black Hole Bomb
H = Hornet Chaser
M = Magma Bazooka
D = Default (Mega Buster)[/spoiler]

dcx2

DSI exceptions are generally data exceptions of some type.  Your DSI exception is being generated by an attempt to read an illegal memory address.  Specifically, if you look at DAR, you can see that the ASM was trying to access address 20004070, which is an invalid Wii address.  The specific instruction which attempted that memory access will be at SRR0 = 800022C8.  That is somewhere in the code handler, which lives at 80001800-80003000.  r3 contains the same value as DAR, so my guess is that the instruction was some form of lwz or stw to 0(r3).

20004070, huh?  That doesn't appear anywhere in your code list.  It looks kinda like an 08 slider code?  Are there other codes that are active?

Are you using Gecko.NET or WiiRDGUI?  Gecko.NET should catch this exception instead of letting the game crash.

I think you can only see that screen if Gecko OS is still running.  Once a game is loaded, you won't see that kind of debugging info on the screen.  This means that your codes are malformed.  I think you can also have this problem if you try to pause-start a game with an unpatched debugger (Gecko.NET applies many patches to the debugger for increased stability)

How are you sending the codes?  Are you using a gct in the SD:\codes\ directory, or are you sending codes over a USB Gecko using one of the remote debuggers (Gecko.NET or WiiRDGUI)?  If you're using a gct, are you using some sort of tool to generate it?  I can't see why having lowercase hex would have anything to do with it, but you might want to try changing all of your code to use uppercase hex.

You don't really need ASM for this, you can use Gecko Register code types to achieve the same effect without ASM.

darkeye14

I'm using Accio Hacks to generate the .gct in SD:\codes\. It apparently is not a fan of lowercase letters, and when I made them all uppercase, it created a different crash dump (which I edited into my old post).

I'm not using Gecko.NET or WiiRDGUI, since I don't own a Gecko USB device. I was just using Gecko OS 1.9.3.1 on the Wii. The only codes that are active are the four I listed before (Weapon Swap L, Weapon Swap R, Weapon Clear, and L+R = Mega Buster).

I initially planned on writing the code entirely in Gecko, but there were a few things that made me switch to ASM:
(1) The algorithm for switching to the next weapon is (weapon_type + 1) mod 11 (for the L button; for the R button, it's just subtraction rather than addition). I was a little confused as to how to implement that in Gecko, especially the mod 11 bit. Looking at the codetypes again, it looks like I can store the weapon type into a Gecko register, add to it with a direct operation, do something (I don't know what yet) to do the mod 11, then load it back. Is my thinking correct here?
(2) I need this code to only activate once per press, rather than once per frame. For example, if I hold down the R button, I shouldn't fly through all the weapons I have; I should just switch once until I let go of the R button and press it again. I guess I store some sort of flag in a Gecko register, then use the "On/Off switch" codetype?
(3) I want to switch only to weapons that I have, rather than to every weapon. To do this, I basically need to keep incrementing (or decrementing) weapon_type until the bit in the game's bitmask is 1. I've never actually done a loop in Gecko, so I don't know how to do that, and as for checking the bitmask, I suppose I could load it into a Gecko register and then AND it with the value of the weapon I'm trying to switch into, then check if it's non-zero? I believe that algorithm works...

Thank you for your response.

dcx2

Your new crash dump shows that the DSI exception was thrown trying to access 00304E6E.  Does this address mean something to you?  Your C0 code actually looks like it's writing to this address...that's not a valid Wii address.  (well, okay, it is if the PPC is in supervisor mode, but only the code handler's interrupt service routine runs in supervisor mode)

I think you may have meant 80304E6E.  You should double check all your addresses.  After looking at your last spoiler in the first post, it looks like you're missing a leading 8 from all your MEM1 addresses.

darkeye14

Ah, that is the value I meant to write, thank you. I fixed that value in the asm, and...I got another crash dump, unfortunately. Even more unfortunately, neither delroth nor I can quite comprehend what it's telling us.

For the record, this is our current code:
Weapon Swap L [delroth]
28319E0A DDFF2000
C0000000 0000000F
3CA08030 60A54E6E
88C50000 2C060001
4D810020 3C608031
60633FFE A0E30000
3C608031 60633FA4
88830000 38840001
2C04000B 40820008
38800000 39000001
7D082030 7D083839
4182FFE4 98830000
3C608031 60634090
38800000 B0830000
38630002 98830000
60C60080 98C50000
4E800020 00000000
E0000000 80008000

Weapon Swap R [delroth]
28319E0A DDFF0200
C0000000 0000000F
3CA08030 60A54E6E
88C50000 2C060001
4D810020 3C608031
60633FFE A0E30000
3C608031 60633FA4
88830000 2C040000
40820008 3880000B
3884FFFF 39000001
7D082030 7D083839
4182FFE4 98830000
3C608031 60634090
38800000 B0830000
38630002 98830000
60C60080 98C50000
4E800020 00000000
E0000000 80008000

Weapon Clear [delroth]
28319E0A DDFF0000
C0000000 00000003
3C608030 60634E6E
88830000 70840001
98830000 4E800020
E0000000 80008000

L + R = Mega Buster [darkeye]
28319E0A DDFF2200
00313FA4 00000000
02314090 00000000
00314092 00000000
E0000000 80008000


And this is the crash dump it gives us:
[spoiler][/spoiler]

Clearly, a null pointer was dereferenced, but looking in the code dump, I don't recognize half the stuff that's going on in there. I recognize little snippets of asm code that's also in our code, but much of it is new to me.

You also suggested implementing this in pure Gecko, which I started to do in case this asm never works out. Here's what I have so far:
Simple Swap L [darkeye]
28319E0A DDFF2000
82000001 80313FA4
86000001 00000001
84000001 80313FA4
E0000000 80008000


I want to make this more robust making it so the user can't go past the last valid weapon (0x0A). To do this, I was going to use an if-statement; if the value in Gecko Register 1 is 0x0B (one past the last valid weapon), then set the value in the register to 0x00. However, I must somewhat shamefully admit that I don't quite know how to do that. Obviously, from looking at the Gecko Codetypes, I'll have to use an A0/A2/etc. code. But no matter what I tried, I couldn't find a code that let me directly compare what was in the register to 0x0B, and whenever I tried to shove 0x0B into some register so I could compare it with register 1, my code never worked. I'm sorry that it's such a newbie request, but I would be greatly appreciative of any code example where a value in a gecko register is compared to a constant value.

dcx2

#5
That's not your code that you see, that's the code handler.  The addresses on this are wrong because it's from a code handler + debugger and you aren't using the debugger, but you can see this is the piece of ASM that was being executed.

[spoiler]80002428:  4182000C   beq-   0x80002434
8000242C:  EC43102A   fadds   f2,f3,f2
80002430:  48000008   b   0x80002438
80002434:  EC4300B2   fmuls   f2,f3,f2
80002438:  D05A0000   stfs   f2,0(r26)
8000243C:  4BFFFB64   b   0x80001fa0
80002440:  7D4802A6   mflr   r10
80002444:  54A51E78   rlwinm   r5,r5,3,25,28
80002448:  7D4A2A14   add   r10,r10,r5
8000244C:  809A0000   lwz   r4,0(r26)
80002450:  81330000   lwz   r9,0(r19)
80002454:  7D4803A6   mtlr   r10
80002458:  4E800020   blr   
8000245C:  40BEFB44   bne-   cr7,0x80001fa0
80002460:  5469C03E   rlwinm   r9,r3,24,0,31
80002464:  7D8E6378   mr   r14,r12
80002468:  48000035   bl   0x8000249c
8000246C:  4192000C   beq-   cr4,0x80002478
80002470:  7E312214   add   r17,r17,r4
80002474:  48000008   b   0x8000247c
80002478:  7D292214   add   r9,r9,r4
8000247C:  5464C43F   rlwinm.   r4,r3,24,16,31

80002480:  38A00000   li   r5,0
80002484:  4182FB1C   beq+   0x80001fa0
80002488:  7D4588AE   lbzx   r10,r5,r17
8000248C:  7D4549AE   stbx   r10,r5,r9
80002490:  38A50001   addi   r5,r5,1
80002494:  7C052000   cmpw   r5,r4
80002498:  4BFFFFEC   b   0x80002484
[/spoiler]

You can see your null pointer is the first bold instruction.  Hmm...r19 isn't one of the known C0 regs.  http://wiird.l0nk.org/forum/index.php/topic,5622.msg55892.html#msg55892

Here's the relevant part of the code handler's source

[spoiler]#CT4============================================================================
#set/add to rN(0) : 80SY000N XXXXXXXX = rN = (ba/po) + XXXXXXXX
#load rN      (1) : 82UY000N XXXXXXXX = rN = [XXXXXXXX] (offset support) (U:8/16/32)
#store rN     (2) : 84UYZZZN XXXXXXXX = store rN in [XXXXXXXX] (offset support) (8/16/32)

#operation 1  (3) : 86TY000N XXXXXXXX = operation rN?XXXXXXXX ([rN]?XXXXXXXX)
#operation 2  (4) : 88TY000N 0000000M = operation rN?rM ([rN]?rM, rN?[rM], [rN]?[rM])

#copy1        (5) : 8AYYYYNM XXXXXXXX = copy YYYY bytes from [rN] to ([rM]+)XXXXXXXX
#copy2        (6) : 8CYYYYNM XXXXXXXX = copy YYYY bytes from ([rN]+)XXXXXX to [rM]


#for copy1/copy2, lf register == 0xF, base address is used.

#of course, sub codes types 0/1, 2/3 and 4/5 can be put together lf we need more subtypes.


_operation_rN:

   bne-   cr7,_readcodes

   rlwinm   r11,r3,2,26,29      #r11  = extract N, makes N*4
   add   r26,r7,r11      #1st value address = rN's address
   lwz   r9,0(r26)      #r9 = rN

   rlwinm   r14,r3,12,30,31      #extracts S, U, T (3bits)

   beq-   cr4,_op0      #lf sub code type = 0

   cmpwi   cr4,r5,5
   bge-   cr4,_op56         #lf sub code type = 5/6

   cmpwi   cr4,r5,3
   bge-   cr4,_op34         #lf sub code type = 3/4


   cmpwi   cr4,r5,1

_op12:   #load/store
   rlwinm.   r5,r3,16,31,31      #+(ba/po) flag : Y
   beq   +8         #address = XXXXXXXX
   add   r4,r12,r4

   cmpwi   cr6,r14,1
   bne-   cr4,_store

_load:
   bgt+   cr6,+24
   beq-   cr6,+12

   lbz   r4,0(r4)      #load byte at address
   b   _store_reg

   lhz   r4,0(r4)      #load halfword at address
   b   _store_reg

   lwz   r4,0(r4)      #load word at address
   b   _store_reg

_store:

   rlwinm   r19,r3,28,20,31      #r9=r3 ror 12 (N84UYZZZ)

_storeloop:
   bgt+   cr6,+32
   beq-   cr6,+16

   stb   r9,0(r4)      #store byte at address
   addi   r4,r4,1
   b   _storeloopend

   sth   r9,0(r4)      #store byte at address
   addi   r4,r4,2
   b   _storeloopend

   stw   r9,0(r4)      #store byte at address
   addi   r4,r4,4
_storeloopend:
   subic.   r19,r19,1
   bge    _storeloop
   b   _readcodes


_op0:   
   rlwinm.   r5,r3,16,31,31      #+(ba/po) flag : Y
   beq   +8         #value = XXXXXXXX
   add   r4,r4,r12      #value = XXXXXXXX+(ba/po)   



   andi.   r5,r14,1      #add flag : S
   beq   _store_reg      #add flag not set (=0), rN=value
   add   r4,r4,r9      #add flag set (=1), rN=rN+value
   b   _store_reg





_op34:   #operation 1 & 2


   rlwinm   r10,r3,16,30,31      #extracts Y

   rlwinm   r14,r4,2,26,29      #r14  = extract M (in r4), makes M*=4

   add   r19,r7,r14      #2nd value address = rM's address
   bne   cr4,+8
   subi   r19,r15,4      #lf CT3, 2nd value address = XXXXXXXX's address


   lwz   r4,0(r26)      #1st value = rN

   lwz   r9,0(r19)      #2nd value = rM/XXXXXXXX


   andi.   r11,r10,1      #lf [] for 1st value
   beq   +8
   mr   r26,r4         


   andi.   r11,r10,2      #lf [] for 2nd value
   beq   +16
   mr   r19,r9
   bne+   cr4,+8   
   add   r19,r12,r19      #lf CT3, 2nd value address = XXXXXXXX+(ba/op)



   rlwinm.   r5,r3,12,28,31      #operation # flag : T


   cmpwi   r5,9
   bge   _op_float


_operation_bl:
   bl   _operation_bl_return


_op450:
   add   r4,r9,r4      #N + M
   b   _store_reg

_op451:
   mullw   r4,r9,r4      #N * M
   b   _store_reg

_op452:
   or   r4,r9,r4      #N | M
   b   _store_reg

_op453:
   and   r4,r9,r4      #N & M
   b   _store_reg

_op454:
   xor   r4,r9,r4      #N ^ M
   b   _store_reg

_op455:
   slw   r4,r9,r4      #N << M
   b   _store_reg

_op456:
   srw   r4,r9,r4      #N >> M
   b   _store_reg

_op457:
   rlwnm   r4,r9,r4,0,31      #N rol M
   b   _store_reg

_op458:
   sraw   r4,r9,r4      #N asr M

_store_reg:
   stw   r4,0(r26)      #Store result in rN/[rN]
   b   _readcodes

_op_float:
   cmpwi   r5,0xA
   bgt   _readcodes

   lfs   f2,0(r26)      #f2 = load 1st value
   lfs   f3,0(r19)      #f3 = load 2nd value
   beq-   _op45A


_op459:
   fadds   f2,f3,f2      #N = N + M (float)
   b   _store_float

_op45A:
   fmuls   f2,f3,f2      #N = N * M (float)

_store_float:
   stfs   f2,0(r26)      #Store result in rN/[rN]
   b   _readcodes

_operation_bl_return:
   mflr   r10
   rlwinm   r5,r5,3,25,28      #r5 = T*8
   add   r10,r10,r5      #jumps to _op5: + r5

   lwz   r4,0(r26)      #load [rN]
   lwz   r9,0(r19)      #2nd value address = rM/XXXXXXXX

   mtlr   r10
   blr

[/spoiler]

It looks like you're somehow activating an 86 or an 88 code type?  That's what this part of the code handler works with.

What regs are you using for your C0 code?  Is it possible you've got a bad branch in your C0 that's jumping to the wrong part of the code handler somehow?  What are you using to assemble your C0 codes?

---

Regarding the if statement, your problem is that there is no 8-bit if code type.  You need to use a 16-bit if and then use the mask field to ignore the byte you aren't interested in.  Make sure you maintain 16-bit alignment.  For example, if you wanted to 8-bit test on the upper byte of a 16-bit halfword at 80000002, you'd use a mask of 00FF.  If you wanted the lower byte, you'd still use address 80000002, but you'd use FF00 instead to ignore the upper byte.  Notably, the mask is inverted before ANDing, so the 1-bits are the ones which are ignored.  This is awkward if you're familiar with bit masking already.

You also mention it's in a GR.  The GR if codes use 16-bits as well, so you still need the mask.  However, you don't need to put the value [EDIT: to compare against into another] GR.  You can test the address of the [first] GR directly with a 28 code type - you just need to know the address of the GR in question.  Modern code handlers use GR0 = 80001808, GR1 = 8000180C, etc.  Older code handlers are 4-bytes earlier...so this technique isn't exactly the most reliable.  In practice, everyone is using the 1931 code handler, though, so 80001808 = GR0 is a safe assumption.