
Game: Madden NFL 20
Engine: Frostbite
Studio: EA
Buy: Origin
Introduction:
EA: where do I even start? EA has a reputation as one of the worst game companies around and for good reason. If someone told me EA was using child labor to run their acid mines I'd probably believe you. In case you need a refresher on their evil deeds over the years checkout the video below:
Today we're continuing on with hooking game input. If you didn't see the previous tutorial on hooking Direct Input 8 you can read that here. So while I was messing around with Tomb Raider @pcG jumped into the chat and asked me what the best way would be to emulate input (since I was trash talking SendInput and the like). I said that hooking Direct Input or the Window Procedure was probably the most efficient way if your code is running inside the process.
We then proceeded to attempt to hook the input of his game of choice: Madden NFL 20. What followed was a long drawn out process whereby we tried to nail-down exactly how the game was processing input. Madden loads dinput8.dll so the assumption was it processes input that way: nope. I then hooked the window procedure and blocked the input messages the game still received input.
Solving the conundrum:
So after all the obvious (read: ways Microsoft recommends you acquire input) I tried to get in the mindset of an EA employee. I work 13 hour days, my wife left me because I never take time off work because EA runs like a prison. This project is 3 months behind schedule and it needs to be out in time for summer. My butthole hurts because I've been thoroughly reamed by the company bull. There's a bug in our input processing code: FUCK IT: USE GetAsyncKeyState.
The most ghetto solution is the correct one. I'm unsure if this applies to all Frostbite Engine games but Madden NFL 20 actually contains a bunch of strings from other games. It also has a bunch of random code and other garbage in there that it never touches. Frostbite is confirmed shitcode.

This is what Frostbite looks like in x64dbg or IDA.
I didn't actually bother reversing the game too much but it looks as though Frostbite loops through an array forever checking the state of various keys to see if they've been pressed then fires the game logic for that key-press. The good news is this is a really unsophisticated way of gathering input and trivial to hook.
Injecting code:
For whatever reason the hardest part of this entire thing was the issues I had actually getting my code injected. My injection tool failed, the GuidedHacking Injector failed, x64dbg couldn't even load the damn library. I even attempted to modify the IAT but the game only ran once and then never ran again after that. Usually I'd look into exactly why this stuff was happening but I wasn't interested I just wanted my damn code in there so I could get the input.
The solution I ended up using was a ProxyDLL which I consider to be the best way to inject code into a process. In my case I noticed that Madden's IAT had an entry for version.dll which is super easy to proxy because most processes only use three functions:
- GetFileVersionInfoSizeA
- VerQueryValueA
- GetFileVersionInfoA
GetAsyncKeyState:
The function prototype for GetAsyncKeyState is:
C++:
SHORT GetAsyncKeyState(
int vKey
);
C++:
/*
48 B8 - MOV RAX, 0ffffffffffffffffh ; This is our placeholder value
FF E0 - JMP RAX
*/
// Typedef for GetAsyncKeyState
typedef SHORT(WINAPI* GetAsyncKeyState_t)(int);
GetAsyncKeyState_t fnGetAsyncKeyState = NULL, , fnNtGetAsyncKeyState = NULL;
// The address to detour
fnGetAsyncKeyState = (GetAsyncKeyState_t)GetProcAddress(LoadLibrary(L"user32.dll"), "GetAsyncKeyState");
// The address we're going to call when we've finished
fnNtGetAsyncKeyState = (GetAsyncKeyState_t)GetProcAddress(LoadLibrary(L"win32u.dll"), "NtUserGetAsyncKeyState");
uint8_t patch[] = { 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0 };
// Move the pointer to start of the jmp address
uintptr_t* hookAddr = (uintptr_t*)(patch + 2);
// Overwrite the placeholder value with our hook address
*hookAddr = (uintptr_t)GetAsyncKeyState_Hook;
// Change memory prots
if (!VirtualProtect(fnGetAsyncKeyState, 12, PAGE_EXECUTE_READWRITE, &oldProt)) {
printf("[-] VP Failed: %d\n", GetLastError());
return -1;
}
// Copy bytes
memcpy(fnGetAsyncKeyState, patch, 12);
// Change it back to the original values
if (!VirtualProtect(fnGetAsyncKeyState, 12, oldProt, &oldProt)) {
printf("[-] VP Failed: %d\n", GetLastError());
return -1;
}
Madden20.exe -> GetAsyncKeyState -> JMP GetAsyncKeyState_Hook -> CALL NtUserGetAsyncKeyState.
The original function is overwritten so we must call NtUserGetAsyncKeyState from our hook.
Here comes the interesting part.
Emulating key presses:
Blocking input is simple enough:
C++:
SHORT GetAsyncKeyState_Hook(int vKey)
{
if (vKey == VK_SPACE){
printf("[X] VK_SPACE\n");
return 0;
}
return fnNtGetAsyncKeyState(vKey);
}
However because they're calling this function in a loop we need a way to time our key-presses so they seem natural. In the DInput tutorial I used GetTickCount64 to time my button presses and it will probably work here too.
So if we follow the same rough format we can implement something like so:
C++:
SHORT GetAsyncKeyState_Hook(int vKey)
{
// Our data structures for key injection
static bool bShouldInject = false;
static ULONGLONG startTime = 0, endTime = 0;
struct KEYDATA
{
int vKey;
DWORD duration;
};
static KEYDATA keys[256];
static int currKey = 0;
static int totalKeys = 0;
// Timing check
endTime = GetTickCount64();
// Delete will be our trigger key
short keyPress = fnNtGetAsyncKeyState(VK_DELETE);
if (bShouldInject && vKey == keys[currKey].vKey &&
(endTime - startTime) > keys[currKey].duration)
{
printf("[+] Sending key: %d\n", keys[currKey].vKey);
startTime = GetTickCount64();
if (currKey++ >= totalKeys) {
printf("Injection finished.\n");
bShouldInject = false;
}
return -32767; // The 'magic' return value
}
else if (keyPress && !bShouldInject)
{
printf("[+] Injecting keys..\n");
// Delete was pressed
bShouldInject = true;
// Send the right arrow key three times
// Pausing for a 1s in between
keys[0].vKey = VK_RIGHT;
keys[0].duration = 0;
keys[1].vKey = VK_RIGHT;
keys[1].duration = 1000;
keys[2].vKey = VK_RIGHT;
keys[2].duration = 1000;
currKey = 0;
totalKeys = 2;
// Reset the clock
startTime = GetTickCount64();
}
return fnNtGetAsyncKeyState(vKey);
}
And that's the "correct" way to simulate key presses in Frostbite.
Final result:
Last edited: