Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a02dc97793 | |||
| bea8a8e0ed | |||
| c9f8b78df7 | |||
| 26dc319155 | |||
| 1b06e33421 | |||
| 85a81d9259 | |||
| c644cb3481 | |||
| 0faf9ce25b | |||
| 0eb62fbf04 | |||
| d59b922f97 | |||
| 5aaf852b79 | |||
| 998b9650d8 | |||
| 776b5f1d0f | |||
| 5ef038041d | |||
| 509a09b4a0 | |||
| 49c7eb5376 | |||
| acd385ac25 | |||
| 48078cc827 | |||
| 4d239a2e30 | |||
| a6c16e4b2b | |||
| a703884a84 | |||
| 514e937d0e | |||
| 4f0b747159 | |||
| 26445a2731 | |||
| 8d4ce2d7c6 | |||
| 7a1a3c9d12 | |||
| 05b0b761c1 | |||
| 29d7f3c174 | |||
| 3e5fbe47dd | |||
| 178f1460a9 | |||
| e385229f4a | |||
| cd8a3e19f9 | |||
| 62b736c205 | |||
| 7841c58a64 | |||
| 99e8aafacf | |||
| f9a0315c1c | |||
| 8cba9f9bfa | |||
| 9b46d98e6a | |||
| d0627a574f | |||
| 315d32d52f | |||
| 881e25ca97 | |||
| 16d2a8d8ec | |||
| 10e4be0fec | |||
| 363eb89fd0 | |||
| 2dbd335d8f | |||
| 1a6a60b643 | |||
| a1c70df8e8 | |||
| 3cda29653b | |||
| 14ca95f553 | |||
| acbe46e5ba | |||
| 81b6cd6a30 | |||
| 94a613a815 | |||
| 50ed6f20fa | |||
| b92e9c2987 | |||
| 0602e012c1 | |||
| a263ec6fb8 | |||
| f51ef200a4 | |||
| 95e1b25f4e | |||
| 31eb2c911c | |||
| c992cee4cf | |||
| 7471b4f12e | |||
| eca8599f0c | |||
| 2338c005b8 | |||
| 13f3c247d0 | |||
| 7c9d69179f | |||
| 655c444353 | |||
| 600719652e | |||
| 8658700a76 | |||
| f8a8678e2e | |||
| e88d2cd28d | |||
| 798408c559 | |||
| 17059d8a76 | |||
| 9569bbd296 | |||
| e5e4dc954a | |||
| b861ad89e2 | |||
| a46bb67d0d | |||
| 41592e7f8c | |||
| b12001584e | |||
| 036ff0c1cb | |||
| 56f0ff9854 | |||
| 3d3eea3c6a | |||
| 7c7ff0d146 | |||
| ae8abe2e16 | |||
| 0b85ec4faf | |||
| 30ab74a4f0 | |||
| b727c928bb | |||
| 1f8c87ae47 | |||
| 3fb218e296 | |||
| 64b7612c90 | |||
| 6c6d6e2b7d | |||
| c8bce690a5 | |||
| 96cc98d52b | |||
| e9c7b3b219 | |||
| 669361aeee | |||
| b0394ac662 | |||
| c140fdfe98 | |||
| 386917c9f0 | |||
| 859fe4037e | |||
| 77ace2d642 | |||
| a42608f635 | |||
| 98325b403c | |||
| 2b7de2613c | |||
| 3e32b7985b | |||
| 9ff415ef3f | |||
| 63aa6328cc | |||
| c9c878dc19 | |||
| 08bd69d186 | |||
| 6fff497457 | |||
| c6a65ab044 | |||
| 4a0f068465 | |||
| 166ae58a9f | |||
| e59bc1fc53 | |||
| 065ff4ee4f | |||
| 34b3affeb1 | |||
| c304cd0542 | |||
| 627e9392b5 | |||
| 74a6dd3891 | |||
| 890949b8db | |||
| 39c3963d5f | |||
| 0416e21b54 | |||
| 90f9247eb1 | |||
| 352330b048 | |||
| 764d177638 | |||
| 943caba91c | |||
| 422b076f19 | |||
| 51e772d959 | |||
| de0515c84c | |||
| a5c6feee95 | |||
| 5f7792b9d9 | |||
| 1e568db8ec | |||
| e1c9ee4fb0 | |||
| 6883fb318d | |||
| 1b80a904b7 | |||
| ad62557e59 | |||
| ae3a23be17 | |||
| 480d20f39e | |||
| d49fb9acc0 | |||
| fa764ba40f | |||
| 023dd7ba1d | |||
| fb9275e13a | |||
| 846dffb59b | |||
| fc8c7eaa8e | |||
| 9c397218a7 | |||
| 098852175e | |||
| bfa1e09b7a | |||
| 2dee327763 | |||
| 44b2c31339 | |||
| ef41957f11 | |||
| 253dd69de1 | |||
| 3b424bd377 | |||
| 5a57ef47a4 | |||
| f431b7b70b | |||
| a7bb6fbdbe | |||
| 2f01828bba | |||
| 4c34b8ab7b | |||
| dd52e49c99 | |||
| a30406c046 | |||
| e7cb7677a0 | |||
| afb4993dee | |||
| 85c7ca9d98 | |||
| cd64ea0b31 | |||
| f3aa797317 | |||
| b2ed9782fc | |||
| fb1c5f7701 | |||
| 4237125bcf | |||
| 5c72b69f56 | |||
| dadf7801a3 | |||
| 7fd9c04fff | |||
| aabe8a2000 | |||
| db589f13d9 | |||
| 77dde7033a | |||
| 3298105447 | |||
| ab69e4898f | |||
| 315be89daa | |||
| 239b6e2009 | |||
| 5e8894dbcf | |||
| b04d390b79 | |||
| 6ed19a8b5b | |||
| cc826a64a7 | |||
| 7cfbab2b09 | |||
| 804eecd53a | |||
| 6a8e01bb58 | |||
| dbaa7b5c18 | |||
| 3c289bb339 | |||
| bca15c8132 | |||
| 6e7fc48d74 | |||
| 24d288ec7d | |||
| f55936985c | |||
| a9617175ce | |||
| 9c80a68c9b | |||
| a3013e76de | |||
| b320ab8775 | |||
| 7daa67ff42 | |||
| 16548700e4 | |||
| 7c278b2fcf | |||
| aa9d9f15fc | |||
| 2fb4c8d3fb | |||
| 0177c3a68d | |||
| 6c618a43d9 | |||
| 88176bccc6 | |||
| 0816d987f8 | |||
| 75c77c6b71 | |||
| 0eb67f698c | |||
| b5aefdc354 | |||
| 87d32727bb | |||
| 468b09ef08 | |||
| a2532378f2 | |||
| 62b7b9804d | |||
| 0f557172ed | |||
| f4eed23a15 | |||
| ac93d959f6 | |||
| 1f465957e6 | |||
| 7e284aba50 | |||
| d234227c4e | |||
| b648dbb476 | |||
| 806d7531c8 | |||
| 9c37530c24 | |||
| 6af37f14b9 | |||
| 865179d7f4 | |||
| 27ed6a81f9 | |||
| c067770961 | |||
| 560e5823c6 | |||
| 14de09eef6 | |||
| b0cdc6888d | |||
| 48da2566b0 | |||
| e01cdd967e | |||
| 4bc60a3a6a | |||
| e7c01d096b |
@@ -0,0 +1 @@
|
||||
Payload/Interface/Addons/SuperAPI/** text eol=lf
|
||||
+49
-20
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'feature/client-tweaks'
|
||||
- 'test/discord-presence'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
@@ -41,8 +42,16 @@ jobs:
|
||||
pyinstaller --version
|
||||
python -m pip show pyinstaller pyinstaller-hooks-contrib
|
||||
|
||||
- name: Configure x86 MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: x86
|
||||
|
||||
- name: Prepare bundled remote fallbacks
|
||||
run: python scripts/prepare_remote_fallbacks.py
|
||||
|
||||
- name: Syntax check
|
||||
run: python -m py_compile setup_tool.py setup_tool_dynamic.py remote_packages.py tests/test_safety.py
|
||||
run: python -m py_compile setup_tool.py setup_tool_dynamic.py setup_tool_responsive.py dxvk_fps.py remote_packages.py tests/test_safety.py tests/test_dxvk_fps_limit.py scripts/prepare_remote_fallbacks.py
|
||||
|
||||
- name: Unit safety tests
|
||||
run: python -m unittest discover -s tests -v
|
||||
@@ -57,7 +66,7 @@ jobs:
|
||||
--add-data "Payload;Payload"
|
||||
--add-data "vanilla-tweaks.exe;."
|
||||
--name "WoW_Modernization_Tool"
|
||||
setup_tool_dynamic.py
|
||||
setup_tool_responsive.py
|
||||
|
||||
- name: Verify EXE exists
|
||||
shell: pwsh
|
||||
@@ -65,37 +74,57 @@ jobs:
|
||||
if (-not (Test-Path "dist/WoW_Modernization_Tool.exe")) { throw "Build output missing" }
|
||||
Get-Item "dist/WoW_Modernization_Tool.exe" | Format-List Name,Length,LastWriteTime
|
||||
|
||||
- name: Create ZIP package
|
||||
shell: pwsh
|
||||
run: |
|
||||
$zip = "dist/WoW_Modernization_Tool.zip"
|
||||
if (Test-Path $zip) { Remove-Item $zip -Force }
|
||||
Compress-Archive -Path "dist/WoW_Modernization_Tool.exe" -DestinationPath $zip -CompressionLevel Optimal
|
||||
if (-not (Test-Path $zip)) { throw "ZIP package missing" }
|
||||
Get-Item $zip | Format-List Name,Length,LastWriteTime
|
||||
|
||||
- name: Upload test artifact
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') && !(github.ref == 'refs/heads/main' && github.event.head_commit.message == 'release v2.3') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WoW_Modernization_Tool-test
|
||||
path: dist/WoW_Modernization_Tool.exe
|
||||
path: |
|
||||
dist/WoW_Modernization_Tool.exe
|
||||
dist/WoW_Modernization_Tool.zip
|
||||
if-no-files-found: error
|
||||
|
||||
# One-time production promotion for v2.0. The exact commit message keeps
|
||||
# normal main pushes from publishing or modifying the v2.0 release.
|
||||
- name: Create GitHub Release v2.0
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.head_commit.message == 'release v2.0' }}
|
||||
# One-time production promotion for v2.3. The exact commit message keeps
|
||||
# normal main pushes from publishing or modifying the v2.3 release.
|
||||
# Keep the Git tag aligned with the exact commit used to build the assets.
|
||||
- name: Synchronize Git tag v2.3
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.head_commit.message == 'release v2.3' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git tag -f v2.3 "${{ github.sha }}"
|
||||
git push origin refs/tags/v2.3 --force
|
||||
|
||||
- name: Create GitHub Release v2.3
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.head_commit.message == 'release v2.3' }}
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: v2.0
|
||||
tag_name: v2.3
|
||||
target_commitish: ${{ github.sha }}
|
||||
files: dist/WoW_Modernization_Tool.exe
|
||||
files: |
|
||||
dist/WoW_Modernization_Tool.exe
|
||||
dist/WoW_Modernization_Tool.zip
|
||||
fail_on_unmatched_files: true
|
||||
name: "WoW Modernization Tool v2.0"
|
||||
overwrite_files: true
|
||||
name: "WoW Modernization Tool v2.3"
|
||||
body_path: RELEASE_NOTES.md
|
||||
generate_release_notes: true
|
||||
previous_tag: v1.1
|
||||
generate_release_notes: false
|
||||
make_latest: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/tags/v2.3' }}
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: dist/WoW_Modernization_Tool.exe
|
||||
fail_on_unmatched_files: true
|
||||
name: "WoW Modernization Tool ${{ github.ref_name }}"
|
||||
body_path: RELEASE_NOTES.md
|
||||
generate_release_notes: true
|
||||
make_latest: true
|
||||
files: |
|
||||
dist/WoW_Modernization_Tool.exe
|
||||
dist/WoW_Modernization_Tool.zip
|
||||
|
||||
@@ -14,7 +14,10 @@ if errorlevel 1 exit /b 1
|
||||
py -3.11 -m pip install --disable-pip-version-check -r requirements-build.txt
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
py -3.11 -m py_compile setup_tool.py setup_tool_dynamic.py remote_packages.py tests/test_safety.py
|
||||
py -3.11 scripts\prepare_remote_fallbacks.py
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
py -3.11 -m py_compile setup_tool.py setup_tool_dynamic.py remote_packages.py tests/test_safety.py scripts\prepare_remote_fallbacks.py
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
py -3.11 -m unittest discover -s tests -v
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Modernization Tool asset provenance
|
||||
|
||||
Audit date: 2026-08-31
|
||||
|
||||
This document tracks project branding and game-facing non-binary assets that
|
||||
need provenance separate from software DLL/EXE licensing.
|
||||
|
||||
## Project icon
|
||||
|
||||
### `PurpleWowLogo.ico`
|
||||
|
||||
- Git blob SHA-1:
|
||||
`7285e997cb6d635101d0d000867af75878894719`
|
||||
- Current use: PyInstaller application icon and bundled application resource
|
||||
- Provenance status: **unresolved-project-branding-asset**
|
||||
|
||||
The file name suggests World of Warcraft-themed branding, but this audit did
|
||||
not establish its original creator or source license. No claim is made that
|
||||
Modernization Tool owns any Blizzard logo, trademark, or underlying game art
|
||||
that may be represented by the icon.
|
||||
|
||||
Until provenance is confirmed, the project should avoid representing the icon
|
||||
as an official Blizzard asset or as evidence of Blizzard affiliation.
|
||||
|
||||
## AutoLogin GlueXML files
|
||||
|
||||
Source reference:
|
||||
|
||||
- https://github.com/MarcelineVQ/turtle-autologin
|
||||
|
||||
Current comparison:
|
||||
|
||||
| File | Status | Current Git blob SHA-1 |
|
||||
| --- | --- | --- |
|
||||
| `Payload/Data/Interface/GlueXML/AutoLogin.xml` | exact upstream Git blob match | `65c7d0c08ba62ad9b1630aae6faa0046f96295d1` |
|
||||
| `Payload/Data/Interface/GlueXML/GlueXML.toc` | exact upstream Git blob match | `11ab97c6dc64385c2c8d717d0182fbee538484ff` |
|
||||
| `Payload/Data/Interface/GlueXML/AutoLogin.lua` | modified upstream-derived variant | `1de856864a16399977e6220714bd1249da0dcefe` |
|
||||
|
||||
The upstream `AutoLogin.lua` Git blob observed during the audit was
|
||||
`979af097982379ca14e4266dfea41f65452a9561`. The Modernization Tool variant
|
||||
contains additional secure-default behavior and therefore is not byte-identical.
|
||||
|
||||
The source repository did not expose a project-wide license file during this
|
||||
audit, so this provenance record does not itself establish redistribution
|
||||
permission.
|
||||
|
||||
## Audio and visual mods
|
||||
|
||||
The tool may bundle or download visual/audio replacements from community
|
||||
projects. Their source URLs and many exact file hashes are recorded in
|
||||
`Payload/Fallback/versions.json`.
|
||||
|
||||
Game-facing media can have rights separate from the software that installs it.
|
||||
Do not infer that an MIT/GPL/BSD license covering installer code automatically
|
||||
covers Blizzard artwork, sounds, or other game assets.
|
||||
|
||||
## Trademark boundary
|
||||
|
||||
World of Warcraft, Warcraft, Blizzard Entertainment, and associated logos,
|
||||
names, artwork, audio, client files, and game assets remain the property of
|
||||
their respective rights holders.
|
||||
|
||||
Modernization Tool is an independent community project and does not claim
|
||||
official status or endorsement.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Modernization Tool binary provenance
|
||||
|
||||
Audit date: 2026-09-03
|
||||
|
||||
This document is the human-readable companion to
|
||||
`Payload/Fallback/versions.json`, which contains machine-readable source,
|
||||
version, SHA-256, and size records for many bundled components.
|
||||
|
||||
This provenance pass restores the previously recorded SuperWoW fallback and verifies the paired SuperAPI source revision.
|
||||
|
||||
## Distribution status
|
||||
|
||||
| Component | Distribution mode after this audit | Source / provenance | License status |
|
||||
| --- | --- | --- | --- |
|
||||
| VanillaFixes | bundled + install support | hannesmann/vanillafixes v1.5.3 | MIT verified |
|
||||
| DXVK | bundled | doitsujin/dxvk v2.6.1 x86 | zlib/libpng verified |
|
||||
| vanilla-tweaks | bundled fallback + online update | tubtubs/vanilla-tweaks | MIT verified |
|
||||
| Nampower | bundled fallback + online update | brues-code/nampower v4.6.1 | BSD-style terms verified |
|
||||
| VanillaHelpers | bundled fallback + online update | isfir/VanillaHelpers v1.1.2 | GPL/LGPL documents verified |
|
||||
| VanillaMultiMonitorFix | bundled fallback + online update | Mates1500/VanillaMultiMonitorFix 0.2 | MIT verified |
|
||||
| ClassicAPI | bundled fallback + online update | brues-code/ClassicAPI | GPL v3 verified |
|
||||
| AuctionQueryThrottle | bundled fallback + online update | brues-code/AuctionQueryThrottle | GPL v3 verified |
|
||||
| SuperWoW | bundled fallback + online update | balakethelock/SuperWoW release 2.2 | redistribution permission reported by the Modernization Tool maintainer on 2026-09-03 |
|
||||
| SuperAPI | bundled fallback + online update | balakethelock/SuperAPI master | redistribution permission reported by the Modernization Tool maintainer on 2026-09-03 |
|
||||
| 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 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 |
|
||||
| FishPing | bundled fallback + online source | notsureawake/FishPing | project-wide license not independently located |
|
||||
| NoErrorSounds | bundled fallback + online source | Macumbafeh/NoErrorSounds | project-wide license not independently located |
|
||||
| Warlock Muted Demons | bundled fallback + online source | spzilyk/Warlock-Muted-Demons | project-wide license not independently located |
|
||||
| WowPresence | release-build bundled fallback + online update | Dusk-92/WowPresence v1.3 | project-owned integration; see component notices |
|
||||
|
||||
## SuperWoW / SuperAPI fallback
|
||||
|
||||
The bundled SuperWoW + SuperAPI fallback has been restored after the
|
||||
Modernization Tool maintainer reported upstream redistribution permission on
|
||||
2026-09-03.
|
||||
|
||||
The online source remains preferred:
|
||||
|
||||
- SuperWoW is resolved from the official stable release;
|
||||
- SuperAPI follows the current upstream `master` revision and is downloaded
|
||||
from the exact resolved commit SHA.
|
||||
|
||||
If the online installation fails and there is no complete existing install to
|
||||
preserve, the tool can install the bundled known-good pair instead.
|
||||
|
||||
Bundled fallback provenance:
|
||||
|
||||
- `Payload/SuperWoWhook.dll`
|
||||
- SuperWoW version: 2.2
|
||||
- SHA-256:
|
||||
`bd214b32c878649e94ce654835946bd05e0ce7710e8f01bae10d8ab50a89351d`
|
||||
- bundled SuperAPI revision:
|
||||
`901322dc88890a2ea10610b8228fb43c9c2a3610`
|
||||
- bundled SuperAPI Git tree:
|
||||
`95bb25752b1f31f7d4ca8d5e416986b5e3031b33`
|
||||
|
||||
The exact fallback metadata is also recorded in
|
||||
`Payload/Fallback/versions.json`.
|
||||
|
||||
## Hash manifests
|
||||
|
||||
For components stored directly in the repository, the canonical
|
||||
machine-readable hashes are kept in `Payload/Fallback/versions.json`.
|
||||
|
||||
WowPresence is prepared as a release-build fallback by
|
||||
`scripts/prepare_remote_fallbacks.py`. The script validates the release ZIP,
|
||||
records its size and SHA-256, and writes
|
||||
`Payload/Fallback/remote_fallbacks.json` before PyInstaller packages it into
|
||||
the executable. Visual MPQ mods are intentionally not bundled as fallbacks.
|
||||
|
||||
When updating a binary:
|
||||
|
||||
1. obtain it from the documented upstream source;
|
||||
2. verify the version;
|
||||
3. update the SHA-256 and size;
|
||||
4. preserve the applicable license/permission;
|
||||
5. update this document if the distribution mode changes.
|
||||
|
||||
## Important boundary
|
||||
|
||||
A matching hash proves file identity against the recorded source artifact. It
|
||||
does not by itself prove that redistribution is licensed. License/permission
|
||||
status must be tracked separately.
|
||||
@@ -0,0 +1,23 @@
|
||||
Modernization Tool - Project License Notice
|
||||
|
||||
Copyright (c) 2026 Dusk-92. All rights reserved.
|
||||
|
||||
This notice applies only to original Modernization Tool source code,
|
||||
documentation, configuration, and other material for which Dusk-92 holds the
|
||||
copyright.
|
||||
|
||||
No rights are granted by this notice to third-party software, binaries,
|
||||
libraries, addons, patches, audio, artwork, trademarks, game data, or other
|
||||
material that may be referenced, downloaded, installed, or redistributed by
|
||||
this project. Those materials remain subject to their own copyright notices,
|
||||
licenses, permissions, and other applicable terms.
|
||||
|
||||
Nothing in this file grants any right to Blizzard Entertainment intellectual
|
||||
property, including World of Warcraft names, marks, artwork, audio, client
|
||||
files, or other game assets.
|
||||
|
||||
Third-party attribution, redistribution status, and preserved license texts are
|
||||
documented in THIRD_PARTY_NOTICES.md, PROJECT_IDENTITY.md, Docs/, and LICENSES/.
|
||||
|
||||
This project is provided as-is, without warranty of any kind, to the maximum
|
||||
extent permitted by applicable law.
|
||||
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2017 Philip Rebohle
|
||||
Copyright (c) 2019 Joshua Ashton
|
||||
Copyright (c) 2019 Robin Kertels
|
||||
Copyright (c) 2023 Jeffrey Ellison
|
||||
|
||||
zlib/libpng license
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
– The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
– Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
|
||||
– This notice may not be removed or altered from any source distribution.
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,26 @@
|
||||
Copyright (c) 2017-2023, namreeb (legal@namreeb.org)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Preserved third-party license texts
|
||||
|
||||
This directory stores documentary copies of licenses for third-party
|
||||
components referenced, downloaded, or redistributed by Modernization Tool.
|
||||
|
||||
The presence of a license here does **not** apply that license to Modernization
|
||||
Tool as a whole. Each license applies only to the upstream material covered by
|
||||
its copyright holder.
|
||||
|
||||
Preserved files:
|
||||
|
||||
- `VanillaFixes-MIT.txt`
|
||||
- `DXVK-zlib.txt`
|
||||
- `vanilla-tweaks-MIT.txt`
|
||||
- `Nampower-BSD.txt`
|
||||
- `VanillaHelpers-GPL-3.0.txt`
|
||||
- `VanillaHelpers-LGPL-3.0.txt`
|
||||
- `VanillaMultiMonitorFix-MIT.txt`
|
||||
- `GPL-3.0.txt` — reference copy for GPL-v3 components such as ClassicAPI and
|
||||
AuctionQueryThrottle
|
||||
- `SuperWoW-LICENSE.txt` — preserved for the SuperWoW component and bundled fallback
|
||||
|
||||
See `../THIRD_PARTY_NOTICES.md` and `../Docs/BINARY_PROVENANCE.md` for the
|
||||
scope, distribution mode, and source of each component.
|
||||
@@ -0,0 +1,16 @@
|
||||
Copyright © 2024 Balake. All rights reserved.
|
||||
|
||||
This program is licensed for personal use only. Commercial
|
||||
use, distribution, modification, or any form of exploitation
|
||||
for profit is strictly prohibited without the express written
|
||||
permission of the copyright holder.
|
||||
|
||||
World of Warcarft is the intellectual property of Blizzard
|
||||
Entertainment, Inc. This mod is not affiliated with or
|
||||
endorsed by Blizzard Entertainment in any way.
|
||||
|
||||
This mod is created and distributed under the principles of
|
||||
fair use, including commentary, criticism, and transformative
|
||||
use for personal enjoyment and educational purposes.
|
||||
|
||||
This mod is provided "as is," without warranty of any kind.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Hannes Mann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Martin Pecánek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 brndd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Modernization Tool project identity
|
||||
|
||||
## Canonical project
|
||||
|
||||
The canonical repository designated by the project maintainer is:
|
||||
|
||||
- https://github.com/Dusk-92/Modernization-Tool
|
||||
|
||||
Mirrors, package caches, downstream forks, repackaged executables, and modified
|
||||
copies hosted elsewhere are independent unless the canonical project explicitly
|
||||
states otherwise.
|
||||
|
||||
## Independent community project
|
||||
|
||||
Modernization Tool is an independent community project. It is not affiliated
|
||||
with, sponsored by, approved by, or endorsed by Blizzard Entertainment.
|
||||
|
||||
Compatibility with World of Warcraft, Vanilla WoW 1.12-compatible clients,
|
||||
Turtle WoW-like environments, or any third-party plugin, addon, patcher, or
|
||||
community project does not imply affiliation, endorsement, partnership, or
|
||||
ownership.
|
||||
|
||||
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
|
||||
marks, artwork, audio, client files, and game assets remain the property of
|
||||
their respective rights holders.
|
||||
|
||||
## Third-party projects
|
||||
|
||||
Modernization Tool can download, install, configure, or in some cases bundle
|
||||
third-party components. Those projects remain independent from Modernization
|
||||
Tool and retain their own copyright, licensing, support, and distribution
|
||||
terms.
|
||||
|
||||
A source link, compatibility label, credit, installer entry, or bundled fallback
|
||||
does not imply that the upstream author endorses Modernization Tool.
|
||||
|
||||
See `THIRD_PARTY_NOTICES.md`, `Docs/BINARY_PROVENANCE.md`, and
|
||||
`LICENSES/` for the current provenance and redistribution record.
|
||||
|
||||
## Support boundary
|
||||
|
||||
Dusk-92 is responsible only for Modernization Tool releases published by the
|
||||
canonical repository. Upstream component authors are not responsible for this
|
||||
tool, its packaging decisions, or downstream modifications.
|
||||
|
||||
## Historical provenance
|
||||
|
||||
Do not erase historical attribution, source URLs, hashes, license notices, or
|
||||
provenance records when a component is updated or replaced. Update the records
|
||||
instead so the repository keeps a traceable history.
|
||||
+102
-11
@@ -140,17 +140,6 @@
|
||||
"source": "https://raw.githubusercontent.com/RetroCro/TurtleWoW-Mods/refs/heads/main/Archive/DLL%20BACKUP/perf_boost.dll",
|
||||
"version": "RetroCro main"
|
||||
},
|
||||
"SuperWoW": {
|
||||
"files": [
|
||||
{
|
||||
"path": "Payload/SuperWoWhook.dll",
|
||||
"sha256": "bd214b32c878649e94ce654835946bd05e0ce7710e8f01bae10d8ab50a89351d",
|
||||
"size": 1000448
|
||||
}
|
||||
],
|
||||
"source": "https://github.com/balakethelock/SuperWoW/releases/download/Release/SuperWoW.release.2.2.zip",
|
||||
"version": "Release"
|
||||
},
|
||||
"UnitXP_SP3": {
|
||||
"files": [
|
||||
{
|
||||
@@ -601,6 +590,108 @@
|
||||
"sha256": "7b0b0fcdb8bb9e82c5a18ab395af670f57da3e03035b27d5e244e4f8b457fdf2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"SuperWoW fallback": {
|
||||
"version": "2.2",
|
||||
"source": "https://github.com/balakethelock/SuperWoW/releases/download/Release/SuperWoW.release.2.2.zip",
|
||||
"superapi_source": "https://github.com/balakethelock/SuperAPI",
|
||||
"superapi_revision": "901322dc88890a2ea10610b8228fb43c9c2a3610",
|
||||
"superapi_tree_sha": "95bb25752b1f31f7d4ca8d5e416986b5e3031b33",
|
||||
"note": "Bundled fallback restored after redistribution permission was reported by the Modernization Tool maintainer on 2026-09-03.",
|
||||
"files": [
|
||||
{
|
||||
"path": "Payload/SuperWoWhook.dll",
|
||||
"sha256": "bd214b32c878649e94ce654835946bd05e0ce7710e8f01bae10d8ab50a89351d",
|
||||
"size": 1000448
|
||||
}
|
||||
],
|
||||
"superapi_files": [
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/README.md",
|
||||
"size": 1020,
|
||||
"git_blob_sha1": "9f38a6a316516de3614e7526b28823cc823a4406"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/SuperAPI.lua",
|
||||
"size": 6904,
|
||||
"git_blob_sha1": "0161abda07898691885a2612e881332d783e6345"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/SuperAPI.toc",
|
||||
"size": 741,
|
||||
"git_blob_sha1": "3204821da231172087cff33848924991ced8e9bc"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/SuperAPIOptions.lua",
|
||||
"size": 10908,
|
||||
"git_blob_sha1": "3fabab494f706814b5fa02c0e0e0d1b9221d52d0"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceAddon-2.0/AceAddon-2.0.lua",
|
||||
"size": 34783,
|
||||
"git_blob_sha1": "1c5e2d59c71e09ca05e5ebe4e7f03ddd3e417f65"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceConsole-2.0/AceConsole-2.0.lua",
|
||||
"size": 67656,
|
||||
"git_blob_sha1": "d4d2f2a43aca30bdf6eb7b8266a0802e6e1fe717"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceDB-2.0/AceDB-2.0.lua",
|
||||
"size": 45257,
|
||||
"git_blob_sha1": "a6bbd10a83bce8682f563c4d0315b246ec554b9b"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceDebug-2.0/AceDebug-2.0.lua",
|
||||
"size": 8049,
|
||||
"git_blob_sha1": "8683c8c76cf91f527fb5f815e762470da96d87ca"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceEvent-2.0/AceEvent-2.0.lua",
|
||||
"size": 28954,
|
||||
"git_blob_sha1": "c6545688770d0a5669ce3948bf649fd4fdb47694"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceHook-2.1/AceHook-2.1.lua",
|
||||
"size": 16946,
|
||||
"git_blob_sha1": "8beaf6607d905b494d9d1e01785156aca86c9f43"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceLibrary/AceLibrary.lua",
|
||||
"size": 23114,
|
||||
"git_blob_sha1": "0f2b2378859a0245d33daecf112cd409594ecb3e"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceLocale-2.2/AceLocale-2.2.lua",
|
||||
"size": 15957,
|
||||
"git_blob_sha1": "fbf9c87306fd7d40d53bd8290a320e73a25e1f12"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceModuleCore-2.0/AceModuleCore-2.0.lua",
|
||||
"size": 13981,
|
||||
"git_blob_sha1": "32c89fb8535f7badc20781178073fd1626f390ec"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/AceOO-2.0/AceOO-2.0.lua",
|
||||
"size": 30469,
|
||||
"git_blob_sha1": "cd372b49650b1943b0ac72f0793c9b22fc1213ff"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/Dewdrop/Dewdrop-2.0.lua",
|
||||
"size": 82495,
|
||||
"git_blob_sha1": "bc72ba607679866c860bcdbcd3000b7faf8df3a0"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/FuBarPlugin/FuBarPlugin-2.0.lua",
|
||||
"size": 44995,
|
||||
"git_blob_sha1": "292c8fcdfa4aa642a159c4a17abbc8d882c2a6af"
|
||||
},
|
||||
{
|
||||
"path": "Payload/Interface/Addons/SuperAPI/libs/Tablet/Tablet-2.0.lua",
|
||||
"size": 83748,
|
||||
"git_blob_sha1": "70f340d9172c30eb7a153d675ed69f1e1f07dbe4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-20
@@ -1,20 +1,20 @@
|
||||
# DXVK configuration for World of Warcraft
|
||||
|
||||
# Uncomment to set framerate limit
|
||||
# d3d9.maxFrameRate = 1000
|
||||
|
||||
# Uncomment to force borderless fullscreen
|
||||
# d3d9.enableDialogMode = True
|
||||
|
||||
# Enable GPL if supported to reduce stuttering (NVIDIA 473.33+, AMD 24.6.1+)
|
||||
dxvk.enableGraphicsPipelineLibrary = Auto
|
||||
# Track pipeline lifetimes to reduce memory usage
|
||||
dxvk.trackPipelineLifetime = True
|
||||
# Limit compiler threads to reduce memory usage
|
||||
dxvk.numCompilerThreads = 4
|
||||
|
||||
# Allow exclusive fullscreen mode on Windows
|
||||
dxvk.allowFse = True
|
||||
|
||||
# Disabled because VanillaFixes sets process DPI awareness
|
||||
d3d9.dpiAware = False
|
||||
# DXVK configuration for World of Warcraft
|
||||
|
||||
# Uncomment to set framerate limit
|
||||
# d3d9.maxFrameRate = 1000
|
||||
|
||||
# Uncomment to force borderless fullscreen
|
||||
# d3d9.enableDialogMode = True
|
||||
|
||||
# Enable GPL if supported to reduce stuttering (NVIDIA 473.33+, AMD 24.6.1+)
|
||||
dxvk.enableGraphicsPipelineLibrary = Auto
|
||||
# Track pipeline lifetimes to reduce memory usage
|
||||
dxvk.trackPipelineLifetime = True
|
||||
# Limit compiler threads to reduce memory usage
|
||||
dxvk.numCompilerThreads = 4
|
||||
|
||||
# Disable exclusive fullscreen on Windows to avoid black screens during Alt+Tab
|
||||
dxvk.allowFse = False
|
||||
|
||||
# Disabled because VanillaFixes sets process DPI awareness
|
||||
d3d9.dpiAware = False
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
# 🛠️ WoW Modernization Tool
|
||||
|
||||
[](https://github.com/Dusk-92/Modernization-Tool/actions/workflows/build.yml)
|
||||
[](https://github.com/Dusk-92/Modernization-Tool/releases/latest)
|
||||
|
||||
A simple all-in-one setup tool for **Vanilla WoW 1.12 compatible clients**.
|
||||
|
||||
Modernization Tool installs and configures client fixes, performance plugins, Vanilla Tweaks, DXVK and optional visual/audio improvements without requiring users to manually manage DLLs or configuration files.
|
||||
|
||||
> 💡 Hover over any setting inside the tool to see an explanation of what it does.
|
||||
|
||||
---
|
||||
|
||||
## ⬇️ Download
|
||||
|
||||
Download the latest version from:
|
||||
|
||||
**[GitHub Releases](https://github.com/Dusk-92/Modernization-Tool/releases/latest)**
|
||||
|
||||
No installation is required. Download and run:
|
||||
|
||||
`WoW_Modernization_Tool.exe`
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
1. Launch **WoW_Modernization_Tool.exe**
|
||||
2. Select your Vanilla WoW game folder.
|
||||
3. Choose your renderer, plugins and optional tweaks.
|
||||
4. Click **Apply Setup & Tweaks**.
|
||||
5. Launch the game using **Play Modernized WoW**.
|
||||
|
||||
The launcher shortcut is automatically created:
|
||||
|
||||
- 📁 inside your WoW folder
|
||||
- 🖥️ on your Windows Desktop
|
||||
|
||||
> ⚠️ Use **Play Modernized WoW** to launch the modernized client.
|
||||
|
||||
---
|
||||
|
||||
# 🎮 Rendering
|
||||
|
||||
Two rendering modes are available:
|
||||
|
||||
### [VanillaFixes](https://github.com/hannesmann/vanillafixes) — DirectX 9
|
||||
|
||||
The default option.
|
||||
|
||||
Uses WoW's native DirectX 9 renderer together with VanillaFixes.
|
||||
|
||||
### VanillaFixes + [DXVK](https://github.com/doitsujin/dxvk) — Vulkan
|
||||
|
||||
Uses DXVK to translate DirectX 9 to Vulkan.
|
||||
|
||||
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 & Sources
|
||||
|
||||
## Recommended Core
|
||||
|
||||
The tool can install and configure several recommended client extensions:
|
||||
|
||||
- **[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](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](https://github.com/tubtubs/vanilla-tweaks)** patcher.
|
||||
|
||||
Available settings include:
|
||||
|
||||
- Field of View
|
||||
- Render Distance
|
||||
- Ground Clutter Distance
|
||||
- Nameplate Distance
|
||||
- Camera Distance
|
||||
- Sound Channels
|
||||
- Always Auto-Loot
|
||||
- Background Sounds
|
||||
- Large Address Aware
|
||||
- Camera Skip Fix
|
||||
- DEP compatibility
|
||||
- Unlimited AddOn Script Memory
|
||||
- Cross-Faction Resurrection Fix
|
||||
- Custom Glues Patch
|
||||
- [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.
|
||||
|
||||
---
|
||||
|
||||
# 🌙 Visual Mods
|
||||
|
||||
Optional visual modifications are available directly from the tool:
|
||||
|
||||
- **[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**.
|
||||
|
||||
---
|
||||
|
||||
# 🔊 Audio Mods
|
||||
|
||||
Optional sound replacements include:
|
||||
|
||||
- **[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**.
|
||||
|
||||
---
|
||||
|
||||
# 🔄 Automatic Updates & Offline Fallbacks
|
||||
|
||||
Supported components are downloaded directly from their upstream projects when possible.
|
||||
|
||||
Modernization Tool is designed to avoid breaking an existing installation if an online source becomes unavailable.
|
||||
|
||||
Depending on the component, the tool can:
|
||||
|
||||
- keep an already installed valid version;
|
||||
- use a bundled known-good fallback where redistribution terms allow it;
|
||||
- avoid re-downloading files that are already current.
|
||||
|
||||
**SuperWoW and SuperAPI prefer their upstream sources:** SuperWoW is downloaded
|
||||
from its official stable release and SuperAPI follows the current upstream
|
||||
`master` revision. If either online source is unavailable, the tool can install
|
||||
the bundled known-good SuperWoW + SuperAPI fallback instead.
|
||||
|
||||
**WowPresence uses layered fallbacks.** A successful download refreshes a
|
||||
validated local cache, while an already tool-managed copy can seed that cache
|
||||
only when its recorded SHA-256 hashes still match. Unknown/manual binaries may
|
||||
be preserved when valid but are never promoted into the validated cache. Cache
|
||||
write failures never block a successful normal install. Release builds also
|
||||
include a verified known-good WowPresence fallback for first-time offline
|
||||
installation.
|
||||
|
||||
**Visual MPQ mods do not use offline fallbacks.** If their source is unavailable,
|
||||
the Tool keeps an already valid installed copy unchanged; a first-time install
|
||||
still requires access to the original source.
|
||||
|
||||
**No1600x1200 uses the bundled known-good copy** instead of following the
|
||||
RetroCro archive repository automatically.
|
||||
|
||||
Downloaded, cached and bundled DLLs are validated before installation.
|
||||
|
||||
---
|
||||
|
||||
# 💾 Safe Configuration Management
|
||||
|
||||
Modernization Tool remembers settings separately for each WoW installation.
|
||||
|
||||
It also:
|
||||
|
||||
- preserves manually added `dlls.txt` entries;
|
||||
- preserves comments and unknown DLL entries;
|
||||
- detects incompatible plugin combinations;
|
||||
- validates the selected Vanilla client before modifying it;
|
||||
- performs important file replacements transactionally;
|
||||
- protects unknown/custom renderer files;
|
||||
- safely manages WDB ownership.
|
||||
|
||||
---
|
||||
|
||||
# 🔐 AutoLogin
|
||||
|
||||
[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.
|
||||
|
||||
Password encryption is enabled by default when available.
|
||||
|
||||
Existing encryption keys are never replaced.
|
||||
|
||||
> 🔒 Your encryption key is stored in the Windows user environment and is not written to the WoW folder.
|
||||
|
||||
---
|
||||
|
||||
# 📝 Updating Modernization Tool
|
||||
|
||||
Updating from an older version does **not** require reinstalling WoW.
|
||||
|
||||
Simply:
|
||||
|
||||
1. Download the latest Modernization Tool release.
|
||||
2. Select your existing WoW folder.
|
||||
3. Review your settings.
|
||||
4. Click **Apply Setup & Tweaks** again.
|
||||
|
||||
Your per-installation settings will be restored automatically.
|
||||
|
||||
---
|
||||
|
||||
# 🛡️ Antivirus Notice
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
# 🔗 Useful Links
|
||||
|
||||
- **[Latest Release](https://github.com/Dusk-92/Modernization-Tool/releases/latest)**
|
||||
- **[OctoWoW Installation & Modernization Guide](https://octowow.st/forum/viewtopic.php?t=831)**
|
||||
|
||||
---
|
||||
|
||||
# ❤️ Credits
|
||||
|
||||
Modernization Tool builds on the work of the community projects and developers linked throughout this README.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Licensing & Provenance
|
||||
|
||||
Modernization Tool is an independent community project. Third-party components
|
||||
retain their own licenses, permissions, and trademarks.
|
||||
|
||||
For detailed redistribution and provenance information, see:
|
||||
|
||||
- [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
|
||||
- [PROJECT_IDENTITY.md](PROJECT_IDENTITY.md)
|
||||
- [Docs/BINARY_PROVENANCE.md](Docs/BINARY_PROVENANCE.md)
|
||||
- [Docs/ASSET_PROVENANCE.md](Docs/ASSET_PROVENANCE.md)
|
||||
- [LICENSES/](LICENSES/)
|
||||
|
||||
SuperWoW and SuperAPI prefer their upstream projects, with a bundled known-good
|
||||
pair available as an offline fallback. Other remote components use the fallback
|
||||
behavior described in **Automatic Updates & Offline Fallbacks** above.
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Modernization Tool is a community project and is not affiliated with or endorsed by Blizzard Entertainment.
|
||||
|
||||
World of Warcraft and Warcraft are trademarks of Blizzard Entertainment.
|
||||
+24
-98
@@ -1,109 +1,35 @@
|
||||
# 🛠️ WoW Modernization Tool v2.0
|
||||
# 🛠️ WoW Modernization Tool v2.3
|
||||
|
||||
This is a major update focused on **easier setup, safer updates, automatic component refreshes and new optional client improvements**.
|
||||
This update focuses on **better compatibility, safer WoW.exe patching, improved recovery, display-scaling support and DXVK configuration**.
|
||||
|
||||
Existing users do **not** need to reinstall WoW. Select your current game folder, review your options and click **Apply Setup & Tweaks**.
|
||||
## ⚙️ Vanilla Tweaks & Compatibility
|
||||
|
||||
## 🔄 Automatic Updates & Offline Protection
|
||||
- Reworked Vanilla Tweaks handling for better compatibility with **Vanilla, Turtle WoW, OctoWoW and compatible clients**.
|
||||
- `WoW_Modernized.exe` is now built and validated transactionally before replacing the previous version.
|
||||
- Vanilla Tweaks now runs before other installation changes.
|
||||
- Improved support for already-patched clients while preserving client-specific loader code.
|
||||
- Removed overly strict build/version checks that could reject compatible clients.
|
||||
- SuperWoW now uses the same FoV selected by the Modernization Tool by synchronizing the `FoV` CVar in `WTF/Config.wtf`.
|
||||
- Render Distance now keeps the executable Farclip ceiling fixed at **3000** while synchronizing the selected runtime value to `WTF/Config.wtf`, preventing stale higher Farclip values from conflicting with `WoW_Modernized.exe`.
|
||||
|
||||
Supported components can now be refreshed directly from their upstream releases when you apply the setup.
|
||||
## 🎨 MPQ & Recovery
|
||||
|
||||
If an online update is unavailable, the tool will keep a valid installed version or use a bundled known-good fallback when available. Existing working files are preserved whenever possible instead of being replaced unnecessarily.
|
||||
- Improved MPQ validation to reject corrupted or invalid archives.
|
||||
- Existing valid files can now be preserved when a remote source is temporarily unavailable.
|
||||
- Improved offline recovery for supported components.
|
||||
- Better handling of local installation and permission errors.
|
||||
|
||||
## 🎮 Rendering & DXVK
|
||||
## 🛡️ Reliability
|
||||
|
||||
Renderer selection is now simpler:
|
||||
- Added an optional **DXVK FPS limiter** that detects the highest refresh rate of attached displays, prefills that value and remains manually editable.
|
||||
- The DXVK FPS limiter is enabled by default for new configurations, persists per WoW installation and is disabled in DirectX 9 mode.
|
||||
- DXVK exclusive fullscreen is now disabled by default to avoid black screens during Alt+Tab on Windows.
|
||||
- The Tool window now adapts better to Windows display scaling and smaller screens, keeping **Apply Setup & Tweaks** accessible.
|
||||
- Expanded automated tests for installation ordering, recovery, executable patching and DXVK FPS configuration.
|
||||
- Improved rollback behavior to reduce the risk of partial installations.
|
||||
|
||||
- **VanillaFixes — DirectX 9** *(default)* — uses WoW's native DirectX 9 renderer.
|
||||
- **VanillaFixes + DXVK — Vulkan** — translates DirectX 9 to Vulkan and may provide smoother frame pacing on modern systems.
|
||||
## ✅ Updating from v2.2
|
||||
|
||||
The bundled DXVK version is now **2.6.1 x86** with a configuration aligned with VanillaFixes.
|
||||
|
||||
Switching back to DirectX 9 now safely moves unknown existing D3D9 wrapper files out of the WoW root instead of deleting them.
|
||||
|
||||
## 🧩 New & Updated Plugins
|
||||
|
||||
- **ClassicAPI** — adds newer WoW API functions so compatible modern addons can work on the Vanilla client.
|
||||
- **AuctionQueryThrottle** — removes the fixed Auction House query delay for much faster searches.
|
||||
- **VanillaMultiMonitorFix** — improves resolution, refresh-rate and monitor detection on multi-monitor setups.
|
||||
- **Interact** — adds a modern interaction key for nearby NPCs, objects, gathering nodes and loot.
|
||||
- **WorldMarkers 0.7.1** — updated optional world-marker support.
|
||||
- **WeirdPerformance 0.7.3** — updated engine/runtime optimizations for client smoothness and reduced overhead.
|
||||
|
||||
## ⚙️ New Client Tweaks
|
||||
|
||||
- **Unlimited AddOn Script Memory** — removes WoW's AddOn memory limit.
|
||||
- **Cross-Faction Resurrection Fix** — improves resurrection handling for released cross-faction players.
|
||||
- **Custom Glues Patch** — enables custom login and character-selection interface modifications.
|
||||
- **Automatically Clear WDB** — clears stale WDB cache data and safely manages the WDB blocker.
|
||||
- **SuperWoW conflict prevention** — equivalent vanilla-tweaks patches are automatically skipped when SuperWoW already handles FoV, Sound Channels, Auto-Loot or Background Sound.
|
||||
|
||||
## 🌙 New Visual Mods
|
||||
|
||||
All visual mods are optional and disabled by default.
|
||||
|
||||
- **Bluemoon Patch** — restores the rare blue moon visual effect.
|
||||
- **Darker Nights** — makes nighttime environments darker and more atmospheric.
|
||||
- **Pretty Night Sky** — replaces the Vanilla night sky with a more detailed starry sky.
|
||||
- **Epoch Water** — replaces the original Vanilla water textures.
|
||||
- **Fog Pushback** — pushes environmental fog farther away for clearer long-distance visibility.
|
||||
- **Pink Herbs** — makes herb nodes bright pink/purple so they are easier to spot.
|
||||
|
||||
## 🔊 New Audio Mods
|
||||
|
||||
All audio mods are optional and disabled by default.
|
||||
|
||||
- **NoErrorSounds** — removes many repetitive spell fizzle/error and interface sounds.
|
||||
- **FishPing** — replaces the fishing bite sound with a much easier-to-hear ping.
|
||||
- **Warlock Muted Demons** — mutes many repetitive Warlock demon voice lines.
|
||||
|
||||
## 🔐 AutoLogin Security Improvements
|
||||
|
||||
When **AutoLogin + Nampower** are enabled together, Modernization Tool now automatically creates or reuses the Windows user encryption key required by Nampower.
|
||||
|
||||
AutoLogin also enables password encryption by default when encryption is available. Existing user encryption keys are never replaced, and an explicit user choice to disable AutoLogin encryption is preserved.
|
||||
|
||||
## 💾 Installer & Safety Improvements
|
||||
|
||||
- Settings are remembered separately for each WoW installation.
|
||||
- Manually added entries and comments in `dlls.txt` are preserved.
|
||||
- Downloaded/bundled DLLs are validated before installation.
|
||||
- Important replacements are performed transactionally to reduce the risk of partial installs.
|
||||
- Existing valid files are kept when an upstream download fails.
|
||||
- Large visual mods are not re-downloaded when the installed revision is already current.
|
||||
- Legacy Modernization Tool files are cleaned up safely.
|
||||
- The selected game folder is validated before changes are made.
|
||||
|
||||
## 🚀 Launcher Improvements
|
||||
|
||||
After installation, **Play Modernized WoW** is created both:
|
||||
|
||||
- in the WoW game folder;
|
||||
- on the Windows Desktop.
|
||||
|
||||
Use either shortcut to launch the modernized client.
|
||||
|
||||
## 📦 Notable Bundled / Known-Good Versions
|
||||
|
||||
- VanillaFixes **1.5.3**
|
||||
- DXVK **2.6.1 x86**
|
||||
- Nampower **4.6.1**
|
||||
- ClassicAPI **1.12.7**
|
||||
- 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**
|
||||
|
||||
## ✅ Updating from v1.1
|
||||
|
||||
Download **WoW_Modernization_Tool.exe**, select your existing WoW installation and click **Apply Setup & Tweaks**.
|
||||
Download the new executable, select your existing WoW folder and click **Apply Setup & Tweaks**.
|
||||
|
||||
A complete WoW reinstall is not required.
|
||||
|
||||
---
|
||||
|
||||
The detailed change list since **v1.1** is generated automatically by GitHub below.
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# Modernization Tool third-party notices
|
||||
|
||||
Audit date: 2026-09-03
|
||||
|
||||
Modernization Tool combines an original installer/configuration layer with
|
||||
third-party client fixes, patchers, addons, DLLs, visual/audio modifications,
|
||||
and external download sources.
|
||||
|
||||
This document records the currently known licensing and distribution boundary.
|
||||
It does not replace upstream licenses or grant rights that the upstream authors
|
||||
did not grant.
|
||||
|
||||
## Project-owned material
|
||||
|
||||
Original Modernization Tool code and documentation authored by Dusk-92 are
|
||||
covered by the project notice in `LICENSE`.
|
||||
|
||||
Third-party material is excluded from that project notice and remains under its
|
||||
own terms.
|
||||
|
||||
### WowPresence
|
||||
|
||||
- Source: https://github.com/Dusk-92/WowPresence
|
||||
- Maintained by Dusk-92
|
||||
- Installed and updated from its GitHub Releases when Discord Rich Presence is selected
|
||||
- Release builds include a verified `WowPresence.zip` offline fallback prepared from the v1.3 release
|
||||
- The source repository does not store duplicate WowPresence binaries; the fallback is fetched and verified during the release build
|
||||
- User configuration under `.modernization_tool/WowPresence/` is preserved across binary updates
|
||||
- See the WowPresence repository and its `THIRD_PARTY_NOTICES.md` for component-specific provenance
|
||||
|
||||
## Bundled components with a verified redistribution license
|
||||
|
||||
The following bundled components have a license text that was located and
|
||||
preserved during this audit:
|
||||
|
||||
### VanillaFixes
|
||||
|
||||
- Source: https://github.com/hannesmann/vanillafixes
|
||||
- Bundled files include `Payload/VanillaFixes.exe` and
|
||||
`Payload/VfPatcher.dll`
|
||||
- License: MIT
|
||||
- Preserved as `LICENSES/VanillaFixes-MIT.txt`
|
||||
|
||||
### DXVK
|
||||
|
||||
- Source: https://github.com/doitsujin/dxvk
|
||||
- Bundled file: `Payload/DXVK_Standard/d3d9.dll`
|
||||
- License: zlib/libpng
|
||||
- Preserved as `LICENSES/DXVK-zlib.txt`
|
||||
|
||||
### vanilla-tweaks
|
||||
|
||||
- Source: https://github.com/tubtubs/vanilla-tweaks
|
||||
- Bundled fallback: `vanilla-tweaks.exe`
|
||||
- License: MIT
|
||||
- Preserved as `LICENSES/vanilla-tweaks-MIT.txt`
|
||||
|
||||
### Nampower
|
||||
|
||||
- Source: https://github.com/brues-code/nampower
|
||||
- Bundled fallback: `Payload/nampower.dll`
|
||||
- License: BSD-style two-clause redistribution terms
|
||||
- Preserved as `LICENSES/Nampower-BSD.txt`
|
||||
|
||||
### VanillaHelpers
|
||||
|
||||
- Source: https://github.com/isfir/VanillaHelpers
|
||||
- Bundled fallback: `Payload/VanillaHelpers.dll`
|
||||
- Upstream carries GNU GPL v3 and GNU LGPL v3 license documents
|
||||
- Preserved as `LICENSES/VanillaHelpers-GPL-3.0.txt` and
|
||||
`LICENSES/VanillaHelpers-LGPL-3.0.txt`
|
||||
|
||||
### VanillaMultiMonitorFix
|
||||
|
||||
- Source: https://github.com/Mates1500/VanillaMultiMonitorFix
|
||||
- Bundled offline fallback under `Payload/Fallback/VanillaMultiMonitorFix/`
|
||||
- License: MIT
|
||||
- Preserved as `LICENSES/VanillaMultiMonitorFix-MIT.txt`
|
||||
|
||||
## SuperWoW and SuperAPI
|
||||
|
||||
SuperWoW's upstream license requires the copyright holder's express permission
|
||||
for redistribution. The Modernization Tool maintainer reported receiving that
|
||||
redistribution permission from the upstream author on 2026-09-03.
|
||||
|
||||
Modernization Tool therefore keeps a known-good SuperWoW + SuperAPI fallback
|
||||
while continuing to prefer the official upstream sources whenever they are
|
||||
available.
|
||||
|
||||
- SuperWoW source: https://github.com/balakethelock/SuperWoW
|
||||
- License copy: `LICENSES/SuperWoW-LICENSE.txt`
|
||||
- Online mode: latest stable upstream release
|
||||
- Bundled fallback: `Payload/SuperWoWhook.dll`
|
||||
- SuperAPI source: https://github.com/balakethelock/SuperAPI
|
||||
- Online mode: current upstream `master` revision resolved to an exact commit
|
||||
- Bundled fallback: `Payload/Interface/Addons/SuperAPI/`
|
||||
|
||||
The exact bundled revision and binary hash are recorded in
|
||||
`Payload/Fallback/versions.json` and `Docs/BINARY_PROVENANCE.md`.
|
||||
|
||||
If the online update is unavailable, a complete valid existing installation is
|
||||
kept first. If repair or first-time installation is still required, the bundled
|
||||
known-good SuperWoW + SuperAPI pair can be installed atomically instead.
|
||||
|
||||
## Components whose redistribution license was not independently located
|
||||
|
||||
The following source projects or fallback sources did not expose a project-wide
|
||||
license file that could be independently verified during this audit, or their
|
||||
license status still needs a dedicated provenance review:
|
||||
|
||||
- UnitXP_SP3 — https://github.com/brues-code/UnitXP_SP3
|
||||
- Interact — https://github.com/lookino/Interact
|
||||
- RetroCro/TurtleWoW-Mods sources used for No1600x1200 and PerfBoost
|
||||
- FishPing — https://github.com/notsureawake/FishPing
|
||||
- NoErrorSounds — https://github.com/Macumbafeh/NoErrorSounds
|
||||
- Warlock Muted Demons — https://github.com/spzilyk/Warlock-Muted-Demons
|
||||
- Vanilla AutoLogin — https://github.com/MarcelineVQ/turtle-autologin
|
||||
- WeirdUtils releases referenced by the payload provenance manifest
|
||||
|
||||
Their source locations and hashes remain documented in
|
||||
`Payload/Fallback/versions.json` and/or `Docs/BINARY_PROVENANCE.md`.
|
||||
|
||||
The absence of a verified license in this audit must not be interpreted as a
|
||||
grant of redistribution rights. If explicit permission or an upstream license
|
||||
is later confirmed, preserve that evidence in `LICENSES/` and update this
|
||||
notice.
|
||||
|
||||
## ClassicAPI and AuctionQueryThrottle
|
||||
|
||||
Modernization Tool downloads current releases of ClassicAPI and
|
||||
AuctionQueryThrottle from their official upstream repositories and also
|
||||
currently retains known-good fallback binaries.
|
||||
|
||||
Both upstream repositories carry GNU GPL v3 license documents. Their licensing
|
||||
and source locations should be preserved alongside any redistributed fallback
|
||||
binaries. A copy of the GPL v3 text is stored in
|
||||
`LICENSES/GPL-3.0.txt`.
|
||||
|
||||
- ClassicAPI: https://github.com/brues-code/ClassicAPI
|
||||
- AuctionQueryThrottle: https://github.com/brues-code/AuctionQueryThrottle
|
||||
|
||||
## AutoLogin provenance
|
||||
|
||||
`Payload/Data/Interface/GlueXML/AutoLogin.xml` and `GlueXML.toc` are
|
||||
byte-identical at Git blob level to the corresponding files in
|
||||
`MarcelineVQ/turtle-autologin` at the time of this audit.
|
||||
|
||||
`AutoLogin.lua` is a modified variant of that upstream file and includes
|
||||
Modernization Tool-specific secure-default behavior. This is documented as
|
||||
adapted third-party material rather than claimed as wholly original.
|
||||
|
||||
## Visual and audio material
|
||||
|
||||
Visual/audio replacements and game-facing assets may have copyright or
|
||||
trademark considerations separate from software licenses.
|
||||
|
||||
See `Docs/ASSET_PROVENANCE.md` for project branding and game-facing asset
|
||||
notes. Source paths for bundled audio fallbacks remain recorded in
|
||||
`Payload/Fallback/versions.json`.
|
||||
|
||||
Visual MPQ mods such as Pink Herbs, Darker Nights, Pretty Night Sky, Epoch
|
||||
Water and Fog Pushback are not redistributed as offline fallbacks. Their
|
||||
original sources remain authoritative. If an already installed managed MPQ is
|
||||
still valid and its source is temporarily unavailable, the Tool preserves that
|
||||
installed copy unchanged.
|
||||
|
||||
## Blizzard / World of Warcraft
|
||||
|
||||
Modernization Tool is unofficial and independent.
|
||||
|
||||
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
|
||||
marks, artwork, audio, client files, and game assets remain the property of
|
||||
their respective rights holders. Nothing in this repository grants additional
|
||||
rights in those materials.
|
||||
|
||||
## Preservation rule
|
||||
|
||||
When a bundled component is updated, replaced, or moved to upstream-only
|
||||
distribution:
|
||||
|
||||
1. keep its source URL and version;
|
||||
2. keep or update the applicable license/permission record;
|
||||
3. preserve hashes for redistributed binaries where practical;
|
||||
4. do not remove historical attribution simply because the file is no longer
|
||||
bundled.
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
DEFAULT_REFRESH_RATE = 60
|
||||
_MAX_FRAME_RATE_RE = re.compile(
|
||||
r"^\s*(?P<comment>#\s*)?d3d9\.maxFrameRate\s*=\s*(?P<value>[+-]?\d+)\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def detect_max_refresh_rate(default=DEFAULT_REFRESH_RATE):
|
||||
"""Return the highest refresh rate reported by active Windows displays."""
|
||||
try:
|
||||
fallback = max(1, int(default))
|
||||
except (TypeError, ValueError):
|
||||
fallback = DEFAULT_REFRESH_RATE
|
||||
|
||||
if os.name != "nt":
|
||||
return fallback
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
class DISPLAY_DEVICEW(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("cb", wintypes.DWORD),
|
||||
("DeviceName", wintypes.WCHAR * 32),
|
||||
("DeviceString", wintypes.WCHAR * 128),
|
||||
("StateFlags", wintypes.DWORD),
|
||||
("DeviceID", wintypes.WCHAR * 128),
|
||||
("DeviceKey", wintypes.WCHAR * 128),
|
||||
]
|
||||
|
||||
class POINTL(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("x", wintypes.LONG),
|
||||
("y", wintypes.LONG),
|
||||
]
|
||||
|
||||
class DEVMODEW(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dmDeviceName", wintypes.WCHAR * 32),
|
||||
("dmSpecVersion", wintypes.WORD),
|
||||
("dmDriverVersion", wintypes.WORD),
|
||||
("dmSize", wintypes.WORD),
|
||||
("dmDriverExtra", wintypes.WORD),
|
||||
("dmFields", wintypes.DWORD),
|
||||
("dmPosition", POINTL),
|
||||
("dmDisplayOrientation", wintypes.DWORD),
|
||||
("dmDisplayFixedOutput", wintypes.DWORD),
|
||||
("dmColor", wintypes.SHORT),
|
||||
("dmDuplex", wintypes.SHORT),
|
||||
("dmYResolution", wintypes.SHORT),
|
||||
("dmTTOption", wintypes.SHORT),
|
||||
("dmCollate", wintypes.SHORT),
|
||||
("dmFormName", wintypes.WCHAR * 32),
|
||||
("dmLogPixels", wintypes.WORD),
|
||||
("dmBitsPerPel", wintypes.DWORD),
|
||||
("dmPelsWidth", wintypes.DWORD),
|
||||
("dmPelsHeight", wintypes.DWORD),
|
||||
("dmDisplayFlags", wintypes.DWORD),
|
||||
("dmDisplayFrequency", wintypes.DWORD),
|
||||
("dmICMMethod", wintypes.DWORD),
|
||||
("dmICMIntent", wintypes.DWORD),
|
||||
("dmMediaType", wintypes.DWORD),
|
||||
("dmDitherType", wintypes.DWORD),
|
||||
("dmReserved1", wintypes.DWORD),
|
||||
("dmReserved2", wintypes.DWORD),
|
||||
("dmPanningWidth", wintypes.DWORD),
|
||||
("dmPanningHeight", wintypes.DWORD),
|
||||
]
|
||||
|
||||
enum_display_devices = ctypes.windll.user32.EnumDisplayDevicesW
|
||||
enum_display_settings = ctypes.windll.user32.EnumDisplaySettingsW
|
||||
enum_display_devices.argtypes = [
|
||||
wintypes.LPCWSTR,
|
||||
wintypes.DWORD,
|
||||
ctypes.POINTER(DISPLAY_DEVICEW),
|
||||
wintypes.DWORD,
|
||||
]
|
||||
enum_display_devices.restype = wintypes.BOOL
|
||||
enum_display_settings.argtypes = [
|
||||
wintypes.LPCWSTR,
|
||||
wintypes.DWORD,
|
||||
ctypes.POINTER(DEVMODEW),
|
||||
]
|
||||
enum_display_settings.restype = wintypes.BOOL
|
||||
|
||||
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP = 0x00000001
|
||||
ENUM_CURRENT_SETTINGS = 0xFFFFFFFF
|
||||
rates = []
|
||||
index = 0
|
||||
|
||||
while True:
|
||||
device = DISPLAY_DEVICEW()
|
||||
device.cb = ctypes.sizeof(DISPLAY_DEVICEW)
|
||||
if not enum_display_devices(None, index, ctypes.byref(device), 0):
|
||||
break
|
||||
index += 1
|
||||
|
||||
if not (device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP):
|
||||
continue
|
||||
|
||||
mode = DEVMODEW()
|
||||
mode.dmSize = ctypes.sizeof(DEVMODEW)
|
||||
if not enum_display_settings(
|
||||
device.DeviceName,
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
ctypes.byref(mode),
|
||||
):
|
||||
continue
|
||||
|
||||
refresh_rate = int(mode.dmDisplayFrequency)
|
||||
# Windows may report 0/1 when the rate is unknown/default.
|
||||
if 1 < refresh_rate < 10000:
|
||||
rates.append(refresh_rate)
|
||||
|
||||
if rates:
|
||||
return max(rates)
|
||||
except (AttributeError, OSError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
return fallback
|
||||
|
||||
|
||||
def read_dxvk_fps_limit(config_path):
|
||||
"""Return (enabled, fps) for the first positive DXVK maxFrameRate line, or None."""
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8", errors="ignore") as handle:
|
||||
lines = handle.read().splitlines()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
for line in lines:
|
||||
match = _MAX_FRAME_RATE_RE.match(line)
|
||||
if match is None:
|
||||
continue
|
||||
try:
|
||||
value = int(match.group("value"))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if value <= 0:
|
||||
continue
|
||||
return match.group("comment") is None, value
|
||||
return None
|
||||
|
||||
|
||||
def apply_dxvk_fps_limit(config_path, enabled, fps):
|
||||
"""Atomically set or comment DXVK's d3d9.maxFrameRate option."""
|
||||
try:
|
||||
value = int(fps)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError("DXVK FPS limit must be a positive integer.") from exc
|
||||
if value <= 0:
|
||||
raise ValueError("DXVK FPS limit must be a positive integer.")
|
||||
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8", errors="ignore") as handle:
|
||||
existing = handle.read()
|
||||
except OSError as exc:
|
||||
raise RuntimeError(f"Could not read dxvk.conf: {exc}") from exc
|
||||
|
||||
setting = (
|
||||
f"d3d9.maxFrameRate = {value}"
|
||||
if enabled
|
||||
else f"# d3d9.maxFrameRate = {value}"
|
||||
)
|
||||
|
||||
output = []
|
||||
replaced = False
|
||||
for line in existing.splitlines():
|
||||
if _MAX_FRAME_RATE_RE.match(line):
|
||||
if not replaced:
|
||||
output.append(setting)
|
||||
replaced = True
|
||||
# Drop duplicate maxFrameRate lines, including legacy signed values,
|
||||
# so only the user's selected setting can win.
|
||||
continue
|
||||
output.append(line)
|
||||
|
||||
if not replaced:
|
||||
if output and output[-1] != "":
|
||||
output.append("")
|
||||
output.append(setting)
|
||||
|
||||
updated = "\n".join(output) + "\n"
|
||||
staged = config_path + ".modernization-new"
|
||||
try:
|
||||
with open(staged, "w", encoding="utf-8", newline="\n") as handle:
|
||||
handle.write(updated)
|
||||
os.replace(staged, config_path)
|
||||
except OSError as exc:
|
||||
raise RuntimeError(f"Could not update dxvk.conf: {exc}") from exc
|
||||
finally:
|
||||
if os.path.exists(staged):
|
||||
try:
|
||||
os.remove(staged)
|
||||
except OSError:
|
||||
pass
|
||||
+1584
-47
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
OUTPUT_ROOT = os.path.join(ROOT, "Payload", "Fallback", "Remote")
|
||||
MANIFEST_PATH = os.path.join(ROOT, "Payload", "Fallback", "remote_fallbacks.json")
|
||||
USER_AGENT = "Modernization-Tool build fallback fetcher"
|
||||
|
||||
|
||||
FALLBACKS = [
|
||||
{
|
||||
"id": "wowpresence",
|
||||
"filename": "WowPresence.zip",
|
||||
"revision": "v1.3",
|
||||
"url": "https://github.com/Dusk-92/WowPresence/releases/download/v1.3/WowPresence.zip",
|
||||
"kind": "wowpresence_zip",
|
||||
"size": 141423,
|
||||
"sha256": "9b2dca9c69b5ab3b6dae3ef3b3efc9671779a227802ebb6ac98cb22bc6ea448a",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
||||
def _sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with open(path, "rb") as handle:
|
||||
while True:
|
||||
chunk = handle.read(1024 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest().lower()
|
||||
|
||||
|
||||
def _download(url, destination, retries=3):
|
||||
os.makedirs(os.path.dirname(destination), exist_ok=True)
|
||||
request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||||
last_error = None
|
||||
for attempt in range(1, retries + 1):
|
||||
temp_path = destination + ".new"
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=300) as response, open(temp_path, "wb") as out:
|
||||
while True:
|
||||
chunk = response.read(1024 * 1024)
|
||||
if not chunk:
|
||||
break
|
||||
out.write(chunk)
|
||||
os.replace(temp_path, destination)
|
||||
return
|
||||
except (OSError, urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as exc:
|
||||
last_error = exc
|
||||
try:
|
||||
os.remove(temp_path)
|
||||
except OSError:
|
||||
pass
|
||||
if attempt < retries:
|
||||
time.sleep(attempt * 2)
|
||||
raise RuntimeError(f"Could not download {url}: {last_error}")
|
||||
|
||||
|
||||
def _verify_x86_pe_bytes(data, label):
|
||||
if len(data) < 1024 or data[:2] != b"MZ":
|
||||
raise RuntimeError(f"{label} is not a valid PE file")
|
||||
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
if pe_offset < 64 or pe_offset > len(data) - 26:
|
||||
raise RuntimeError(f"{label} has an invalid PE offset")
|
||||
if data[pe_offset:pe_offset + 4] != b"PE\0\0":
|
||||
raise RuntimeError(f"{label} is missing the PE signature")
|
||||
machine = struct.unpack_from("<H", data, pe_offset + 4)[0]
|
||||
optional_size = struct.unpack_from("<H", data, pe_offset + 20)[0]
|
||||
optional_offset = pe_offset + 24
|
||||
if optional_size < 2 or optional_offset + 2 > len(data):
|
||||
raise RuntimeError(f"{label} has an invalid optional header")
|
||||
optional_magic = struct.unpack_from("<H", data, optional_offset)[0]
|
||||
if machine != 0x014C or optional_magic != 0x010B:
|
||||
raise RuntimeError(f"{label} is not a 32-bit x86 PE file")
|
||||
|
||||
|
||||
def _verify_wowpresence_zip(path):
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
entries = {name.rsplit("/", 1)[-1].lower(): name for name in archive.namelist()}
|
||||
for filename in ("WowPresence.dll", "WowPresence.exe"):
|
||||
member = entries.get(filename.lower())
|
||||
if member is None:
|
||||
raise RuntimeError(f"{filename} is missing from {path}")
|
||||
_verify_x86_pe_bytes(archive.read(member), filename)
|
||||
|
||||
|
||||
def main():
|
||||
shutil.rmtree(OUTPUT_ROOT, ignore_errors=True)
|
||||
os.makedirs(OUTPUT_ROOT, exist_ok=True)
|
||||
|
||||
manifest = {
|
||||
"schema": 1,
|
||||
"generated_for": "Modernization Tool release build",
|
||||
"fallbacks": {},
|
||||
}
|
||||
|
||||
for item in FALLBACKS:
|
||||
destination = os.path.join(OUTPUT_ROOT, item["id"], item["filename"])
|
||||
print(f"Preparing bundled fallback: {item['id']}", flush=True)
|
||||
_download(item["url"], destination)
|
||||
|
||||
if item["kind"] == "wowpresence_zip":
|
||||
_verify_wowpresence_zip(destination)
|
||||
else:
|
||||
raise RuntimeError(f"Unknown fallback kind: {item['kind']}")
|
||||
|
||||
digest = _sha256(destination)
|
||||
expected = item.get("sha256")
|
||||
if expected and digest != expected.lower():
|
||||
raise RuntimeError(
|
||||
f"SHA-256 mismatch for {item['id']}: expected {expected}, got {digest}"
|
||||
)
|
||||
|
||||
size = os.path.getsize(destination)
|
||||
expected_size = item.get("size")
|
||||
if expected_size is not None and size != expected_size:
|
||||
raise RuntimeError(
|
||||
f"Size mismatch for {item['id']}: expected {expected_size}, got {size}"
|
||||
)
|
||||
|
||||
manifest["fallbacks"][item["id"]] = {
|
||||
"filename": item["filename"],
|
||||
"kind": item["kind"],
|
||||
"revision": item["revision"],
|
||||
"source": item["url"],
|
||||
"size": size,
|
||||
"sha256": digest,
|
||||
}
|
||||
print(
|
||||
f"FALLBACK {item['id']} size={size} sha256={digest}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
staged = MANIFEST_PATH + ".new"
|
||||
with open(staged, "w", encoding="utf-8", newline="\n") as handle:
|
||||
json.dump(manifest, handle, indent=2, sort_keys=True)
|
||||
handle.write("\n")
|
||||
os.replace(staged, MANIFEST_PATH)
|
||||
print(f"Wrote {MANIFEST_PATH}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+149
-129
@@ -107,10 +107,11 @@ class WowSetupTool:
|
||||
"minimapicons.dll": "Adds TBC/WotLK-style minimap tracking icons for NPCs and objects, combined into a new native tracking dropdown.",
|
||||
"pngscreenshots.dll": "Saves screenshots as compressed PNG files on a background thread to completely eliminate frame drops when taking pictures.",
|
||||
"worldmarkers.dll": "Place up to 5 animated colored markers (Cataclysm style) in the world for raid positioning. Syncs automatically with other users.",
|
||||
"WowPresence.dll": "Shows your WoW character activity on Discord using WowPresence by Dusk-92. Modernization Tool downloads and updates WowPresence from its GitHub releases, stores its configuration and status under .modernization_tool\\WowPresence, and preconfigures the OctoWoW Discord Application ID. Advanced users can change .modernization_tool\\WowPresence\\discord_application_id for another Discord application. If Discord runs as administrator, WoW must also run as administrator for Rich Presence to work.",
|
||||
|
||||
# Tweaks Tab
|
||||
"fov": "Calculates horizontal Field of View mathematically scaled to maintain vertical aspect space based on your screen ratio.",
|
||||
"farclip": "Increases the maximum terrain render distance. Vanilla default is 777. Tweaks default is 1500.",
|
||||
"farclip": "Sets the active terrain render distance used by the game. Vanilla default is 777. The Tool keeps the executable Farclip ceiling fixed at 3000.",
|
||||
"frill": "Changes the ground clutter (grass) render distance. Vanilla default is 70. Tweaks default is 300.",
|
||||
"nameplate": "Increases the distance at which enemy nameplates become visible. Vanilla default is 20. Tweaks default is 41.",
|
||||
"cam": "Increases the maximum camera zoom-out distance. Vanilla default is 50. Max safe limit is 100.",
|
||||
@@ -129,7 +130,7 @@ class WowSetupTool:
|
||||
"pretty_night_sky": "Replaces the Vanilla night sky with a more detailed starry sky. Installed file: Data\\patch-Z.mpq. The original hosted patch-9 name is deliberately changed so the official numeric patch-9.mpq is never overwritten.",
|
||||
"epoch_water": "Replaces Vanilla water textures with the Epoch Water visual pack. Installed file: Data\\patch-W.mpq.",
|
||||
"fog_pushback": "Pushes environmental fog farther back for a clearer long-distance view. Installed file: Data\\patch-Y.mpq. Works best together with an increased Farclip value.",
|
||||
"pink_herbs": "Turns most herb-node textures bright pink/purple to make gathering nodes easier to spot. Installed file: Data\\patch-H.mpq.",
|
||||
"pink_herbs": "Turns most herb-node textures bright pink/purple to make gathering nodes easier to spot. Installed file: Data\\patch-V.mpq. The patch-V name avoids conflicts with other visual packs that use patch-H.mpq.",
|
||||
"no_error_sounds": "Installs the complete NoErrorSounds pack: muted spell fizzle sounds plus its included muted interface sounds. Installed as loose WAV files under Sound\\Spells\\Fizzle and Sound\\interface (no MPQ).",
|
||||
"fish_ping": "Replaces the fishing bite sound with a much more noticeable ping. Installed file: Sound\\Spells\\Tradeskills\\FishBite.wav (no MPQ). Designed specifically for WoW Vanilla 1.12.1.",
|
||||
"warlock_muted_demons": "Mutes the repeated voice lines from Warlock demons using Vanilla-compatible loose sound replacements. Installed as loose WAV files under Data\\Sound\\Creature (no MPQ)."
|
||||
@@ -172,7 +173,8 @@ class WowSetupTool:
|
||||
"logsessions.dll": tk.BooleanVar(value=False),
|
||||
"minimapicons.dll": tk.BooleanVar(value=False),
|
||||
"pngscreenshots.dll": tk.BooleanVar(value=False),
|
||||
"worldmarkers.dll": tk.BooleanVar(value=False)
|
||||
"worldmarkers.dll": tk.BooleanVar(value=False),
|
||||
"WowPresence.dll": tk.BooleanVar(value=False)
|
||||
}
|
||||
|
||||
self.addon_dependencies = {
|
||||
@@ -525,6 +527,7 @@ class WowSetupTool:
|
||||
"Disable one of them before applying the setup.",
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def load_settings(self, target_dir):
|
||||
@@ -541,7 +544,6 @@ class WowSetupTool:
|
||||
self._load_legacy_install_state(target_dir)
|
||||
self._normalize_plugin_conflicts()
|
||||
self.toggle_safety_limits()
|
||||
self.update_superwow_managed_controls()
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -559,7 +561,6 @@ class WowSetupTool:
|
||||
|
||||
self._normalize_plugin_conflicts()
|
||||
self.toggle_safety_limits()
|
||||
self.update_superwow_managed_controls()
|
||||
|
||||
if recovered_from_damage:
|
||||
messagebox.showwarning(
|
||||
@@ -716,58 +717,7 @@ class WowSetupTool:
|
||||
cb.pack(anchor='w', padx=10, pady=4)
|
||||
ToolTip(cb, self.descriptions.get(dll, ""))
|
||||
|
||||
def _superwow_enabled(self):
|
||||
var = self.core_plugins.get("SuperWoWhook.dll")
|
||||
return bool(var is not None and var.get())
|
||||
|
||||
def update_superwow_managed_controls(self):
|
||||
"""Disable vanilla-tweaks controls that SuperWoW already handles."""
|
||||
active = self._superwow_enabled()
|
||||
|
||||
if hasattr(self, "superwow_notice"):
|
||||
if active:
|
||||
self.superwow_notice.configure(
|
||||
text="✓ SuperWoW enabled — FoV, Sound Channels, Auto-loot and Background sounds are handled by SuperWoW. Their vanilla-tweaks patches are skipped.",
|
||||
background="#EAF4FF",
|
||||
foreground="#005A9E",
|
||||
)
|
||||
else:
|
||||
self.superwow_notice.configure(
|
||||
text="SuperWoW disabled — FoV, Sound Channels, Auto-loot and Background sounds are controlled by vanilla-tweaks.",
|
||||
background="#F4F4F4",
|
||||
foreground="#444444",
|
||||
)
|
||||
|
||||
if hasattr(self, "fov_ratio_combo"):
|
||||
self.fov_ratio_combo.configure(state="disabled" if active else "readonly")
|
||||
if hasattr(self, "fov_entry"):
|
||||
self.fov_entry.configure(state="disabled" if active else "normal")
|
||||
if hasattr(self, "sound_scale"):
|
||||
self.sound_scale.configure(state="disabled" if active else "normal")
|
||||
if hasattr(self, "sound_entry"):
|
||||
self.sound_entry.configure(state="disabled" if active else "normal")
|
||||
if hasattr(self, "cb_loot"):
|
||||
self.cb_loot.configure(state="disabled" if active else "normal")
|
||||
if hasattr(self, "cb_bg"):
|
||||
self.cb_bg.configure(state="disabled" if active else "normal")
|
||||
|
||||
def build_tweaks_tab(self, parent):
|
||||
self.superwow_notice = tk.Label(
|
||||
parent,
|
||||
text="",
|
||||
background="#EAF4FF",
|
||||
foreground="#005A9E",
|
||||
font=("Segoe UI", 9, "bold"),
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
padx=8,
|
||||
pady=4,
|
||||
anchor="w",
|
||||
justify="left",
|
||||
wraplength=620,
|
||||
)
|
||||
self.superwow_notice.pack(fill="x", padx=10, pady=(5, 2))
|
||||
|
||||
fov_frame = ttk.LabelFrame(parent, text="Field of View (FoV) Calculator")
|
||||
fov_frame.pack(fill='x', padx=10, pady=5)
|
||||
|
||||
@@ -799,7 +749,7 @@ class WowSetupTool:
|
||||
frame_nums.pack(fill='x', padx=15, pady=0)
|
||||
frame_nums.columnconfigure(1, weight=1)
|
||||
|
||||
self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 10000, "farclip")
|
||||
self.create_slider_row(frame_nums, 0, "Render distance (Farclip) [Safe Max: 1500]:", self.vt_farclip, 777, 1500, 3000, "farclip")
|
||||
self.create_slider_row(frame_nums, 1, "Ground clutter (Frilldistance) [Safe Max: 300]:", self.vt_frill, 70, 300, 1000, "frill")
|
||||
self.create_slider_row(frame_nums, 2, "Nameplate range [Safe Max: 41]:", self.vt_nameplate, 20, 41, 150, "nameplate")
|
||||
self.create_slider_row(frame_nums, 3, "Camera distance [Safe Max: 100]:", self.vt_maxcam, 50, 100, 250, "cam")
|
||||
@@ -865,8 +815,6 @@ class WowSetupTool:
|
||||
cb_clear_wdb.grid(row=4, column=0, sticky='w', padx=10, pady=2)
|
||||
ToolTip(cb_clear_wdb, self.descriptions["clear_wdb"])
|
||||
|
||||
self.update_superwow_managed_controls()
|
||||
|
||||
|
||||
def build_visual_audio_tab(self, parent):
|
||||
ttk.Label(
|
||||
@@ -1082,6 +1030,10 @@ class WowSetupTool:
|
||||
"WOW_ENCRYPTION_KEY automatically; existing keys are never replaced)"
|
||||
)
|
||||
|
||||
text_area.insert("end", "\n• WowPresence: ", "bold")
|
||||
text_area.insert("end", "by Dusk-92 | ")
|
||||
insert_link("Source Repository", "https://github.com/Dusk-92/WowPresence")
|
||||
|
||||
# WeirdUtils
|
||||
text_area.insert("end", "\n\nWeirdUtils Suite\n", "header")
|
||||
text_area.insert(
|
||||
@@ -1302,10 +1254,45 @@ class WowSetupTool:
|
||||
# 3. Clean unselected Optional plugins.
|
||||
for dll_name, var in self.optional_plugins.items():
|
||||
if not var.get():
|
||||
remove_managed_file(
|
||||
os.path.join(target, dll_name),
|
||||
f"managed plugin {dll_name}",
|
||||
)
|
||||
if dll_name == "WowPresence.dll":
|
||||
# Only undo a WowPresence installation that this tool
|
||||
# actually owns. A standalone/manual WowPresence install
|
||||
# must survive an Apply with this option unchecked.
|
||||
if remote_packages.managed_mod_has_manifest(
|
||||
target,
|
||||
remote_packages.WOWPRESENCE_MANAGED_ID,
|
||||
):
|
||||
preexisting_entry = remote_packages.managed_mod_manifest_value(
|
||||
target,
|
||||
remote_packages.WOWPRESENCE_MANAGED_ID,
|
||||
"dlls_entry_preexisting",
|
||||
None,
|
||||
)
|
||||
if not isinstance(preexisting_entry, bool):
|
||||
# Conservative migration for early test manifests:
|
||||
# a saved DLL backup means WowPresence predated the
|
||||
# tool, so preserve its dlls.txt entry.
|
||||
preexisting_entry = remote_packages._managed_backup_exists(
|
||||
target,
|
||||
remote_packages.WOWPRESENCE_MANAGED_ID,
|
||||
"WowPresence.dll",
|
||||
)
|
||||
|
||||
if not preexisting_entry:
|
||||
self._remove_dlls_entry(target, "WowPresence.dll")
|
||||
|
||||
# This restores any backed-up manual binaries, or
|
||||
# removes only the copies installed by the tool.
|
||||
remote_packages.remove_managed_mod(
|
||||
target,
|
||||
remote_packages.WOWPRESENCE_MANAGED_ID,
|
||||
)
|
||||
# Keep .modernization_tool\WowPresence user configuration.
|
||||
else:
|
||||
remove_managed_file(
|
||||
os.path.join(target, dll_name),
|
||||
f"managed plugin {dll_name}",
|
||||
)
|
||||
|
||||
|
||||
@staticmethod
|
||||
@@ -1677,31 +1664,31 @@ class WowSetupTool:
|
||||
),
|
||||
]
|
||||
|
||||
# MPQ fallbacks were intentionally removed. Clean caches created by
|
||||
# earlier test builds so they cannot be reused later.
|
||||
for _, managed_id, _, _ in visual_defs:
|
||||
remote_packages.remove_package_cache(target, managed_id)
|
||||
|
||||
try:
|
||||
# Large visual MPQs deliberately stay online-only, but they are not
|
||||
# downloaded again on every Apply. A revision bump in remote_packages
|
||||
# is enough to trigger one fresh download when a source/version changes.
|
||||
# Visual installers own their current-version checks.
|
||||
# Already valid MPQs are reused without a download.
|
||||
for key, managed_id, display_name, installer in visual_defs:
|
||||
if self.visual_mods[key].get():
|
||||
revision = remote_packages.VISUAL_MOD_REVISIONS[managed_id]
|
||||
if remote_packages.managed_mpq_is_current(
|
||||
target,
|
||||
managed_id,
|
||||
revision,
|
||||
):
|
||||
continue
|
||||
|
||||
try:
|
||||
installer(target, progress=progress)
|
||||
except Exception as exc:
|
||||
if remote_packages.managed_mod_is_installed(target, managed_id):
|
||||
except remote_packages.RemoteSourceUnavailable as exc:
|
||||
if remote_packages.managed_mpq_is_usable(target, managed_id):
|
||||
warnings.append(
|
||||
f"{display_name}: update source unavailable; existing installed copy kept."
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"{display_name} installation failed and no bundled MPQ backup is provided:\n{exc}"
|
||||
f"{display_name} source is unavailable and no valid installed copy can be kept:\n{exc}"
|
||||
) from exc
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
f"{display_name} installation failed locally:\n{exc}"
|
||||
) from exc
|
||||
else:
|
||||
remote_packages.remove_managed_mod(target, managed_id)
|
||||
|
||||
@@ -1792,12 +1779,12 @@ class WowSetupTool:
|
||||
escaped_script = script_path.replace("'", "''")
|
||||
launcher = (
|
||||
"$ErrorActionPreference='Stop'; "
|
||||
f"$scriptPath = '{escaped_script}'; "
|
||||
"$argumentLine = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File \"' "
|
||||
"+ $scriptPath + '\"'; "
|
||||
"try { "
|
||||
"$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs "
|
||||
"-Wait -PassThru -ArgumentList @("
|
||||
"'-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass',"
|
||||
f"'-File','{escaped_script}'"
|
||||
"); "
|
||||
"-Wait -PassThru -ArgumentList $argumentLine; "
|
||||
"exit $p.ExitCode "
|
||||
"} catch { Write-Error $_; exit 1 }"
|
||||
)
|
||||
@@ -2021,6 +2008,12 @@ class WowSetupTool:
|
||||
vanilla_fixes_exe = os.path.join(target_dir, "VanillaFixes.exe")
|
||||
modernized_exe = os.path.join(target_dir, "WoW_Modernized.exe")
|
||||
|
||||
# Keep the launcher path unchanged. When enabled, WowPresence.dll
|
||||
# starts WowPresence.exe from its worker thread after WoW is running.
|
||||
launcher_exe = vanilla_fixes_exe
|
||||
launcher_arguments = "WoW_Modernized.exe"
|
||||
launcher_description = "Launch Vanilla WoW with VanillaFixes and Tweaks"
|
||||
|
||||
if not os.path.isfile(vanilla_fixes_exe):
|
||||
raise RuntimeError(
|
||||
"VanillaFixes.exe is missing, so the launcher shortcuts cannot be created."
|
||||
@@ -2056,10 +2049,10 @@ class WowSetupTool:
|
||||
Set oWS = WScript.CreateObject("WScript.Shell")
|
||||
sLinkFile = "{vbs_escape(staged_shortcut)}"
|
||||
Set oLink = oWS.CreateShortcut(sLinkFile)
|
||||
oLink.TargetPath = "{vbs_escape(vanilla_fixes_exe)}"
|
||||
oLink.Arguments = "WoW_Modernized.exe"
|
||||
oLink.TargetPath = "{vbs_escape(launcher_exe)}"
|
||||
oLink.Arguments = "{vbs_escape(launcher_arguments)}"
|
||||
oLink.WorkingDirectory = "{vbs_escape(target_dir)}"
|
||||
oLink.Description = "Launch Vanilla WoW with VanillaFixes and Tweaks"
|
||||
oLink.Description = "{vbs_escape(launcher_description)}"
|
||||
{icon_vbs_line}
|
||||
oLink.Save
|
||||
WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
@@ -2292,11 +2285,23 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
self._park_external_renderer_file(target, filename)
|
||||
|
||||
|
||||
def _managed_dll_entries(self):
|
||||
"""Return every dlls.txt entry owned by this tool."""
|
||||
def _managed_dll_entries(self, target=None):
|
||||
"""Return dlls.txt entries this tool is allowed to rewrite."""
|
||||
managed = {"dxvk"}
|
||||
managed.update(name.casefold() for name in self.core_plugins)
|
||||
managed.update(name.casefold() for name in self.optional_plugins)
|
||||
|
||||
for name, var in self.optional_plugins.items():
|
||||
if name.casefold() == "wowpresence.dll" and target is not None:
|
||||
selected = bool(var.get())
|
||||
owned = remote_packages.managed_mod_has_manifest(
|
||||
target,
|
||||
remote_packages.WOWPRESENCE_MANAGED_ID,
|
||||
)
|
||||
if not selected and not owned:
|
||||
# A standalone WowPresence.dll line is user-owned.
|
||||
continue
|
||||
managed.add(name.casefold())
|
||||
|
||||
managed.update(
|
||||
name.casefold()
|
||||
for name in (
|
||||
@@ -2304,14 +2309,48 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
"AuctionQueryThrottle.dll",
|
||||
"VanillaMultiMonitorFix.dll",
|
||||
"Interact.dll",
|
||||
# Legacy test-branch name. Owning it here removes the stale
|
||||
# entry from dlls.txt when migrating to WowPresence.dll.
|
||||
"DiscordPresence.dll",
|
||||
)
|
||||
)
|
||||
return managed
|
||||
|
||||
@staticmethod
|
||||
def _remove_dlls_entry(target, entry):
|
||||
"""Remove one exact DLL entry while preserving all unrelated lines."""
|
||||
dlls_path = os.path.join(target, "dlls.txt")
|
||||
if not os.path.isfile(dlls_path):
|
||||
return
|
||||
wanted = str(entry).strip().casefold()
|
||||
try:
|
||||
with open(dlls_path, "r", encoding="utf-8", errors="ignore") as handle:
|
||||
lines = handle.read().splitlines()
|
||||
except OSError as exc:
|
||||
raise RuntimeError(f"Could not read existing dlls.txt: {exc}") from exc
|
||||
|
||||
kept = [
|
||||
line for line in lines
|
||||
if line.strip().casefold() != wanted
|
||||
]
|
||||
staged = dlls_path + ".modernization-new"
|
||||
try:
|
||||
with open(staged, "w", encoding="utf-8", newline="\n") as handle:
|
||||
if kept:
|
||||
handle.write("\n".join(kept) + "\n")
|
||||
os.replace(staged, dlls_path)
|
||||
except OSError as exc:
|
||||
try:
|
||||
if os.path.exists(staged):
|
||||
os.remove(staged)
|
||||
except OSError:
|
||||
pass
|
||||
raise RuntimeError(f"Could not update existing dlls.txt: {exc}") from exc
|
||||
|
||||
def _write_dlls_file(self, target, active_managed_lines):
|
||||
"""Rewrite only tool-owned dlls.txt entries and preserve user entries."""
|
||||
dlls_path = os.path.join(target, "dlls.txt")
|
||||
managed_names = self._managed_dll_entries()
|
||||
managed_names = self._managed_dll_entries(target)
|
||||
preserved = []
|
||||
seen_preserved = set()
|
||||
|
||||
@@ -2418,24 +2457,16 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
return digest.hexdigest()
|
||||
|
||||
def _vanilla_tweaks_signature(self):
|
||||
"""Return only settings that change WoW_Modernized.exe patch output."""
|
||||
superwow_active = self._superwow_enabled()
|
||||
"""Return settings that change WoW_Modernized.exe patch output."""
|
||||
return {
|
||||
"superwow_active": bool(superwow_active),
|
||||
"fov": None if superwow_active else round(float(self.vt_fov.get()), 4),
|
||||
"fov": round(float(self.vt_fov.get()), 4),
|
||||
"farclip": int(self.vt_farclip.get()),
|
||||
"frill": int(self.vt_frill.get()),
|
||||
"nameplate": int(self.vt_nameplate.get()),
|
||||
"sound_channels": (
|
||||
None if superwow_active else int(self.vt_soundchan.get())
|
||||
),
|
||||
"sound_channels": int(self.vt_soundchan.get()),
|
||||
"max_camera": int(self.vt_maxcam.get()),
|
||||
"quickloot": (
|
||||
None if superwow_active else bool(self.vt_quickloot.get())
|
||||
),
|
||||
"background_sound": (
|
||||
None if superwow_active else bool(self.vt_bg_sound.get())
|
||||
),
|
||||
"quickloot": bool(self.vt_quickloot.get()),
|
||||
"background_sound": bool(self.vt_bg_sound.get()),
|
||||
"large_address_aware": bool(self.vt_laa.get()),
|
||||
"camera_fix": bool(self.vt_cam_fix.get()),
|
||||
"crossfaction_res": bool(self.vt_crossfaction_res.get()),
|
||||
@@ -2453,12 +2484,10 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
raise FileNotFoundError("vanilla-tweaks.exe was not found.")
|
||||
|
||||
args = [tweaks_exe]
|
||||
superwow_active = self._superwow_enabled()
|
||||
|
||||
if modern_cli:
|
||||
# tubtubs/vanilla-tweaks keeps these four patches opt-in. When
|
||||
# SuperWoW is active, deliberately leave them unpatched.
|
||||
if not superwow_active and abs(self.vt_fov.get() - 1.5708) >= 0.0001:
|
||||
# tubtubs/vanilla-tweaks keeps these patches opt-in.
|
||||
if abs(self.vt_fov.get() - 1.5708) >= 0.0001:
|
||||
args.extend(["--fov", str(self.vt_fov.get()), "--fov-patch"])
|
||||
|
||||
if self.vt_farclip.get() == 777:
|
||||
@@ -2476,7 +2505,7 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
else:
|
||||
args.extend(["--nameplatedistance", str(self.vt_nameplate.get())])
|
||||
|
||||
if not superwow_active and self.vt_soundchan.get() != 12:
|
||||
if self.vt_soundchan.get() != 12:
|
||||
args.extend([
|
||||
"--soundchannels",
|
||||
str(self.vt_soundchan.get()),
|
||||
@@ -2486,9 +2515,9 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
if self.vt_maxcam.get() != 50:
|
||||
args.extend(["--maxcameradistance", str(self.vt_maxcam.get())])
|
||||
|
||||
if not superwow_active and self.vt_quickloot.get():
|
||||
if self.vt_quickloot.get():
|
||||
args.append("--quickloot")
|
||||
if not superwow_active and self.vt_bg_sound.get():
|
||||
if self.vt_bg_sound.get():
|
||||
args.append("--sound-in-background")
|
||||
if not self.vt_laa.get():
|
||||
args.append("--no-largeaddressaware")
|
||||
@@ -2501,30 +2530,21 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
||||
if not self.vt_bluemoon.get():
|
||||
args.append("--no-bluemoonpatch")
|
||||
else:
|
||||
# Legacy bundled brndd patcher enables these older patches by
|
||||
# default, so explicitly disable all four when SuperWoW handles them.
|
||||
if superwow_active:
|
||||
args.extend([
|
||||
"--no-fov",
|
||||
"--no-soundchannels",
|
||||
"--no-quickloot",
|
||||
"--no-sound-in-background",
|
||||
])
|
||||
# Legacy bundled brndd patcher kept only as an offline fallback.
|
||||
if abs(self.vt_fov.get() - 1.5708) < 0.0001:
|
||||
args.append("--no-fov")
|
||||
else:
|
||||
if abs(self.vt_fov.get() - 1.5708) < 0.0001:
|
||||
args.append("--no-fov")
|
||||
else:
|
||||
args.extend(["--fov", str(self.vt_fov.get())])
|
||||
args.extend(["--fov", str(self.vt_fov.get())])
|
||||
|
||||
if self.vt_soundchan.get() == 12:
|
||||
args.append("--no-soundchannels")
|
||||
else:
|
||||
args.extend(["--soundchannels", str(self.vt_soundchan.get())])
|
||||
if self.vt_soundchan.get() == 12:
|
||||
args.append("--no-soundchannels")
|
||||
else:
|
||||
args.extend(["--soundchannels", str(self.vt_soundchan.get())])
|
||||
|
||||
if not self.vt_quickloot.get():
|
||||
args.append("--no-quickloot")
|
||||
if not self.vt_bg_sound.get():
|
||||
args.append("--no-sound-in-background")
|
||||
if not self.vt_quickloot.get():
|
||||
args.append("--no-quickloot")
|
||||
if not self.vt_bg_sound.get():
|
||||
args.append("--no-sound-in-background")
|
||||
|
||||
if self.vt_farclip.get() == 777:
|
||||
args.append("--no-farclip")
|
||||
|
||||
+759
-783
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
||||
import json
|
||||
import os
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, ttk
|
||||
|
||||
import dxvk_fps
|
||||
import setup_tool_dynamic
|
||||
|
||||
|
||||
class ResponsiveModernWowSetupTool(setup_tool_dynamic.ModernWowSetupTool):
|
||||
"""Production entry point with display-scaling-safe UI and DXVK FPS control."""
|
||||
|
||||
def __init__(self, root):
|
||||
# Define these before the parent constructor: WowSetupTool.__init__ calls
|
||||
# overridden UI/settings methods while it is still initializing.
|
||||
self.detected_refresh_rate = dxvk_fps.detect_max_refresh_rate()
|
||||
self.limit_dxvk_fps = tk.BooleanVar(master=root, value=True)
|
||||
self.dxvk_fps_limit = tk.IntVar(
|
||||
master=root,
|
||||
value=self.detected_refresh_rate,
|
||||
)
|
||||
self.dxvk_fps_checkbox = None
|
||||
self.dxvk_fps_entry = None
|
||||
self.dxvk_fps_unit_label = None
|
||||
|
||||
super().__init__(root)
|
||||
|
||||
# Keep the FPS controls synchronized when the renderer radio selection
|
||||
# changes or when saved settings restore a different renderer.
|
||||
self._rendering_mode_trace = self.rendering_mode.trace_add(
|
||||
"write",
|
||||
lambda *_args: self._update_dxvk_fps_state(),
|
||||
)
|
||||
self._update_dxvk_fps_state()
|
||||
|
||||
# Preserve all existing installer behavior and only change how the
|
||||
# top-level Tk window allocates and exposes its controls.
|
||||
root.resizable(True, True)
|
||||
|
||||
# Reserve the bottom action button before the expandable notebook so
|
||||
# Windows DPI/text scaling cannot push Apply outside the visible area.
|
||||
notebook = next(
|
||||
(child for child in root.winfo_children() if isinstance(child, ttk.Notebook)),
|
||||
None,
|
||||
)
|
||||
apply_button = getattr(self, "apply_button", None)
|
||||
if notebook is not None and apply_button is not None:
|
||||
notebook.pack_forget()
|
||||
apply_button.pack_forget()
|
||||
apply_button.pack(side="bottom", pady=10, fill="x", padx=20)
|
||||
notebook.pack(fill="both", expand=True, padx=10, pady=(5, 10))
|
||||
|
||||
root.update_idletasks()
|
||||
|
||||
# Prefer the widgets' requested size when the monitor has room, while
|
||||
# keeping a margin so the window remains reachable on smaller screens.
|
||||
screen_width = max(1, root.winfo_screenwidth())
|
||||
screen_height = max(1, root.winfo_screenheight())
|
||||
max_width = max(640, screen_width - 80)
|
||||
max_height = max(520, screen_height - 80)
|
||||
|
||||
requested_width = max(680, root.winfo_reqwidth())
|
||||
requested_height = max(660, root.winfo_reqheight())
|
||||
width = min(requested_width, max_width)
|
||||
height = min(requested_height, max_height)
|
||||
|
||||
root.geometry(f"{width}x{height}")
|
||||
root.minsize(min(640, max_width), min(520, max_height))
|
||||
|
||||
def build_main_tab(self, parent):
|
||||
super().build_main_tab(parent)
|
||||
|
||||
optional_label = None
|
||||
for child in parent.winfo_children():
|
||||
try:
|
||||
if child.cget("text") == "Optional Mods:":
|
||||
optional_label = child
|
||||
break
|
||||
except tk.TclError:
|
||||
continue
|
||||
|
||||
fps_frame = ttk.Frame(parent)
|
||||
pack_options = {
|
||||
"fill": "x",
|
||||
"padx": 20,
|
||||
"pady": (8, 2),
|
||||
}
|
||||
if optional_label is not None:
|
||||
pack_options["before"] = optional_label
|
||||
fps_frame.pack(**pack_options)
|
||||
|
||||
controls = ttk.Frame(fps_frame)
|
||||
controls.pack(anchor="w")
|
||||
|
||||
self.dxvk_fps_checkbox = ttk.Checkbutton(
|
||||
controls,
|
||||
text="Limit DXVK FPS",
|
||||
variable=self.limit_dxvk_fps,
|
||||
command=self._update_dxvk_fps_state,
|
||||
)
|
||||
self.dxvk_fps_checkbox.pack(side="left")
|
||||
|
||||
self.dxvk_fps_entry = ttk.Entry(
|
||||
controls,
|
||||
textvariable=self.dxvk_fps_limit,
|
||||
width=7,
|
||||
)
|
||||
self.dxvk_fps_entry.pack(side="left", padx=(10, 4))
|
||||
|
||||
self.dxvk_fps_unit_label = ttk.Label(controls, text="FPS")
|
||||
self.dxvk_fps_unit_label.pack(side="left")
|
||||
|
||||
help_label = ttk.Label(
|
||||
fps_frame,
|
||||
text=(
|
||||
"Auto-detected from the fastest active display at startup. "
|
||||
"You can change it manually. DXVK only."
|
||||
),
|
||||
font=("Segoe UI", 8, "italic"),
|
||||
)
|
||||
help_label.pack(anchor="w", padx=(22, 0), pady=(1, 0))
|
||||
|
||||
tooltip = (
|
||||
"Limits DXVK with d3d9.maxFrameRate. The initial value uses the "
|
||||
f"highest active display refresh rate detected at startup "
|
||||
f"({self.detected_refresh_rate} Hz). You can enter another positive "
|
||||
"integer. DirectX 9 is never modified by this option."
|
||||
)
|
||||
setup_tool_dynamic.ToolTip(self.dxvk_fps_checkbox, tooltip)
|
||||
setup_tool_dynamic.ToolTip(self.dxvk_fps_entry, tooltip)
|
||||
|
||||
self._update_dxvk_fps_state()
|
||||
|
||||
def _update_dxvk_fps_state(self):
|
||||
is_dxvk = getattr(self, "rendering_mode", None) is not None and (
|
||||
self.rendering_mode.get() == "dxvk"
|
||||
)
|
||||
enabled = bool(self.limit_dxvk_fps.get())
|
||||
|
||||
checkbox = getattr(self, "dxvk_fps_checkbox", None)
|
||||
entry = getattr(self, "dxvk_fps_entry", None)
|
||||
unit_label = getattr(self, "dxvk_fps_unit_label", None)
|
||||
|
||||
if checkbox is not None:
|
||||
checkbox.configure(state="normal" if is_dxvk else "disabled")
|
||||
if entry is not None:
|
||||
entry.configure(state="normal" if is_dxvk and enabled else "disabled")
|
||||
if unit_label is not None:
|
||||
if is_dxvk and enabled:
|
||||
unit_label.state(["!disabled"])
|
||||
else:
|
||||
unit_label.state(["disabled"])
|
||||
|
||||
def _collect_settings(self):
|
||||
settings = super()._collect_settings()
|
||||
try:
|
||||
fps_value = int(self.dxvk_fps_limit.get())
|
||||
except (tk.TclError, TypeError, ValueError):
|
||||
fps_value = int(self.detected_refresh_rate)
|
||||
|
||||
settings["dxvk_fps_limit"] = {
|
||||
"enabled": bool(self.limit_dxvk_fps.get()),
|
||||
"value": fps_value,
|
||||
}
|
||||
return settings
|
||||
|
||||
def _apply_settings_dict(self, saved):
|
||||
super()._apply_settings_dict(saved)
|
||||
|
||||
fps_settings = saved.get("dxvk_fps_limit") if isinstance(saved, dict) else None
|
||||
if isinstance(fps_settings, dict):
|
||||
enabled = fps_settings.get("enabled")
|
||||
value = fps_settings.get("value")
|
||||
if isinstance(enabled, bool):
|
||||
self.limit_dxvk_fps.set(enabled)
|
||||
if (
|
||||
isinstance(value, int)
|
||||
and not isinstance(value, bool)
|
||||
and value > 0
|
||||
):
|
||||
self.dxvk_fps_limit.set(value)
|
||||
|
||||
self._update_dxvk_fps_state()
|
||||
|
||||
def load_settings(self, target_dir):
|
||||
# Existing v2.3 installations predate this option. Preserve their
|
||||
# current DXVK limit state instead of silently enabling a new cap.
|
||||
has_saved_fps_setting = False
|
||||
settings_path = self._settings_path(target_dir)
|
||||
try:
|
||||
with open(settings_path, "r", encoding="utf-8") as handle:
|
||||
saved = json.load(handle)
|
||||
has_saved_fps_setting = isinstance(
|
||||
saved.get("dxvk_fps_limit") if isinstance(saved, dict) else None,
|
||||
dict,
|
||||
)
|
||||
except (OSError, json.JSONDecodeError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
loaded = super().load_settings(target_dir)
|
||||
|
||||
if (
|
||||
not has_saved_fps_setting
|
||||
and self._looks_like_managed_install(target_dir)
|
||||
and self.rendering_mode.get() == "dxvk"
|
||||
):
|
||||
existing = dxvk_fps.read_dxvk_fps_limit(
|
||||
os.path.join(target_dir, "dxvk.conf")
|
||||
)
|
||||
if existing is None:
|
||||
self.limit_dxvk_fps.set(False)
|
||||
else:
|
||||
enabled, value = existing
|
||||
self.limit_dxvk_fps.set(enabled)
|
||||
self.dxvk_fps_limit.set(value)
|
||||
|
||||
self._update_dxvk_fps_state()
|
||||
return loaded
|
||||
|
||||
def validate_limits(self):
|
||||
if not super().validate_limits():
|
||||
return False
|
||||
|
||||
if self.rendering_mode.get() != "dxvk" or not self.limit_dxvk_fps.get():
|
||||
return True
|
||||
|
||||
try:
|
||||
value = int(self.dxvk_fps_limit.get())
|
||||
except (tk.TclError, TypeError, ValueError):
|
||||
messagebox.showerror(
|
||||
"Input Error",
|
||||
"DXVK FPS Limit must contain a positive whole number.",
|
||||
)
|
||||
return False
|
||||
|
||||
if value <= 0:
|
||||
messagebox.showerror(
|
||||
"Input Error",
|
||||
"DXVK FPS Limit must be greater than 0.",
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def configure_dxvk(self, target):
|
||||
super().configure_dxvk(target)
|
||||
|
||||
# DirectX 9 follows the existing renderer cleanup path above. Never
|
||||
# create or modify dxvk.conf when DXVK is not selected.
|
||||
if self.rendering_mode.get() != "dxvk":
|
||||
return
|
||||
|
||||
dxvk_fps.apply_dxvk_fps_limit(
|
||||
os.path.join(target, "dxvk.conf"),
|
||||
bool(self.limit_dxvk_fps.get()),
|
||||
int(self.dxvk_fps_limit.get()),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = ResponsiveModernWowSetupTool(root)
|
||||
root.mainloop()
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
|
||||
class DxvkConfigDefaultsTests(unittest.TestCase):
|
||||
def test_exclusive_fullscreen_is_disabled_by_default(self):
|
||||
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
config_path = os.path.join(repo_root, "Payload", "dxvk.conf")
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as handle:
|
||||
active_settings = [
|
||||
line.strip()
|
||||
for line in handle
|
||||
if line.strip() and not line.lstrip().startswith("#")
|
||||
]
|
||||
|
||||
allow_fse_settings = [
|
||||
line for line in active_settings if line.lower().startswith("dxvk.allowfse")
|
||||
]
|
||||
self.assertEqual(allow_fse_settings, ["dxvk.allowFse = False"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,226 @@
|
||||
import ctypes
|
||||
import os
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import dxvk_fps
|
||||
import setup_tool_dynamic
|
||||
import setup_tool_responsive
|
||||
|
||||
|
||||
class FakeVar:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
def set(self, value):
|
||||
self.value = value
|
||||
|
||||
|
||||
class DxvkFpsLimitTests(unittest.TestCase):
|
||||
def write_config(self, root, text):
|
||||
path = os.path.join(root, "dxvk.conf")
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as handle:
|
||||
handle.write(text)
|
||||
return path
|
||||
|
||||
def read_config(self, path):
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
return handle.read()
|
||||
|
||||
def detect_with_fake_displays(self, devices, default=77):
|
||||
"""Run Windows display detection against deterministic fake Win32 APIs."""
|
||||
# Each record is (device name, state flags, refresh rate, settings_ok).
|
||||
def enum_display_devices(_device, index, device_ptr, _flags):
|
||||
if index >= len(devices):
|
||||
return False
|
||||
name, state_flags, _rate, _settings_ok = devices[index]
|
||||
device_ptr._obj.DeviceName = name
|
||||
device_ptr._obj.StateFlags = state_flags
|
||||
return True
|
||||
|
||||
def enum_display_settings(device_name, _mode, mode_ptr):
|
||||
for name, _state_flags, rate, settings_ok in devices:
|
||||
if name != device_name:
|
||||
continue
|
||||
if not settings_ok:
|
||||
return False
|
||||
mode_ptr._obj.dmDisplayFrequency = rate
|
||||
return True
|
||||
return False
|
||||
|
||||
fake_user32 = types.SimpleNamespace(
|
||||
EnumDisplayDevicesW=enum_display_devices,
|
||||
EnumDisplaySettingsW=enum_display_settings,
|
||||
)
|
||||
fake_windll = types.SimpleNamespace(user32=fake_user32)
|
||||
|
||||
with mock.patch.object(dxvk_fps.os, "name", "nt"), mock.patch.object(
|
||||
ctypes,
|
||||
"windll",
|
||||
fake_windll,
|
||||
create=True,
|
||||
):
|
||||
return dxvk_fps.detect_max_refresh_rate(default=default)
|
||||
|
||||
def test_enable_replaces_bundled_commented_value_and_preserves_other_options(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self.write_config(
|
||||
root,
|
||||
"# DXVK configuration\n"
|
||||
"# d3d9.maxFrameRate = 1000\n"
|
||||
"dxvk.allowFse = False\n",
|
||||
)
|
||||
|
||||
dxvk_fps.apply_dxvk_fps_limit(path, True, 165)
|
||||
updated = self.read_config(path)
|
||||
|
||||
self.assertIn("d3d9.maxFrameRate = 165\n", updated)
|
||||
self.assertNotIn("# d3d9.maxFrameRate = 1000", updated)
|
||||
self.assertIn("dxvk.allowFse = False\n", updated)
|
||||
self.assertEqual(updated.count("d3d9.maxFrameRate"), 1)
|
||||
self.assertEqual(dxvk_fps.read_dxvk_fps_limit(path), (True, 165))
|
||||
|
||||
def test_disable_comments_the_setting(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self.write_config(
|
||||
root,
|
||||
"d3d9.maxFrameRate = 144\n"
|
||||
"dxvk.numCompilerThreads = 4\n",
|
||||
)
|
||||
|
||||
dxvk_fps.apply_dxvk_fps_limit(path, False, 144)
|
||||
updated = self.read_config(path)
|
||||
|
||||
self.assertIn("# d3d9.maxFrameRate = 144\n", updated)
|
||||
self.assertNotIn("\nd3d9.maxFrameRate = 144\n", "\n" + updated)
|
||||
self.assertEqual(dxvk_fps.read_dxvk_fps_limit(path), (False, 144))
|
||||
|
||||
def test_duplicate_limit_lines_are_collapsed_to_one(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self.write_config(
|
||||
root,
|
||||
"d3d9.maxFrameRate = 60\n"
|
||||
"# d3d9.maxFrameRate = 120\n"
|
||||
"dxvk.allowFse = False\n",
|
||||
)
|
||||
|
||||
dxvk_fps.apply_dxvk_fps_limit(path, True, 240)
|
||||
updated = self.read_config(path)
|
||||
|
||||
self.assertEqual(updated.count("d3d9.maxFrameRate"), 1)
|
||||
self.assertIn("d3d9.maxFrameRate = 240\n", updated)
|
||||
self.assertIn("dxvk.allowFse = False\n", updated)
|
||||
|
||||
def test_signed_legacy_values_are_replaced_without_duplicates(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self.write_config(
|
||||
root,
|
||||
"d3d9.maxFrameRate = -120\n"
|
||||
"# d3d9.maxFrameRate = +90\n"
|
||||
"dxvk.allowFse = False\n",
|
||||
)
|
||||
|
||||
dxvk_fps.apply_dxvk_fps_limit(path, True, 165)
|
||||
updated = self.read_config(path)
|
||||
|
||||
self.assertEqual(updated.count("d3d9.maxFrameRate"), 1)
|
||||
self.assertIn("d3d9.maxFrameRate = 165\n", updated)
|
||||
self.assertNotIn("-120", updated)
|
||||
self.assertNotIn("+90", updated)
|
||||
self.assertIn("dxvk.allowFse = False\n", updated)
|
||||
|
||||
def test_missing_limit_line_is_appended(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self.write_config(root, "dxvk.allowFse = False\n")
|
||||
|
||||
dxvk_fps.apply_dxvk_fps_limit(path, True, 75)
|
||||
updated = self.read_config(path)
|
||||
|
||||
self.assertIn("dxvk.allowFse = False\n", updated)
|
||||
self.assertTrue(updated.endswith("d3d9.maxFrameRate = 75\n"))
|
||||
|
||||
def test_invalid_values_are_rejected_without_modifying_file(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
original = "# d3d9.maxFrameRate = 1000\n"
|
||||
path = self.write_config(root, original)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
dxvk_fps.apply_dxvk_fps_limit(path, True, 0)
|
||||
|
||||
self.assertEqual(self.read_config(path), original)
|
||||
|
||||
def test_refresh_detection_uses_fastest_attached_valid_display(self):
|
||||
detected = self.detect_with_fake_displays(
|
||||
[
|
||||
("DISPLAY1", 0x00000001, 60, True),
|
||||
# Faster but not attached to the desktop: must be ignored.
|
||||
("DISPLAY2", 0x00000000, 360, True),
|
||||
("DISPLAY3", 0x00000001, 165, True),
|
||||
# Windows unknown/default frequency: must be ignored.
|
||||
("DISPLAY4", 0x00000001, 1, True),
|
||||
# Current settings query failed: must be ignored.
|
||||
("DISPLAY5", 0x00000001, 240, False),
|
||||
],
|
||||
default=77,
|
||||
)
|
||||
self.assertEqual(detected, 165)
|
||||
|
||||
def test_refresh_detection_falls_back_when_no_usable_display_rate_exists(self):
|
||||
detected = self.detect_with_fake_displays(
|
||||
[
|
||||
("DISPLAY1", 0x00000000, 240, True),
|
||||
("DISPLAY2", 0x00000001, 0, True),
|
||||
("DISPLAY3", 0x00000001, 1, True),
|
||||
("DISPLAY4", 0x00000001, 165, False),
|
||||
],
|
||||
default=77,
|
||||
)
|
||||
self.assertEqual(detected, 77)
|
||||
|
||||
def test_refresh_detection_always_returns_a_positive_integer(self):
|
||||
detected = dxvk_fps.detect_max_refresh_rate(default=77)
|
||||
self.assertIsInstance(detected, int)
|
||||
self.assertGreater(detected, 0)
|
||||
|
||||
@mock.patch.object(setup_tool_dynamic.ModernWowSetupTool, "configure_dxvk")
|
||||
@mock.patch("setup_tool_responsive.dxvk_fps.apply_dxvk_fps_limit")
|
||||
def test_directx9_never_applies_fps_config(self, apply_limit, parent_configure):
|
||||
tool = setup_tool_responsive.ResponsiveModernWowSetupTool.__new__(
|
||||
setup_tool_responsive.ResponsiveModernWowSetupTool
|
||||
)
|
||||
tool.rendering_mode = FakeVar("directx9")
|
||||
tool.limit_dxvk_fps = FakeVar(True)
|
||||
tool.dxvk_fps_limit = FakeVar(165)
|
||||
|
||||
tool.configure_dxvk("C:/WoW")
|
||||
|
||||
parent_configure.assert_called_once_with("C:/WoW")
|
||||
apply_limit.assert_not_called()
|
||||
|
||||
@mock.patch.object(setup_tool_dynamic.ModernWowSetupTool, "configure_dxvk")
|
||||
@mock.patch("setup_tool_responsive.dxvk_fps.apply_dxvk_fps_limit")
|
||||
def test_dxvk_applies_selected_fps_value(self, apply_limit, parent_configure):
|
||||
tool = setup_tool_responsive.ResponsiveModernWowSetupTool.__new__(
|
||||
setup_tool_responsive.ResponsiveModernWowSetupTool
|
||||
)
|
||||
tool.rendering_mode = FakeVar("dxvk")
|
||||
tool.limit_dxvk_fps = FakeVar(True)
|
||||
tool.dxvk_fps_limit = FakeVar(144)
|
||||
|
||||
tool.configure_dxvk("C:/WoW")
|
||||
|
||||
parent_configure.assert_called_once_with("C:/WoW")
|
||||
apply_limit.assert_called_once_with(
|
||||
os.path.join("C:/WoW", "dxvk.conf"),
|
||||
True,
|
||||
144,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,137 @@
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import setup_tool_dynamic
|
||||
|
||||
|
||||
class _Var:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class FarclipConfigSyncTests(unittest.TestCase):
|
||||
def _tool(self, farclip=777):
|
||||
tool = object.__new__(setup_tool_dynamic.ModernWowSetupTool)
|
||||
tool.vt_farclip = _Var(farclip)
|
||||
return tool
|
||||
|
||||
@staticmethod
|
||||
def _add_numeric_vars(tool):
|
||||
tool.vt_fov = _Var(1.9199)
|
||||
tool.vt_frill = _Var(300)
|
||||
tool.vt_nameplate = _Var(41)
|
||||
tool.vt_maxcam = _Var(100)
|
||||
tool.vt_soundchan = _Var(64)
|
||||
|
||||
def test_executable_farclip_ceiling_is_always_3000(self):
|
||||
tool = self._tool(777)
|
||||
self._add_numeric_vars(tool)
|
||||
desired = tool._desired_normalized_values()
|
||||
self.assertEqual(desired["farclip"], 3000.0)
|
||||
|
||||
tool.vt_farclip = _Var(3000)
|
||||
desired = tool._desired_normalized_values()
|
||||
self.assertEqual(desired["farclip"], 3000.0)
|
||||
|
||||
def test_farclip_above_3000_is_rejected_even_with_direct_internal_call(self):
|
||||
tool = self._tool(5000)
|
||||
self._add_numeric_vars(tool)
|
||||
with self.assertRaisesRegex(RuntimeError, "Farclip"):
|
||||
tool._desired_normalized_values()
|
||||
|
||||
def test_normalization_signature_bump_forces_existing_install_refresh(self):
|
||||
tool = self._tool(777)
|
||||
self._add_numeric_vars(tool)
|
||||
tool.vt_quickloot = _Var(True)
|
||||
tool.vt_bg_sound = _Var(True)
|
||||
tool.vt_laa = _Var(True)
|
||||
tool.vt_cam_fix = _Var(True)
|
||||
tool.vt_crossfaction_res = _Var(False)
|
||||
tool.vt_custom_glues = _Var(True)
|
||||
tool.vt_bluemoon = _Var(False)
|
||||
signature = tool._vanilla_tweaks_signature()
|
||||
self.assertEqual(signature["selected_patch_normalization"], 4)
|
||||
self.assertEqual(signature["farclip"], 3000)
|
||||
|
||||
tool.vt_farclip = _Var(1500)
|
||||
runtime_changed = tool._vanilla_tweaks_signature()
|
||||
self.assertEqual(runtime_changed["farclip"], 3000)
|
||||
self.assertEqual(signature, runtime_changed)
|
||||
|
||||
def test_existing_farclip_cvar_is_replaced_without_touching_other_settings(self):
|
||||
tool = self._tool(777)
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
wtf_dir = os.path.join(root, "WTF")
|
||||
os.makedirs(wtf_dir)
|
||||
config_path = os.path.join(wtf_dir, "Config.wtf")
|
||||
with open(config_path, "w", encoding="utf-8") as handle:
|
||||
handle.write('SET locale "enUS"\nSET farclip "2100"\nSET gxWindow "1"\n')
|
||||
|
||||
tool._configure_farclip_cvar(root)
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertIn('SET locale "enUS"', result)
|
||||
self.assertIn('SET gxWindow "1"', result)
|
||||
self.assertIn('SET farclip "777"', result)
|
||||
self.assertNotIn('SET farclip "2100"', result)
|
||||
|
||||
def test_missing_farclip_cvar_is_appended(self):
|
||||
tool = self._tool(1500)
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
wtf_dir = os.path.join(root, "WTF")
|
||||
os.makedirs(wtf_dir)
|
||||
config_path = os.path.join(wtf_dir, "Config.wtf")
|
||||
with open(config_path, "w", encoding="utf-8") as handle:
|
||||
handle.write('SET locale "enUS"')
|
||||
|
||||
tool._configure_farclip_cvar(root)
|
||||
|
||||
with open(config_path, "r", encoding="utf-8") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
'SET locale "enUS"\nSET farclip "1500"\n',
|
||||
)
|
||||
|
||||
def test_missing_config_wtf_is_created(self):
|
||||
tool = self._tool(1000)
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
tool._configure_farclip_cvar(root)
|
||||
config_path = os.path.join(root, "WTF", "Config.wtf")
|
||||
with open(config_path, "r", encoding="utf-8") as handle:
|
||||
result = handle.read()
|
||||
self.assertEqual(result, 'SET farclip "1000"\n')
|
||||
|
||||
def test_farclip_cvar_rejects_value_above_fixed_ceiling(self):
|
||||
tool = self._tool(3001)
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
with self.assertRaisesRegex(RuntimeError, "3000"):
|
||||
tool._configure_farclip_cvar(root)
|
||||
self.assertFalse(os.path.exists(os.path.join(root, "WTF", "Config.wtf")))
|
||||
|
||||
def test_configure_script_memory_also_synchronizes_farclip(self):
|
||||
tool = self._tool(777)
|
||||
tool.core_plugins = {"SuperWoWhook.dll": _Var(False)}
|
||||
with mock.patch.object(
|
||||
setup_tool_dynamic._ModernWowSetupToolCore,
|
||||
"configure_script_memory",
|
||||
) as parent_configure, mock.patch.object(
|
||||
tool,
|
||||
"_configure_farclip_cvar",
|
||||
) as sync_farclip:
|
||||
tool.configure_script_memory("C:/WoW")
|
||||
|
||||
parent_configure.assert_called_once_with("C:/WoW")
|
||||
sync_farclip.assert_called_once_with("C:/WoW")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,301 @@
|
||||
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[:2] = b"MZ"
|
||||
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_visual_install_scopes_strict_hooks_and_restores_helpers(self):
|
||||
tool = object.__new__(dynamic.ModernWowSetupTool)
|
||||
original_verify = remote_packages._verify_mpq
|
||||
original_current = remote_packages.managed_mpq_is_current
|
||||
calls = []
|
||||
|
||||
def fake_visual_install(_instance, target):
|
||||
calls.append(target)
|
||||
self.assertIs(remote_packages._verify_mpq, dynamic._strict_verify_mpq)
|
||||
self.assertIs(
|
||||
remote_packages.managed_mpq_is_current,
|
||||
dynamic._strict_managed_mpq_is_current,
|
||||
)
|
||||
return "done"
|
||||
|
||||
with mock.patch.object(
|
||||
dynamic._ModernWowSetupToolCore,
|
||||
"configure_visual_audio",
|
||||
fake_visual_install,
|
||||
):
|
||||
self.assertEqual(tool.configure_visual_audio("GAME"), "done")
|
||||
|
||||
self.assertEqual(calls, ["GAME"])
|
||||
self.assertIs(remote_packages._verify_mpq, original_verify)
|
||||
self.assertIs(remote_packages.managed_mpq_is_current, original_current)
|
||||
|
||||
def test_strict_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(
|
||||
dynamic._strict_managed_mpq_is_current(
|
||||
temp,
|
||||
"visual_test",
|
||||
"1",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class VanillaTweaksSourceValidationTests(unittest.TestCase):
|
||||
def test_community_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"
|
||||
before = bytes(data)
|
||||
|
||||
tool._validate_client_identity(data)
|
||||
tool._validate_vanilla_tweaks_state(
|
||||
data,
|
||||
allow_foreign_custom_glues=True,
|
||||
)
|
||||
tool._validate_source_numeric_values(data)
|
||||
self.assertEqual(bytes(data), before)
|
||||
|
||||
def test_fixed_build_string_does_not_block_compatible_patch_sites(self):
|
||||
tool = _make_tool()
|
||||
data = _base_client_data()
|
||||
data[
|
||||
dynamic._CLIENT_BUILD_OFFSET:
|
||||
dynamic._CLIENT_BUILD_OFFSET + len(dynamic._CLIENT_BUILD)
|
||||
] = b"9999"
|
||||
|
||||
tool._validate_client_identity(data)
|
||||
tool._validate_vanilla_tweaks_state(
|
||||
data,
|
||||
allow_foreign_custom_glues=True,
|
||||
)
|
||||
|
||||
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._validate_source_numeric_values(data)
|
||||
|
||||
def test_normalization_overwrites_intermediate_numeric_values(self):
|
||||
tool = _make_tool()
|
||||
data = _base_client_data()
|
||||
|
||||
# The upstream patcher may temporarily emit different numeric values.
|
||||
# They are not trusted as final output: the Tool overwrites them below.
|
||||
struct.pack_into("<f", data, 0x4089B4, 2.5)
|
||||
struct.pack_into("<f", data, 0x40FED8, 2345.0)
|
||||
struct.pack_into("<f", data, 0x467958, 444.0)
|
||||
struct.pack_into("<f", data, 0x40C448, 88.0)
|
||||
struct.pack_into("<f", data, 0x4089A4, 123.0)
|
||||
data[0x435D38:0x435D3C] = b"96\x00\x00"
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "WoW_Modernized.exe")
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(data)
|
||||
|
||||
tool._normalize_selected_vanilla_tweaks_output(path)
|
||||
with open(path, "rb") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertAlmostEqual(
|
||||
struct.unpack_from("<f", result, 0x4089B4)[0],
|
||||
1.9199,
|
||||
places=4,
|
||||
)
|
||||
self.assertEqual(struct.unpack_from("<f", result, 0x40FED8)[0], 3000.0)
|
||||
self.assertEqual(struct.unpack_from("<f", result, 0x467958)[0], 300.0)
|
||||
self.assertEqual(struct.unpack_from("<f", result, 0x40C448)[0], 41.0)
|
||||
self.assertEqual(struct.unpack_from("<f", result, 0x4089A4)[0], 100.0)
|
||||
self.assertEqual(result[0x435D38:0x435D3C], b"64\x00\x00")
|
||||
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,193 @@
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import setup_tool_dynamic as dynamic
|
||||
|
||||
|
||||
class _Var:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class _Root:
|
||||
def configure(self, **_kwargs):
|
||||
return None
|
||||
|
||||
def update_idletasks(self):
|
||||
return None
|
||||
|
||||
|
||||
def _make_runtime_tool(target, events):
|
||||
"""Create the minimum state needed to execute the real base run_installation."""
|
||||
tool = object.__new__(dynamic.ModernWowSetupTool)
|
||||
tool._install_in_progress = False
|
||||
tool.wow_dir = _Var(target)
|
||||
tool.root = _Root()
|
||||
tool._close_download_progress = lambda: None
|
||||
|
||||
tool.validate_installation_dir = lambda value: (
|
||||
events.append(("validate_directory", value)) or True
|
||||
)
|
||||
tool.validate_limits = lambda: (
|
||||
events.append(("validate_limits", target)) or True
|
||||
)
|
||||
tool.validate_plugin_conflicts = lambda: (
|
||||
events.append(("preflight", target)) or True
|
||||
)
|
||||
|
||||
def record_target(name):
|
||||
return lambda value: events.append((name, value))
|
||||
|
||||
tool.copy_base_files = record_target("copy_base_files")
|
||||
tool.configure_dxvk = record_target("configure_dxvk")
|
||||
tool.configure_plugins = record_target("configure_plugins")
|
||||
tool.configure_autologin_encryption = lambda: events.append(
|
||||
("configure_autologin_encryption", target)
|
||||
)
|
||||
tool.configure_visual_audio = record_target("configure_visual_audio")
|
||||
tool.configure_script_memory = record_target("configure_script_memory")
|
||||
tool.configure_wdb_cache = record_target("configure_wdb_cache")
|
||||
tool.apply_process_mitigations = record_target("apply_process_mitigations")
|
||||
tool.create_launcher_shortcut = record_target("create_launcher_shortcut")
|
||||
tool.cleanup_legacy_outputs = record_target("cleanup_legacy_outputs")
|
||||
tool.save_settings = record_target("save_settings")
|
||||
return tool
|
||||
|
||||
|
||||
class RealInstallationOrderingTests(unittest.TestCase):
|
||||
def test_real_installation_runs_vanilla_tweaks_once_before_every_write(self):
|
||||
events = []
|
||||
|
||||
with tempfile.TemporaryDirectory() as target:
|
||||
initial_entries = set(os.listdir(target))
|
||||
tool = _make_runtime_tool(target, events)
|
||||
|
||||
def fake_clean(_instance, value):
|
||||
events.append(("clean_unselected_files", value))
|
||||
|
||||
def fake_vanilla_tweaks(_instance, value):
|
||||
events.append(("vanilla_tweaks", value))
|
||||
return os.path.join(value, "WoW_Modernized.exe")
|
||||
|
||||
with mock.patch.object(
|
||||
dynamic._ModernWowSetupToolCore,
|
||||
"clean_unselected_files",
|
||||
fake_clean,
|
||||
create=True,
|
||||
), mock.patch.object(
|
||||
dynamic._ModernWowSetupToolCore,
|
||||
"run_vanilla_tweaks",
|
||||
fake_vanilla_tweaks,
|
||||
create=True,
|
||||
), mock.patch(
|
||||
"setup_tool.messagebox.showinfo"
|
||||
) as showinfo, mock.patch(
|
||||
"setup_tool.messagebox.showerror"
|
||||
) as showerror:
|
||||
self.assertIsNone(tool.run_installation())
|
||||
|
||||
self.assertEqual(
|
||||
events,
|
||||
[
|
||||
("validate_directory", target),
|
||||
("validate_limits", target),
|
||||
("preflight", target),
|
||||
("vanilla_tweaks", target),
|
||||
("clean_unselected_files", target),
|
||||
("copy_base_files", target),
|
||||
("configure_dxvk", target),
|
||||
("configure_plugins", target),
|
||||
("configure_autologin_encryption", target),
|
||||
("configure_visual_audio", target),
|
||||
("configure_script_memory", target),
|
||||
("configure_wdb_cache", target),
|
||||
("apply_process_mitigations", target),
|
||||
("create_launcher_shortcut", target),
|
||||
("cleanup_legacy_outputs", target),
|
||||
("save_settings", target),
|
||||
],
|
||||
)
|
||||
showinfo.assert_called_once()
|
||||
showerror.assert_not_called()
|
||||
|
||||
# run_installation installs temporary instance wrappers. They
|
||||
# must be gone when Apply returns so a later Apply starts clean.
|
||||
self.assertNotIn("clean_unselected_files", tool.__dict__)
|
||||
self.assertNotIn("run_vanilla_tweaks", tool.__dict__)
|
||||
self.assertIs(tool.clean_unselected_files.__func__, fake_clean)
|
||||
self.assertIs(tool.run_vanilla_tweaks.__func__, fake_vanilla_tweaks)
|
||||
|
||||
# All real mutators above were replaced with record-only callbacks.
|
||||
# Any newly-added direct filesystem write in run_installation would
|
||||
# make this integration-style test visibly change the temp folder.
|
||||
self.assertEqual(set(os.listdir(target)), initial_entries)
|
||||
self.assertFalse(tool._install_in_progress)
|
||||
|
||||
def test_real_installation_vanilla_tweaks_failure_blocks_all_writes(self):
|
||||
events = []
|
||||
|
||||
with tempfile.TemporaryDirectory() as target:
|
||||
initial_entries = set(os.listdir(target))
|
||||
tool = _make_runtime_tool(target, events)
|
||||
|
||||
def fake_clean(_instance, value):
|
||||
events.append(("clean_unselected_files", value))
|
||||
|
||||
def failing_vanilla_tweaks(_instance, value):
|
||||
events.append(("vanilla_tweaks_failed", value))
|
||||
raise RuntimeError("patcher failed before install writes")
|
||||
|
||||
with mock.patch.object(
|
||||
dynamic._ModernWowSetupToolCore,
|
||||
"clean_unselected_files",
|
||||
fake_clean,
|
||||
create=True,
|
||||
), mock.patch.object(
|
||||
dynamic._ModernWowSetupToolCore,
|
||||
"run_vanilla_tweaks",
|
||||
failing_vanilla_tweaks,
|
||||
create=True,
|
||||
), mock.patch(
|
||||
"setup_tool.messagebox.showinfo"
|
||||
) as showinfo, mock.patch(
|
||||
"setup_tool.messagebox.showerror"
|
||||
) as showerror:
|
||||
self.assertIsNone(tool.run_installation())
|
||||
|
||||
self.assertEqual(
|
||||
events,
|
||||
[
|
||||
("validate_directory", target),
|
||||
("validate_limits", target),
|
||||
("preflight", target),
|
||||
("vanilla_tweaks_failed", target),
|
||||
],
|
||||
)
|
||||
showinfo.assert_not_called()
|
||||
showerror.assert_called_once()
|
||||
self.assertEqual(showerror.call_args.args[0], "Installation Error")
|
||||
self.assertIn(
|
||||
"patcher failed before install writes",
|
||||
showerror.call_args.args[1],
|
||||
)
|
||||
|
||||
# The temporary wrappers must also be restored on the error path.
|
||||
self.assertNotIn("clean_unselected_files", tool.__dict__)
|
||||
self.assertNotIn("run_vanilla_tweaks", tool.__dict__)
|
||||
self.assertIs(tool.clean_unselected_files.__func__, fake_clean)
|
||||
self.assertIs(
|
||||
tool.run_vanilla_tweaks.__func__,
|
||||
failing_vanilla_tweaks,
|
||||
)
|
||||
|
||||
self.assertEqual(set(os.listdir(target)), initial_entries)
|
||||
self.assertFalse(tool._install_in_progress)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import remote_packages
|
||||
import setup_tool_dynamic as dynamic
|
||||
|
||||
|
||||
def _classic_mpq_bytes(
|
||||
*,
|
||||
archive_size=96,
|
||||
hash_table_offset=32,
|
||||
block_table_offset=48,
|
||||
inner_magic=b"MPQ\x1A",
|
||||
sector_size_shift=3,
|
||||
):
|
||||
header = inner_magic + struct.pack(
|
||||
"<IIHHIIII",
|
||||
32,
|
||||
archive_size,
|
||||
0,
|
||||
sector_size_shift,
|
||||
hash_table_offset,
|
||||
block_table_offset,
|
||||
1,
|
||||
1,
|
||||
)
|
||||
data = bytearray(archive_size)
|
||||
data[:32] = header
|
||||
return data
|
||||
|
||||
|
||||
def _write_wrapped_mpq(
|
||||
path,
|
||||
*,
|
||||
header_offset=32,
|
||||
user_header_size=16,
|
||||
user_data_size=None,
|
||||
inner_magic=b"MPQ\x1A",
|
||||
hash_table_offset=32,
|
||||
block_table_offset=48,
|
||||
sector_size_shift=3,
|
||||
):
|
||||
inner = _classic_mpq_bytes(
|
||||
inner_magic=inner_magic,
|
||||
hash_table_offset=hash_table_offset,
|
||||
block_table_offset=block_table_offset,
|
||||
sector_size_shift=sector_size_shift,
|
||||
)
|
||||
total_size = max(16, header_offset + len(inner))
|
||||
data = bytearray(total_size)
|
||||
if user_data_size is None:
|
||||
user_data_size = max(0, header_offset - user_header_size)
|
||||
data[:16] = b"MPQ\x1B" + struct.pack(
|
||||
"<III",
|
||||
user_data_size,
|
||||
header_offset,
|
||||
user_header_size,
|
||||
)
|
||||
if 0 <= header_offset <= total_size - len(inner):
|
||||
data[header_offset:header_offset + len(inner)] = inner
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(data)
|
||||
|
||||
|
||||
class ExecutableCompatibilityTests(unittest.TestCase):
|
||||
def test_fixed_build_version_identity_guard_is_disabled(self):
|
||||
data = bytearray(0x46795C + 16)
|
||||
dynamic.ModernWowSetupTool._validate_client_identity(data)
|
||||
|
||||
|
||||
class WrappedMpqValidationTests(unittest.TestCase):
|
||||
def test_accepts_valid_user_data_wrapped_mpq(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
_write_wrapped_mpq(path)
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_user_data_wrapper_with_invalid_archive_offset(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
data = bytearray(128)
|
||||
data[:16] = b"MPQ\x1B" + struct.pack("<III", 0, 8, 16)
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(data)
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"invalid nested archive offset",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_user_data_wrapper_with_out_of_bounds_archive_offset(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
data = bytearray(128)
|
||||
data[:16] = b"MPQ\x1B" + struct.pack("<III", 0, 120, 16)
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(data)
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"invalid nested archive offset",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_user_data_wrapper_with_invalid_header_size(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
data = bytearray(128)
|
||||
data[:16] = b"MPQ\x1B" + struct.pack("<III", 24, 32, 8)
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(data)
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"invalid nested archive offset",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_user_data_size_smaller_than_user_header(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
_write_wrapped_mpq(path, user_data_size=8)
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"invalid user-data size",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_user_data_size_beyond_nested_archive_offset(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
_write_wrapped_mpq(path, user_data_size=40)
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"invalid user-data size",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_wrapper_with_corrupt_nested_mpq(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
_write_wrapped_mpq(path, inner_magic=b"NOPE")
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"not a valid MPQ archive",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_wrapped_mpq_with_out_of_bounds_tables(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
_write_wrapped_mpq(path, block_table_offset=88)
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"out-of-bounds block table",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
def test_rejects_zero_sector_size_shift(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
path = os.path.join(temp, "wrapped.mpq")
|
||||
_write_wrapped_mpq(path, sector_size_shift=0)
|
||||
with self.assertRaisesRegex(
|
||||
remote_packages.RemotePackageError,
|
||||
"invalid sector-size shift",
|
||||
):
|
||||
dynamic._strict_verify_mpq(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,117 @@
|
||||
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, farclip=777, script_memory=False):
|
||||
tool = object.__new__(dynamic.ModernWowSetupTool)
|
||||
tool.core_plugins = {
|
||||
"SuperWoWhook.dll": FakeVar(superwow),
|
||||
}
|
||||
tool.vt_fov = FakeVar(fov)
|
||||
tool.vt_farclip = FakeVar(farclip)
|
||||
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 farclip "777"', 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"\nSET farclip "777"\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:
|
||||
result = handle.read()
|
||||
self.assertIn('SET FoV "1.5"', result)
|
||||
self.assertIn('SET locale "frFR"', result)
|
||||
self.assertIn('SET farclip "777"', result)
|
||||
self.assertNotIn('SET FoV "1.9199"', result)
|
||||
|
||||
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)
|
||||
self.assertIn('SET farclip "777"', 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:
|
||||
result = handle.read()
|
||||
self.assertIn('SET FoV "1.9199"', result)
|
||||
self.assertIn('SET farclip "777"', result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,637 @@
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import setup_tool_dynamic
|
||||
from setup_tool import WowSetupTool
|
||||
from setup_tool_dynamic import ModernWowSetupTool
|
||||
|
||||
|
||||
class FakeVar:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def get(self):
|
||||
return self.value
|
||||
|
||||
def set(self, value):
|
||||
self.value = value
|
||||
|
||||
|
||||
class VanillaTweaksNormalizationTests(unittest.TestCase):
|
||||
def _tool(
|
||||
self,
|
||||
*,
|
||||
fov,
|
||||
sound,
|
||||
quickloot,
|
||||
background,
|
||||
farclip=777,
|
||||
frill=300,
|
||||
nameplate=41,
|
||||
maxcam=100,
|
||||
laa=True,
|
||||
camera=True,
|
||||
custom_glues=True,
|
||||
crossfaction=False,
|
||||
bluemoon=False,
|
||||
):
|
||||
tool = ModernWowSetupTool.__new__(ModernWowSetupTool)
|
||||
tool.vt_fov = FakeVar(fov)
|
||||
tool.vt_soundchan = FakeVar(sound)
|
||||
tool.vt_quickloot = FakeVar(quickloot)
|
||||
tool.vt_bg_sound = FakeVar(background)
|
||||
tool.vt_farclip = FakeVar(farclip)
|
||||
tool.vt_frill = FakeVar(frill)
|
||||
tool.vt_nameplate = FakeVar(nameplate)
|
||||
tool.vt_maxcam = FakeVar(maxcam)
|
||||
tool.vt_laa = FakeVar(laa)
|
||||
tool.vt_cam_fix = FakeVar(camera)
|
||||
tool.vt_crossfaction_res = FakeVar(crossfaction)
|
||||
tool.vt_custom_glues = FakeVar(custom_glues)
|
||||
tool.vt_bluemoon = FakeVar(bluemoon)
|
||||
tool.core_plugins = {"SuperWoWhook.dll": FakeVar(True)}
|
||||
return tool
|
||||
|
||||
def _write_exe(
|
||||
self,
|
||||
root,
|
||||
quick1,
|
||||
quick2,
|
||||
background,
|
||||
fov,
|
||||
sound,
|
||||
*,
|
||||
farclip=777.0,
|
||||
frill=70.0,
|
||||
nameplate=20.0,
|
||||
maxcam=50.0,
|
||||
laa_patched=False,
|
||||
camera_patched=False,
|
||||
custom_patched=False,
|
||||
crossfaction_byte=0x01,
|
||||
bluemoon_bytes=None,
|
||||
filename="WoW_Modernized.exe",
|
||||
):
|
||||
data = bytearray(0x46795C + 16)
|
||||
|
||||
# Use a minimal but structurally valid PE32 header. The selected PE
|
||||
# offset deliberately places COFF Characteristics at 0x126, matching
|
||||
# the real client's Large Address Aware patch location.
|
||||
pe_offset = 0x110
|
||||
data[:2] = b"MZ"
|
||||
struct.pack_into("<I", data, 0x3C, pe_offset)
|
||||
data[pe_offset:pe_offset + 4] = b"PE\x00\x00"
|
||||
struct.pack_into("<H", data, pe_offset + 4, 0x014C)
|
||||
struct.pack_into("<H", data, pe_offset + 6, 3)
|
||||
struct.pack_into("<H", data, pe_offset + 20, 0x00E0)
|
||||
struct.pack_into("<H", data, pe_offset + 24, 0x010B)
|
||||
|
||||
data[
|
||||
setup_tool_dynamic._CLIENT_BUILD_OFFSET:
|
||||
setup_tool_dynamic._CLIENT_BUILD_OFFSET
|
||||
+ len(setup_tool_dynamic._CLIENT_BUILD)
|
||||
] = setup_tool_dynamic._CLIENT_BUILD
|
||||
data[
|
||||
setup_tool_dynamic._CLIENT_VERSION_OFFSET:
|
||||
setup_tool_dynamic._CLIENT_VERSION_OFFSET
|
||||
+ len(setup_tool_dynamic._CLIENT_VERSION)
|
||||
] = setup_tool_dynamic._CLIENT_VERSION
|
||||
|
||||
data[0x0C1ECF:0x0C1ED1] = quick1
|
||||
data[0x0C2B25:0x0C2B27] = quick2
|
||||
data[0x3A4869] = background
|
||||
data[0x126:0x128] = b"\x2F\x01" if laa_patched else b"\x0F\x01"
|
||||
|
||||
for offset, original, patched in setup_tool_dynamic._CAMERA_REGIONS:
|
||||
selected = patched if camera_patched else original
|
||||
data[offset:offset + len(selected)] = selected
|
||||
|
||||
for offset, original, patched in setup_tool_dynamic._CUSTOM_GLUES_SITES:
|
||||
data[offset] = patched if custom_patched else original
|
||||
|
||||
struct.pack_into("<f", data, 0x4089B4, fov)
|
||||
struct.pack_into("<f", data, 0x40FED8, farclip)
|
||||
struct.pack_into("<f", data, 0x467958, frill)
|
||||
struct.pack_into("<f", data, 0x40C448, nameplate)
|
||||
struct.pack_into("<f", data, 0x4089A4, maxcam)
|
||||
data[0x435D38:0x435D3C] = sound
|
||||
|
||||
data[0x2067DE] = crossfaction_byte
|
||||
if bluemoon_bytes is None:
|
||||
bluemoon_bytes = bytes(range(1, 14))
|
||||
data[0x3E5B83:0x3E5B83 + len(bluemoon_bytes)] = bluemoon_bytes
|
||||
|
||||
# Unrelated data must survive normalization byte-for-byte.
|
||||
data[0x123456:0x12345E] = b"KEEPTHIS"
|
||||
|
||||
path = os.path.join(root, filename)
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(data)
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def _read_float(data, offset):
|
||||
return struct.unpack("<f", data[offset:offset + 4])[0]
|
||||
|
||||
@staticmethod
|
||||
def _write_custom_state(path, values):
|
||||
with open(path, "r+b") as handle:
|
||||
for value, (offset, _original, _patched) in zip(
|
||||
values,
|
||||
setup_tool_dynamic._CUSTOM_GLUES_SITES,
|
||||
):
|
||||
handle.seek(offset)
|
||||
handle.write(bytes((value,)))
|
||||
|
||||
@staticmethod
|
||||
def _read_custom_state(data):
|
||||
return tuple(
|
||||
data[offset]
|
||||
for offset, _original, _patched in setup_tool_dynamic._CUSTOM_GLUES_SITES
|
||||
)
|
||||
|
||||
def test_disabled_selections_restore_inherited_patches(self):
|
||||
tool = self._tool(
|
||||
fov=1.5708,
|
||||
sound=12,
|
||||
quickloot=False,
|
||||
background=False,
|
||||
farclip=777,
|
||||
frill=70,
|
||||
nameplate=20,
|
||||
maxcam=50,
|
||||
laa=False,
|
||||
camera=False,
|
||||
custom_glues=False,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._write_exe(
|
||||
root,
|
||||
b"\x75\x10",
|
||||
b"\x75\x0B",
|
||||
0x27,
|
||||
1.919862,
|
||||
b"64\x00\x00",
|
||||
farclip=3000.0,
|
||||
frill=300.0,
|
||||
nameplate=41.0,
|
||||
maxcam=100.0,
|
||||
laa_patched=True,
|
||||
camera_patched=True,
|
||||
custom_patched=True,
|
||||
)
|
||||
tool._normalize_selected_vanilla_tweaks_output(path)
|
||||
with open(path, "rb") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertEqual(result[0x0C1ECF:0x0C1ED1], b"\x74\x10")
|
||||
self.assertEqual(result[0x0C2B25:0x0C2B27], b"\x74\x0B")
|
||||
self.assertEqual(result[0x3A4869], 0x14)
|
||||
self.assertEqual(result[0x126:0x128], b"\x0F\x01")
|
||||
self.assertAlmostEqual(self._read_float(result, 0x4089B4), 1.5708, places=4)
|
||||
self.assertEqual(self._read_float(result, 0x40FED8), 3000.0)
|
||||
self.assertEqual(self._read_float(result, 0x467958), 70.0)
|
||||
self.assertEqual(self._read_float(result, 0x40C448), 20.0)
|
||||
self.assertEqual(self._read_float(result, 0x4089A4), 50.0)
|
||||
self.assertEqual(result[0x435D38:0x435D3C], b"12\x00\x00")
|
||||
for offset, original, _patched in setup_tool_dynamic._CAMERA_REGIONS:
|
||||
self.assertEqual(result[offset:offset + len(original)], original)
|
||||
for offset, original, _patched in setup_tool_dynamic._CUSTOM_GLUES_SITES:
|
||||
self.assertEqual(result[offset], original)
|
||||
self.assertEqual(result[0x123456:0x12345E], b"KEEPTHIS")
|
||||
|
||||
def test_enabled_selections_override_vanilla_input(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
farclip=1500,
|
||||
frill=300,
|
||||
nameplate=41,
|
||||
maxcam=100,
|
||||
laa=True,
|
||||
camera=True,
|
||||
custom_glues=True,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._write_exe(
|
||||
root,
|
||||
b"\x90\x90",
|
||||
b"\x90\x90",
|
||||
0x14,
|
||||
1.5707963705,
|
||||
b"12\x00\x00",
|
||||
)
|
||||
tool._normalize_selected_vanilla_tweaks_output(path)
|
||||
with open(path, "rb") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertEqual(result[0x0C1ECF:0x0C1ED1], b"\x75\x10")
|
||||
self.assertEqual(result[0x0C2B25:0x0C2B27], b"\x75\x0B")
|
||||
self.assertEqual(result[0x3A4869], 0x27)
|
||||
self.assertEqual(result[0x126:0x128], b"\x2F\x01")
|
||||
self.assertAlmostEqual(self._read_float(result, 0x4089B4), 1.9199, places=4)
|
||||
self.assertEqual(self._read_float(result, 0x40FED8), 3000.0)
|
||||
self.assertEqual(self._read_float(result, 0x467958), 300.0)
|
||||
self.assertEqual(self._read_float(result, 0x40C448), 41.0)
|
||||
self.assertEqual(self._read_float(result, 0x4089A4), 100.0)
|
||||
self.assertEqual(result[0x435D38:0x435D3C], b"64\x00\x00")
|
||||
for offset, _original, patched in setup_tool_dynamic._CAMERA_REGIONS:
|
||||
self.assertEqual(result[offset:offset + len(patched)], patched)
|
||||
for offset, _original, patched in setup_tool_dynamic._CUSTOM_GLUES_SITES:
|
||||
self.assertEqual(result[offset], patched)
|
||||
self.assertEqual(result[0x123456:0x12345E], b"KEEPTHIS")
|
||||
|
||||
def test_unknown_quickloot_bytes_fail_safe(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._write_exe(
|
||||
root,
|
||||
b"\xEB\x10",
|
||||
b"\x74\x0B",
|
||||
0x14,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
)
|
||||
with self.assertRaisesRegex(RuntimeError, "Unexpected QuickLoot bytes"):
|
||||
tool._normalize_selected_vanilla_tweaks_output(path)
|
||||
|
||||
def test_unknown_custom_glues_bytes_fail_safe_without_source_proof(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
custom_glues=False,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._write_exe(
|
||||
root,
|
||||
b"\x74\x10",
|
||||
b"\x74\x0B",
|
||||
0x14,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
)
|
||||
with open(path, "r+b") as handle:
|
||||
handle.seek(0x2F113A)
|
||||
handle.write(b"\xAA")
|
||||
|
||||
with self.assertRaisesRegex(RuntimeError, "Unexpected Custom GlueXML bytes"):
|
||||
tool._normalize_selected_vanilla_tweaks_output(path)
|
||||
|
||||
def test_source_preflight_accepts_and_records_foreign_custom_glues(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
custom_glues=True,
|
||||
)
|
||||
foreign = (0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xF1)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
source = self._write_exe(
|
||||
root,
|
||||
b"\x75\x10",
|
||||
b"\x75\x0B",
|
||||
0x27,
|
||||
1.919862,
|
||||
b"12\x00\x00",
|
||||
farclip=3000.0,
|
||||
frill=300.0,
|
||||
nameplate=41.0,
|
||||
maxcam=50.0,
|
||||
laa_patched=True,
|
||||
camera_patched=True,
|
||||
filename="WoW.exe",
|
||||
)
|
||||
self._write_custom_state(source, foreign)
|
||||
|
||||
preserved = tool._preflight_vanilla_tweaks_source(root)
|
||||
|
||||
self.assertEqual(preserved, foreign)
|
||||
self.assertEqual(tool._vt_preserved_custom_glues, foreign)
|
||||
|
||||
def test_blue_moon_and_crossfaction_keep_legacy_output(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
crossfaction=True,
|
||||
bluemoon=True,
|
||||
)
|
||||
blue_bytes = bytes.fromhex("a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad")
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = self._write_exe(
|
||||
root,
|
||||
b"\x74\x10",
|
||||
b"\x74\x0B",
|
||||
0x14,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
crossfaction_byte=0x7A,
|
||||
bluemoon_bytes=blue_bytes,
|
||||
)
|
||||
tool._normalize_selected_vanilla_tweaks_output(path)
|
||||
with open(path, "rb") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertEqual(result[0x2067DE], 0x7A)
|
||||
self.assertEqual(
|
||||
result[0x3E5B83:0x3E5B83 + len(blue_bytes)],
|
||||
blue_bytes,
|
||||
)
|
||||
|
||||
def test_source_preflight_rejects_unknown_quickloot_before_patcher(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
self._write_exe(
|
||||
root,
|
||||
b"\xEB\x10",
|
||||
b"\x74\x0B",
|
||||
0x14,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
filename="WoW.exe",
|
||||
)
|
||||
output_exe = os.path.join(root, "WoW_Modernized.exe")
|
||||
previous_output = b"known-good existing modernized executable"
|
||||
with open(output_exe, "wb") as handle:
|
||||
handle.write(previous_output)
|
||||
|
||||
patcher = mock.Mock()
|
||||
with mock.patch.object(WowSetupTool, "run_vanilla_tweaks", new=patcher):
|
||||
with self.assertRaisesRegex(RuntimeError, "Unexpected QuickLoot bytes"):
|
||||
tool._run_vanilla_tweaks_transactional(
|
||||
root,
|
||||
tweaks_exe="fake-vanilla-tweaks.exe",
|
||||
modern_cli=True,
|
||||
)
|
||||
|
||||
patcher.assert_not_called()
|
||||
with open(output_exe, "rb") as handle:
|
||||
self.assertEqual(handle.read(), previous_output)
|
||||
self.assertFalse(os.path.exists(output_exe + ".modernization-new"))
|
||||
self.assertFalse(
|
||||
any(name.startswith(".modernization-vt-") for name in os.listdir(root))
|
||||
)
|
||||
|
||||
def test_transaction_keeps_existing_output_when_normalization_fails(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
self._write_exe(
|
||||
root,
|
||||
b"\x74\x10",
|
||||
b"\x74\x0B",
|
||||
0x14,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
filename="WoW.exe",
|
||||
)
|
||||
|
||||
output_exe = os.path.join(root, "WoW_Modernized.exe")
|
||||
previous_output = b"known-good existing modernized executable"
|
||||
with open(output_exe, "wb") as handle:
|
||||
handle.write(previous_output)
|
||||
|
||||
def fake_run(_tool, staging_target, tweaks_exe=None, modern_cli=False):
|
||||
return self._write_exe(
|
||||
staging_target,
|
||||
b"\xEB\x10",
|
||||
b"\x74\x0B",
|
||||
0x14,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
)
|
||||
|
||||
with mock.patch.object(WowSetupTool, "run_vanilla_tweaks", new=fake_run):
|
||||
with self.assertRaisesRegex(RuntimeError, "Unexpected QuickLoot bytes"):
|
||||
tool._run_vanilla_tweaks_transactional(
|
||||
root,
|
||||
tweaks_exe="fake-vanilla-tweaks.exe",
|
||||
modern_cli=True,
|
||||
)
|
||||
|
||||
with open(output_exe, "rb") as handle:
|
||||
self.assertEqual(handle.read(), previous_output)
|
||||
self.assertFalse(os.path.exists(output_exe + ".modernization-new"))
|
||||
self.assertFalse(
|
||||
any(name.startswith(".modernization-vt-") for name in os.listdir(root))
|
||||
)
|
||||
|
||||
def test_transaction_restores_foreign_custom_glues_after_patcher(self):
|
||||
tool = self._tool(
|
||||
fov=1.5708,
|
||||
sound=12,
|
||||
quickloot=False,
|
||||
background=False,
|
||||
farclip=777,
|
||||
frill=70,
|
||||
nameplate=20,
|
||||
maxcam=50,
|
||||
laa=False,
|
||||
camera=False,
|
||||
custom_glues=True,
|
||||
)
|
||||
foreign = (0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xF1)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
source = self._write_exe(
|
||||
root,
|
||||
b"\x75\x10",
|
||||
b"\x75\x0B",
|
||||
0x27,
|
||||
1.919862,
|
||||
b"64\x00\x00",
|
||||
farclip=3000.0,
|
||||
frill=300.0,
|
||||
nameplate=41.0,
|
||||
maxcam=100.0,
|
||||
laa_patched=True,
|
||||
camera_patched=True,
|
||||
filename="WoW.exe",
|
||||
)
|
||||
self._write_custom_state(source, foreign)
|
||||
|
||||
output_exe = os.path.join(root, "WoW_Modernized.exe")
|
||||
with open(output_exe, "wb") as handle:
|
||||
handle.write(b"previous output")
|
||||
|
||||
def fake_run(_tool, staging_target, tweaks_exe=None, modern_cli=False):
|
||||
return self._write_exe(
|
||||
staging_target,
|
||||
b"\x75\x10",
|
||||
b"\x75\x0B",
|
||||
0x27,
|
||||
1.919862,
|
||||
b"64\x00\x00",
|
||||
farclip=3000.0,
|
||||
frill=300.0,
|
||||
nameplate=41.0,
|
||||
maxcam=100.0,
|
||||
laa_patched=True,
|
||||
camera_patched=True,
|
||||
custom_patched=True,
|
||||
)
|
||||
|
||||
with mock.patch.object(WowSetupTool, "run_vanilla_tweaks", new=fake_run):
|
||||
tool._run_vanilla_tweaks_transactional(
|
||||
root,
|
||||
tweaks_exe="fake-vanilla-tweaks.exe",
|
||||
modern_cli=True,
|
||||
)
|
||||
|
||||
with open(output_exe, "rb") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertEqual(self._read_custom_state(result), foreign)
|
||||
self.assertEqual(result[0x0C1ECF:0x0C1ED1], b"\x74\x10")
|
||||
self.assertEqual(result[0x0C2B25:0x0C2B27], b"\x74\x0B")
|
||||
self.assertEqual(result[0x3A4869], 0x14)
|
||||
self.assertEqual(result[0x126:0x128], b"\x0F\x01")
|
||||
|
||||
def test_transaction_commits_total_normalized_output_after_validation(self):
|
||||
tool = self._tool(
|
||||
fov=1.5708,
|
||||
sound=12,
|
||||
quickloot=False,
|
||||
background=False,
|
||||
farclip=777,
|
||||
frill=70,
|
||||
nameplate=20,
|
||||
maxcam=50,
|
||||
laa=False,
|
||||
camera=False,
|
||||
custom_glues=False,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
self._write_exe(
|
||||
root,
|
||||
b"\x74\x10",
|
||||
b"\x74\x0B",
|
||||
0x14,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
filename="WoW.exe",
|
||||
)
|
||||
|
||||
output_exe = os.path.join(root, "WoW_Modernized.exe")
|
||||
with open(output_exe, "wb") as handle:
|
||||
handle.write(b"previous output")
|
||||
|
||||
def fake_run(_tool, staging_target, tweaks_exe=None, modern_cli=False):
|
||||
return self._write_exe(
|
||||
staging_target,
|
||||
b"\x75\x10",
|
||||
b"\x75\x0B",
|
||||
0x27,
|
||||
1.5708,
|
||||
b"12\x00\x00",
|
||||
farclip=777.0,
|
||||
frill=70.0,
|
||||
nameplate=20.0,
|
||||
maxcam=50.0,
|
||||
laa_patched=True,
|
||||
camera_patched=True,
|
||||
custom_patched=True,
|
||||
)
|
||||
|
||||
with mock.patch.object(WowSetupTool, "run_vanilla_tweaks", new=fake_run):
|
||||
result_path = tool._run_vanilla_tweaks_transactional(
|
||||
root,
|
||||
tweaks_exe="fake-vanilla-tweaks.exe",
|
||||
modern_cli=True,
|
||||
)
|
||||
|
||||
self.assertEqual(result_path, output_exe)
|
||||
with open(output_exe, "rb") as handle:
|
||||
result = handle.read()
|
||||
|
||||
self.assertEqual(result[0x0C1ECF:0x0C1ED1], b"\x74\x10")
|
||||
self.assertEqual(result[0x0C2B25:0x0C2B27], b"\x74\x0B")
|
||||
self.assertEqual(result[0x3A4869], 0x14)
|
||||
self.assertEqual(result[0x126:0x128], b"\x0F\x01")
|
||||
self.assertAlmostEqual(self._read_float(result, 0x4089B4), 1.5708, places=4)
|
||||
self.assertEqual(self._read_float(result, 0x40FED8), 3000.0)
|
||||
self.assertEqual(self._read_float(result, 0x467958), 70.0)
|
||||
self.assertEqual(self._read_float(result, 0x40C448), 20.0)
|
||||
self.assertEqual(self._read_float(result, 0x4089A4), 50.0)
|
||||
self.assertEqual(result[0x435D38:0x435D3C], b"12\x00\x00")
|
||||
for offset, original, _patched in setup_tool_dynamic._CAMERA_REGIONS:
|
||||
self.assertEqual(result[offset:offset + len(original)], original)
|
||||
for offset, original, _patched in setup_tool_dynamic._CUSTOM_GLUES_SITES:
|
||||
self.assertEqual(result[offset], original)
|
||||
self.assertFalse(os.path.exists(output_exe + ".modernization-new"))
|
||||
self.assertFalse(
|
||||
any(name.startswith(".modernization-vt-") for name in os.listdir(root))
|
||||
)
|
||||
|
||||
def test_validation_gate_runs_source_preflight(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
)
|
||||
tool.wow_dir = FakeVar("C:/game")
|
||||
tool.optional_plugins = {"no1600x1200.dll": FakeVar(False)}
|
||||
tool.vmmfix_enabled = FakeVar(False)
|
||||
|
||||
with mock.patch.object(
|
||||
tool,
|
||||
"_preflight_vanilla_tweaks_source",
|
||||
return_value=None,
|
||||
) as preflight:
|
||||
self.assertTrue(tool.validate_plugin_conflicts())
|
||||
|
||||
preflight.assert_called_once_with("C:/game")
|
||||
|
||||
def test_normalization_policy_forces_one_marker_refresh(self):
|
||||
tool = self._tool(
|
||||
fov=1.9199,
|
||||
sound=64,
|
||||
quickloot=True,
|
||||
background=True,
|
||||
)
|
||||
|
||||
old_signature = WowSetupTool._vanilla_tweaks_signature(tool)
|
||||
new_signature = tool._vanilla_tweaks_signature()
|
||||
|
||||
self.assertNotIn("selected_patch_normalization", old_signature)
|
||||
self.assertEqual(new_signature["selected_patch_normalization"], 4)
|
||||
|
||||
tool.core_plugins["SuperWoWhook.dll"].set(False)
|
||||
self.assertEqual(new_signature, tool._vanilla_tweaks_signature())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,177 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import remote_packages
|
||||
from setup_tool_dynamic import ModernWowSetupTool
|
||||
|
||||
|
||||
class WowPresenceRecoveryTests(unittest.TestCase):
|
||||
def _tool(self):
|
||||
tool = ModernWowSetupTool.__new__(ModernWowSetupTool)
|
||||
tool._report_download_progress = mock.Mock()
|
||||
tool._warn_offline = mock.Mock()
|
||||
tool._valid_x86_dll = mock.Mock(return_value=True)
|
||||
return tool
|
||||
|
||||
def test_legacy_managed_wowpresence_is_repaired_offline(self):
|
||||
tool = self._tool()
|
||||
error = remote_packages.RemotePackageError("GitHub request failed: offline")
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"wowpresence_install_trust_state",
|
||||
return_value="managed_unverified",
|
||||
),
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"install_cached_wowpresence",
|
||||
) as cached,
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"install_bundled_wowpresence",
|
||||
) as bundled,
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"ensure_wowpresence_config",
|
||||
) as ensure_config,
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"cache_installed_wowpresence",
|
||||
) as seed_cache,
|
||||
):
|
||||
tool._recover_wowpresence_offline("C:/WoW", error)
|
||||
|
||||
cached.assert_called_once_with(
|
||||
"C:/WoW",
|
||||
progress=tool._report_download_progress,
|
||||
)
|
||||
bundled.assert_not_called()
|
||||
ensure_config.assert_not_called()
|
||||
seed_cache.assert_not_called()
|
||||
tool._warn_offline.assert_called_once()
|
||||
|
||||
def test_legacy_managed_state_is_marked_for_online_refresh(self):
|
||||
tool = self._tool()
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"wowpresence_install_trust_state",
|
||||
return_value="managed_unverified",
|
||||
),
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"_set_managed_manifest_values",
|
||||
) as update_manifest,
|
||||
):
|
||||
state = tool._prepare_wowpresence_managed_state("C:/WoW")
|
||||
|
||||
self.assertEqual(state, "managed_unverified")
|
||||
update_manifest.assert_called_once_with(
|
||||
"C:/WoW",
|
||||
remote_packages.WOWPRESENCE_MANAGED_ID,
|
||||
revision="__legacy_unverified__",
|
||||
)
|
||||
|
||||
def test_unmanaged_wowpresence_is_not_marked_for_refresh(self):
|
||||
tool = self._tool()
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"wowpresence_install_trust_state",
|
||||
return_value="unmanaged",
|
||||
),
|
||||
mock.patch.object(
|
||||
remote_packages,
|
||||
"_set_managed_manifest_values",
|
||||
) as update_manifest,
|
||||
):
|
||||
state = tool._prepare_wowpresence_managed_state("C:/WoW")
|
||||
|
||||
self.assertEqual(state, "unmanaged")
|
||||
update_manifest.assert_not_called()
|
||||
|
||||
def test_remote_wowpresence_failure_uses_offline_recovery(self):
|
||||
tool = self._tool()
|
||||
tool._prepare_wowpresence_managed_state = mock.Mock()
|
||||
tool._recover_wowpresence_offline = mock.Mock()
|
||||
error = remote_packages.RemotePackageError("GitHub request failed: offline")
|
||||
|
||||
with mock.patch.object(
|
||||
remote_packages,
|
||||
"install_wowpresence",
|
||||
side_effect=error,
|
||||
) as install:
|
||||
result = tool._install_wowpresence_with_fallback("C:/WoW")
|
||||
|
||||
self.assertIsNone(result)
|
||||
tool._prepare_wowpresence_managed_state.assert_called_once_with("C:/WoW")
|
||||
install.assert_called_once_with(
|
||||
"C:/WoW",
|
||||
progress=tool._report_download_progress,
|
||||
)
|
||||
tool._recover_wowpresence_offline.assert_called_once_with("C:/WoW", error)
|
||||
|
||||
def test_local_wowpresence_failure_is_not_hidden_by_fallback(self):
|
||||
tool = self._tool()
|
||||
tool._prepare_wowpresence_managed_state = mock.Mock()
|
||||
tool._recover_wowpresence_offline = mock.Mock()
|
||||
error = remote_packages.RemotePackageError(
|
||||
"Could not prepare C:/WoW/WowPresence.dll for replacement. Close WoW and try again."
|
||||
)
|
||||
|
||||
with mock.patch.object(
|
||||
remote_packages,
|
||||
"install_wowpresence",
|
||||
side_effect=error,
|
||||
):
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"WowPresence installation failed locally",
|
||||
):
|
||||
tool._install_wowpresence_with_fallback("C:/WoW")
|
||||
|
||||
tool._recover_wowpresence_offline.assert_not_called()
|
||||
|
||||
def test_wowpresence_source_failure_classifier_rejects_local_io(self):
|
||||
tool = self._tool()
|
||||
|
||||
self.assertTrue(
|
||||
tool._wowpresence_source_failure(
|
||||
remote_packages.RemotePackageError("GitHub request failed: offline")
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
tool._wowpresence_source_failure(
|
||||
remote_packages.RemotePackageError(
|
||||
"SHA-256 mismatch for downloaded file (expected a, got b)."
|
||||
)
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
tool._wowpresence_source_failure(
|
||||
remote_packages.RemotePackageError(
|
||||
"WowPresence.dll is not a valid Windows PE binary (missing MZ header)."
|
||||
)
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
tool._wowpresence_source_failure(
|
||||
remote_packages.RemotePackageError(
|
||||
"Could not prepare C:/WoW/WowPresence.dll for replacement."
|
||||
)
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
tool._wowpresence_source_failure(
|
||||
remote_packages.RemotePackageError(
|
||||
"Could not validate WowPresence.dll: permission denied"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user