Advertisement
Guest User

Untitled

a guest
Jun 20th, 2024
105
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.32 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. import sys
  4. import subprocess
  5. import argparse
  6. from pathlib import Path
  7. import struct
  8. import re
  9. from shutil import rmtree
  10.  
  11.  
  12. if __name__ == "__main__":
  13.  
  14.     patcher_args = None #sys.argv[1:sys.argv.index("--")]
  15.  
  16.     parser = argparse.ArgumentParser(description="Patch Elden Ring executable and launch it without EAC.")
  17.  
  18.     parser.add_argument("-r", "--rate", type=int, default=60, help="Modify the frame rate limit (e.g. 30, 120, 165 or whatever).")
  19.     parser.add_argument("-x", "--executable", action='store', type=str, default="eldenring.exe", help="The executable to launch, relative to the games folder.")
  20.     parser.add_argument("--with-eac", action='store_true', help="Run game with EAC (Use at own your risk)")
  21.     parser.add_argument("--disable-rune-loss", action='store_true', help="Disable losing runes upon death.")
  22.     parser.add_argument("--all", action='store_true', help="Enable all options except rate adjustment and gamplay changes like `--disable-rune-loss`.")
  23.     parser.add_argument("-u", "--ultrawide", action='store_true', help="Removes black bars when using a resolution with an aspect ratio other than 16:9.")
  24.     parser.add_argument("-v", "--disable-vignette", action='store_true', help="Disables the vignette overlay.")
  25.     parser.add_argument("-c", "--disable-ca", action='store_true', help="Disables chromatic abberation.")
  26.     parser.add_argument("-a", "--increase-animation-distance", action='store_true', help="Increase animation distance.")
  27.     parser.add_argument("-s", "--skip-intro", action='store_true', help="Skip intro logos.")
  28.     parser.add_argument("-f", "--remove-60hz-fullscreen", action='store_true', help="Remove 60hz lock in fullscreen.")
  29.     patch = parser.parse_args(patcher_args)
  30.  
  31.     if patch.with_eac and patch.executable != "eldenring.exe":
  32.         print("er-patcher: --with-eac is mutually exclusive with --executable")
  33.         sys.exit(1)
  34.  
  35.     game_dir = Path(".")
  36.     with open(game_dir / "eldenring.exe", "rb") as f:
  37.         exe_hex = f.read().hex()
  38.  
  39.     if patch.rate != 60 and patch.rate > 0:
  40.         r_pattern = "c7 43 20 89 88 88 3c eb 43 89 73 18 eb ca 89 73 18".replace(" ", "")
  41.         if (res := re.search(r_pattern, exe_hex)) is not None:
  42.             r_addr = res.span()[0] + 6
  43.             r_patch = struct.pack('<f', 1 / patch.rate).hex()
  44.             exe_hex = exe_hex[:r_addr] + r_patch + exe_hex[r_addr + len(r_patch):]
  45.         else:
  46.             print("er-patcher: rate pattern scan failed")
  47.  
  48.     if patch.disable_rune_loss:
  49.         rl_pattern = "b0 01 .. 8b .. e8 .. .. .. .. .. 8b .. .. .. 32 c0 .. 83 .. 28 c3".replace(" ", "")
  50.         if (res := re.search(rl_pattern, exe_hex)) is not None:
  51.             rl_addr = res.span()[0] + 6
  52.             rl_patch = "90 90 90 90 90".replace(" ", "")  # NOP
  53.             exe_hex = exe_hex[:rl_addr] + rl_patch + exe_hex[rl_addr + len(rl_patch):]
  54.         else:
  55.             print("er-patcher: disable rune loss pattern scan failed")
  56.  
  57.     if patch.ultrawide or patch.all:
  58.         uw_pattern = "74 4f 45 8b 94 cc".replace(" ", "")
  59.         if (res := re.search(uw_pattern, exe_hex)) is not None:
  60.             uw_addr = res.span()[0]
  61.             uw_patch = "eb"
  62.             exe_hex = exe_hex[:uw_addr] + uw_patch + exe_hex[uw_addr + len(uw_patch):]
  63.         else:
  64.             print("er-patcher: ultrawide pattern scan failed")
  65.  
  66.     if patch.disable_vignette or patch.all:
  67.         v_pattern = 'f3 0f 10 .. .. f3 0f 59 .. .. .. .. .. e8 .. .. .. .. f3 41 0f .. .. f3 45 0f .. .. 4c 8d .. .. .. .. .. .. 48'.replace(" ", "")
  68.         if (res := re.search(v_pattern, exe_hex)) is not None:
  69.             v_addr = res.span()[0] + 46
  70.             v_patch = "f3 0f 5c c0 90".replace(" ", "")  # SUBSS XMM0,XMM0; NOP;  all NOP does work too
  71.             exe_hex = exe_hex[:v_addr] + v_patch + exe_hex[v_addr + len(v_patch):]
  72.         else:
  73.             print("er-patcher: disable_vignette pattern scan failed")
  74.  
  75.     if patch.disable_ca or patch.all:
  76.         ca_pattern = "0f 11 43 60 48 8d 8b 80 00 00 00 0f 10 87 a0 00 00 00 0f 11 41 f0 48 8d 87 b0 00 00 00 0f 10 08 0f 11 09".replace(" ", "")
  77.         if (res := re.search(ca_pattern, exe_hex)) is not None:
  78.             ca_addr = res.span()[0] + 94
  79.             ca_orig = "0f 11 49 20".replace(" ", "")
  80.             ca_patch = "66 0f ef c9".replace(" ", "")  # PXOR XMM1,XMM1
  81.             if exe_hex[ca_addr:ca_addr + len(ca_patch)] == ca_orig:
  82.                 exe_hex = exe_hex[:ca_addr] + ca_patch + exe_hex[ca_addr + len(ca_patch):]
  83.         else:
  84.             print("er-patcher: disable_ca pattern scan failed")
  85.  
  86.     if patch.increase_animation_distance or patch.all:
  87.         iad_pattern = "e8 .. .. .. .. 0f 28 .. 0f 28 .. e8 .. .. .. .. f3 0f .. .. 0f 28 .. f3 41 0f 5e 4c 24 54".replace(" ", "")
  88.         if (res := re.search(iad_pattern, exe_hex)) is not None:
  89.             iad_addr = res.span()[0] + 46
  90.             iad_patch = "0f 57 c9 66 0f ef c9".replace(" ", "")  # DIVSS XMM1,dword ptr [R12 + 0x54]  ->  XORPS XMM1,XMM1; PXOR XMM1,XMM1
  91.             exe_hex = exe_hex[:iad_addr] + iad_patch + exe_hex[iad_addr + len(iad_patch):]
  92.         else:
  93.             print("er-patcher: increase_animation_distance pattern scan failed")
  94.  
  95.     if patch.skip_intro or patch.all:
  96.         si_pattern = "80 bf b8 00 00 00 00 74 53 48".replace(" ", "")
  97.         if (res := re.search(si_pattern, exe_hex)) is not None:
  98.             si_addr = res.span()[0] + 14
  99.             si_patch = "90 90".replace(" ", "")
  100.             exe_hex = exe_hex[:si_addr] + si_patch + exe_hex[si_addr + len(si_patch):]
  101.         else:
  102.             print("er-patcher: skip_intro pattern scan failed")
  103.  
  104.     if patch.remove_60hz_fullscreen or patch.all:
  105.         fs_pattern = "c7 45 ef 3c 00 00 00".replace(" ", "")
  106.         if (res := re.search(fs_pattern, exe_hex)) is not None:
  107.             fs_addr = res.span()[0] + 6
  108.             fs_patch = "00"
  109.             exe_hex = exe_hex[:fs_addr] + fs_patch + exe_hex[fs_addr + len(fs_patch):]
  110.         else:
  111.             print("er-patcher: remove_60hz_fullscreen pattern scan failed")
  112.  
  113.     game_dir_patched = Path("er-patcher-tmp")
  114.     if not game_dir_patched.is_dir():
  115.         game_dir_patched.mkdir()
  116.  
  117.     with open(game_dir_patched / "eldenring.exe", "wb") as f:
  118.         f.write(bytes.fromhex(exe_hex))
  119.  
  120.     del exe_hex
  121.  
  122.     # recreate game directory tree in game_dir_patched
  123.     game_dirs = [d for d in game_dir.rglob("*") if d.is_dir()]
  124.     for d in game_dirs:
  125.         if d == game_dir_patched:
  126.             continue
  127.         if not (game_dir_patched / d).is_dir():
  128.             (game_dir_patched / d).mkdir(parents=True)
  129.  
  130.     # hard link game files to game_dir_patched; symbolic links would be easier
  131.     # to handle but by default windows 10 doesn't allow them
  132. #    game_files = [f for f in game_dir.rglob("*") if f.is_file()]
  133. #    for f in game_files:
  134. #        if f.name in ["eldenring.exe", "er-patcher"]:
  135. #            continue
  136. #        if not (game_dir_patched / f).is_file():
  137. #            (game_dir_patched / f).hardlink_to(f)
  138.  
  139.     # start patched exe directly to avoid EAC
  140. #    steam_cmd = sys.argv[1 + sys.argv.index("--"):]
  141. #    steam_cmd[-1] = Path(steam_cmd[-1]).parent.absolute() / game_dir_patched / ("start_protected_game.exe" if patch.with_eac else patch.executable)
  142. #    subprocess.run(steam_cmd, cwd=steam_cmd[-1].parent.absolute())
  143.  
  144.     # cleanup
  145. #    rmtree(game_dir_patched)
  146.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement