Trials: Rising EAC Bypass/Steam Deck Compatability

GitHub - kylefmohr/Trials-Rising-Steam-Deck-Patch: Disabling Easy Anti-Cheat in trialsrising.exe so that the game works on Linux + Steam Deck
Disabling Easy Anti-Cheat in trialsrising.exe so that the game works on Linux + Steam Deck - kylefmohr/Trials-Rising-Steam-Deck-Patch

tl;dr: If you'd like to finally play Trials: Rising on your Steam Deck or Linux computer, check out this patcher I've created here!


Update 7/10/2025: It seems as if there was a minor but impactful change to the executable structure, so the old method that this patch uses *appears* to apply correctly, but then the game doesn't launch. I was able to isolate the changes by using an older version of the executable, and created a new method of patching that allows the game to work on the Steam Deck once again.

I recommend using the precompiled patcher, as the script now has a Python dependency, and getting that installed can be a bigger hurdle on the locked down Steam Deck than it might be otherwise.

Here's a direct link to the precompiled patcher

If you prefer to run the Python script directly, there are instructions on the README.md in the GitHub repo linked above.

In either instance, if you run into any issues:

  • First try verifying the game files via Steam (Right click on the game > Properties > Installed Files > Verify Files)
  • Then try running the patch right after that

If they update the game again, this will probably break, and I will look into a more durable way of patching this.


Update 5/9/2025:

Trials: Rising got its first update in years, which modified the structure of the binary. I was able to find a way to modify the new one accordingly, so patcher.py has been updated on GitHub, and everything still works properly. If you previously had the game working on Steam Deck and it stopped working after the update, run the newest version of patcher.py from my GitHub repo. I used essentially the same method as is detailed in this writeup, but some things are slightly different in my patch now.

How I discovered it


I recently came across a method of bypassing Easy Anti-Cheat in Trials Rising, which is of interest to me because of how EAC is incompatible with running the game on Linux, and therefore the game is incompatible with the Steam Deck.

The bypass method appeared on PCGamingWiki's entry for Trials: Rising, and included just a few short steps, without going into much detail.

Image.png

The PCGamingWiki section in question

I attempted to make contact with the user that added this entry to the wiki, just to ask how it worked, how they found it, etc. I haven't heard back, but will update this if I do.

I was able to answer most of these questions myself, so I thought I'd share my analysis of how this patch appears to work.


The Original Purpose of The Code


I ran trialsrising.exe through both Ghidra and IDA's disassembly/decompilation process, but generally found Ghidra's output more useful in this case.

By navigating to the given offset of the version we're using (Steam version, so offset 0x0178ACC0), we can get a general idea of what it is that we're patching.

(Editor's note: I renamed the function from something generic to initializeAntiCheat to make this a bit easier to understand. I also changed the return type from unknown to bool)

image.png

The C Pseudocode of the function at the given offset 0x0178ACC0 generated by Ghidra

  • The Pseudocode might be more readable inline here

/* WARNING: Unknown calling convention -- yet parameter storage is locked */

bool initializeAntiCheat(void)

{
  int *piVar1;
  int iVar2;
  longlong lVar3;
  code *pcVar4;
  code *pcVar5;
  longlong *plVar6;
  undefined8 *puVar7;
  undefined8 uVar8;
  longlong in_RCX;
  longlong in_RDX;
  longlong *plVar9;
  undefined auStack_b8 [8];
  longlong lStack_b0;
  undefined8 uStack_a8;
  undefined8 uStack_a0;
  undefined8 uStack_98;
  undefined8 uStack_90;
  undefined auStack_88 [112];
  
  uStack_90 = 0xfffffffffffffffe;
  lVar3 = (*DAT_1420685b8)("../EasyAntiCheat/EasyAntiCheat_x64.dll");
  *(longlong *)(in_RCX + 0x10) = lVar3;
  if (lVar3 != 0) {
    pcVar4 = (code *)(*DAT_1420685c0)(lVar3,"CreateGameClient");
    pcVar5 = (code *)(*DAT_1420685c0)(*(undefined8 *)(in_RCX + 0x10),"CreateClientAuth");
    if (pcVar4 != (code *)0x0) {
      plVar6 = (longlong *)(*pcVar4)("GameClientP2PInterfaceV010");
      plVar9 = *(longlong **)(in_RCX + 0x18);
      *(longlong **)(in_RCX + 0x18) = plVar6;
      if (plVar9 != (longlong *)0x0) {
        (**(code **)(*plVar9 + 8))(plVar9);
        plVar6 = *(longlong **)(in_RCX + 0x18);
      }
      if (plVar6 != (longlong *)0x0) {
        (**(code **)(*plVar6 + 0x58))(plVar6,0,0);
        (**(code **)**(undefined8 **)(in_RCX + 0x18))
                  (*(undefined8 **)(in_RCX + 0x18),FUN_1400734d0,&LAB_141789060);
        if (pcVar5 != (code *)0x0) {
          puVar7 = (undefined8 *)(*pcVar5)("ClientAuthInterfaceV001");
          plVar9 = *(longlong **)(in_RCX + 0x20);
          *(undefined8 **)(in_RCX + 0x20) = puVar7;
          if (plVar9 != (longlong *)0x0) {
            (**(code **)(*plVar9 + 8))(plVar9);
            puVar7 = *(undefined8 **)(in_RCX + 0x20);
          }
          (**(code **)*puVar7)();
          if (*(longlong *)(in_RDX + 8) != 0) {
            LOCK();
            piVar1 = (int *)(*(longlong *)(in_RDX + 8) + 8);
            iVar2 = *piVar1;
            *piVar1 = *piVar1 + -1;
            UNLOCK();
            if ((iVar2 == 1) && (*(longlong *)(in_RDX + 8) != 0)) {
              thunk_FUN_1496ff9a0();
            }
            *(undefined8 *)(in_RDX + 8) = 0;
          }
          return true;
        }
      }
    }
  }
  FUN_1412da4c0(auStack_88,0,0x68);
  uStack_a8 = 0;
  uStack_a0 = 0;
  uStack_98 = 0;
  lStack_b0 = 0;
  lVar3 = in_RDX + 8;
  if (*(longlong *)(in_RDX + 8) != 0) {
    lVar3 = *(longlong *)(in_RDX + 8) + 0xc;
  }
  thunk_FUN_14922e110(auStack_b8,"../Launch_Trials_Rising.exe %s",lVar3);
  if (lStack_b0 == 0) {
    plVar9 = &lStack_b0;
  }
  else {
    plVar9 = (longlong *)(lStack_b0 + 0xc);
  }
  iVar2 = (*DAT_142068548)(0,plVar9,0,0,0,0x4000000,0,&DAT_1420d0ba4,auStack_88,&uStack_a8);
  if (iVar2 == 0) {
    if (lStack_b0 != 0) {
      LOCK();
      piVar1 = (int *)(lStack_b0 + 8);
      iVar2 = *piVar1;
      *piVar1 = *piVar1 + -1;
      UNLOCK();
      if ((iVar2 == 1) && (lStack_b0 != 0)) {
        thunk_FUN_1496ff9a0();
      }
    }
    uVar8 = (*DAT_142068a10)();
    (*DAT_142068a78)(uVar8,"Failed to start EAC client",&DAT_14232afc0,0x1030);
  }
  else {
    (*DAT_142068738)(uStack_a0);
    (*DAT_142068738)(uStack_a8);
    if (lStack_b0 != 0) {
      LOCK();
      piVar1 = (int *)(lStack_b0 + 8);
      iVar2 = *piVar1;
      *piVar1 = *piVar1 + -1;
      UNLOCK();
      if ((iVar2 == 1) && (lStack_b0 != 0)) {
        thunk_FUN_1496ff9a0();
      }
    }
  }
  if (*(longlong *)(in_RDX + 8) != 0) {
    LOCK();
    piVar1 = (int *)(*(longlong *)(in_RDX + 8) + 8);
    iVar2 = *piVar1;
    *piVar1 = *piVar1 + -1;
    UNLOCK();
    if ((iVar2 == 1) && (*(longlong *)(in_RDX + 8) != 0)) {
      thunk_FUN_1496ff9a0();
    }
    *(undefined8 *)(in_RDX + 8) = 0;
  }
  return false;
}


As always with disassembling programs, there will be a lot of unknowns. Fortunately there are multiple aspects that help clarify what this function does and why we might want to patch it:

  • After the variable initializations, on the second line of the function, it appears to load the Easy Anti-Cheat DLL, which is presumably instrumental in initializing the anticheat.
  • An additional string near the end of the function confirms that this function has to do with the initialization of the anticheat, showing what may be logged if EAC fails to start.
  • Additionally, the fact that we can see a clearly defined failure state of the function helps us uncover something extremely important: this function returns true if EAC is loaded properly, otherwise returns false if there's an error.

Now keeping those things in mind, what does the function look like once we've applied the provided hex?

image.png

Pretty simple!

A couple of things surprised me about this solution's simplicity:

  • Were there really no other mitigations in place to prevent this kind of modification by the user, for example, something as simple as checking the hash of the trialsrising.exe file on launch?
  • How did the person who discovered this do so, and what was their motivation for doing so?

Regardless, I'm just glad there's finally a way to play this on the Deck rather than dual booting Windows, because the game runs great on Linux/SteamOS