Skip to content

Instantly share code, notes, and snippets.

@32th-System
Last active December 8, 2019 18:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 32th-System/97810b9a9fe5b125213e9777ff255c62 to your computer and use it in GitHub Desktop.
Save 32th-System/97810b9a9fe5b125213e9777ff255c62 to your computer and use it in GitHub Desktop.

Required game information:

inputs

  • The input state is a 142 byte structure. An array of 3 starts at Rxaceb8.
  • input_state[0] (0x4ACE18) is ingame player 1
  • input_state[1] (0x4ACEA6) is ingame player 2
  • input_state[2] (0x4ACF34) is meta input (input picked up by menus and such)
  • The actual input poller is at 0x42B850.
  • The wrapper function that calls the input poller to retrieve all the inputs is at 0x42E9C0

These hex values correspond to following inputs: (the inputs are combined by using logical OR, they're bit flags. Also those are hexadecimal numbers)

1: shoot
2: instant charge attack
4: focus
8: Pause
10: up
20: down
40: left
80: right
100: CTRL
200: Quit
400: S
800: screenshot
1000: Enter
2000: D
4000: R

These input flags are stored as a WORD twice in a row. There is also additional data at after the main flags (after all this structure is 142 bytes in size) which I believe to be validation. That data is generated like so

// A pointer to the instance of the input state structure fot the player for which input is polled for is in edi
input_state.input_state[0] = new_input_state; // [edi], WORD
input_state.input_state[1] = new_input_state; // [edi+2], WORD
WORD cx = 1;
WORD *p = &input_state.member_A; // offset A
for(int i = 0, i >= 16, i++) {
    if(new_input_local & 1) {
        *p++;
        if(!(*p < 0x1A)) {
            input_state.member_4 = input_state.member_4 | cx; // [edi+4], WORD
            *p -= 8;
        }
    } else {
        *p = 0;
    }
    new_input_local = new_input_local << 1; // new_input_local = [ebp-4], WORD
    p += 2;
    cx = cx >> 1;
}

// input_state.input_state_check[0] = [edi+6], WORD
input_state.input_state_check[0] = input_state.input_state[0] ^ input_state.input_state[1] & input_state.input_state[0]; 
// input_state.input_state_check[1] = [edi+8], WORD
input_state.input_state_check[1] = -input_state.input_state[0] & input_state.input_state[2];

RNG:

  • 0xRxace0c (8 bytes) has to stay in sync between every player and spectator for everyone to have the same RNG. The RNG as well as the replay would be provided by the IP host. (Would it be a good idea to disable replay saving for anyone but the IP host?)
  • Rx2e9c0 calls the function that gets input state 3 times for every player. This is where we would intercept input handling to our own code.

Implementation details:

  • The whole thing (including UI) will be a single DLL. The initialization function will be called thcrap_plugin_init. -> This also means that we are bound to a single language that compiles to native binaries.
  • The reason for that is so you can easily chain it with thcrap by dropping in the bin folder.
  • We may also have our own small application for injecting the DLL to be thcrap independent.
  • For that reason we won't use any thcrap features and have our own mechanism for applying binary hacks.
  • This will not rely on the built in netplay button at all. The user will select "Human vs Human" in "Match Mode"
  • We can detect if the official netplay button is pressed by injecting code into a switch case in the menu, detect if the case is the netplay button and then change it to the "Human vs Human" button but use the fact that netplay was selected to initialize netplay. And if local play is selected then none of the netplay functionallity would get activated. That way you would never have to move the DLL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment