Sync SuperWoW FoV with Modernization Tool #10
@@ -1,5 +1,7 @@
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import struct
|
||||
import sys
|
||||
import tkinter as tk
|
||||
@@ -483,6 +485,80 @@ class ModernWowSetupTool(_ModernWowSetupToolCore):
|
||||
finally:
|
||||
_restore_mpq_runtime_hooks(originals)
|
||||
|
||||
def _configure_superwow_fov_cvar(self, target):
|
||||
"""Keep SuperWoW's FoV CVar aligned with the Tool-selected FoV."""
|
||||
superwow = self.core_plugins.get("SuperWoWhook.dll")
|
||||
if superwow is None or not superwow.get():
|
||||
return
|
||||
|
||||
try:
|
||||
fov = float(self.vt_fov.get())
|
||||
except (TypeError, ValueError, tk.TclError) as exc:
|
||||
raise RuntimeError("Field of View is not a valid numeric value.") from exc
|
||||
if not math.isfinite(fov) or not 0.5 <= fov <= 3.5:
|
||||
raise RuntimeError(
|
||||
"Field of View value is outside the supported WoW 1.12.1 range."
|
||||
)
|
||||
|
||||
wtf_dir = os.path.join(target, "WTF")
|
||||
config_path = os.path.join(wtf_dir, "Config.wtf")
|
||||
original_mode = None
|
||||
restore_readonly = False
|
||||
staged = config_path + ".modernization-fov"
|
||||
|
||||
try:
|
||||
os.makedirs(wtf_dir, exist_ok=True)
|
||||
|
||||
if os.path.exists(config_path):
|
||||
original_mode = os.stat(config_path).st_mode
|
||||
if not (original_mode & stat.S_IWRITE):
|
||||
os.chmod(config_path, original_mode | stat.S_IWRITE)
|
||||
restore_readonly = True
|
||||
|
||||
existing = ""
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8", errors="ignore") as handle:
|
||||
existing = handle.read()
|
||||
|
||||
setting = f'SET FoV "{format(fov, ".9g")}"'
|
||||
pattern = re.compile(
|
||||
r'^\s*SET\s+FoV\s+"[^"]*"\s*$',
|
||||
re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
if pattern.search(existing):
|
||||
updated = pattern.sub(setting, existing)
|
||||
else:
|
||||
if existing and not existing.endswith(("\n", "\r")):
|
||||
existing += "\n"
|
||||
updated = existing + setting + "\n"
|
||||
|
||||
with open(staged, "w", encoding="utf-8", newline="") as handle:
|
||||
handle.write(updated)
|
||||
os.replace(staged, config_path)
|
||||
|
||||
except PermissionError as exc:
|
||||
raise RuntimeError(
|
||||
"Windows denied access to WTF\\Config.wtf while synchronizing "
|
||||
"the SuperWoW FoV. Close WoW and any program using the file, "
|
||||
"then try again."
|
||||
) from exc
|
||||
finally:
|
||||
if os.path.exists(staged):
|
||||
try:
|
||||
os.remove(staged)
|
||||
except OSError:
|
||||
pass
|
||||
if restore_readonly and original_mode is not None and os.path.exists(config_path):
|
||||
try:
|
||||
os.chmod(config_path, original_mode)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def configure_script_memory(self, target):
|
||||
"""Apply base Config.wtf settings, then synchronize SuperWoW FoV."""
|
||||
super().configure_script_memory(target)
|
||||
self._configure_superwow_fov_cvar(target)
|
||||
|
||||
def run_installation(self):
|
||||
"""Run the EXE patch transaction before the installer's first file write.
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import setup_tool_dynamic as dynamic
|
||||
|
||||
|
||||
class FakeVar:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class SuperWowFovSyncTests(unittest.TestCase):
|
||||
def _tool(self, *, superwow=True, fov=1.9199, script_memory=False):
|
||||
tool = object.__new__(dynamic.ModernWowSetupTool)
|
||||
tool.core_plugins = {
|
||||
"SuperWoWhook.dll": FakeVar(superwow),
|
||||
}
|
||||
tool.vt_fov = FakeVar(fov)
|
||||
tool.vt_script_memory = FakeVar(script_memory)
|
||||
return tool
|
||||
|
||||
@staticmethod
|
||||
def _config_path(root):
|
||||
return os.path.join(root, "WTF", "Config.wtf")
|
||||
|
||||
def test_superwow_enabled_updates_existing_fov(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._config_path(root)
|
||||
os.makedirs(os.path.dirname(path))
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
handle.write(
|
||||
'SET gxWindow "1"\n'
|
||||
'SET FoV "1.5"\n'
|
||||
'SET locale "enUS"\n'
|
||||
)
|
||||
|
||||
self._tool().configure_script_memory(root)
|
||||
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
result = handle.read()
|
||||
self.assertIn('SET FoV "1.9199"', result)
|
||||
self.assertIn('SET gxWindow "1"', result)
|
||||
self.assertIn('SET locale "enUS"', result)
|
||||
self.assertNotIn('SET FoV "1.5"', result)
|
||||
|
||||
def test_superwow_enabled_creates_missing_fov_setting(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._config_path(root)
|
||||
|
||||
self._tool(fov=2.1).configure_script_memory(root)
|
||||
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
result = handle.read()
|
||||
self.assertEqual(result, 'SET FoV "2.1"\n')
|
||||
|
||||
def test_superwow_disabled_leaves_existing_fov_untouched(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._config_path(root)
|
||||
os.makedirs(os.path.dirname(path))
|
||||
original = 'SET FoV "1.5"\nSET locale "frFR"\n'
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
handle.write(original)
|
||||
|
||||
self._tool(superwow=False).configure_script_memory(root)
|
||||
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
self.assertEqual(handle.read(), original)
|
||||
|
||||
def test_script_memory_and_superwow_fov_are_both_applied(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._config_path(root)
|
||||
os.makedirs(os.path.dirname(path))
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
handle.write('SET locale "enUS"\n')
|
||||
|
||||
self._tool(script_memory=True).configure_script_memory(root)
|
||||
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
result = handle.read()
|
||||
self.assertIn('SET scriptMemory "0"', result)
|
||||
self.assertIn('SET FoV "1.9199"', result)
|
||||
|
||||
def test_readonly_config_mode_is_restored(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._config_path(root)
|
||||
os.makedirs(os.path.dirname(path))
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
handle.write('SET FoV "1.5"\n')
|
||||
os.chmod(path, stat.S_IREAD)
|
||||
|
||||
self._tool().configure_script_memory(root)
|
||||
|
||||
mode = os.stat(path).st_mode
|
||||
self.assertFalse(mode & stat.S_IWRITE)
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
self.assertIn('SET FoV "1.9199"', handle.read())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user