For the purpose of synchronising when each player loads into a race, we can take control of the function that handles player input in the Practice mode options menu. By disabling the function entirely, we can also prevent the user from manually changing race options that should be set by the server. Then, by manually calling the function that usually runs when the player presses the A button on the OK option, all players can load into the race at the same time.
Finding the function
It’s a bit crude, but by pressing A to start a race and then immediately pausing the emulation, we can find code that’s running after we press A. We then set a breakpoint on that line and reload the save state; repeat until the execution only breaks in the screen transition. Then, we can trace back the execution until we find a function that only breaks immediately after the A button is pressed.
After some trial and error, and tedious tracing back, we find a branch instruction that usually branches, but as soon as we press A to load the race, it doesn’t branch. So, replacing this branch instruction with NOP so it never branches, the race starts loading without us pressing anything. Forcing it to always branch results in input being disabled in that menu.
Editing the function
We want to control exactly when that branch instruction runs. So, we can replace it with a branch instruction that checks some memory address we control ourself - then when we want to load the race, we simply change the data in that memory address.
The current branch instruction compares the value in register 3 with the value in register 0. Register 3 is set by a function called a few lines earlier, so we can control register 3 by removing that function call and replacing it with instructions to load a byte into register 3:
bl [address]
becomes
lis r3, 0x8060
lbz r3, 0 (r3)
which loads a byte from 0x80600000. Setting that byte to 1 will now load the race immediately (if the options menu is open).
Address
The address of the changed function call at runtime is found at reference pointer + 0x3e28f0, where reference pointer is found at 0x800030c8. The function is loaded from fze.sel.rel.