Files
enshrouded-ru-voice/install.py
T

129 lines
5.4 KiB
Python

"""Install (or remove) the Russian narration mod. Python 3.11+, standard library only, Windows.
usage: python install.py [--game <Enshrouded folder>] install EML + the mod, dry run
python install.py --uninstall [--game ...] restore the game files, remove EML + the mod
What it does:
1. finds the game (Steam library folders) unless --game is given; refuses while the game is running;
2. downloads EML from github.com/Brabb3l/kfc-parser release v0.2.0 into tools/ (dbghelp.dll, emm.exe,
kfc-parser.exe) and checks their SHA-256;
3. copies dbghelp.dll into the game folder (the EML proxy: it applies mods when the game starts, keeps
enshrouded.kfc.bak and patches enshrouded.kfc, never the original .dat files) and mod/ru_voice_notes
into <game>/mods/;
4. dry run: emm.exe run --export must print "[ru_voice] replaced 525 notes, 0 failed".
The mod plays the Russian takes in place of the English voiceover of the 525 voiced notes: keep the
game's voice language English.
"""
import hashlib
import os
import pathlib
import re
import shutil
import subprocess
import sys
import urllib.request
ROOT = pathlib.Path(__file__).resolve().parent
MOD = "ru_voice_notes"
REL = "https://github.com/Brabb3l/kfc-parser/releases/download/v0.2.0/"
SHA256 = { # release v0.2.0 assets
"dbghelp.dll": "038b7a1787026007a68c82a6adeb8e79c2e644f936d4cd00f8b0db206a2349fa",
"emm.exe": "b1133f6b8ea7000596cc7710901bd8307b48165b0e650c0487a611d90ea49305",
"kfc-parser.exe": "b5384a926885a0b59da0a2475ff1fd1c2a0679156e2dbcdb52d1899aabc85b75",
}
def find_game():
roots = []
try:
import winreg
for hive, key in ((winreg.HKEY_CURRENT_USER, r"Software\Valve\Steam"),
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Valve\Steam")):
try:
with winreg.OpenKey(hive, key) as k:
for name in ("SteamPath", "InstallPath"):
try:
roots.append(pathlib.Path(winreg.QueryValueEx(k, name)[0]))
except OSError:
pass
except OSError:
pass
except ImportError:
pass
libs = set()
for r in roots:
libs.add(r)
vdf = r / "steamapps" / "libraryfolders.vdf"
if vdf.exists():
libs |= {pathlib.Path(p.replace("\\\\", "\\")) for p in re.findall(r'"path"\s+"([^"]+)"', vdf.read_text("utf-8", "replace"))}
for lib in libs:
g = lib / "steamapps" / "common" / "Enshrouded"
if (g / "enshrouded.kfc").exists():
return g
raise SystemExit("Enshrouded not found in the Steam libraries: pass --game <folder with enshrouded.kfc>")
def game_running():
out = subprocess.run(["tasklist", "/FI", "IMAGENAME eq enshrouded.exe"], capture_output=True, text=True).stdout
return "enshrouded.exe" in out.lower()
def fetch_tools():
tools = ROOT / "tools"
tools.mkdir(exist_ok=True)
for name, sha in SHA256.items():
f = tools / name
if not f.exists():
print("download", REL + name)
urllib.request.urlretrieve(REL + name, f)
h = hashlib.sha256(f.read_bytes()).hexdigest()
if sha and h != sha:
f.unlink()
raise SystemExit(f"{name}: SHA-256 {h} does not match the release ({sha}); deleted")
return tools
def emm(tools, game, *args):
r = subprocess.run([str(tools / "emm.exe"), *args, "-g", str(game)], capture_output=True, text=True,
encoding="utf-8", errors="replace")
return r.returncode, r.stdout + r.stderr
def main():
a = sys.argv[1:]
game = pathlib.Path(a[a.index("--game") + 1]) if "--game" in a else find_game()
print("game:", game)
if game_running():
raise SystemExit("close Enshrouded first (EML cannot patch files the game holds open)")
tools = fetch_tools()
if "--uninstall" in a:
if (game / "enshrouded.kfc.bak").exists():
code, out = emm(tools, game, "restore")
print(out[-500:])
shutil.rmtree(game / "mods" / MOD, ignore_errors=True)
(game / "dbghelp.dll").unlink(missing_ok=True)
print("UNINSTALLED: the original game files are back; Steam 'Verify integrity' also restores them")
return
src = ROOT / "mod" / MOD
if not (src / "mod.json").exists():
raise SystemExit(f"{src} is empty: git lfs pull (the takes are in Git LFS), or build it (README)")
small = [f for f in (src / "data").glob("*.qoa") if f.stat().st_size < 1024]
if small:
raise SystemExit(f"{len(small)} .qoa files are LFS pointers, not audio: run 'git lfs pull' first")
shutil.copyfile(tools / "dbghelp.dll", game / "dbghelp.dll")
dst = game / "mods" / MOD
shutil.rmtree(dst, ignore_errors=True)
shutil.copytree(src, dst)
print("mod copied to", dst)
code, out = emm(tools, game, "run", "--export")
line = [l for l in out.splitlines() if "[ru_voice]" in l]
print(re.sub(r"\x1b\[[0-9;]*m", "", line[-1]) if line else out[-1500:])
if not line or " 0 failed" not in line[-1]:
raise SystemExit("dry run failed: see the output above")
print("INSTALLED: start the game from Steam as usual; notes are read in Russian")
if __name__ == "__main__":
os.environ.setdefault("PYTHONUTF8", "1")
main()