release v2.2

This commit was merged in pull request #8.
This commit is contained in:
2026-09-03 16:42:37 +02:00
committed by GitHub
parent 098852175e
commit 9c397218a7
6 changed files with 544 additions and 154 deletions
+4 -4
View File
@@ -21,10 +21,10 @@ No remaining payload binary was modified during this provenance pass.
| ClassicAPI | bundled fallback + online update | brues-code/ClassicAPI | GPL v3 verified |
| AuctionQueryThrottle | bundled fallback + online update | brues-code/AuctionQueryThrottle | GPL v3 verified |
| SuperWoW | **upstream-only** | balakethelock/SuperWoW release | redistribution restricted upstream |
| SuperAPI | **upstream-only** | balakethelock/SuperAPI master | no bundled copy after this audit |
| SuperAPI | **upstream-only** | balakethelock/SuperAPI master | no bundled copy; current master revision is resolved before download |
| UnitXP_SP3 | bundled fallback + online update | brues-code/UnitXP_SP3 v90 | project-wide license not independently located |
| Interact | bundled fallback + online update | lookino/Interact v1.0.4 | project-wide license not independently located |
| No1600x1200 | bundled fallback + online update | RetroCro/TurtleWoW-Mods | project-wide license not independently located |
| No1600x1200 | **bundled known-good only** | RetroCro/TurtleWoW-Mods archive source | project-wide license not independently located |
| PerfBoost | bundled | RetroCro/TurtleWoW-Mods backup source | project-wide license not independently located |
| WeirdPerformance | bundled | Dusk92/WeirdUtils 0.7.3 | provenance recorded; license record pending |
| WeirdUtils modules | bundled | MarcelineVQ/WeirdUtils releases | provenance recorded; license record pending |
@@ -46,8 +46,8 @@ That bundled fallback is removed by this audit because the current upstream
license restricts redistribution without express written permission.
The installation feature itself is retained: `remote_packages.install_superwow`
already downloads the official upstream release and downloads SuperAPI directly
from its upstream repository.
downloads the official SuperWoW release and the current SuperAPI master
revision directly from its upstream repository.
## SuperAPI change
+50 -108
View File
@@ -17,8 +17,6 @@ Download the latest version from:
**[GitHub Releases](https://github.com/Dusk-92/Modernization-Tool/releases/latest)**
Current major release: **v2.2**
No installation is required. Download and run:
`WoW_Modernization_Tool.exe`
@@ -46,60 +44,58 @@ The launcher shortcut is automatically created:
Two rendering modes are available:
### VanillaFixes — DirectX 9
### [VanillaFixes](https://github.com/hannesmann/vanillafixes) — DirectX 9
The default option.
Uses WoW's native DirectX 9 renderer together with VanillaFixes.
### VanillaFixes + DXVK — Vulkan
### VanillaFixes + [DXVK](https://github.com/doitsujin/dxvk) — Vulkan
Uses DXVK to translate DirectX 9 to Vulkan.
The bundled version is currently:
**DXVK 2.6.1 x86**
DXVK is bundled with the tool and can be selected directly from the renderer options.
Existing custom DirectX wrapper files are backed up rather than blindly deleted when switching renderers.
---
# 🧩 Client Plugins
# 🧩 Client Plugins & Sources
## Recommended Core
The tool can install and configure several recommended client extensions:
- **Nampower** — improves spell responsiveness and latency handling.
- **UnitXP_SP3** — adds networking, targeting, Lua and client improvements.
- **SuperWoW** — expands the Vanilla Lua API and improves addon compatibility.
- **TransmogFix** — prevents performance drops caused by rapid appearance updates.
- **PerfBoost** — provides additional unit rendering/performance controls.
- **WeirdPerformance** — lightweight client optimizations aimed at improving FPS and reducing UI memory leaks for smoother gameplay.
- **VanillaHelpers** — extends several Vanilla client limits.
- **ClassicAPI** — adds newer WoW API functions for compatible addons.
- **AuctionQueryThrottle** — removes the fixed delay between Auction House queries.
- **[Nampower](https://github.com/brues-code/nampower)** — improves spell responsiveness and latency handling. Settings addon: [NampowerSettings](https://github.com/brues-code/NampowerSettings).
- **[UnitXP_SP3](https://github.com/brues-code/UnitXP_SP3)** — adds networking, targeting, Lua and client improvements.
- **[SuperWoW](https://github.com/balakethelock/SuperWoW)** — expands the Vanilla Lua API and improves addon compatibility. Its companion API addon is [SuperAPI](https://github.com/balakethelock/SuperAPI).
- **[TransmogFix](https://codeberg.org/MarcelineVQ/WeirdUtils)** — prevents performance drops caused by rapid appearance updates.
- **[PerfBoost](https://github.com/RetroCro/TurtleWoW-Mods)** — provides additional unit rendering/performance controls. Configuration addon: [PerfBoost Settings](https://gitea.com/avitasia/PerfBoostSettings).
- **[WeirdPerformance](https://codeberg.org/Dusk92/WeirdUtils)** — lightweight client optimizations aimed at improving FPS and reducing UI memory leaks for smoother gameplay.
- **[VanillaHelpers](https://github.com/isfir/VanillaHelpers)** — extends several Vanilla client limits.
- **[ClassicAPI](https://github.com/brues-code/ClassicAPI)** — adds newer WoW API functions for compatible addons.
- **[AuctionQueryThrottle](https://github.com/brues-code/AuctionQueryThrottle)** — removes the fixed delay between Auction House queries.
## Optional Plugins
Optional client-side improvements include:
- **VanillaMultiMonitorFix** — improved resolution, refresh-rate and monitor detection.
- **Interact** — adds a modern Interact key for NPCs, objects, gathering nodes and loot.
- **No1600x1200** — fixes the old Vanilla resolution limitation on some systems.
- **BigCursor** — upscales the hardware cursor for better visibility on modern high-resolution displays without sacrificing sharpness.
- **CustomAssets** — allows loose game files to be loaded directly from the `Data/` folder and supports custom multi-character patch names without repacking MPQ archives.
- **LogSessions** — automatically organizes combat and chat logs into clean per-character, per-day files when you log in.
- **MinimapIcons** — adds TBC/WotLK-style minimap tracking icons for NPCs and game objects, with a combined tracking menu and saved preferences.
- **PNG Screenshots** — saves screenshots as compressed PNG files instead of the default uncompressed TGA format, using background processing to minimize frame-time impact.
- **WorldMarkers** — lets party or raid leaders place up to five animated, Cataclysm-style colored world markers for positioning and tactical planning.
- **Discord Rich Presence** — installs and keeps **WowPresence** up to date from [Dusk-92/WowPresence](https://github.com/Dusk-92/WowPresence). The Tool preconfigures the OctoWoW Discord Application ID and lets you choose which character details are shown on Discord, including **Name, Guild, Race, Faction, Class, Level and Zone**. Custom Discord Application IDs are preserved across updates.
- **[VanillaMultiMonitorFix](https://github.com/Mates1500/VanillaMultiMonitorFix)** — improved resolution, refresh-rate and monitor detection.
- **[Interact](https://github.com/lookino/Interact)** — adds a modern Interact key for NPCs, objects, gathering nodes and loot.
- **[No1600x1200](https://github.com/RetroCro/TurtleWoW-Mods#no1600x1200)** — fixes the old Vanilla resolution limitation on some systems.
- **[BigCursor](https://codeberg.org/MarcelineVQ/WeirdUtils)** — upscales the hardware cursor for better visibility on modern high-resolution displays without sacrificing sharpness.
- **[CustomAssets](https://codeberg.org/MarcelineVQ/WeirdUtils)** — allows loose game files to be loaded directly from the `Data/` folder and supports custom multi-character patch names without repacking MPQ archives.
- **[LogSessions](https://codeberg.org/MarcelineVQ/WeirdUtils)** — automatically organizes combat and chat logs into clean per-character, per-day files when you log in.
- **[MinimapIcons](https://codeberg.org/MarcelineVQ/WeirdUtils)** — adds TBC/WotLK-style minimap tracking icons for NPCs and game objects, with a combined tracking menu and saved preferences.
- **[PNG Screenshots](https://codeberg.org/MarcelineVQ/WeirdUtils)** — saves screenshots as compressed PNG files instead of the default uncompressed TGA format, using background processing to minimize frame-time impact.
- **[WorldMarkers](https://codeberg.org/MarcelineVQ/WeirdUtils)** — lets party or raid leaders place up to five animated, Cataclysm-style colored world markers for positioning and tactical planning.
- **[Discord Rich Presence](https://github.com/Dusk-92/WowPresence)** — installs and keeps **WowPresence** up to date. The Tool preconfigures the OctoWoW Discord Application ID and lets you choose which character details are shown on Discord, including **Name, Guild, Race, Faction, Class, Level and Zone**. Custom Discord Application IDs are preserved across updates.
---
# ⚙️ Vanilla Tweaks
Modernization Tool integrates the modern **tubtubs/vanilla-tweaks** patcher.
Modernization Tool integrates the modern **[tubtubs/vanilla-tweaks](https://github.com/tubtubs/vanilla-tweaks)** patcher.
Available settings include:
@@ -117,7 +113,7 @@ Available settings include:
- Unlimited AddOn Script Memory
- Cross-Faction Resurrection Fix
- Custom Glues Patch
- Automatic WDB management
- [Automatic WDB management](https://github.com/RetroCro/TurtleWoW-Mods#automatically-clear-wdb-folder-every-time-you-launch-turtle-wow)
When **SuperWoW** already provides a feature, the equivalent vanilla-tweaks patch is automatically skipped to avoid duplicate modifications.
@@ -127,12 +123,12 @@ When **SuperWoW** already provides a feature, the equivalent vanilla-tweaks patc
Optional visual modifications are available directly from the tool:
- **Bluemoon Patch** — restores the rare blue moon effect.
- **Darker Nights** — darker and more atmospheric nights.
- **Pretty Night Sky** — improved starry night sky.
- **Epoch Water** — alternative water textures.
- **Fog Pushback** — moves environmental fog farther away.
- **Pink Herbs** — makes herb nodes much easier to spot.
- **[Bluemoon Patch](https://github.com/tubtubs/vanilla-tweaks)** — restores the rare blue moon effect.
- **[Darker Nights](https://projectreforged.github.io/vanilla/downloads/turtle/)** — darker and more atmospheric nights.
- **[Pretty Night Sky](https://github.com/RetroCro/TurtleWoW-Mods)** — improved starry night sky.
- **[Epoch Water](https://github.com/RetroCro/TurtleWoW-Mods)** — alternative water textures.
- **[Fog Pushback](https://github.com/RetroCro/TurtleWoW-Mods)** — moves environmental fog farther away.
- **[Pink Herbs](https://github.com/seacrabsam/patch-herb)** — makes herb nodes much easier to spot.
All visual mods are **disabled by default**.
@@ -142,9 +138,9 @@ All visual mods are **disabled by default**.
Optional sound replacements include:
- **NoErrorSounds** — removes repetitive error/fizzle and interface sounds.
- **FishPing** — replaces the fishing bite sound with a clearer ping.
- **Warlock Muted Demons** — mutes repetitive Warlock demon voice lines.
- **[NoErrorSounds](https://github.com/Macumbafeh/NoErrorSounds)** — removes repetitive error/fizzle and interface sounds.
- **[FishPing](https://github.com/notsureawake/FishPing)** — replaces the fishing bite sound with a clearer ping.
- **[Warlock Muted Demons](https://github.com/spzilyk/Warlock-Muted-Demons)** — mutes repetitive Warlock demon voice lines.
All audio mods are **disabled by default**.
@@ -162,11 +158,15 @@ Depending on the component, the tool can:
- use a bundled known-good fallback where redistribution terms allow it;
- avoid re-downloading files that are already current.
**SuperWoW and SuperAPI are upstream-only:** they are downloaded directly from
their official upstream projects when enabled and are not bundled as offline
fallbacks. A first-time SuperWoW installation therefore requires network access.
**SuperWoW and SuperAPI are upstream-only:** SuperWoW is downloaded from its
official stable release and SuperAPI follows the current upstream `master`
revision. They are not bundled as offline fallbacks, so a first-time SuperWoW
installation requires network access.
Downloaded DLLs are also validated before installation.
**No1600x1200 uses the bundled known-good copy** instead of following the
RetroCro archive repository automatically.
Downloaded and bundled DLLs are validated before installation.
---
@@ -188,7 +188,7 @@ It also:
# 🔐 AutoLogin
AutoLogin can optionally save account and character shortcuts on the login screen.
[Vanilla AutoLogin](https://github.com/MarcelineVQ/turtle-autologin) can optionally save account and character shortcuts on the login screen.
When **AutoLogin + Nampower** are enabled together, Modernization Tool automatically creates or reuses the Windows user encryption key required by Nampower.
@@ -200,27 +200,6 @@ Existing encryption keys are never replaced.
---
# 📦 Current Notable Versions
| Component | Version |
|---|---|
| VanillaFixes | 1.5.3 |
| DXVK | 2.6.1 x86 |
| Nampower | 4.6.1 |
| ClassicAPI | 1.13.3 |
| AuctionQueryThrottle | 1.2.0 |
| UnitXP_SP3 | v90 |
| SuperWoW | 2.2 |
| VanillaHelpers | 1.1.2 |
| WeirdPerformance | 0.7.3 |
| WorldMarkers | 0.7.1 |
| VanillaMultiMonitorFix | 0.2 |
| Interact | 1.0.4 |
Where supported, the tool will attempt to obtain the latest compatible upstream version automatically.
---
# 📝 Updating Modernization Tool
Updating from an older version does **not** require reinstalling WoW.
@@ -238,7 +217,9 @@ Your per-installation settings will be restored automatically.
# 🛡️ Antivirus Notice
Because Modernization Tool modifies a game executable and installs client-side DLL plugins, some antivirus products may flag the executable or some included components.
Because Modernization Tool creates a patched `WoW_Modernized.exe` and installs client-side DLL plugins, some antivirus products may flag the executable or some included components.
The original `WoW.exe` is not modified in place.
Always download Modernization Tool from the official GitHub Releases page.
@@ -247,54 +228,15 @@ Always download Modernization Tool from the official GitHub Releases page.
# 🔗 Useful Links
- **[Latest Release](https://github.com/Dusk-92/Modernization-Tool/releases/latest)**
- **[v2.2 Release Notes](https://github.com/Dusk-92/Modernization-Tool/releases/tag/v2.2)**
- **[OctoWoW Installation & Modernization Guide](https://octowow.st/forum/viewtopic.php?t=831)**
---
# ❤️ Credits
Modernization Tool brings together work from several community projects and developers.
Modernization Tool builds on the work of the community projects and developers linked throughout this README.
## Rendering & Client Patching
- [VanillaFixes](https://github.com/hannesmann/vanillafixes)
- [DXVK](https://github.com/doitsujin/dxvk)
- [vanilla-tweaks](https://github.com/tubtubs/vanilla-tweaks)
## Core Engine & API Plugins
- [VanillaHelpers](https://github.com/isfir/VanillaHelpers)
- [PerfBoost Settings](https://gitea.com/avitasia/PerfBoostSettings)
- [UnitXP_SP3](https://github.com/brues-code/UnitXP_SP3)
- [SuperWoW](https://github.com/balakethelock/SuperWoW)
- [SuperAPI](https://github.com/balakethelock/SuperAPI)
- [ClassicAPI](https://github.com/brues-code/ClassicAPI)
- [AuctionQueryThrottle](https://github.com/brues-code/AuctionQueryThrottle)
- [Nampower](https://github.com/brues-code/nampower)
- [NampowerSettings](https://github.com/brues-code/NampowerSettings)
- [No1600x1200](https://github.com/RetroCro/TurtleWoW-Mods#no1600x1200)
- [VanillaMultiMonitorFix](https://github.com/Mates1500/VanillaMultiMonitorFix)
- [Interact](https://github.com/lookino/Interact)
- [WowPresence](https://github.com/Dusk-92/WowPresence)
## Visual & Audio Mods
- [Bluemoon Patch via vanilla-tweaks](https://github.com/tubtubs/vanilla-tweaks)
- [Darker Nights — Project Reforged](https://projectreforged.github.io/vanilla/downloads/turtle/)
- [Pretty Night Sky / Epoch Water / Fog Pushback — RetroCro TurtleWoW Mods](https://github.com/RetroCro/TurtleWoW-Mods)
- [Pink Herbs](https://github.com/seacrabsam/patch-herb)
- [NoErrorSounds](https://github.com/Macumbafeh/NoErrorSounds)
- [FishPing](https://github.com/notsureawake/FishPing)
- [Warlock Muted Demons](https://github.com/spzilyk/Warlock-Muted-Demons)
- [Automatic WDB management guide](https://github.com/RetroCro/TurtleWoW-Mods#automatically-clear-wdb-folder-every-time-you-launch-turtle-wow)
## Other Bundled Enhancements
- [Vanilla AutoLogin](https://github.com/MarcelineVQ/turtle-autologin)
- [WeirdUtils](https://codeberg.org/Dusk92/WeirdUtils)
Additional attribution and source links are available directly in the **Credits** tab of Modernization Tool.
Thank you to all upstream authors and contributors whose work makes this project possible. Additional attribution is available directly in the **Credits** tab of Modernization Tool.
---
@@ -311,8 +253,8 @@ For detailed redistribution and provenance information, see:
- [Docs/ASSET_PROVENANCE.md](Docs/ASSET_PROVENANCE.md)
- [LICENSES/](LICENSES/)
SuperWoW and SuperAPI are downloaded directly from their upstream projects and
are not bundled as offline fallbacks.
SuperWoW and the current SuperAPI master revision are downloaded directly from
their upstream projects and are not bundled as offline fallbacks.
---
+8 -6
View File
@@ -14,18 +14,20 @@
- Added a `Show character details` option to quickly enable all Discord details.
- Updated WowPresence integration for the new privacy system.
## 🔄 Updates & Downloads
## 🔄 Updates & Reliability
- Added smart update checks for remote components.
- Unchanged components are no longer downloaded again on every Apply.
- Modified or missing managed files are automatically detected and repaired.
- Release assets are detected even when an upstream project replaces a file under the same tag.
- Improved vanilla-tweaks update detection to avoid unnecessary downloads and repatching.
- Improved update checks for GitHub branch-based components.
## 📚 Documentation
- Updated Discord Rich Presence documentation.
- Improved branch-based component downloads to use the exact resolved revision.
- SuperAPI continues to follow upstream `master` while installing the exact detected revision.
- Added integrity checks for bundled components before installation.
- Improved atomic installation of bundled components and dependent addons.
- Improved Nampower and UnitXP addon handling during updates and offline fallback.
- No1600x1200 now uses the bundled known-good version.
- Fixed stale `.modernization-backup-*` files remaining after successful updates.
## ✅ Updating from v2.1
+52 -14
View File
@@ -353,6 +353,31 @@ def _remove_path(path):
os.remove(path)
def _cleanup_transaction_backups(target):
"""Best-effort cleanup of stale backups once a valid live target exists."""
target = os.path.abspath(target)
if not os.path.lexists(target):
return
parent = os.path.dirname(target)
prefix = os.path.basename(target) + ".modernization-backup-"
try:
names = os.listdir(parent)
except OSError:
return
for name in names:
if not name.startswith(prefix):
continue
backup = os.path.join(parent, name)
try:
_remove_path(backup)
except OSError:
# The old file may still be locked by WoW/Windows. Keep it safe
# and retry automatically the next time this package is verified.
pass
def _transactional_replace_bundle(items, label="component bundle"):
"""Replace a group of files/directories as one rollback-safe transaction.
@@ -469,15 +494,10 @@ def _transactional_replace_bundle(items, label="component bundle"):
) from exc
else:
# Commit succeeded. Old copies are no longer needed.
# Commit succeeded. Old copies are no longer needed. Clean the current
# transaction backup plus any stale backup left by an earlier run.
for record in records:
if record["backup_created"] and os.path.lexists(record["backup"]):
try:
_remove_path(record["backup"])
except OSError:
# A stale backup is harmless; never invalidate a successful
# installation just because cleanup was denied.
pass
_cleanup_transaction_backups(record["target"])
finally:
# Staged paths are safe to remove. Backups are intentionally not
@@ -1162,7 +1182,17 @@ def _package_state_is_current(target_dir, package_id, revision):
current = _snapshot_package_paths(target_dir, paths.keys())
except OSError:
return False
return current == paths
is_current = current == paths
if is_current:
# A verified current package no longer needs rollback artifacts from
# earlier successful installs. Retry best-effort cleanup here so a
# Windows file lock does not leave them around indefinitely.
for relative_path in paths:
_cleanup_transaction_backups(
os.path.join(target_dir, relative_path)
)
return is_current
def _record_package_state_safely(target_dir, package_id, revision, relative_paths):
@@ -1726,7 +1756,7 @@ def install_pink_herbs(target_dir, progress=None):
return f"seacrabsam/patch-herb main@{revision[:7]}"
temp_path = _download(
"https://raw.githubusercontent.com/seacrabsam/patch-herb/main/patch-H.mpq",
f"https://raw.githubusercontent.com/seacrabsam/patch-herb/{revision}/patch-H.mpq",
suffix=".mpq",
progress=progress,
label="Downloading Pink Herbs",
@@ -1762,8 +1792,15 @@ def install_pink_herbs(target_dir, progress=None):
return f"seacrabsam/patch-herb main@{revision[:7]}"
def _download_github_branch_archive(repo, branch, progress=None, label="Downloading sound mod"):
url = f"https://codeload.github.com/{repo}/zip/refs/heads/{branch}"
def _download_github_branch_archive(
repo,
branch,
progress=None,
label="Downloading sound mod",
revision=None,
):
revision = revision or _branch_head_sha(repo, branch)
url = f"https://codeload.github.com/{repo}/zip/{revision}"
return _download(
url,
suffix=".zip",
@@ -1812,6 +1849,7 @@ def _install_github_sound_pack(target_dir, mod_id, repo, branch, source_folder,
branch,
progress=progress,
label=label,
revision=revision,
)
extract_root = tempfile.mkdtemp(prefix=f"modernization_{mod_id}_")
try:
@@ -1992,7 +2030,7 @@ def install_no1600x1200(target_dir, progress=None):
url = (
"https://raw.githubusercontent.com/RetroCro/TurtleWoW-Mods/"
"refs/heads/main/Archive/DLL%20BACKUP/no1600x1200.dll"
f"{revision}/Archive/DLL%20BACKUP/no1600x1200.dll"
)
temp_path = _download(
url,
@@ -2198,7 +2236,7 @@ def install_superwow(target_dir, progress=None):
# Prepare SuperAPI completely before changing the installed DLL.
_emit_progress(progress, "Preparing SuperAPI update...", None, None)
superapi_zip = _download(
"https://codeload.github.com/balakethelock/SuperAPI/zip/refs/heads/master",
f"https://codeload.github.com/balakethelock/SuperAPI/zip/{superapi_revision}",
suffix=".zip",
progress=progress,
label="Downloading SuperAPI addon",
+188 -22
View File
@@ -43,6 +43,136 @@ class ModernWowSetupTool(WowSetupTool):
self._download_indeterminate = False
super().__init__(root)
def _bundled_manifest_record(self, relative_path):
"""Return the expected size/hash record for one bundled payload file."""
metadata_path = os.path.join(
get_base_path(),
"Payload",
"Fallback",
"versions.json",
)
wanted = relative_path.replace("\\", "/").casefold()
try:
with open(metadata_path, "r", encoding="utf-8") as handle:
data = json.load(handle)
except (OSError, json.JSONDecodeError, ValueError, TypeError) as exc:
raise RuntimeError(
"Could not read bundled component integrity metadata."
) from exc
components = data.get("components") if isinstance(data, dict) else None
if not isinstance(components, dict):
raise RuntimeError("Bundled component integrity metadata is invalid.")
for component in components.values():
files = component.get("files") if isinstance(component, dict) else None
if not isinstance(files, list):
continue
for record in files:
if not isinstance(record, dict):
continue
path = record.get("path")
if isinstance(path, str) and path.replace("\\", "/").casefold() == wanted:
return record
raise RuntimeError(
f"No integrity record exists for bundled file {relative_path}."
)
def _verified_bundled_file(self, relative_path, label):
"""Validate a bundled file before it is allowed to touch the WoW folder."""
record = self._bundled_manifest_record(relative_path)
source = os.path.join(
get_base_path(),
*relative_path.replace("\\", "/").split("/"),
)
if not os.path.isfile(source):
raise RuntimeError(f"Bundled file is missing: {relative_path}")
expected_size = record.get("size")
if not isinstance(expected_size, int) or expected_size <= 0:
raise RuntimeError(f"Invalid bundled size metadata for {relative_path}.")
if os.path.getsize(source) != expected_size:
raise RuntimeError(
f"Bundled {label} has an unexpected size and will not be installed."
)
expected_sha = record.get("sha256")
if (
not isinstance(expected_sha, str)
or len(expected_sha) != 64
or any(ch not in "0123456789abcdefABCDEF" for ch in expected_sha)
):
raise RuntimeError(f"Invalid bundled SHA-256 metadata for {relative_path}.")
expected_sha = expected_sha.lower()
if self._file_sha256(source).lower() != expected_sha:
raise RuntimeError(
f"Bundled {label} failed its SHA-256 integrity check."
)
if os.path.splitext(source)[1].lower() in (".dll", ".exe"):
remote_packages._verify_x86_pe(source, f"bundled {label}")
return source, expected_sha
def _install_verified_bundled_file(self, relative_path, target_path, label):
source, expected_sha = self._verified_bundled_file(relative_path, label)
if os.path.isfile(target_path):
try:
if self._file_sha256(target_path).lower() == expected_sha:
return
except OSError:
pass
remote_packages._atomic_replace_file(source, target_path)
def copy_base_files(self, target):
"""Install only true base files; component addons are handled with their DLLs."""
payload_dir = os.path.join(get_base_path(), "Payload")
if not os.path.isdir(payload_dir):
return
if self.install_autologin.get():
data_source = os.path.join(payload_dir, "Data")
if os.path.isdir(data_source):
shutil.copytree(
data_source,
os.path.join(target, "Data"),
dirs_exist_ok=True,
)
# Do not copy Payload/Interface wholesale here. Nampower and UnitXP
# addons must stay paired with the exact DLL version selected later.
vanilla_fixes, vf_sha = self._verified_bundled_file(
"Payload/VanillaFixes.exe",
"VanillaFixes.exe",
)
vf_patcher, patcher_sha = self._verified_bundled_file(
"Payload/VfPatcher.dll",
"VfPatcher.dll",
)
target_fixes = os.path.join(target, "VanillaFixes.exe")
target_patcher = os.path.join(target, "VfPatcher.dll")
already_current = False
if os.path.isfile(target_fixes) and os.path.isfile(target_patcher):
try:
already_current = (
self._file_sha256(target_fixes).lower() == vf_sha
and self._file_sha256(target_patcher).lower() == patcher_sha
)
except OSError:
already_current = False
if not already_current:
remote_packages._transactional_replace_bundle(
[
("file", vanilla_fixes, target_fixes),
("file", vf_patcher, target_patcher),
],
label="VanillaFixes",
)
def _collect_settings(self):
settings = super()._collect_settings()
settings["discord_presence"] = {
@@ -421,7 +551,7 @@ class ModernWowSetupTool(WowSetupTool):
"SuperWoWhook.dll": ("SuperWoW", "by balakethelock"),
"transmogfix.dll": ("TransmogFix", "by MarcelineVQ"),
"perf_boost.dll": ("PerfBoost", "by avitasia"),
"weirdperformance.dll": ("WeirdPerformance", "Dusk92 build 0.7.3"),
"weirdperformance.dll": ("WeirdPerformance", "by Dusk-92"),
"VanillaHelpers.dll": ("VanillaHelpers", "by isfir"),
}
@@ -606,9 +736,9 @@ class ModernWowSetupTool(WowSetupTool):
f"Could not download the latest {dll_name}, and no bundled fallback exists.\n\n{error}"
) from error
remote_packages._verify_x86_pe(
source_dll,
f"bundled fallback {dll_name}",
self._verified_bundled_file(
f"Payload/{dll_name}",
f"{dll_name} fallback",
)
source_addon = None
@@ -1053,10 +1183,48 @@ class ModernWowSetupTool(WowSetupTool):
except Exception as exc:
self._fallback_core_dll(payload_base, target, dll_name, exc)
elif dll_name == "perf_boost.dll":
source_dll, _ = self._verified_bundled_file(
"Payload/perf_boost.dll",
"PerfBoost",
)
source_addon = os.path.join(
payload_base,
"Interface",
"Addons",
"perfboostsettings",
)
if not os.path.isdir(source_addon):
raise RuntimeError(
"Bundled PerfBoost settings addon is missing."
)
remote_packages._transactional_replace_bundle(
[
(
"file",
source_dll,
os.path.join(target, "perf_boost.dll"),
),
(
"dir",
source_addon,
os.path.join(
target,
"Interface",
"AddOns",
"perfboostsettings",
),
),
],
label="PerfBoost",
)
else:
source_dll = os.path.join(payload_base, dll_name)
if os.path.exists(source_dll):
shutil.copy2(source_dll, target)
self._install_verified_bundled_file(
f"Payload/{dll_name}",
os.path.join(target, dll_name),
dll_name,
)
dlls_text_lines.append(dll_name)
@@ -1093,18 +1261,14 @@ class ModernWowSetupTool(WowSetupTool):
# Optional client fixes, in the same order as the UI.
no1600_var = self.optional_plugins["no1600x1200.dll"]
if no1600_var.get():
try:
remote_packages.install_no1600x1200(
target,
progress=self._report_download_progress,
)
except Exception as exc:
self._fallback_core_dll(
payload_base,
target,
"no1600x1200.dll",
exc,
)
# No1600x1200 comes from an archive/mirror repository rather
# than a maintained release channel. Use the bundled,
# hash-verified known-good copy for predictable installs.
self._install_verified_bundled_file(
"Payload/no1600x1200.dll",
os.path.join(target, "no1600x1200.dll"),
"no1600x1200.dll",
)
dlls_text_lines.append("no1600x1200.dll")
if self.vmmfix_enabled.get():
@@ -1175,9 +1339,11 @@ class ModernWowSetupTool(WowSetupTool):
self._discord_broadcast_mask(),
)
else:
source_dll = os.path.join(payload_weirdu, dll_name)
if os.path.exists(source_dll):
shutil.copy2(source_dll, target)
self._install_verified_bundled_file(
f"Payload/WeirdUtils/{dll_name}",
os.path.join(target, dll_name),
dll_name,
)
dlls_text_lines.append(dll_name)
+242
View File
@@ -1,3 +1,4 @@
import hashlib
import json
import os
import struct
@@ -645,6 +646,62 @@ class SmartUpdateTests(unittest.TestCase):
remote_packages._package_state_is_current(root, "example", "v1")
)
def test_current_package_cleans_stale_transaction_backup(self):
with tempfile.TemporaryDirectory() as root:
dll = os.path.join(root, "UnitXP_SP3.dll")
with open(dll, "wb") as handle:
handle.write(b"current-unitxp")
remote_packages._record_package_state(
root,
"unitxp_sp3",
"v90",
["UnitXP_SP3.dll"],
)
stale_backup = dll + ".modernization-backup-941614e28c62"
with open(stale_backup, "wb") as handle:
handle.write(b"old-unitxp")
self.assertTrue(
remote_packages._package_state_is_current(
root,
"unitxp_sp3",
"v90",
)
)
self.assertFalse(os.path.exists(stale_backup))
self.assertTrue(os.path.isfile(dll))
def test_non_current_package_keeps_transaction_backup_for_recovery(self):
with tempfile.TemporaryDirectory() as root:
dll = os.path.join(root, "UnitXP_SP3.dll")
with open(dll, "wb") as handle:
handle.write(b"current-unitxp")
remote_packages._record_package_state(
root,
"unitxp_sp3",
"v90",
["UnitXP_SP3.dll"],
)
stale_backup = dll + ".modernization-backup-recovery"
with open(stale_backup, "wb") as handle:
handle.write(b"old-unitxp")
with open(dll, "wb") as handle:
handle.write(b"modified-after-state")
self.assertFalse(
remote_packages._package_state_is_current(
root,
"unitxp_sp3",
"v90",
)
)
self.assertTrue(os.path.exists(stale_backup))
def test_release_asset_revision_detects_replaced_asset_under_same_tag(self):
release_a = {
"tag_name": "Release",
@@ -1172,5 +1229,190 @@ class SettingsRecoveryTests(unittest.TestCase):
self.assertEqual(handle.read(), damaged)
class BundledComponentSafetyTests(unittest.TestCase):
def test_bundled_integrity_uses_recorded_sha256_and_size(self):
tool = ModernWowSetupTool.__new__(ModernWowSetupTool)
with tempfile.TemporaryDirectory() as root:
payload = os.path.join(root, "Payload")
fallback = os.path.join(payload, "Fallback")
os.makedirs(fallback)
source = os.path.join(payload, "component.bin")
with open(source, "wb") as handle:
handle.write(b"known bundled component")
expected_sha = hashlib.sha256(
b"known bundled component"
).hexdigest()
with open(
os.path.join(fallback, "versions.json"),
"w",
encoding="utf-8",
) as handle:
json.dump(
{
"components": {
"test": {
"files": [
{
"path": "Payload/component.bin",
"size": os.path.getsize(source),
"sha256": expected_sha,
}
]
}
}
},
handle,
)
with mock.patch("setup_tool_dynamic.get_base_path", return_value=root):
verified, digest = tool._verified_bundled_file(
"Payload/component.bin",
"test component",
)
self.assertEqual(verified, source)
self.assertEqual(digest, expected_sha)
with open(source, "ab") as handle:
handle.write(b"!")
with self.assertRaises(RuntimeError):
tool._verified_bundled_file(
"Payload/component.bin",
"test component",
)
def test_copy_base_files_does_not_copy_component_addons(self):
tool = ModernWowSetupTool.__new__(ModernWowSetupTool)
tool.install_autologin = FakeVar(False)
with tempfile.TemporaryDirectory() as root:
payload = os.path.join(root, "Payload")
os.makedirs(os.path.join(payload, "Interface", "Addons", "nampowersettings"))
vanilla_fixes = os.path.join(payload, "VanillaFixes.exe")
vf_patcher = os.path.join(payload, "VfPatcher.dll")
for path in (vanilla_fixes, vf_patcher):
with open(path, "wb") as handle:
handle.write(b"test")
tool._verified_bundled_file = mock.Mock(
side_effect=[
(vanilla_fixes, "a" * 64),
(vf_patcher, "b" * 64),
]
)
target = os.path.join(root, "game")
os.makedirs(target)
with (
mock.patch("setup_tool_dynamic.get_base_path", return_value=root),
mock.patch.object(
remote_packages,
"_transactional_replace_bundle",
) as transaction,
):
tool.copy_base_files(target)
self.assertFalse(os.path.exists(os.path.join(target, "Interface")))
transaction.assert_called_once()
items = transaction.call_args.args[0]
self.assertEqual(
[os.path.basename(item[2]) for item in items],
["VanillaFixes.exe", "VfPatcher.dll"],
)
def test_no1600_uses_bundled_copy_without_online_update(self):
tool = ModernWowSetupTool.__new__(ModernWowSetupTool)
tool.rendering_mode = FakeVar("directx9")
tool.core_plugins = {}
tool.classicapi_enabled = FakeVar(False)
tool.auction_throttle_enabled = FakeVar(False)
tool.addon_dependencies = {}
tool.optional_plugins = {
"no1600x1200.dll": FakeVar(True),
}
tool.vmmfix_enabled = FakeVar(False)
tool.interact_enabled = FakeVar(False)
tool._install_verified_bundled_file = mock.Mock()
tool._write_dlls_file = mock.Mock()
tool._close_download_progress = mock.Mock()
tool._report_download_progress = mock.Mock()
with tempfile.TemporaryDirectory() as root:
with mock.patch.object(
remote_packages,
"install_no1600x1200",
) as online_installer:
tool.configure_plugins(root)
online_installer.assert_not_called()
tool._install_verified_bundled_file.assert_called_once_with(
"Payload/no1600x1200.dll",
mock.ANY,
"no1600x1200.dll",
)
self.assertEqual(
os.path.basename(
tool._install_verified_bundled_file.call_args.args[1]
),
"no1600x1200.dll",
)
def test_superapi_tracks_master_revision(self):
release = {
"name": "SuperWoW 2.2",
"tag_name": "Release",
"id": 1,
"assets": [
{
"name": "SuperWoW.zip",
"id": 2,
"updated_at": "2026-07-16T00:00:00Z",
"size": 123,
}
],
}
with (
mock.patch.object(remote_packages, "_latest_release", return_value=release),
mock.patch.object(
remote_packages,
"_package_state_is_current",
return_value=True,
) as state,
mock.patch.object(
remote_packages,
"_branch_head_sha",
return_value="abc1234",
) as branch_head,
):
remote_packages.install_superwow("C:/WoW")
branch_head.assert_called_once_with("balakethelock/SuperAPI", "master")
revision = state.call_args.args[2]
self.assertIn("superapi:abc1234", revision)
def test_branch_archive_download_can_be_pinned_to_resolved_revision(self):
with (
mock.patch.object(remote_packages, "_download", return_value="archive.zip") as download,
mock.patch.object(remote_packages, "_branch_head_sha") as branch_head,
):
result = remote_packages._download_github_branch_archive(
"owner/repo",
"main",
revision="abc1234",
)
self.assertEqual(result, "archive.zip")
branch_head.assert_not_called()
self.assertEqual(
download.call_args.args[0],
"https://codeload.github.com/owner/repo/zip/abc1234",
)
if __name__ == "__main__":
unittest.main()