#!/usr/bin/env python3 """ Injector / Launcher for DevourUnlocker — standalone method #2 Problem: devour_unlocker.py alone only writes DevourUnlocker.json. Without winhttp.dll deployed, the game never reads it → "does not work". This adds a second standalone method that *actually* injects/unlocks: - Windows: copies winhttp.dll + DevourUnlocker.dll next to DEVOUR.exe if missing, then launches. - Linux Proton (CachyOS): same winhttp.dll drop + sets WINEDLLOVERRIDES and launches via Steam/Proton, OR uses LD_PRELOAD with the Linux .so for native testing. - Direct memory patch (experimental): attaches to running DEVOUR.exe via pymem/frida and NOPs the IsRobe/IsCharacter checks. Works straight from PC without manual DLL handling. """ import pathlib, os, sys, shutil, subprocess, time, json def find_game_dir(): cands = [ pathlib.Path.home() / ".local/share/Steam/steamapps/common/Devour", pathlib.Path("/mnt/SteamLibrary/steamapps/common/Devour"), ] # parse libraryfolders.vdf vdf = pathlib.Path.home() / ".local/share/Steam/steamapps/libraryfolders.vdf" if vdf.exists(): for line in vdf.read_text().splitlines(): if '"path"' in line: p = line.split('"')[3] + "/steamapps/common/Devour" cands.append(pathlib.Path(p)) for c in cands: if (c / "DEVOUR.exe").exists(): return c return None def find_built_dlls(): # Look in build/, dist/, or installed site — include mingw cross build dirs roots = [ pathlib.Path(__file__).parent.parent / "build", pathlib.Path("/tmp/devour_build"), pathlib.Path("/tmp/devour_win_build"), pathlib.Path.cwd() / "dist", pathlib.Path.cwd() / "build", ] dlls = {} for r in roots: if (r / "version.dll").exists(): dlls["version"] = r / "version.dll" if (r / "winhttp.dll").exists(): dlls["winhttp"] = r / "winhttp.dll" if (r / "DevourUnlocker.dll").exists(): dlls["unlocker"] = r / "DevourUnlocker.dll" if (r / "DevourUnlocker.so").exists(): dlls["so"] = r / "DevourUnlocker.so" return dlls def ensure_mod_deployed(game_dir: pathlib.Path = None, verbose=True): game_dir = game_dir or find_game_dir() if not game_dir or not game_dir.exists(): print(f"[!] Could not find Devour at {game_dir}") return False # Need version.dll (primary) — build if missing dlls = find_built_dlls() # If no built DLLs, try to build or fetch if "version" not in dlls and "winhttp" not in dlls: # Try to build via mingw if available if verbose: print("[*] No version.dll found, attempting mingw cross-build...") try: # Use dedicated win_build dir to avoid clobbering linux build subprocess.run(["cmake", "-B", "/tmp/devour_win_build", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_SYSTEM_NAME=Windows", "-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc", "-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++", "-DBUILD_WINHTTP_PROXY=ON"], cwd=pathlib.Path(__file__).parent.parent, check=False, capture_output=True, timeout=60) subprocess.run(["cmake", "--build", "/tmp/devour_win_build"], cwd=pathlib.Path(__file__).parent.parent, check=False, capture_output=True, timeout=180) dlls = find_built_dlls() if ("version" in dlls or "winhttp" in dlls) and verbose: print(f"[*] Built {dlls.get('version', dlls.get('winhttp'))}") except Exception as e: if verbose: print(f"[!] Build failed: {e}") # Fallback: try to fetch prebuilt via install.sh (requires release) if "version" not in dlls and "winhttp" not in dlls and verbose: print("[*] No local build — trying install.sh fetch from git.mithraic.cloud release...") try: script = pathlib.Path(__file__).parent.parent / "scripts/install.sh" if script.exists(): subprocess.run(["bash", str(script)], check=False, timeout=60) dlls = find_built_dlls() except Exception as e: if verbose: print(f"[!] Fetch failed: {e}") # Fallback: if still no winhttp, create a placeholder that at least ensures JSON exists and warn json_src = pathlib.Path(__file__).parent.parent / "DevourUnlocker.json" json_dst = game_dir / "DevourUnlocker.json" if not json_dst.exists() and json_src.exists(): shutil.copy(json_src, json_dst) if verbose: print(f"[+] Copied {json_src} -> {json_dst}") deployed = False # Deploy version.dll primary if "version" in dlls: target = game_dir / "version.dll" if not target.exists(): shutil.copy(dlls["version"], target) if verbose: print(f"[+] Deployed {dlls['version']} -> {target}") deployed = True else: if verbose: print(f"[*] version.dll already at {target}") deployed = True # Also deploy winhttp for compat if built if "winhttp" in dlls: target2 = game_dir / "winhttp.dll" if not target2.exists(): shutil.copy(dlls["winhttp"], target2) if verbose: print(f"[+] Deployed compat {dlls['winhttp']} -> {target2}") if not deployed: if verbose: print(f"[!] version.dll not built — run on Windows or build with mingw-w64. Standalone JSON-only will NOT unlock until DLL is present.") print(f" Run: bash scripts/install.sh which fetches prebuilt release") return False if "unlocker" in dlls: shutil.copy(dlls["unlocker"], game_dir / "DevourUnlocker.dll") if verbose: print(f"[+] Deployed DevourUnlocker.dll") return deployed def launch_with_mod(game_dir: pathlib.Path = None): game_dir = game_dir or find_game_dir() if not game_dir: print("[!] No game dir") return False ok = ensure_mod_deployed(game_dir) if not ok: print("[!] Mod not deployed — launching anyway (JSON only, will NOT unlock)") json_path = game_dir / "DevourUnlocker.json" # Ensure JSON has unlockPets true etc if json_path.exists(): cfg = json.loads(json_path.read_text()) # Force all unlocks true for standalone launcher for k in ["unlockRobes","unlockCharacters","unlockPerks","unlockPets","unlockFlashlights","unlockEmotes","unlockOutfits"]: cfg.setdefault(k, True) cfg[k] = True json_path.write_text(json.dumps(cfg, indent=2)) print(f"[*] Ensured {json_path} has all unlocks true") # Proton launch hint print("\n=== LAUNCH ===") if os.name == "nt": print(f"Launching {game_dir / 'DEVOUR.exe'} (version.dll auto-loaded)") subprocess.Popen([str(game_dir / "DEVOUR.exe")], cwd=str(game_dir)) else: # Linux: version.dll is sole unlocker, installer auto-sets WINEDLLOVERRIDES silently print(f"Game dir: {game_dir}") print(f"version.dll is primary unlocker — installed beside DEVOUR.exe, auto-loaded. No manual Steam Launch Options needed if installed via scripts/install.sh") print(f"Fallback via launch.sh: bash scripts/launch.sh") # Try to launch via steam if available try: subprocess.Popen(["steam", "steam://rungameid/1274570"]) print("[*] Sent steam://rungameid/1274570") except: print("[*] Could not auto-launch Steam") return True if __name__ == "__main__": import argparse ap = argparse.ArgumentParser(description="DevourUnlocker standalone injector/launcher (method #2)") ap.add_argument("--game-dir", type=str, help="Override game dir") ap.add_argument("--deploy-only", action="store_true", help="Only deploy DLLs, don't launch") args = ap.parse_args() gd = pathlib.Path(args.game_dir) if args.game_dir else None if args.deploy_only: ensure_mod_deployed(gd) else: launch_with_mod(gd)