Files
Modernization-Tool/tests/test_final_safety_hardening.py
T
2026-09-04 14:10:50 +02:00

257 lines
8.6 KiB
Python

import math
import os
import struct
import tempfile
import unittest
from unittest import mock
import remote_packages
import setup_tool_dynamic as dynamic
class _Var:
def __init__(self, value):
self.value = value
def get(self):
return self.value
def _make_tool():
tool = object.__new__(dynamic.ModernWowSetupTool)
tool.vt_fov = _Var(1.9199)
tool.vt_farclip = _Var(1500)
tool.vt_frill = _Var(300)
tool.vt_nameplate = _Var(41)
tool.vt_maxcam = _Var(100)
tool.vt_soundchan = _Var(64)
tool.vt_quickloot = _Var(True)
tool.vt_bg_sound = _Var(True)
tool.vt_laa = _Var(True)
tool.vt_cam_fix = _Var(True)
tool.vt_custom_glues = _Var(True)
return tool
def _base_client_data():
data = bytearray(0x46795C)
data[
dynamic._CLIENT_BUILD_OFFSET:
dynamic._CLIENT_BUILD_OFFSET + len(dynamic._CLIENT_BUILD)
] = dynamic._CLIENT_BUILD
data[
dynamic._CLIENT_VERSION_OFFSET:
dynamic._CLIENT_VERSION_OFFSET + len(dynamic._CLIENT_VERSION)
] = dynamic._CLIENT_VERSION
data[0x0C1ECF:0x0C1ED1] = b"\x74\x10"
data[0x0C2B25:0x0C2B27] = b"\x74\x0B"
data[0x3A4869] = 0x14
data[0x126:0x128] = b"\x0F\x01"
for offset, original, _patched in dynamic._CAMERA_REGIONS:
data[offset:offset + len(original)] = original
for offset, original, _patched in dynamic._CUSTOM_GLUES_SITES:
data[offset] = original
struct.pack_into("<f", data, 0x4089B4, math.pi / 2)
struct.pack_into("<f", data, 0x40FED8, 777.0)
struct.pack_into("<f", data, 0x467958, 70.0)
struct.pack_into("<f", data, 0x40C448, 20.0)
struct.pack_into("<f", data, 0x4089A4, 50.0)
data[0x435D38:0x435D3C] = b"12\x00\x00"
return data
def _write_valid_mpq(path):
archive_size = 96
header = b"MPQ\x1A" + struct.pack(
"<IIHHIIII",
32,
archive_size,
0,
3,
32,
48,
1,
1,
)
data = bytearray(archive_size)
data[:32] = header
with open(path, "wb") as handle:
handle.write(data)
class MpqValidationTests(unittest.TestCase):
def test_strict_mpq_accepts_structurally_valid_classic_header(self):
with tempfile.TemporaryDirectory() as temp:
path = os.path.join(temp, "patch.mpq")
_write_valid_mpq(path)
dynamic._strict_verify_mpq(path)
def test_strict_mpq_rejects_magic_only_or_truncated_archive(self):
with tempfile.TemporaryDirectory() as temp:
path = os.path.join(temp, "patch.mpq")
with open(path, "wb") as handle:
handle.write(b"MPQ\x1A" + b"\x00" * 60)
with self.assertRaises(remote_packages.RemotePackageError):
dynamic._strict_verify_mpq(path)
def test_runtime_remote_package_validator_is_hardened(self):
self.assertIs(remote_packages._verify_mpq, dynamic._strict_verify_mpq)
def test_current_mpq_check_uses_full_header_validation(self):
with tempfile.TemporaryDirectory() as temp:
data_dir = os.path.join(temp, "Data")
os.makedirs(data_dir)
path = os.path.join(data_dir, "patch-X.mpq")
with open(path, "wb") as handle:
handle.write(b"MPQ\x1A" + b"\x00" * 60)
with mock.patch.object(
remote_packages,
"managed_mod_is_current",
return_value=True,
), mock.patch.object(
remote_packages,
"_load_managed_manifest",
return_value=[os.path.join("Data", "patch-X.mpq")],
):
self.assertFalse(
remote_packages.managed_mpq_is_current(temp, "visual_test", "1")
)
class VanillaTweaksFingerprintTests(unittest.TestCase):
def test_configured_5875_numeric_values_remain_compatible(self):
tool = _make_tool()
data = _base_client_data()
# Legitimate community launchers can already carry user-selected values.
struct.pack_into("<f", data, 0x4089B4, math.radians(100))
struct.pack_into("<f", data, 0x40FED8, 2500.0)
struct.pack_into("<f", data, 0x467958, 120.0)
struct.pack_into("<f", data, 0x40C448, 35.0)
struct.pack_into("<f", data, 0x4089A4, 80.0)
data[0x435D38:0x435D3C] = b"32\x00\x00"
tool._validate_client_identity(data)
tool._validate_vanilla_tweaks_state(
data,
allow_foreign_custom_glues=True,
)
snapshot = tool._capture_source_numeric_states(
data,
tool._desired_normalized_values(),
)
self.assertEqual(snapshot["frill"], struct.pack("<f", 120.0))
self.assertEqual(snapshot["sound"], b"32\x00\x00")
def test_wrong_client_build_is_rejected(self):
tool = _make_tool()
data = _base_client_data()
data[
dynamic._CLIENT_BUILD_OFFSET:
dynamic._CLIENT_BUILD_OFFSET + len(dynamic._CLIENT_BUILD)
] = b"9999"
with self.assertRaisesRegex(RuntimeError, "build 5875"):
tool._validate_client_identity(data)
def test_out_of_range_source_numeric_state_is_rejected(self):
tool = _make_tool()
data = _base_client_data()
struct.pack_into("<f", data, 0x40FED8, 50000.0)
with self.assertRaisesRegex(RuntimeError, "Farclip"):
tool._capture_source_numeric_states(
data,
tool._desired_normalized_values(),
)
def test_staged_numeric_output_must_be_source_or_selected_value(self):
tool = _make_tool()
source = _base_client_data()
desired = tool._desired_normalized_values()
snapshot = tool._capture_source_numeric_states(source, desired)
staged = bytearray(source)
struct.pack_into("<f", staged, 0x40FED8, 2345.0)
with self.assertRaisesRegex(RuntimeError, "produced by vanilla-tweaks"):
tool._validate_staged_numeric_states(staged, snapshot, desired)
struct.pack_into("<f", staged, 0x40FED8, desired["farclip"])
tool._validate_staged_numeric_states(staged, snapshot, desired)
class InstallationOrderingTests(unittest.TestCase):
def test_vanilla_tweaks_runs_once_before_first_install_write(self):
tool = object.__new__(dynamic.ModernWowSetupTool)
events = []
original_clean = lambda target: events.append(("clean", target))
original_vt = lambda target: (
events.append(("vanilla_tweaks", target))
or os.path.join(target, "WoW_Modernized.exe")
)
tool.clean_unselected_files = original_clean
tool.run_vanilla_tweaks = original_vt
def fake_base_install(instance):
events.append(("base", "validated"))
instance.clean_unselected_files("GAME")
events.append(("base", "after_clean"))
instance.run_vanilla_tweaks("GAME")
return "done"
with mock.patch.object(
dynamic._ModernWowSetupToolCore,
"run_installation",
fake_base_install,
):
self.assertEqual(tool.run_installation(), "done")
self.assertEqual(
events,
[
("base", "validated"),
("vanilla_tweaks", "GAME"),
("clean", "GAME"),
("base", "after_clean"),
],
)
self.assertIs(tool.clean_unselected_files, original_clean)
self.assertIs(tool.run_vanilla_tweaks, original_vt)
def test_vanilla_tweaks_failure_prevents_first_install_write(self):
tool = object.__new__(dynamic.ModernWowSetupTool)
events = []
def original_clean(target):
events.append(("clean", target))
def original_vt(_target):
events.append(("vanilla_tweaks", "failed"))
raise RuntimeError("patcher failed")
tool.clean_unselected_files = original_clean
tool.run_vanilla_tweaks = original_vt
def fake_base_install(instance):
instance.clean_unselected_files("GAME")
self.fail("clean_unselected_files should not complete after VT failure")
with mock.patch.object(
dynamic._ModernWowSetupToolCore,
"run_installation",
fake_base_install,
):
with self.assertRaisesRegex(RuntimeError, "patcher failed"):
tool.run_installation()
self.assertEqual(events, [("vanilla_tweaks", "failed")])
self.assertIs(tool.clean_unselected_files, original_clean)
self.assertIs(tool.run_vanilla_tweaks, original_vt)
if __name__ == "__main__":
unittest.main()