Discord overlay's incompatibility with Origin's igo[32/64].dll

Discord overlay's incompatibility with Origin's igo[32/64].dll

Recently, I was informed of an issue with Burnout Paradise Remastered freezing when alt-tabbing back in. Further investigations revealed a broader incompatibility between the Discord overlay and Origin games.

Before being approached, I had not been aware of any such issue. I never experienced it myself. It was only when I discovered the presence of DiscordHook.dll that I figured that enabling the overlay may have been the cause.

I could have just chalked this off as another game-specific incompatibility with the Discord overlay but I decided to figure out exactly where and why it was stalling. As it turns out, the actual bug is one that covers all Origin games. The symptoms just vary from game to game.

Burnout Paradise Remastered: a case study

First of all, where is the code freezing in BPR? The situation was complex: the main thread was locked on a semaphore, which is normally "released" by a background thread. This background thread was also locked on a different semaphore, again normally released by a 3rd thread. On inspecting this 3rd thread, I found an infinite loop on the following code:

while (ShowCursor(FALSE) >= 0);

The reason for this code is because of an oddity in the Windows API. Ber'Zophus explains it well:

But ShowCursor is not a void method. It actually returns an integer (a signed one). See, as it turns out, the simple act of whether a mouse cursor is shown or not is not just "yes" or "no", as stupid as that sounds. Instead, there's really an integer counter. If this counter is greater than or equal to zero, the mouse cursor will be shown. If it's less than zero, the mouse cursor will not be shown. All that ShowCursor does is adjust this counter. If you call ShowCursor(true);, it just adds one to the counter. If you call ShowCursor(false);, it just subtracts one from this counter. Then, ShowCursor returns what the counter has become in the end.

Okay, so that explains the necessity of the loop, but why is it never reaching -1? The problem is in those hooking ShowCursor.

The root cause

Looking at the code in memory, Discord has a hook on ShowCursor which doesn't do much (just handling for when its overlay is visible which wasn't applicable here) and then jumps to the code that was originally there, which was actually Origin's hook.

This is where it all breaks down. The first thing that all Origin hooks do is check whether its hook is still "alive" and unaltered. The following is how the start of the ShowCursor hook looks:

// start of said ShowCursorHook function
if (origShowCursorPtr != NULL && hasIGOHookedShowCursor)
{
    if (!CheckHook(origShowCursorPtr, ShowCursorHook, FALSE, TRUE))
    {
        sub_10016F90(...); // unimportant incrementer
        return 0;
    }
}
else
{
    // Log that hook is dead.
}

// process

What CheckHook does is it it checks that the contents of ShowCursor currently match a "snapshot" of how it looked when IGO hooked it. Since Discord has hooked into it since, it does not match so returns FALSE. This leads to the IGO ShowCursor hook to return zero every time. This is our problem. It intentionally does not call the original function in this case.

We can confirm this by checking the logs. [Note that the addresses mentioned here were correct as of 2018-03-14 and will likely change between updates. Let me know if you would like an updated address.] First reproduce the freeze, then change the byte at igo32.dll+11FCB8+220 to 01 for a couple seconds and then change back to 00. Now check the latest IGO log in C:\ProgramData\Origin\Logs. The exact file path can also be found in memory at igo32.dll+11FCB8 in UTF-16. In this log, you'll notice a lot of something like this:

WARN	02:11:17 AM (0)	39036	         HookAPI.cpp:  143		Hook was altered: 661B03F0
WARN	02:11:17 AM (0)	39036	         HookAPI.cpp:  143		Hook was altered: 661B03F0
WARN	02:11:17 AM (0)	39036	         HookAPI.cpp:  143		Hook was altered: 661B03F0
WARN	02:11:17 AM (0)	39036	         HookAPI.cpp:  143		Hook was altered: 661B03F0

661B03F0 in this case is the pointer to the original ShowCursor function prior to IGO's hooking.

The search for a solution

How can Discord address this? Isn't IGO incompatible with everything? Not quite. An interesting export in IGO DLLs is Is3rdPartyDllLoaded. IGO checks for the presence for one of the following:

fraps64.dll
fraps32.dll
fraps.dll
detoured.dll
detoured64.dll
d3doverriderhooks
evgaprecisionhooks
msiafterburnerhooks
rivatunerhooks
rtsshooks
dxtorycore
dxtorymm
dxtoryhk
pshook
loadlibinterceptor
dx11interceptor
dx10interceptor
dx9interceptor
dx8interceptor
openglinterceptor
ambx_interfacescom
mumble_ol
xfire_toucan
easyhook
ltc_game
ltc_fpsi
ltc_help
ltc_pkts
bdcam
bdcap
graphicscapture
gameoverlayrenderer
razorhook

If Discord managed to get in touch with IGO team, DiscordHook could possibly be added to the list. Trying it out myself, adding discordhook to the list solves the problem.

If Discord can't work to do this, then other solutions are a bit more trickier. Possibilities like hooking the original ShowCursor function copied by IGO, modifying the above list dynamically etc. all aren't feasible since their addresses will change between updates. Patching the exported CheckHook function to just return TRUE regardless does work, and will likely continue to do so between updates, though this goes against the deliberate design decision made by the IGO team.

Other games: the broader spectrum

What I've described here is a general issue that affects all Origin games. However the symptoms were due to BPR's use of while (ShowCursor(FALSE) >= 0);, which isn't necessarily something every game does. This is why the problems experienced can vary per game.

I do not have the funds nor the resources to test every single Origin game, however I did check Apex Legends as an example giving its recent popularity. Discord users have reported an issue with the mouse cursor being stuck visible while the Discord overlay is enabled (but not necessarily shown). And indeed, as shown above, IGO prevents the original ShowCursor being called in presence of another hook like Discord and thus preventing the cursor from hiding.

I have also focussed primarily on ShowCursor here as an example. Origin and Discord don't just hook this single function. There are many different functions hooked and they all follow the same pattern. There will be issues with some of them too. Some issues in some games may perhaps be so subtle that people don't realise this incompatibility is the cause.