"""Install the Game State Integration config that lets Dota 2 talk to gsi_watch.py. Usage: python gsi_setup.py # auto-detect Dota 2 and write the cfg python gsi_setup.py --path "D:\\Steam\\steamapps\\common\\dota 2 beta" python gsi_setup.py --remove # uninstall the cfg python gsi_setup.py --check # only report where things are After installing, add -gamestateintegration to Dota 2's launch options (Steam library -> right-click Dota 2 -> Properties) and restart the game. """ import re import sys from pathlib import Path from common import load_config CFG_NAME = "gamestate_integration_climperor.cfg" CFG_TEMPLATE = """"Climperor" {{ "uri" "http://127.0.0.1:{port}/" "timeout" "5.0" "buffer" "0.1" "throttle" "0.5" "heartbeat" "30.0" "data" {{ "provider" "1" "map" "1" "player" "1" "hero" "1" }} }} """ FALLBACK_ROOTS = [ r"C:\Program Files (x86)\Steam", r"C:\Steam", r"D:\Steam", r"D:\SteamLibrary", r"E:\SteamLibrary", ] def steam_roots() -> list[Path]: """Candidate Steam library roots, from the registry plus libraryfolders.vdf.""" roots: list[Path] = [] try: import winreg with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Valve\Steam") as key: roots.append(Path(winreg.QueryValueEx(key, "SteamPath")[0])) except Exception: # noqa: BLE001 - registry is best-effort pass roots += [Path(p) for p in FALLBACK_ROOTS] # libraryfolders.vdf lists every additional install drive for root in list(roots): vdf = root / "steamapps" / "libraryfolders.vdf" if not vdf.is_file(): continue try: text = vdf.read_text(encoding="utf-8", errors="ignore") except OSError: continue for match in re.finditer(r'"path"\s+"([^"]+)"', text): roots.append(Path(match.group(1).replace("\\\\", "\\"))) seen: set[str] = set() unique: list[Path] = [] for r in roots: k = str(r).lower() if k not in seen: seen.add(k) unique.append(r) return unique def find_dota() -> Path | None: """Locate the 'dota 2 beta' install directory.""" for root in steam_roots(): candidate = root / "steamapps" / "common" / "dota 2 beta" if (candidate / "game" / "dota").is_dir(): return candidate return None def gsi_dir(dota: Path) -> Path: return dota / "game" / "dota" / "cfg" / "gamestate_integration" def main() -> None: args = sys.argv[1:] if "--path" in args: dota = Path(args[args.index("--path") + 1]) if not (dota / "game" / "dota").is_dir(): sys.exit(f"not a Dota 2 install directory: {dota}") else: dota = find_dota() if dota is None: sys.exit( "could not find Dota 2 automatically.\n" "Pass it explicitly, e.g.:\n" ' python gsi_setup.py --path "D:\\Steam\\steamapps\\common\\dota 2 beta"' ) target = gsi_dir(dota) / CFG_NAME print(f"dota 2 : {dota}") print(f"gsi cfg : {target}") if "--check" in args: print(f"installed: {target.is_file()}") return if "--remove" in args: if target.is_file(): target.unlink() print("removed.") else: print("nothing to remove.") return port = load_config().get("gsi", {}).get("port", 3223) target.parent.mkdir(parents=True, exist_ok=True) target.write_text(CFG_TEMPLATE.format(port=port), encoding="utf-8") print(f"installed, endpoint http://127.0.0.1:{port}/") print() print("Next steps:") print(" 1. Steam library -> Dota 2 -> Properties -> Launch Options:") print(" add -gamestateintegration") print(" 2. Restart Dota 2.") print(" 3. Run python gsi_watch.py") if __name__ == "__main__": main()