168 lines
5.7 KiB
Python
168 lines
5.7 KiB
Python
"""Install / uninstall Forge into an RPG Maker MV or MZ game folder.
|
|
|
|
Credits: len — https://gitgud.io/zero64801/forge-mvmz (Forge plugin)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from util.forge.config import PLUGIN_BY_ENGINE, plugin_entry, prepare_forge_js
|
|
|
|
_PKG_ROOT = Path(__file__).resolve().parent
|
|
|
|
|
|
def bundled_plugin_path(engine: str) -> Path:
|
|
from util.forge.config import bundled_plugin_path as _path
|
|
|
|
return _path(engine)
|
|
|
|
|
|
def detect_engine(game_root: Path) -> tuple[str, Path, Path] | None:
|
|
"""Return (engine, plugins_js, plugins_dir) or None if not MV/MZ."""
|
|
root = Path(game_root)
|
|
mv_js = root / "www" / "js" / "plugins.js"
|
|
mz_js = root / "js" / "plugins.js"
|
|
if mv_js.is_file():
|
|
return "MV", mv_js, root / "www" / "js" / "plugins"
|
|
if mz_js.is_file():
|
|
return "MZ", mz_js, root / "js" / "plugins"
|
|
return None
|
|
|
|
|
|
def detect_mz(game_root: Path) -> tuple[Path, Path] | None:
|
|
"""Return (plugins_js, plugins_dir) for MZ only, or None."""
|
|
info = detect_engine(game_root)
|
|
if info is None or info[0] != "MZ":
|
|
return None
|
|
return info[1], info[2]
|
|
|
|
|
|
def _plugin_name(engine: str) -> str:
|
|
return PLUGIN_BY_ENGINE[engine]
|
|
|
|
|
|
def _read_plugins_js(plugins_js: Path) -> tuple[str, str]:
|
|
content = plugins_js.read_text(encoding="utf-8")
|
|
nl = "\r\n" if "\r\n" in content else "\n"
|
|
return content, nl
|
|
|
|
|
|
def _is_declared(content: str, plugin_name: str) -> bool:
|
|
return bool(re.search(rf'"name"\s*:\s*"{re.escape(plugin_name)}"', content))
|
|
|
|
|
|
def status(game_root: Path) -> dict:
|
|
"""Return install state for the game folder."""
|
|
info = detect_engine(game_root)
|
|
if info is None:
|
|
return {
|
|
"ok": False,
|
|
"engine": None,
|
|
"installed": False,
|
|
"declared": False,
|
|
"plugin_file": None,
|
|
"message": "No RPG Maker MV/MZ game found (missing plugins.js).",
|
|
}
|
|
engine, plugins_js, plugins_dir = info
|
|
plugin_name = _plugin_name(engine)
|
|
target = plugins_dir / f"{plugin_name}.js"
|
|
content, _ = _read_plugins_js(plugins_js)
|
|
declared = _is_declared(content, plugin_name)
|
|
file_there = target.is_file()
|
|
return {
|
|
"ok": True,
|
|
"engine": engine,
|
|
"installed": declared or file_there,
|
|
"declared": declared,
|
|
"plugin_file": file_there,
|
|
"plugins_js": str(plugins_js),
|
|
"target": str(target),
|
|
"message": (
|
|
"Installed"
|
|
if declared and file_there
|
|
else "Partially installed"
|
|
if declared or file_there
|
|
else "Not installed"
|
|
),
|
|
}
|
|
|
|
|
|
def install(game_root: Path, source_js: Path | None = None, cfg: dict | None = None) -> tuple[bool, str]:
|
|
"""Copy Forge_MV.js or Forge_MZ.js into the game and declare it in plugins.js."""
|
|
info = detect_engine(game_root)
|
|
if info is None:
|
|
return False, "No RPG Maker MV/MZ game found at that path."
|
|
|
|
engine, plugins_js, plugins_dir = info
|
|
plugin_name = _plugin_name(engine)
|
|
default_src = bundled_plugin_path(engine)
|
|
|
|
if source_js and not Path(source_js).is_file():
|
|
return False, f"{plugin_name}.js not found: {source_js}"
|
|
if not source_js and not default_src.is_file():
|
|
return False, f"{plugin_name}.js not found: {default_src}"
|
|
|
|
target = plugins_dir / f"{plugin_name}.js"
|
|
content, nl = _read_plugins_js(plugins_js)
|
|
|
|
plugins_dir.mkdir(parents=True, exist_ok=True)
|
|
target.write_text(prepare_forge_js(engine, source_js, cfg), encoding="utf-8")
|
|
|
|
hotkey = (cfg or {}).get("forgeHotkey", "F10")
|
|
ui_scale = (cfg or {}).get("uiScale", "auto")
|
|
entry = plugin_entry(engine, hotkey, ui_scale)
|
|
if not _is_declared(content, plugin_name):
|
|
idx = content.rfind("];")
|
|
if idx < 0:
|
|
return False, "Could not find plugin list end ( ]; ) in plugins.js."
|
|
before = content[:idx].rstrip()
|
|
after = content[idx:]
|
|
sep = nl if before.endswith(",") else "," + nl
|
|
content = before + sep + entry + nl + " " + after
|
|
plugins_js.write_text(content, encoding="utf-8", newline="")
|
|
|
|
return True, f"Forge installed for RPG Maker {engine}. Press {hotkey} in-game to open."
|
|
|
|
|
|
def uninstall(game_root: Path) -> tuple[bool, str]:
|
|
"""Remove Forge from plugins.js and delete the plugin file."""
|
|
info = detect_engine(game_root)
|
|
if info is None:
|
|
return False, "No RPG Maker MV/MZ game found at that path."
|
|
|
|
engine, plugins_js, plugins_dir = info
|
|
plugin_name = _plugin_name(engine)
|
|
target = plugins_dir / f"{plugin_name}.js"
|
|
content, nl = _read_plugins_js(plugins_js)
|
|
|
|
if _is_declared(content, plugin_name):
|
|
kept = [
|
|
line for line in re.split(r"\r?\n", content)
|
|
if not re.search(rf'"name"\s*:\s*"{re.escape(plugin_name)}"', line)
|
|
]
|
|
new_text = nl.join(kept)
|
|
new_text = re.sub(r",(\s*)\];", r"\1];", new_text)
|
|
plugins_js.write_text(new_text, encoding="utf-8", newline="")
|
|
|
|
if target.is_file():
|
|
target.unlink()
|
|
|
|
return True, "Forge uninstalled."
|
|
|
|
|
|
def apply_config(game_root: Path, cfg: dict | None = None) -> tuple[bool, str]:
|
|
"""Rewrite an installed Forge plugin with current playtest settings."""
|
|
info = detect_engine(game_root)
|
|
if info is None:
|
|
return False, "No RPG Maker MV/MZ game found at that path."
|
|
|
|
engine, _, plugins_dir = info
|
|
plugin_name = _plugin_name(engine)
|
|
target = plugins_dir / f"{plugin_name}.js"
|
|
if not target.is_file():
|
|
return False, "Forge is not installed in this game folder."
|
|
|
|
target.write_text(prepare_forge_js(engine, cfg=cfg), encoding="utf-8")
|
|
return True, f"Forge settings applied to the installed {plugin_name} plugin."
|