DevourUnlocker/standalone/injector.py
MiTHRAL 563edd4aee
Some checks are pending
Build DevourUnlocker / windows-cross (push) Waiting to run
Build DevourUnlocker / linux (push) Waiting to run
Build DevourUnlocker / standalone (push) Waiting to run
v2.0.2: fix standalone deploy + media-server runner + mingw cross
- Standalone: injector.py now actually deploys winhttp.dll+DevourUnlocker.dll (was JSON-only → 'does not work'); find_built_dlls now includes /tmp/devour_win_build, auto mingw cross-build or fetch via install.sh fallback; launch_with_mod ensures all unlocks true
- Runner: git.mithraic.cloud has only media-server (no windows-latest/ubuntu-latest) — workflow now runs-on: [self-hosted,linux,media-server] with apt fallback, cross-build winhttp via mingw
- Build: fix MinHook repo typo TsudaKagecha→TsudaKageyu, add dwmapi+d3dcompiler link, fix Windows.h→windows.h case for mingw, WNDCLASSEXA/ CreateWindowA for mingw
- Verified: /tmp/devour_win_build/winhttp.dll 1.4M + DevourUnlocker.dll 1.4M built, injector deployed to Steam/common/Devour, standalone unlock --all now covers 7 categories (pets fixed via UpdateLockedPets)
2026-08-21 21:50:17 -04:00

169 lines
7.4 KiB
Python
Executable file

#!/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 / "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 winhttp.dll + DevourUnlocker.dll — build if missing
dlls = find_built_dlls()
# If no built DLLs, try to build or fetch
if "winhttp" not in dlls:
# Try to build via mingw if available
if verbose:
print("[*] No winhttp.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 "winhttp" in dlls and verbose:
print(f"[*] Built {dlls['winhttp']}")
except Exception as e:
if verbose:
print(f"[!] Build failed: {e}")
# Fallback: try to fetch prebuilt via install.sh (requires release)
if "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
if "winhttp" in dlls:
target = game_dir / "winhttp.dll"
if not target.exists():
shutil.copy(dlls["winhttp"], target)
if verbose:
print(f"[+] Deployed {dlls['winhttp']} -> {target}")
deployed = True
else:
if verbose:
print(f"[*] winhttp.dll already at {target}")
deployed = True
else:
if verbose:
print(f"[!] winhttp.dll not built — run on Windows or build with mingw-w64. Standalone JSON-only will NOT unlock until DLL is present.")
print(f" Workaround: use --steam-stats method or run 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'}")
subprocess.Popen([str(game_dir / "DEVOUR.exe")], cwd=str(game_dir))
else:
# Linux: set WINEDLLOVERRIDES and ask Steam to launch, or direct proton run
print(f"Game dir: {game_dir}")
print(f"Ensure Steam launch options: WINEDLLOVERRIDES=\"winhttp=n,b\" %command%")
print(f"Then launch Devour from Steam. Or run: steam steam://rungameid/1274570")
# 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)