The thread on Smashboards has devolved into the aforementioned goon persisting that he was the original author and that subsequent discussion and GameShark codes are in fact falsified, and whether another member should or should not kill himself for wasting air. I wish I was joking
So here's the relevant stuff...
__________
Why the Crash Happens
Remember the thing that started this all, what with Final Destination crashing when selecting it for a multiplayer match? The culprit begins right here:
80104C34 2F 01 00 10 SLTIU AT, T8, 0x0010 ; Set AT if level is < 0x10
80104C38 10 20 00 18 BEQ AT, R0, 0x80104C9C Two lines of code tucked into the Vs. stage loader check if the level happens to be 0x10 (a check immediately prior branches for everything greater than or equal to 0x11).
Here's what the code does: if Final Destination was set, the function then jumps to 0x80104C84, which has two function calls: first to 0x80192764, then to 0x80104850. If Final Destination was NOT set, then instead the function jumps to 0x80104C9C, where there's another call to 0x80104850. The code paths then merge after that. Why they didn't merge before calling 0x80104850, who knows. It was probably in the source code that way.
Obviously, that call to 0x80192764 is what's causing the problem. Turning that into some form of non-operation will prevent the crash from occurring. So the GameShark code in the original post can be cut down a couple of lines:
D0099113 0018
80099113 0019
D1104C84 0C06
81104C84 2000ā Regrettably, this more efficient version of the code isn't the one that will go down in history, because I already made a splash with the original one and some moron at Smashboards caused a scene with it. |-:
The contents of memory at 0x80192764 (the site of that function) are... well, undefined. Really. It's garbage data. It doesn't make any sense to program in a function call to garbage data, so it's clear that there used to be something there. Keep in mind that this code path only applies to the multiplayer stage loader. It's not processing any Master Hand-related code, it's just trying to execute nothingness and crashing.
This may be a lowly hypothesis, but hear me out... Whatever that function used to do only happened when Final Destination was being loaded in Vs. mode, and was since removed. Battlefield and Metal Mario's stage both load and play just fine in Vs. mode. But Final Destination, well, it has that background that changes according to Master Hand's HP. I know that I personally can't think of an elegant way to hook into that background code in a Vs. match, and what if HAL didn't have a good way either? Think about it: all versions of Final Destination since then have had timed scenery changes and are available in Vs. mode.
So I think it went like this: Final Destination's graphics weren't cooperating, so they left the stage out of Vs. mode. But then since they left out one special stage, they had to leave out the others too: Battlefield and Metal Mario's level, lest the players start asking questions about it.
Whatever the case, one thing is clear: it doesn't have anything to do with Master Hand.
__________
Random Override
One user asked if it was possible to replace the behavior of the "Random" stage selection, and user tehz as well as myself looked into it. What we found was that the stage select menu's cursor is checked against the value 9 (which is the "Random" selection) and rolls a random number if it was selected. It also checks if Mushroom Kingdom was rolled yet not unlocked, in which case it rolls another random number. Lastly, it checks the previously-rolled level and rolls a new one if the next one matches it, preventing the same stage from being selected twice in a row.
The code looks like this at runtime:
80133BBC 3C 04 80 13 LUI A0, 0x8013 # Load stage select cursor from 0x80134BD8
80133BC0 8C 84 4B D8 LW A0, 0x4BD8 (A0)
80133BC4 24 01 00 09 ADDIU AT, R0, 0x0009
80133BC8 3C 11 80 0A LUI S1, 0x800A
80133BCC 14 81 00 0D BNE A0, AT, 0x80133C04 # If cursor == 9 then {
80133BD0 26 31 4A D0 ADDIU S1, S1, 0x4AD0 # Occurs before branch--S1 = 0x800A4AD0
80133BD4 0C 00 62 8C JAL 0x80018A30 # Roll a random number...
80133BD8 24 04 00 09 ADDIU A0, R0, 0x0009 # ... modulo 9 (occurs before function call)
80133BDC 00 04 08 25 OR S0, V0, R0
80133BE0 0C 04 C6 EB JAL 0x80131BAC # Check if Mushroom Kingdom was rolled, but not unlocked
80133BE4 00 40 20 25 OR A0, V0, R0 # V0 is non-zero if the check fails
80133BE8 14 40 FF FA BNEZ V0, 0x80133BD4 # If yes and no, roll another number
80133BEC 00 00 00 00 NOP
80133BF0 92 29 00 0F LBU T1, 0x000F (S1) # Load previous stage roll from 0x800A4ADF
80133BF4 12 09 FF F7 BEQ S0, T1, 0x80133BD4 # If we rolled it again, roll another number
80133BF8 00 00 00 00 NOP
80133BFC 10 00 00 06 BEQ R0, R0, 0x80133C18
80133C00 A2 30 00 0F SB S0, 0x000F (S1) # Occurs before branch--Write new rolled stage roll
# } else {
80133C04 0C 04 C9 0C JAL 0x80132430 # Load level index by stage select cursor value
80133C08 00 00 00 00 NOP
80133C0C 3C 11 80 0A LUI S1, 0x800A # S1 wasn't modified by the function, but whatever
80133C10 26 31 4A D0 ADDIU S1, S1, 0x4AD0
80133C14 A2 22 00 0F SB V0, 0x000F (S1) # Write new rolled (and translated) stage roll
# } See that NOP at 0x80133BF8? The compiler put that in there because the instruction following a branch always executes before the program counter is updated. tehz used the GameShark to overwrite that instruction (remember, this code is loaded into RAM for faster access) to load a level index of our choosing into S0, effectively ignoring the result of the random number generator:
D1133C00 A230
81133BF8 2010
D1133C00 A230
81133BFA 00?? ... where "??" is the index of the level to load instead of a random one. In effect, this adds a tenth stage to the stage select menu.
tehz also brought up the idea of expanding the pool of random levels to include such courses as the Metal Mario stage, Battlefield and Final Destination. The problem with that is that, unlike the first nine, the index values are not sequential, so you can't just change the number to divide at the end of the random number generation. So I worked on that one.
Instead of checking for Mushroom Kingdom in that one function call up there, I decided to redirect the program to a function of my own design instead that would use the output of the RNG to pick a stage from a custom pool. I found what I believe to be unused bytes in static memory at 0x800A4D40. So the modulo number needs to be changed from 9 to 12 (to incorporate the 3 stages I want to add), and the destination of the function call needs to be updated:
D1133BD8 2404
81133BDA 000Cā
D1133BE2 C6EB
81133BE0 0C02
D1133BE2 C6EB
81133BE2 9350ā Clear as mud? The JAL instruction on MIPS is absolute, not relative, and since all instructions are 32 bits, the actual bus address is that value multiplied by 4. So in other words, 0x29350 * 4 = 0x(80)0A4D40. Aaaanyway...
The function I designed to go at 0x800A4D40 is as follows:
800A4D40 3C 10 80 0A LUI S0, 0x800A
800A4D44 02 04 80 20 ADD S0, S0, A0
800A4D48 82 10 4D 54 LB S0, 0x4D54 (S0)
800A4D4C 03 E0 00 08 JR RA
800A4D50 20 02 00 00 ADDI V0, R0, 0x0000 This adds the random number (stored in register A0 prior to the function call) to 0x800A4D54 and loads the byte at that address as the level index to use. When it exits, it clears register V0 so the branch in the calling function (the one that prevents a locked Mushroom Kingdom from being selected) doesn't get taken.
Since this code doesn't get swapped in and out depending on what the program is doing, it can be written unconditionally and save a lot of lines of GameShark code:
810A4D40 3C10
810A4D42 800A
810A4D44 0204
810A4D46 8020
810A4D48 8210
810A4D4A 4D54
810A4D4C 03E0
810A4D4E 0008
810A4D50 2002
810A4D52 0000ā Still kind of a lot, but it works.
So what's at 0x800A4D54, you ask? Well, it's the bytes immediately following this function soooo... whatever we want. In this case, I want 12 level indexes: 00, 01, 02, 03, 04, 05, 06, 07, 08, 0D, 0E and 10, for the stages I want. GameShark does that for us too:
810A4D54 0001
810A4D56 0203
810A4D58 0405
810A4D5A 0607
810A4D5C 080D
810A4D5E 0E10ā If you take the time to punch that into a GameShark and run the game, the Random option on the stage select menu works normally, except that all nine stages have a 1/12 chance of occurring, as well as 1/12 for Metal Mario's stage, Battlefield and Final Destination. Neat! |