Implementing Custom AI in a Fighting Game – Part 1

Back in 2020 I used to play a videogame called Jump Force. Altough the popularity wasn’t high, I still enjoyed it a lot. You could chose to play as any of the popular anime characters like Goku, Naruto or Luffy. But it has a major flaw: the enemy bots were weak and to me that was a major bummer. I’ve learned the basics and some advanced mechanics, plus bots had almost no idea what to do in case I was guarding my character.

That wasn’t fun. Unfortunately due to the poor success of the game, the developers announced that they would put the game to sleep. This further decreased the daily population of players, which made matchmaking longer and longer, I did not want to wait in queue to play vs good players, I wanted to play right now!

In theory, the plan was simple:

  1. Emulate a secondary controller
  2. Hook the memory responsible for handling controller inputs
  3. Read player state (the environment)
  4. Force a controller input at the assembly level

This should be enough to have a strong player.

 

One major rule, I can use any language and tool I want to reverse engineer the game, but the Ai logic must be written in 100% assembly.

Hooking XInput

Since we don’t currently know how the game handles internal inputs for the bots, I decided to go for hijacking controller inputs, this way we are sure that actions sent to the game will always be valid.

This is pretty easy to find, the XINPUT_GAMEPAD structure is known and since wButtons is a 16 bit value I already had my starting point. I’ll use CE memory scanner to find changes in values that correspond to the hex values which I’m pressing, till I’m left with one address, then I can see which instruction accesses it and boom! I found a function that constantly handles inputs. All I have to do now, is to write an assembly patch that can accept custom values.

Hooking player state

We have to get some basic information about the game, here’s an example.

Same process as before scan for changes either floating points or integers. Optimization in compilers tend to use those types a lot unless the developers specified otherwise.

There also another state that we have to find and this one, isn’t always easy to find. We have to find the current action_id of our character and rival. In the above screenshot both players are in an “Idle” state, this is usually considered to be the default one, meaning (by logic) if this was something I would code in an enum of possible states, this would be set to 0.

And the logic was correct, this way I found around 500 different states. But before I could code any rules I had to manually map them out, and make some sort of simplified state. This is because some actions were basically identical, for example by repeatedly pressing the attack button, our character would perform a sequence of attacks.

Regardless of the id of the character, an example sequence would be 41 -> 42 -> 43 those were light punches. So I allocated some memory (in 64bit processes I always allocate 8 bytes just to be sure everything is properly aligned and I have some margin of freedom) and made a simplified version, or in my case all punches were set to id 10. Same went for skills, each character has 4 unique abilities, I’ve noticed that all abilities were always above id 150. This did the trick:

; After separating the two player pointers (this is an oversimplification)
; Now P1_ACTION holds the value 12, which means 'usingSkill'
; RCX+24 holds the current raw action_id
call @Simplify_Actions
mov rax,[rcx+2C]
mov [P1_ACTION],eax
; ...
; ret

@Simplify_Actions:
    cmp [rcx+24],#150
    jl @f
        mov [rcx+2C],#12   ; Unused free space in the struct. 12 = Using ability
    @@:
ret

I went from having 500 unique moves to only 35, which is a massive improvement. Remember, the logic must be written only in assembly as part of the project’s rule. This allows us to make less comparisons and the code becomes way easier to handle.

The first prototype

The setup required many steps and it wasn’t easy, but it worked! I was so proud of myself, the logic took about 5 hours of coding and 550 lines of asm code. My character could send actions based on what was happening.

On the top right of the screen, you can see the current action of both players. At the time this was all my bot was able to see.

But I wasn’t done yet, it needed strong improvements.

Even though I had a working proof of concept, a POC is not a great fighter. In fact it would get defeated quite easily by the strongest bot the game had to offer. If I wanted better bots, mine had to defeat it, then it could be considered a worthy opponent.

The problems

No real control: The main problem was that I had still no control over the bots, I wasn’t able to alter any of their logic, so far only of a real controller. I had to find some sort of “main value” that had full control over the bot’s decisions. But even the fact that I’m required to use a controller, in order to use this modification, an emulated or physical controller was needed.

I wanted to share this mod with the public, so others could enjoy the same fun as me. Someone might not be so technical or simply doesn’t have a controller.

Incomplete logic: Fighter games often require sharp reflexes and some strategic planning. The strongest players in fighting games, are usually the most aggressive. Since the inputs are tied to the FPS (Frame per second) and my game was set to a limit to 60, my bot could take an action every 16.67ms (which is way faster than the average human reaction time)

Those were the two main obstacles I had.

The solution

Instead of replacing my own controller inputs, I should find out how the game handles bot inputs, after all it’s an UnrealEngine 4 game, we got plenty of tools, how.. hard.. could it .. be. Turns out around 200 hours.

This is due to confusion caused by my inexperience, not necessarily in reversing executables, but handling bits instead of bytes.

Luckily for me the game was written in C++ and the developers did not strip all RTTI symbols. This saved me time because I could quickly figure out some of the classes the game was trying to access. One of those was “RB::RBVirtualPad“ which from now own I will call VPAD.

VPAD holds both button and stick values, but the layout isn’t like an XInput, though it’s still 16bit. I manually mapped the values and from there I had full control of the bots.

-- VPAD bits
local AWAKEN         = 0x8000
local ESCAPE         = 0x4000
local FOLLOW         = 0x2000
local GUARD          = 0x1000
local ULTIMATE       = 0x0800
local SKILL_CIRCLE   = 0x0400
local SKILL_TRIANGLE = 0x0200
local SKILL_SQUARE   = 0x0100
local SWAP           = 0x0080
local CHARGE         = 0x0040
local HEAVY          = 0x0020
local LIGHT          = 0x0010
local GRAB           = 0x0008
local JUMP           = 0x0004
local MOVE           = 0x0002
local UNKNOWN        = 0x0001

Now that I had full control of the bits and more states to play with, I could take the bot intent and do a manual check before it was sent out, here’s an example:

; If the rival just escaped, initiate new aggression tactic
anti_escape:
    cmp [P1_ACTION],#16
    je @f
       ret
    @@:
    mov [DEBUG_STATE],#5
    push rbx
      ;xor rbx,rbx ; fully clear bot's controller
      mov rbx,[rcx+10]

      bts rbx, #13  ; set bit 13 (follow, value 8192)
      bts rbx, #8   ; set bit 8 (skills, value 256)
      bts rbx, #9   ; set bit 9 (skills, value 512)
      bts rbx, #10  ; set bit 10 (skills, value 1024)
      btr rbx, #11  ; clear bit 11 (ult, value 2048)

      mov [rcx+10], rbx
    pop rbx
ret

By treating each bit in the VPAD controller as a whitelist for actions, the game would automatically discard invalid moves and I would have the final say. I spent another 10 to 20 hours perfectioning this filter, my rule-based ai was now a success. It fought aggressively, made rapid changes to its decisions and made 5 different difficulties. Since the game has 7, I reached 12.

The new Ai mod had reached almost 1000 lines of code and was well received by the community, I got lots of suggestions and people reported issues.

Showcase of video Bot 7 (our prospective) vs. AsmBot 12

Closing statement and conclusion

Rule-based bots in fighting games are for sure a powerful solution.

For the sake of the shortness of this blog post and to make it less complex than it has to be, I had to leave out some details, such as the time spent navigating through the structures, figuring out which value does what, mapping functions via some tools like UE4SS, Ghidra and x64dbg.

This has been a long lasting project that I’m continuing to improve from time to time, I’ve put so much effort into learning the dynamics and it has been really fascinating seeing bots fight like crazy, that’s how I like bots, strong but not impossible to beat, unless I give myself the option to!

I haven’t linked the assembly code, as it’s part of a .CT file (Cheat Table) but in the description of the video showcase above, there’s a link to the mod page.