Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3973c5b302 | ||
|
|
f99bd7e7b2 | ||
|
|
f0e36e7fc9 | ||
|
|
d29d6b8020 | ||
|
|
de08b39896 | ||
|
|
5cadbda323 | ||
|
|
5ebcebffdd | ||
|
|
2d4126ad5e | ||
|
|
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
|
||||||
+46
-44
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
- 'feature/client-tweaks'
|
- 'test/**'
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -12,25 +12,31 @@ on:
|
|||||||
- 'main'
|
- 'main'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: modernization-${{ github.workflow }}-${{ github.ref }}
|
group: modernization-${{ forgejo.workflow }}-${{ forgejo.event_name }}-${{ forgejo.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-2025
|
runs-on: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
# Self-hosted Windows runner: use the Python already installed on the host.
|
||||||
uses: actions/setup-python@v5
|
# actions/setup-python tries to modify machine-wide registry keys and needs
|
||||||
with:
|
# elevated rights on this runner.
|
||||||
python-version: '3.11.9'
|
- name: Verify runner Python
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$version = python -c "import platform; print(platform.python_version())"
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Python is not available on the runner PATH." }
|
||||||
|
Write-Host "Runner Python: $version"
|
||||||
|
if ($version -ne "3.12.10") {
|
||||||
|
throw "Expected Python 3.12.10 on the self-hosted runner, found $version."
|
||||||
|
}
|
||||||
|
|
||||||
- name: Install pinned build toolchain
|
- name: Install pinned build toolchain
|
||||||
run: python -m pip install --disable-pip-version-check -r requirements-build.txt
|
run: python -m pip install --disable-pip-version-check -r requirements-build.txt
|
||||||
@@ -41,13 +47,20 @@ jobs:
|
|||||||
pyinstaller --version
|
pyinstaller --version
|
||||||
python -m pip show pyinstaller pyinstaller-hooks-contrib
|
python -m pip show pyinstaller pyinstaller-hooks-contrib
|
||||||
|
|
||||||
|
# Direct pushes to test/** run validation only. PRs, main, tags and
|
||||||
|
# explicit manual runs still validate a real Windows build.
|
||||||
|
- name: Prepare bundled remote fallbacks
|
||||||
|
if: ${{ forgejo.event_name != 'push' || forgejo.ref == 'refs/heads/main' || startsWith(forgejo.ref, 'refs/tags/v') }}
|
||||||
|
run: python scripts/prepare_remote_fallbacks.py
|
||||||
|
|
||||||
- name: Syntax check
|
- 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
|
- name: Unit safety tests
|
||||||
run: python -m unittest discover -s tests -v
|
run: python -m unittest discover -s tests -v
|
||||||
|
|
||||||
- name: Build EXE
|
- name: Build EXE
|
||||||
|
if: ${{ forgejo.event_name != 'push' || forgejo.ref == 'refs/heads/main' || startsWith(forgejo.ref, 'refs/tags/v') }}
|
||||||
run: >
|
run: >
|
||||||
pyinstaller
|
pyinstaller
|
||||||
--noconsole
|
--noconsole
|
||||||
@@ -57,45 +70,34 @@ jobs:
|
|||||||
--add-data "Payload;Payload"
|
--add-data "Payload;Payload"
|
||||||
--add-data "vanilla-tweaks.exe;."
|
--add-data "vanilla-tweaks.exe;."
|
||||||
--name "WoW_Modernization_Tool"
|
--name "WoW_Modernization_Tool"
|
||||||
setup_tool_dynamic.py
|
setup_tool_responsive.py
|
||||||
|
|
||||||
- name: Verify EXE exists
|
- name: Verify EXE exists
|
||||||
shell: pwsh
|
if: ${{ forgejo.event_name != 'push' || forgejo.ref == 'refs/heads/main' || startsWith(forgejo.ref, 'refs/tags/v') }}
|
||||||
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
if (-not (Test-Path "dist/WoW_Modernization_Tool.exe")) { throw "Build output missing" }
|
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
|
Get-Item "dist/WoW_Modernization_Tool.exe" | Format-List Name,Length,LastWriteTime
|
||||||
|
|
||||||
- name: Upload test artifact
|
- name: Create ZIP package
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ forgejo.event_name != 'push' || forgejo.ref == 'refs/heads/main' || startsWith(forgejo.ref, 'refs/tags/v') }}
|
||||||
uses: actions/upload-artifact@v4
|
shell: powershell
|
||||||
|
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
|
||||||
|
|
||||||
|
# Keep the finished EXE/ZIP available for manual release publishing.
|
||||||
|
# Artifacts are uploaded for explicit manual runs and version tags.
|
||||||
|
- name: Upload build artifact
|
||||||
|
if: ${{ forgejo.event_name == 'workflow_dispatch' || startsWith(forgejo.ref, 'refs/tags/v') }}
|
||||||
|
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: WoW_Modernization_Tool-test
|
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
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
# 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' }}
|
|
||||||
uses: softprops/action-gh-release@v3
|
|
||||||
with:
|
|
||||||
tag_name: v2.0
|
|
||||||
target_commitish: ${{ github.sha }}
|
|
||||||
files: dist/WoW_Modernization_Tool.exe
|
|
||||||
fail_on_unmatched_files: true
|
|
||||||
name: "WoW Modernization Tool v2.0"
|
|
||||||
body_path: RELEASE_NOTES.md
|
|
||||||
generate_release_notes: true
|
|
||||||
previous_tag: v1.1
|
|
||||||
make_latest: true
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
||||||
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
|
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ if errorlevel 1 exit /b 1
|
|||||||
py -3.11 -m pip install --disable-pip-version-check -r requirements-build.txt
|
py -3.11 -m pip install --disable-pip-version-check -r requirements-build.txt
|
||||||
if errorlevel 1 exit /b 1
|
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
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
py -3.11 -m unittest discover -s tests -v
|
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.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"fallbacks": {
|
||||||
|
"wowpresence": {
|
||||||
|
"filename": "WowPresence.zip",
|
||||||
|
"kind": "wowpresence_zip",
|
||||||
|
"revision": "v1.3",
|
||||||
|
"sha256": "9b2dca9c69b5ab3b6dae3ef3b3efc9671779a227802ebb6ac98cb22bc6ea448a",
|
||||||
|
"size": 141423,
|
||||||
|
"source": "https://octowow.st/git/Dusk/WowPresence/releases/download/v1.3/WowPresence.zip"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generated_for": "Modernization Tool release build",
|
||||||
|
"schema": 1
|
||||||
|
}
|
||||||
+102
-11
@@ -140,17 +140,6 @@
|
|||||||
"source": "https://raw.githubusercontent.com/RetroCro/TurtleWoW-Mods/refs/heads/main/Archive/DLL%20BACKUP/perf_boost.dll",
|
"source": "https://raw.githubusercontent.com/RetroCro/TurtleWoW-Mods/refs/heads/main/Archive/DLL%20BACKUP/perf_boost.dll",
|
||||||
"version": "RetroCro main"
|
"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": {
|
"UnitXP_SP3": {
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
@@ -601,6 +590,108 @@
|
|||||||
"sha256": "7b0b0fcdb8bb9e82c5a18ab395af670f57da3e03035b27d5e244e4f8b457fdf2"
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -13,8 +13,8 @@ dxvk.trackPipelineLifetime = True
|
|||||||
# Limit compiler threads to reduce memory usage
|
# Limit compiler threads to reduce memory usage
|
||||||
dxvk.numCompilerThreads = 4
|
dxvk.numCompilerThreads = 4
|
||||||
|
|
||||||
# Allow exclusive fullscreen mode on Windows
|
# Disable exclusive fullscreen on Windows to avoid black screens during Alt+Tab
|
||||||
dxvk.allowFse = True
|
dxvk.allowFse = False
|
||||||
|
|
||||||
# Disabled because VanillaFixes sets process DPI awareness
|
# Disabled because VanillaFixes sets process DPI awareness
|
||||||
d3d9.dpiAware = False
|
d3d9.dpiAware = False
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
# 🛠️ WoW Modernization Tool
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
**[OctoGit Releases](https://octowow.st/git/Dusk/Modernization-Tool/releases)**
|
||||||
|
|
||||||
|
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://octowow.st/git/Dusk/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://octowow.st/git/Dusk/Modernization-Tool/releases)**
|
||||||
|
- **[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.
|
## ✅ Updating from v2.2
|
||||||
- **VanillaFixes + DXVK — Vulkan** — translates DirectX 9 to Vulkan and may provide smoother frame pacing on modern systems.
|
|
||||||
|
|
||||||
The bundled DXVK version is now **2.6.1 x86** with a configuration aligned with VanillaFixes.
|
Download the new executable, select your existing WoW folder and click **Apply Setup & Tweaks**.
|
||||||
|
|
||||||
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**.
|
|
||||||
|
|
||||||
A complete WoW reinstall is not required.
|
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.",
|
"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.",
|
"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.",
|
"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
|
# Tweaks Tab
|
||||||
"fov": "Calculates horizontal Field of View mathematically scaled to maintain vertical aspect space based on your screen ratio.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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).",
|
"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.",
|
"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)."
|
"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),
|
"logsessions.dll": tk.BooleanVar(value=False),
|
||||||
"minimapicons.dll": tk.BooleanVar(value=False),
|
"minimapicons.dll": tk.BooleanVar(value=False),
|
||||||
"pngscreenshots.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 = {
|
self.addon_dependencies = {
|
||||||
@@ -525,6 +527,7 @@ class WowSetupTool:
|
|||||||
"Disable one of them before applying the setup.",
|
"Disable one of them before applying the setup.",
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def load_settings(self, target_dir):
|
def load_settings(self, target_dir):
|
||||||
@@ -541,7 +544,6 @@ class WowSetupTool:
|
|||||||
self._load_legacy_install_state(target_dir)
|
self._load_legacy_install_state(target_dir)
|
||||||
self._normalize_plugin_conflicts()
|
self._normalize_plugin_conflicts()
|
||||||
self.toggle_safety_limits()
|
self.toggle_safety_limits()
|
||||||
self.update_superwow_managed_controls()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -559,7 +561,6 @@ class WowSetupTool:
|
|||||||
|
|
||||||
self._normalize_plugin_conflicts()
|
self._normalize_plugin_conflicts()
|
||||||
self.toggle_safety_limits()
|
self.toggle_safety_limits()
|
||||||
self.update_superwow_managed_controls()
|
|
||||||
|
|
||||||
if recovered_from_damage:
|
if recovered_from_damage:
|
||||||
messagebox.showwarning(
|
messagebox.showwarning(
|
||||||
@@ -716,58 +717,7 @@ class WowSetupTool:
|
|||||||
cb.pack(anchor='w', padx=10, pady=4)
|
cb.pack(anchor='w', padx=10, pady=4)
|
||||||
ToolTip(cb, self.descriptions.get(dll, ""))
|
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):
|
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 = ttk.LabelFrame(parent, text="Field of View (FoV) Calculator")
|
||||||
fov_frame.pack(fill='x', padx=10, pady=5)
|
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.pack(fill='x', padx=15, pady=0)
|
||||||
frame_nums.columnconfigure(1, weight=1)
|
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, 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, 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")
|
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)
|
cb_clear_wdb.grid(row=4, column=0, sticky='w', padx=10, pady=2)
|
||||||
ToolTip(cb_clear_wdb, self.descriptions["clear_wdb"])
|
ToolTip(cb_clear_wdb, self.descriptions["clear_wdb"])
|
||||||
|
|
||||||
self.update_superwow_managed_controls()
|
|
||||||
|
|
||||||
|
|
||||||
def build_visual_audio_tab(self, parent):
|
def build_visual_audio_tab(self, parent):
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
@@ -1082,6 +1030,10 @@ class WowSetupTool:
|
|||||||
"WOW_ENCRYPTION_KEY automatically; existing keys are never replaced)"
|
"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
|
# WeirdUtils
|
||||||
text_area.insert("end", "\n\nWeirdUtils Suite\n", "header")
|
text_area.insert("end", "\n\nWeirdUtils Suite\n", "header")
|
||||||
text_area.insert(
|
text_area.insert(
|
||||||
@@ -1302,10 +1254,45 @@ class WowSetupTool:
|
|||||||
# 3. Clean unselected Optional plugins.
|
# 3. Clean unselected Optional plugins.
|
||||||
for dll_name, var in self.optional_plugins.items():
|
for dll_name, var in self.optional_plugins.items():
|
||||||
if not var.get():
|
if not var.get():
|
||||||
remove_managed_file(
|
if dll_name == "WowPresence.dll":
|
||||||
os.path.join(target, dll_name),
|
# Only undo a WowPresence installation that this tool
|
||||||
f"managed plugin {dll_name}",
|
# 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
|
@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:
|
try:
|
||||||
# Large visual MPQs deliberately stay online-only, but they are not
|
# Visual installers own their current-version checks.
|
||||||
# downloaded again on every Apply. A revision bump in remote_packages
|
# Already valid MPQs are reused without a download.
|
||||||
# is enough to trigger one fresh download when a source/version changes.
|
|
||||||
for key, managed_id, display_name, installer in visual_defs:
|
for key, managed_id, display_name, installer in visual_defs:
|
||||||
if self.visual_mods[key].get():
|
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:
|
try:
|
||||||
installer(target, progress=progress)
|
installer(target, progress=progress)
|
||||||
except Exception as exc:
|
except remote_packages.RemoteSourceUnavailable as exc:
|
||||||
if remote_packages.managed_mod_is_installed(target, managed_id):
|
if remote_packages.managed_mpq_is_usable(target, managed_id):
|
||||||
warnings.append(
|
warnings.append(
|
||||||
f"{display_name}: update source unavailable; existing installed copy kept."
|
f"{display_name}: update source unavailable; existing installed copy kept."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(
|
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
|
) from exc
|
||||||
|
except Exception as exc:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{display_name} installation failed locally:\n{exc}"
|
||||||
|
) from exc
|
||||||
else:
|
else:
|
||||||
remote_packages.remove_managed_mod(target, managed_id)
|
remote_packages.remove_managed_mod(target, managed_id)
|
||||||
|
|
||||||
@@ -1792,12 +1779,12 @@ class WowSetupTool:
|
|||||||
escaped_script = script_path.replace("'", "''")
|
escaped_script = script_path.replace("'", "''")
|
||||||
launcher = (
|
launcher = (
|
||||||
"$ErrorActionPreference='Stop'; "
|
"$ErrorActionPreference='Stop'; "
|
||||||
|
f"$scriptPath = '{escaped_script}'; "
|
||||||
|
"$argumentLine = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File \"' "
|
||||||
|
"+ $scriptPath + '\"'; "
|
||||||
"try { "
|
"try { "
|
||||||
"$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs "
|
"$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs "
|
||||||
"-Wait -PassThru -ArgumentList @("
|
"-Wait -PassThru -ArgumentList $argumentLine; "
|
||||||
"'-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass',"
|
|
||||||
f"'-File','{escaped_script}'"
|
|
||||||
"); "
|
|
||||||
"exit $p.ExitCode "
|
"exit $p.ExitCode "
|
||||||
"} catch { Write-Error $_; exit 1 }"
|
"} catch { Write-Error $_; exit 1 }"
|
||||||
)
|
)
|
||||||
@@ -2021,6 +2008,12 @@ class WowSetupTool:
|
|||||||
vanilla_fixes_exe = os.path.join(target_dir, "VanillaFixes.exe")
|
vanilla_fixes_exe = os.path.join(target_dir, "VanillaFixes.exe")
|
||||||
modernized_exe = os.path.join(target_dir, "WoW_Modernized.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):
|
if not os.path.isfile(vanilla_fixes_exe):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"VanillaFixes.exe is missing, so the launcher shortcuts cannot be created."
|
"VanillaFixes.exe is missing, so the launcher shortcuts cannot be created."
|
||||||
@@ -2056,10 +2049,10 @@ class WowSetupTool:
|
|||||||
Set oWS = WScript.CreateObject("WScript.Shell")
|
Set oWS = WScript.CreateObject("WScript.Shell")
|
||||||
sLinkFile = "{vbs_escape(staged_shortcut)}"
|
sLinkFile = "{vbs_escape(staged_shortcut)}"
|
||||||
Set oLink = oWS.CreateShortcut(sLinkFile)
|
Set oLink = oWS.CreateShortcut(sLinkFile)
|
||||||
oLink.TargetPath = "{vbs_escape(vanilla_fixes_exe)}"
|
oLink.TargetPath = "{vbs_escape(launcher_exe)}"
|
||||||
oLink.Arguments = "WoW_Modernized.exe"
|
oLink.Arguments = "{vbs_escape(launcher_arguments)}"
|
||||||
oLink.WorkingDirectory = "{vbs_escape(target_dir)}"
|
oLink.WorkingDirectory = "{vbs_escape(target_dir)}"
|
||||||
oLink.Description = "Launch Vanilla WoW with VanillaFixes and Tweaks"
|
oLink.Description = "{vbs_escape(launcher_description)}"
|
||||||
{icon_vbs_line}
|
{icon_vbs_line}
|
||||||
oLink.Save
|
oLink.Save
|
||||||
WScript.Echo oWS.SpecialFolders("Desktop")
|
WScript.Echo oWS.SpecialFolders("Desktop")
|
||||||
@@ -2292,11 +2285,23 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
|||||||
self._park_external_renderer_file(target, filename)
|
self._park_external_renderer_file(target, filename)
|
||||||
|
|
||||||
|
|
||||||
def _managed_dll_entries(self):
|
def _managed_dll_entries(self, target=None):
|
||||||
"""Return every dlls.txt entry owned by this tool."""
|
"""Return dlls.txt entries this tool is allowed to rewrite."""
|
||||||
managed = {"dxvk"}
|
managed = {"dxvk"}
|
||||||
managed.update(name.casefold() for name in self.core_plugins)
|
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(
|
managed.update(
|
||||||
name.casefold()
|
name.casefold()
|
||||||
for name in (
|
for name in (
|
||||||
@@ -2304,14 +2309,48 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
|||||||
"AuctionQueryThrottle.dll",
|
"AuctionQueryThrottle.dll",
|
||||||
"VanillaMultiMonitorFix.dll",
|
"VanillaMultiMonitorFix.dll",
|
||||||
"Interact.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
|
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):
|
def _write_dlls_file(self, target, active_managed_lines):
|
||||||
"""Rewrite only tool-owned dlls.txt entries and preserve user entries."""
|
"""Rewrite only tool-owned dlls.txt entries and preserve user entries."""
|
||||||
dlls_path = os.path.join(target, "dlls.txt")
|
dlls_path = os.path.join(target, "dlls.txt")
|
||||||
managed_names = self._managed_dll_entries()
|
managed_names = self._managed_dll_entries(target)
|
||||||
preserved = []
|
preserved = []
|
||||||
seen_preserved = set()
|
seen_preserved = set()
|
||||||
|
|
||||||
@@ -2418,24 +2457,16 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
|||||||
return digest.hexdigest()
|
return digest.hexdigest()
|
||||||
|
|
||||||
def _vanilla_tweaks_signature(self):
|
def _vanilla_tweaks_signature(self):
|
||||||
"""Return only settings that change WoW_Modernized.exe patch output."""
|
"""Return settings that change WoW_Modernized.exe patch output."""
|
||||||
superwow_active = self._superwow_enabled()
|
|
||||||
return {
|
return {
|
||||||
"superwow_active": bool(superwow_active),
|
"fov": round(float(self.vt_fov.get()), 4),
|
||||||
"fov": None if superwow_active else round(float(self.vt_fov.get()), 4),
|
|
||||||
"farclip": int(self.vt_farclip.get()),
|
"farclip": int(self.vt_farclip.get()),
|
||||||
"frill": int(self.vt_frill.get()),
|
"frill": int(self.vt_frill.get()),
|
||||||
"nameplate": int(self.vt_nameplate.get()),
|
"nameplate": int(self.vt_nameplate.get()),
|
||||||
"sound_channels": (
|
"sound_channels": int(self.vt_soundchan.get()),
|
||||||
None if superwow_active else int(self.vt_soundchan.get())
|
|
||||||
),
|
|
||||||
"max_camera": int(self.vt_maxcam.get()),
|
"max_camera": int(self.vt_maxcam.get()),
|
||||||
"quickloot": (
|
"quickloot": bool(self.vt_quickloot.get()),
|
||||||
None if superwow_active else bool(self.vt_quickloot.get())
|
"background_sound": bool(self.vt_bg_sound.get()),
|
||||||
),
|
|
||||||
"background_sound": (
|
|
||||||
None if superwow_active else bool(self.vt_bg_sound.get())
|
|
||||||
),
|
|
||||||
"large_address_aware": bool(self.vt_laa.get()),
|
"large_address_aware": bool(self.vt_laa.get()),
|
||||||
"camera_fix": bool(self.vt_cam_fix.get()),
|
"camera_fix": bool(self.vt_cam_fix.get()),
|
||||||
"crossfaction_res": bool(self.vt_crossfaction_res.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.")
|
raise FileNotFoundError("vanilla-tweaks.exe was not found.")
|
||||||
|
|
||||||
args = [tweaks_exe]
|
args = [tweaks_exe]
|
||||||
superwow_active = self._superwow_enabled()
|
|
||||||
|
|
||||||
if modern_cli:
|
if modern_cli:
|
||||||
# tubtubs/vanilla-tweaks keeps these four patches opt-in. When
|
# tubtubs/vanilla-tweaks keeps these patches opt-in.
|
||||||
# SuperWoW is active, deliberately leave them unpatched.
|
if abs(self.vt_fov.get() - 1.5708) >= 0.0001:
|
||||||
if not superwow_active and abs(self.vt_fov.get() - 1.5708) >= 0.0001:
|
|
||||||
args.extend(["--fov", str(self.vt_fov.get()), "--fov-patch"])
|
args.extend(["--fov", str(self.vt_fov.get()), "--fov-patch"])
|
||||||
|
|
||||||
if self.vt_farclip.get() == 777:
|
if self.vt_farclip.get() == 777:
|
||||||
@@ -2476,7 +2505,7 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
|||||||
else:
|
else:
|
||||||
args.extend(["--nameplatedistance", str(self.vt_nameplate.get())])
|
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([
|
args.extend([
|
||||||
"--soundchannels",
|
"--soundchannels",
|
||||||
str(self.vt_soundchan.get()),
|
str(self.vt_soundchan.get()),
|
||||||
@@ -2486,9 +2515,9 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
|||||||
if self.vt_maxcam.get() != 50:
|
if self.vt_maxcam.get() != 50:
|
||||||
args.extend(["--maxcameradistance", str(self.vt_maxcam.get())])
|
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")
|
args.append("--quickloot")
|
||||||
if not superwow_active and self.vt_bg_sound.get():
|
if self.vt_bg_sound.get():
|
||||||
args.append("--sound-in-background")
|
args.append("--sound-in-background")
|
||||||
if not self.vt_laa.get():
|
if not self.vt_laa.get():
|
||||||
args.append("--no-largeaddressaware")
|
args.append("--no-largeaddressaware")
|
||||||
@@ -2501,30 +2530,21 @@ WScript.Echo oWS.SpecialFolders("Desktop")
|
|||||||
if not self.vt_bluemoon.get():
|
if not self.vt_bluemoon.get():
|
||||||
args.append("--no-bluemoonpatch")
|
args.append("--no-bluemoonpatch")
|
||||||
else:
|
else:
|
||||||
# Legacy bundled brndd patcher enables these older patches by
|
# Legacy bundled brndd patcher kept only as an offline fallback.
|
||||||
# default, so explicitly disable all four when SuperWoW handles them.
|
if abs(self.vt_fov.get() - 1.5708) < 0.0001:
|
||||||
if superwow_active:
|
args.append("--no-fov")
|
||||||
args.extend([
|
|
||||||
"--no-fov",
|
|
||||||
"--no-soundchannels",
|
|
||||||
"--no-quickloot",
|
|
||||||
"--no-sound-in-background",
|
|
||||||
])
|
|
||||||
else:
|
else:
|
||||||
if abs(self.vt_fov.get() - 1.5708) < 0.0001:
|
args.extend(["--fov", str(self.vt_fov.get())])
|
||||||
args.append("--no-fov")
|
|
||||||
else:
|
|
||||||
args.extend(["--fov", str(self.vt_fov.get())])
|
|
||||||
|
|
||||||
if self.vt_soundchan.get() == 12:
|
if self.vt_soundchan.get() == 12:
|
||||||
args.append("--no-soundchannels")
|
args.append("--no-soundchannels")
|
||||||
else:
|
else:
|
||||||
args.extend(["--soundchannels", str(self.vt_soundchan.get())])
|
args.extend(["--soundchannels", str(self.vt_soundchan.get())])
|
||||||
|
|
||||||
if not self.vt_quickloot.get():
|
if not self.vt_quickloot.get():
|
||||||
args.append("--no-quickloot")
|
args.append("--no-quickloot")
|
||||||
if not self.vt_bg_sound.get():
|
if not self.vt_bg_sound.get():
|
||||||
args.append("--no-sound-in-background")
|
args.append("--no-sound-in-background")
|
||||||
|
|
||||||
if self.vt_farclip.get() == 777:
|
if self.vt_farclip.get() == 777:
|
||||||
args.append("--no-farclip")
|
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