"""Forge plugin build — install-time hotkey and UI scale injection. Vanilla Forge_MV.js / Forge_MZ.js from len's repo stay untouched on disk. Patches are applied in memory when installing or applying settings to a game. """ from __future__ import annotations import json import re from pathlib import Path from util.playtest.config import load_config from util.forge.scale_patches import apply_ui_scale_patches _PKG_ROOT = Path(__file__).resolve().parent PLUGIN_BY_ENGINE = { "MV": "Forge_MV", "MZ": "Forge_MZ", } def bundled_plugin_path(engine: str) -> Path: name = PLUGIN_BY_ENGINE.get(engine) if not name: raise ValueError(f"Unsupported engine: {engine}") return _PKG_ROOT / f"{name}.js" def _js_literal(value) -> str: if value is None: return "null" if isinstance(value, bool): return "true" if value else "false" if isinstance(value, (int, float)): return str(value) return json.dumps(str(value)) def plugin_entry(engine: str, hotkey: str, ui_scale: str = "auto") -> str: hk = _js_literal(hotkey.strip() or "F10") scale = _js_literal(ui_scale.strip() or "auto") name = PLUGIN_BY_ENGINE[engine] if engine == "MZ": return ( f' {{ "name": "{name}", "status": true, ' f'"description": "Forge — in-game cheat & editor overlay", ' f'"parameters": {{ "hotkey": {hk}, "speedKey": "Control", ' f'"startOpen": "false", "itemMaxOverride": "0", "uiScale": {scale} }} }}' ) return ( f' {{ "name": "{name}", "status": true, ' f'"description": "Forge — in-game cheat & editor overlay", ' f'"parameters": {{ "Hotkey": {hk}, "SpeedKey": "Control", ' f'"StartOpen": "false", "ItemMaxOverride": "0", "UiScale": {scale} }} }}' ) def _patch_forge_hotkey(forge_text: str, hotkey: str, engine: str) -> str: hk = hotkey.strip() or "F10" if engine == "MZ": pattern = ( r"(\* @param hotkey\s*\n" r"(?:\s*\*[^\n]*\n)*?" r"\s*\* @default )F10" ) forge_text, n = re.subn(pattern, rf"\g<1>{hk}", forge_text, count=1) if n == 0: raise ValueError("Could not patch @default hotkey in Forge_MZ.js") return forge_text pattern = ( r"(\* @param Hotkey\s*\n" r"(?:\s*\*[^\n]*\n)*?" r"\s*\* @default )F10" ) forge_text, n = re.subn(pattern, rf"\g<1>{hk}", forge_text, count=1) if n == 0: raise ValueError("Could not patch @default Hotkey in Forge_MV.js") return forge_text def _patch_forge_ui_scale_default(forge_text: str, ui_scale: str, engine: str) -> str: scale = ui_scale.strip() or "auto" if engine == "MZ": pattern = r"(\* @param uiScale\s*\n(?:\s*\*[^\n]*\n)*?\s*\* @default )auto" if scale != "auto": forge_text, n = re.subn(pattern, rf"\g<1>{scale}", forge_text, count=1) if n == 0: raise ValueError("Could not patch @default uiScale in Forge_MZ.js") return forge_text pattern = r"(\* @param UiScale\s*\n(?:\s*\*[^\n]*\n)*?\s*\* @default )auto" if scale != "auto": forge_text, n = re.subn(pattern, rf"\g<1>{scale}", forge_text, count=1) if n == 0: raise ValueError("Could not patch @default UiScale in Forge_MV.js") return forge_text def _patch_forge_mtc_defaults(forge_text: str, hotkey: str, ui_scale: str) -> str: hk = json.dumps(hotkey.strip() or "F10") scale = json.dumps(str(ui_scale or "auto").strip() or "auto") forge_text = re.sub(r"_hotkey: 'F10'", f"_hotkey: {hk}", forge_text, count=1) forge_text = re.sub(r"_uiScale: 'auto'", f"_uiScale: {scale}", forge_text, count=1) return forge_text def _patch_forge_runtime(forge_text: str, hotkey: str, ui_scale: str, engine: str) -> str: hk = json.dumps(hotkey.strip() or "F10") scale = json.dumps(str(ui_scale or "auto").strip() or "auto") if engine == "MZ": forge_text = re.sub( r"window\.Forge\._hotkey = \(P\.hotkey \|\| '[^']*'\)\.trim\(\);", f"window.Forge._hotkey = {hk};", forge_text, count=1, ) forge_text = re.sub( r"window\.Forge\._uiScale = \(P\.uiScale \|\| '[^']*'\)\.trim\(\);", f"window.Forge._uiScale = {scale};", forge_text, count=1, ) return forge_text forge_text = re.sub( r"window\.Forge\._hotkey = \(P\.Hotkey \|\| '[^']*'\)\.trim\(\);", f"window.Forge._hotkey = {hk};", forge_text, count=1, ) forge_text = re.sub( r"window\.Forge\._uiScale = \(P\.UiScale \|\| '[^']*'\)\.trim\(\);", f"window.Forge._uiScale = {scale};", forge_text, count=1, ) return forge_text def prepare_forge_js(engine: str, source: Path | None = None, cfg: dict | None = None) -> str: """Build the Forge plugin written into a game folder (vanilla source + Dazed patches).""" src = source or bundled_plugin_path(engine) effective = {**load_config(), **(cfg or {})} hotkey = effective.get("forgeHotkey", "F10") ui_scale = effective.get("uiScale", "auto") text = apply_ui_scale_patches(src.read_text(encoding="utf-8"), engine) text = _patch_forge_hotkey(text, hotkey, engine) text = _patch_forge_ui_scale_default(text, str(ui_scale), engine) text = _patch_forge_mtc_defaults(text, hotkey, str(ui_scale)) text = _patch_forge_runtime(text, hotkey, str(ui_scale), engine) return text # Back-compat alias def prepare_forge_mz_js(source: Path | None = None, cfg: dict | None = None) -> str: return prepare_forge_js("MZ", source, cfg)