From 7e2d4a981edd115893879f349f86cc4c27c55d47 Mon Sep 17 00:00:00 2001 From: Kyle Mohr <6644803+kylefmohr@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:59:42 -0500 Subject: [PATCH] complete overhaul --- .github/workflows/release.yml | 50 +++++++++++++ README.md | 16 ++++ patcher.py | 136 ++++++++++++++++------------------ patcher.spec | 38 ++++++++++ requirements.txt | 1 + 5 files changed, 169 insertions(+), 72 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 patcher.spec create mode 100644 requirements.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7a023e8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Package Application with Pyinstaller and Create Release + +on: + push: + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Package Application + uses: JackMcKew/pyinstaller-action-linux@main + with: + path: . + spec: patcher.spec + + - name: Prepare artifact + run: mv dist/patcher dist/trials_patcher + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: trials_patcher + path: dist/trials_patcher + + release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: trials_patcher + + - name: Make executable + run: chmod +x trials_patcher + + - name: Create and publish release + uses: ncipollo/release-action@v1 + with: + artifacts: "trials_patcher" + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 8b95c03..03f95ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,22 @@ # Trials-Rising-Steam-Deck-Patch Disabling Easy Anti-Cheat in trialsrising.exe so that the game works on Linux + Steam Deck +**Updated 2025-07-10**: This patcher now works differently. +### Easy method: +Download the latest release from the releases tab, and run it on your Steam Deck. It will patch the game for you. If you've previously patched the game, go to the game's properties in Steam > Installed Files > Verify Integrity of Game Files to restore the original file, then run this patcher. + +### Run from source: +via the command line on your Steam Deck/Linux machine: +```git clone https://github.com/kylemohr/Trials-Rising-Steam-Deck-Patch.git +cd Trials-Rising-Steam-Deck-Patch +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +python patcher.py +``` + +if this fails at the `pip install` step, you may need to install `python3-pip` via `pacman`. This will likely require you to run `sudo steamos-devmode enable` first, then `sudo pacman -Syu python3-pip`. **You don't need to do this if you're using the easy method above.** + ### I highly recommend using GE-Proton-9-1 for this game You can find instructions for how to do this [here](https://www.rockpapershotgun.com/how-to-install-proton-ge-on-the-steam-deck) diff --git a/patcher.py b/patcher.py index cb8609a..9fb8fa1 100644 --- a/patcher.py +++ b/patcher.py @@ -1,79 +1,71 @@ -import os -import shutil +import bsdiff4 +import zlib import getpass +import shutil +from sys import exit -def modify_exe(input_path): - steam_version_offset = 0x0178bac2 - #ubisoft_connect_version_offset = 0x01789EE0 - expected_bytes = bytes.fromhex("40 0f b6 c7 0f 28 74 24 50 48 83 c4 68 5f 5b c3") - replacement_bytes = bytes.fromhex("31 C0 B0 01 0f 28 74 24 50 48 83 c4 68 5f 5b c3") - expected_path_linux = '/home/' + getpass.getuser() + '/.local/share/Steam/steamapps/common/Trials Rising/datapack/trialsrising.exe' - - if not os.path.exists(input_path): - if os.path.exists(expected_path_linux): - input_path = expected_path_linux - else: - print("Error: Could not find trialsrising.exe") - return False - +def verify_checksum(file_path, expected_checksum): + """ + Verify the checksum of a file against an expected checksum. + + :param file_path: Path to the file to verify. + :param expected_checksum: Expected checksum value. + :return: True if the checksum matches, False otherwise. + """ try: - with open(input_path, 'rb') as infile: - data = bytearray(infile.read()) - - def check_and_patch(offset): - if offset + len(expected_bytes) > len(data): - return False, "Offset exceeds file size" - read_bytes = bytes(data[offset: offset + len(expected_bytes)]) - # Search for the expected bytes throughout the entire file, not just at the offset - # for i in range(len(data) - len(expected_bytes)): - # read_bytes = bytes(data[i: i + len(expected_bytes)]) - # if read_bytes == expected_bytes: - # print(f"Found expected bytes at offset: {hex(i)}") - # offset = i - # continue - # exit() - if read_bytes == expected_bytes: - print(f"Bytes matched at offset: {hex(offset)}. Modifying...") - for i, byte in enumerate(replacement_bytes): - data[offset + i] = byte - return True, None - elif read_bytes == replacement_bytes: - print(f"Bytes already modified at offset: {hex(offset)}, no changes made.") - return False, "Program already patched" - else: - print(f"These bytes: {read_bytes.hex()} did not match expected bytes: {expected_bytes.hex()}") - return False, "Bytes did not match" - - modified, message = check_and_patch(steam_version_offset) - - if not modified: - print(f"Error: {message}") - return False - - if modified: - # backup original file - backup_path = os.path.splitext(input_path)[0] + "_original.exe" - shutil.copy(input_path, backup_path) - print(f"Backed up original file to: {backup_path}") - # write patched exe - with open(input_path, 'wb') as outfile: - outfile.write(data) - print(f"Successfully patched file: {input_path}") - return True - return False - - - except FileNotFoundError: - print(f"Error: Input file not found: {input_path}") - return False - except IOError as e: - print(f"Error: IO error occurred: {e}") + actual_checksum = zlib.crc32(open(file_path, 'rb').read()) + print(f"Expected checksum: {expected_checksum}, Actual checksum: {actual_checksum}") + return actual_checksum == expected_checksum + except Exception as e: + print(f"Error verifying checksum: {e}") return False +def apply_patch(old_file_path, new_file_path, patch_bytes): + """ + Apply a binary patch to an old file to create a new file. + + :param old_file_path: Path to the old file. + :param new_file_path: Path where the new file will be saved. + :param patch_bytes: The binary patch data. + """ + try: + with open(old_file_path, 'rb') as old_file: + old_data = old_file.read() + + new_data = bsdiff4.patch(old_data, patch_bytes) + + with open(new_file_path, 'wb') as new_file: + new_file.write(new_data) + + print(f"Patch applied successfully. New file created at {new_file_path}") + except Exception as e: + print(f"Error applying patch: {e}") -if __name__ == '__main__': - input_file = input("Enter the path of the trialsrising.exe file, or press Enter to use the default path: ") - if modify_exe(input_file): - print("Process completed.") +patch_bytes = b"\x42\x53\x44\x49\x46\x46\x34\x30\x3E\x00\x00\x00\x00\x00\x00\x00\x2E\x01\x00\x00\x00\x00\x00\x00\xA8\x0C\x6E\x14\x00\x00\x00\x00\x42\x5A\x68\x39\x31\x41\x59\x26\x53\x59\xED\xED\xC0\x27\x00\x00\x07\x63\x51\xC0\x44\x14\x00\x20\x00\x00\x01\x40\x00\x00\x40\x10\x00\x02\x00\x20\x00\x31\x00\xD3\x4D\x04\x34\x06\x9A\x50\xA0\xEC\x51\x46\xD7\x2F\x17\x72\x45\x38\x50\x90\xED\xED\xC0\x27\x42\x5A\x68\x39\x31\x41\x59\x26\x53\x59\xD5\x2F\x49\xC5\x00\xBD\x1A\xC9\x89\xC0\x20\x00\x10\x00\x40\x80\x40\x20\x00\x10\x00\x20\x18\x20\x00\x50\x80\x69\xA6\x81\x35\x54\x1A\x1B\x53\x28\xD7\x08\x08\x12\xEF\xA9\x49\x20\xAA\xB3\x7F\x7D\x9A\xDE\x72\xA2\xA0\x25\xF9\x8A\x0A\xC9\x32\x9A\xC8\x70\x4F\x16\xF8\x0A\xFC\x72\x00\x06\x00\x00\x00\x41\x00\x01\x84\x02\x6A\x32\x15\x01\x2D\x48\x54\x04\xB9\x8A\x0A\xC9\x32\x9A\xC8\x70\x4F\x16\xF8\x0A\xFC\x72\x00\x06\x00\x00\x00\x41\x00\x01\x84\x02\x6A\x32\x15\x01\x2D\x48\x54\x04\xB9\x8A\x0A\xC9\x32\x9A\xC8\x70\x4F\x16\xF8\x0A\xFC\x72\x00\x06\x00\x00\x00\x41\x00\x01\x84\x02\x6A\x32\x15\x01\x2D\x48\x54\x04\xB9\x8A\x0A\xC9\x32\x9A\xC8\x70\x4F\x16\xF8\x0A\xFC\x72\x00\x06\x00\x00\x00\x41\x00\x01\x84\x02\x6A\x32\x15\x01\x2D\x48\x54\x04\xB9\x8A\x0A\xC9\x32\x9A\xC8\x70\x4F\x16\xF8\x0A\xFC\x72\x00\x06\x00\x00\x00\x41\x00\x01\x84\x02\x6A\x32\x15\x01\x2D\x48\x54\x04\xB9\x8A\x0A\xC9\x32\x9A\xC8\x70\x4F\x16\xF8\x0A\xFC\x72\x00\x06\x00\x00\x00\x41\x00\x01\x84\x02\x6A\x32\x15\x01\x2D\x48\x54\x04\xB9\x8A\x0A\xC9\x32\x9A\xCB\xF2\xD1\x6B\xA0\x05\x23\x1E\x40\x06\x00\x00\x02\x00\x00\x41\x00\x01\x86\x60\x29\x4D\x35\x42\x84\x4D\x81\x14\x22\x78\xBB\x92\x29\xC2\x84\x80\xF2\xFA\xB3\xB0\x42\x5A\x68\x39\x17\x72\x45\x38\x50\x90\x00\x00\x00\x00" +expected_path = '/home/' + getpass.getuser() + '/.local/share/Steam/steamapps/common/Trials Rising/datapack/trialsrising.exe' +new_path = expected_path + '.new' +expected_checksum_before = 0xF197E04E +expected_checksum_after = 0x883925BD + +if verify_checksum(expected_path, expected_checksum_before): + apply_patch(expected_path, new_path, patch_bytes) + + if verify_checksum(new_path, expected_checksum_after): + print("Patch applied successfully.") else: - print("Process failed.") + print("Patch failed: checksum mismatch after patching. Please open an issue on GitHub.") + exit(1) + + try: + shutil.move(new_path, expected_path) + except Exception as e: + print(f"Error replacing old file with new file: {e}") + exit(1) +else: + if(verify_checksum(expected_path, expected_checksum_after)): + print("Patch has already been applied, no changes made.") + exit(0) + print("Patch failed: checksum mismatch before patching. Go to the game's properties in Steam > Installed Files > Verify integrity of game files, then try again") + print("If that doesn't work, please open an issue on GitHub.") + exit(1) + diff --git a/patcher.spec b/patcher.spec new file mode 100644 index 0000000..027b8ff --- /dev/null +++ b/patcher.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['patcher.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='patcher', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..496aedd --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +bsdiff4 \ No newline at end of file