release v2.2
This commit was merged in pull request #8.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user