- UnityPlayer.dll imports VERSION.dll + WINHTTP.dll (objdump -p) — version.dll beside DEVOUR.exe is auto-loaded on Windows, installer now silently auto-sets WINEDLLOVERRIDES=version=n,b in Steam localconfig.vdf for Proton so user just presses Play - src/main_version.cpp: now full HookThread (wait GameAssembly.dll + installPatches) linking devour_core (was minimal no-hooks 103K, now 14M full like winhttp) — unlocks all categories: robes/characters/perks/pets/flashlights/emotes/outfits via IsRobe/IsCharacter/HasPerk/HasOutfit + UpdateLocked* hooks - CMakeLists.txt: version target links devour_core + winpthread static, primary = version.dll less flagged than winhttp; winhttp retained only as optional compat - scripts/install.sh: copy version.dll primary (all categories), auto-set LaunchOptions silent, message no manual step; scripts/launch.sh: now uses version=n,b auto, checks version.dll - standalone/injector.py: find_built_dlls prefers version.dll, ensure_mod_deployed deploys version primary; standalone/devour_unlocker.py: diagnose hints updated to version.dll - .github/workflows/build.yml: runs-on docker (Forgejo runner media-server labels docker v6.4.0), package version.dll primary zip, linux job also docker
179 lines
8 KiB
Python
Executable file
179 lines
8 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 / "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)
|