Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c905d0558e |
@@ -8,4 +8,3 @@ build/
|
||||
Wow*.exe
|
||||
*.backup.exe
|
||||
Logs/
|
||||
!Wow_Original.exe
|
||||
|
||||
+13
-17
@@ -1,24 +1,20 @@
|
||||
[submodule "vendor/warcraftxl"]
|
||||
path = vendor/warcraftxl
|
||||
url = https://github.com/WarcraftXL/wxl-core.git
|
||||
branch = main
|
||||
[submodule "vendor/modules/wxl-modern-assets"]
|
||||
path = vendor/modules/wxl-modern-assets
|
||||
url = https://github.com/WarcraftXL/wxl-modern-assets.git
|
||||
branch = main
|
||||
[submodule "vendor/modules/wxl-modern-render"]
|
||||
path = vendor/modules/wxl-modern-render
|
||||
url = https://github.com/WarcraftXL/wxl-modern-render.git
|
||||
branch = main
|
||||
[submodule "vendor/modules/wxl-unit-outline"]
|
||||
path = vendor/modules/wxl-unit-outline
|
||||
url = https://github.com/WarcraftXL/wxl-unit-outline.git
|
||||
branch = v1.1
|
||||
branch = main
|
||||
[submodule "vendor/modules/wxl-modern-adt"]
|
||||
path = vendor/modules/wxl-modern-adt
|
||||
url = https://github.com/WarcraftXL/wxl-modern-adt.git
|
||||
branch = main
|
||||
[submodule "vendor/warcraftxl"]
|
||||
path = vendor/warcraftxl
|
||||
url = https://github.com/WarcraftXL/wxl-core.git
|
||||
branch = v1.1
|
||||
[submodule "vendor/modules/wxl-db2"]
|
||||
path = vendor/modules/wxl-db2
|
||||
url = https://github.com/WarcraftXL/wxl-db2.git
|
||||
[submodule "vendor/modules/wxl-modern-m2"]
|
||||
path = vendor/modules/wxl-modern-m2
|
||||
url = https://github.com/WarcraftXL/wxl-modern-m2.git
|
||||
[submodule "vendor/modules/wxl-modern-wmo"]
|
||||
path = vendor/modules/wxl-modern-wmo
|
||||
url = https://github.com/WarcraftXL/wxl-modern-wmo.git
|
||||
[submodule "vendor/modules/wxl-grasswind"]
|
||||
path = vendor/modules/wxl-grasswind
|
||||
url = https://github.com/WarcraftXL/wxl-grasswind.git
|
||||
|
||||
+53
-63
@@ -1,77 +1,67 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(MoonWellWarcraftXL LANGUAGES CXX)
|
||||
|
||||
# WarcraftXL is an untouched upstream v1.1 submodule. Every project-specific
|
||||
# feature below is built as a separately loaded Extensions/<name>/<name>.dll.
|
||||
# WarcraftXL remains an upstream submodule. MoonWell modules are attached to
|
||||
# its aggregate DLL target here, so updating the framework does not mix our
|
||||
# client-specific offsets and policy into the upstream checkout.
|
||||
add_subdirectory(vendor/warcraftxl)
|
||||
|
||||
set(WXL_CORE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/warcraftxl")
|
||||
set(WXL_EXTERNAL_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/modules")
|
||||
set(WXL_LOCAL_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/modules")
|
||||
|
||||
function(wxl_add_external_extension extension_name extension_dir)
|
||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||
return()
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE extension_sources CONFIGURE_DEPENDS "${extension_dir}/src/*.cpp")
|
||||
if(NOT extension_sources)
|
||||
message(FATAL_ERROR "WarcraftXL extension '${extension_name}' has no sources in ${extension_dir}/src")
|
||||
endif()
|
||||
|
||||
set(extension_shared_sources "")
|
||||
if(extension_name STREQUAL "wxl-modern-m2")
|
||||
file(GLOB_RECURSE extension_shared_sources CONFIGURE_DEPENDS
|
||||
"${WXL_CORE_DIR}/src/engine/assets/shared/models/m2/*.cpp")
|
||||
elseif(extension_name STREQUAL "wxl-modern-blp")
|
||||
file(GLOB_RECURSE extension_shared_sources CONFIGURE_DEPENDS
|
||||
"${WXL_CORE_DIR}/src/engine/assets/shared/textures/blp/*.cpp")
|
||||
endif()
|
||||
|
||||
file(GLOB wxl_sdk_sources CONFIGURE_DEPENDS "${WXL_CORE_DIR}/src/game/*.cpp")
|
||||
add_library(${extension_name} SHARED
|
||||
${extension_sources}
|
||||
${extension_shared_sources}
|
||||
${wxl_sdk_sources})
|
||||
set_target_properties(${extension_name} PROPERTIES
|
||||
OUTPUT_NAME "${extension_name}"
|
||||
PREFIX "")
|
||||
target_include_directories(${extension_name} PRIVATE
|
||||
"${extension_dir}/src"
|
||||
"${WXL_CORE_DIR}/include"
|
||||
"${WXL_CORE_DIR}/src")
|
||||
target_compile_features(${extension_name} PRIVATE cxx_std_20)
|
||||
target_compile_definitions(${extension_name} PRIVATE
|
||||
WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS WXL_EXTENSION)
|
||||
|
||||
if(CLIENT_PATH)
|
||||
add_custom_command(TARGET ${extension_name} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
"${CLIENT_PATH}/Extensions/${extension_name}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:${extension_name}>"
|
||||
"${CLIENT_PATH}/Extensions/${extension_name}/${extension_name}.dll"
|
||||
COMMENT "Deploy ${extension_name}.dll -> Extensions/${extension_name}")
|
||||
endif()
|
||||
function(wxl_collect_sources output)
|
||||
file(GLOB_RECURSE collected CONFIGURE_DEPENDS ${ARGN})
|
||||
set(${output} "${collected}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Official WarcraftXL 1.1 extensions.
|
||||
wxl_add_external_extension(wxl-db2 "${WXL_EXTERNAL_MODULES_DIR}/wxl-db2")
|
||||
wxl_add_external_extension(wxl-modern-adt "${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt")
|
||||
wxl_add_external_extension(wxl-modern-blp "${WXL_LOCAL_MODULES_DIR}/wxl-modern-blp")
|
||||
wxl_add_external_extension(wxl-modern-m2 "${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-m2")
|
||||
wxl_add_external_extension(wxl-modern-wmo "${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-wmo")
|
||||
wxl_add_external_extension(wxl-grasswind "${WXL_EXTERNAL_MODULES_DIR}/wxl-grasswind")
|
||||
wxl_add_external_extension(wxl-unit-outline "${WXL_EXTERNAL_MODULES_DIR}/wxl-unit-outline")
|
||||
if(TARGET WarcraftXL)
|
||||
wxl_collect_sources(WXL_MODERN_ASSETS_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/src/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/shared/*.cpp")
|
||||
if(WXL_MODERN_ASSETS_EXCLUDE_REGEX)
|
||||
list(FILTER WXL_MODERN_ASSETS_RUNTIME EXCLUDE REGEX
|
||||
"${WXL_MODERN_ASSETS_EXCLUDE_REGEX}")
|
||||
endif()
|
||||
wxl_collect_sources(WXL_MODERN_RENDER_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-render/src/*.cpp")
|
||||
wxl_collect_sources(WXL_UNIT_OUTLINE_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-unit-outline/src/*.cpp")
|
||||
wxl_collect_sources(WXL_MODERN_ADT_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/src/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/shared/*.cpp")
|
||||
|
||||
# MoonWell-owned extensions. wxl-fdid-moonwell sorts after wxl-db2 and before
|
||||
# the modern asset extensions, so it can layer custom CSV mappings over wxl.fdid.
|
||||
wxl_add_external_extension(MoonWell "${WXL_LOCAL_MODULES_DIR}/moonwell")
|
||||
wxl_add_external_extension(wxl-fdid-moonwell "${WXL_LOCAL_MODULES_DIR}/moonwell-fdid")
|
||||
wxl_add_external_extension(wxl-moonwell-storage-fallback
|
||||
"${WXL_LOCAL_MODULES_DIR}/moonwell-storage-fallback")
|
||||
target_sources(WarcraftXL PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/moonwell/src/MoonWell.cpp"
|
||||
${WXL_MODERN_ASSETS_RUNTIME}
|
||||
${WXL_MODERN_RENDER_RUNTIME}
|
||||
${WXL_UNIT_OUTLINE_RUNTIME}
|
||||
${WXL_MODERN_ADT_RUNTIME})
|
||||
|
||||
# Recovery loader retained for existing -NativeRenderer workflows. WarcraftXL
|
||||
# 1.1 already ships a native D3D9 forwarding proxy as its standard renderer.
|
||||
# wxl-modern-render needs its module-root includes and D3D12 import library.
|
||||
include("${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-render/module.cmake")
|
||||
endif()
|
||||
|
||||
if(TARGET WarcraftXLHost)
|
||||
wxl_collect_sources(WXL_MOONWELL_HOST
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/moonwell/host/*.cpp")
|
||||
wxl_collect_sources(WXL_MODERN_ASSETS_HOST
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/host/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/shared/*.cpp")
|
||||
if(WXL_MODERN_ASSETS_EXCLUDE_REGEX)
|
||||
list(FILTER WXL_MODERN_ASSETS_HOST EXCLUDE REGEX
|
||||
"${WXL_MODERN_ASSETS_EXCLUDE_REGEX}")
|
||||
endif()
|
||||
wxl_collect_sources(WXL_MODERN_ADT_HOST
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/host/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/shared/*.cpp")
|
||||
|
||||
target_sources(WarcraftXLHost PRIVATE
|
||||
${WXL_MOONWELL_HOST}
|
||||
${WXL_MODERN_ASSETS_HOST}
|
||||
${WXL_MODERN_ADT_HOST})
|
||||
endif()
|
||||
|
||||
# Minimal native-D3D9 proxy retained as a recovery renderer. The regular
|
||||
# package uses WarcraftXL's D3D9On12 proxy required by wxl-modern-render.
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||
add_library(MoonWellLoader SHARED
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/loader/d3d9.cpp"
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
"key": "my_mount",
|
||||
"name": "My Mount",
|
||||
"name_ru": "Мой транспорт",
|
||||
"race_mask": 1101,
|
||||
"icon": "my_icon.jpg",
|
||||
"archive": "Creature/MyMount",
|
||||
"model_file": "my_mount.m2",
|
||||
"template_display_id": 2404,
|
||||
@@ -67,6 +69,23 @@
|
||||
компаньон-кот с display ID `5448`. Шаблон задаёт физические размеры, звук и прочие поля DBC;
|
||||
параметры объекта позволяют их переопределить.
|
||||
|
||||
Для `pets` поле `race_mask` ограничивает строку коллекции набором рас (`1101` — Альянс,
|
||||
`690` — Орда, `0` или отсутствие поля — все расы). Поле `icon` указывает JPG, PNG или TGA рядом
|
||||
с моделью либо путь от корня проекта. Сборщик конвертирует изображение в BLP2, выдаёт стабильный
|
||||
`spellIconID`, добавляет запись в `SpellIcon.dbc` через MoonWell provider и назначает её summon-spell.
|
||||
|
||||
## Конвертация иконок
|
||||
|
||||
Встроенная команда CLI преобразует JPG, PNG или TGA в совместимый с клиентом BLP2:
|
||||
|
||||
```powershell
|
||||
tool.exe icon "путь/к/icon.jpg" ["путь/к/icon.blp"]
|
||||
```
|
||||
|
||||
Если выходной путь не указан, `.blp` создаётся рядом с исходным файлом. Конвертер автоматически
|
||||
приводит изображение к ближайшему квадратному размеру степени двойки (не более 512×512), кодирует
|
||||
его в DXT1 и создаёт mipmap-уровни. Например, иконка 56×56 будет преобразована в 64×64.
|
||||
|
||||
## Установка на сервер
|
||||
|
||||
Для каждого элемента `pets` сборщик автоматически выдаёт `creatureEntry`, `summonSpellID` и
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Исходники клиентских изменений MoonWell для World of Warcraft 3.3.5a (build 12340).
|
||||
|
||||
Клиент переведён на [WarcraftXL](https://github.com/WarcraftXL/wxl-core) **1.1.220**: `Wow.exe` больше не содержит
|
||||
Клиент переведён на [WarcraftXL](https://github.com/WarcraftXL): `Wow.exe` больше не содержит
|
||||
MoonWell-патчей. Неподписанный оригинальный executable загружается как обычно, локальный
|
||||
`d3d9.dll` proxy подхватывает `WarcraftXL.dll`, а модуль MoonWell применяет проверенные изменения
|
||||
только к памяти запущенного процесса.
|
||||
@@ -14,25 +14,25 @@ MoonWell-патчей. Неподписанный оригинальный execu
|
||||
- одиннадцатое значение `GetCharacterInfo` — `charFlags`, включая флаг предателя `0x40000000`;
|
||||
- загрузка WarcraftXL без import-table patch и без отдельного injector.
|
||||
|
||||
При установке сборщик включает Large Address Aware в копии `Wow.exe`; исходный
|
||||
`Wow_Original.exe` остаётся неизменным и используется как проверенный шаблон.
|
||||
Large Address Aware намеренно не включён: Windows читает этот PE-флаг до загрузки DLL, поэтому
|
||||
сохранить его и одновременно оставить файл байт-в-байт оригинальным невозможно. Это означает
|
||||
стандартный 2-ГБ лимит адресного пространства для 32-битного клиента.
|
||||
|
||||
## Структура
|
||||
|
||||
```text
|
||||
modules/ отдельные MoonWell extensions для WarcraftXL 1.1
|
||||
modules/moonwell/ MoonWell runtime-модуль WarcraftXL
|
||||
vendor/warcraftxl/ закреплённый upstream git submodule
|
||||
vendor/modules/ закреплённые модули WarcraftXL
|
||||
src/Data/ исходники MPQ-патчей
|
||||
tool/ сборщик MPQ
|
||||
build-warcraftxl.ps1 сборка Win32 runtime, proxy и extensions
|
||||
build-warcraftxl.ps1 сборка Win32 runtime/proxy и x64 asset host
|
||||
run.ps1 полная сборка, установка и запуск клиента
|
||||
Wow_Original.exe локальный оригинал build 12340 (не хранится в Git)
|
||||
```
|
||||
|
||||
Подключены совместимые с 1.1 extensions: `wxl-db2`, `wxl-modern-adt`, `wxl-modern-m2`,
|
||||
`wxl-modern-wmo`, `wxl-grasswind` и `wxl-unit-outline`. BLP-адаптер и MoonWell-изменения
|
||||
собираются как отдельные DLL; исходники upstream core не изменяются.
|
||||
Подключены модули `wxl-modern-assets`, `wxl-modern-render`, `wxl-unit-outline` и
|
||||
`wxl-modern-adt`. Их точные ревизии вместе с ревизией ядра закреплены git submodule.
|
||||
Для обновления всех зависимостей WarcraftXL:
|
||||
|
||||
```powershell
|
||||
@@ -44,7 +44,7 @@ git submodule update --remote vendor/warcraftxl vendor/modules/*
|
||||
|
||||
## Требования
|
||||
|
||||
- Visual Studio 2022 с C++ toolchain для Win32;
|
||||
- Visual Studio 2022 с C++ toolchain для Win32 и x64;
|
||||
- CMake 3.20+;
|
||||
- Rust/Cargo для существующего MPQ-сборщика;
|
||||
- локальный оригинальный `Wow_Original.exe` с SHA-256
|
||||
@@ -64,7 +64,7 @@ git submodule update --init --recursive
|
||||
.\deploy.ps1
|
||||
```
|
||||
|
||||
Скрипт инициализирует зависимости, собирает MPQ-патчи, Win32 runtime, native D3D9 proxy и extensions,
|
||||
Скрипт инициализирует зависимости, собирает MPQ-патчи, Win32 runtime, D3D9 proxy и x64 Host,
|
||||
обновляет `dist`, устанавливает комплект в клиент и формирует `manifest.json`. Путь берётся из
|
||||
`WOW_HOME`/`.env`, а при их отсутствии используется `C:\Program Files (x86)\World of Warcraft`.
|
||||
|
||||
@@ -77,6 +77,8 @@ git submodule update --init --recursive
|
||||
# Быстро обновить только WarcraftXL без пересборки MPQ
|
||||
.\deploy.ps1 -SkipDataBuild
|
||||
|
||||
# Аварийный native D3D9 вместо D3D9On12
|
||||
.\deploy.ps1 -NativeRenderer
|
||||
```
|
||||
|
||||
Без установки в клиент:
|
||||
@@ -97,8 +99,18 @@ git submodule update --init --recursive
|
||||
1. проверяет хеш `Wow_Original.exe`;
|
||||
2. сохраняет прежний модифицированный клиент как `Wow.moonwell-patched.backup.exe`;
|
||||
3. восстанавливает оригинальный `Wow.exe`;
|
||||
4. устанавливает `WarcraftXL.dll`, native D3D9 proxy и DLL в `Extensions\`;
|
||||
5. при `-PackagePath` кладёт полный runtime-комплект в `dist` для launcher/S3.
|
||||
4. устанавливает `WarcraftXL.dll`, D3D9On12 proxy и `Utils\WarcraftXLHost.exe`;
|
||||
5. монтирует шейдеры modern ADT как loose patch `Data\Patch-WXL.MPQ`;
|
||||
6. при `-PackagePath` кладёт полный runtime-комплект в `dist` для launcher/S3.
|
||||
|
||||
Основной proxy использует D3D9On12, необходимый `wxl-modern-render`. Эффекты постобработки в самом
|
||||
модуле по умолчанию выключены. Для диагностики или несовместимой видеосистемы можно собрать и
|
||||
установить native-переходник без modern-render:
|
||||
|
||||
```powershell
|
||||
.\build-warcraftxl.ps1 -Configuration Release -NativeRenderer `
|
||||
-ClientPath 'C:\Program Files (x86)\World of Warcraft' -Deploy
|
||||
```
|
||||
|
||||
Резервная DLL всегда также сохраняется как `Utils\d3d9-native.dll`.
|
||||
|
||||
@@ -109,7 +121,8 @@ $env:WOW_HOME = 'C:\Program Files (x86)\World of Warcraft'
|
||||
.\run.ps1
|
||||
```
|
||||
|
||||
Диагностика запуска находится в `Logs\wxl-core.log` и `Logs\d3d9proxy.log` внутри клиента.
|
||||
Диагностика запуска находится в `Logs\wxl-core.log`, `Logs\d3d9proxy.log` и
|
||||
`Utils\WarcraftXLHost.log` внутри клиента.
|
||||
|
||||
## MPQ-пакеты
|
||||
|
||||
@@ -125,6 +138,6 @@ WarcraftXL не требуется и в объектное хранилище
|
||||
|
||||
## Лицензирование
|
||||
|
||||
WarcraftXL распространяется по GPL-3.0 и подключён как отдельный неизменённый upstream submodule.
|
||||
MoonWell extensions распространяются с доступным соответствующим исходным кодом. Репозиторий не
|
||||
должен публиковать Blizzard assets или `Wow.exe`.
|
||||
WarcraftXL распространяется по GPL-3.0 и подключён как отдельный upstream submodule. MoonWell-модуль,
|
||||
скомпонованный в `WarcraftXL.dll`, должен распространяться с соблюдением GPL-3.0 и доступным
|
||||
соответствующим исходным кодом. Репозиторий не должен публиковать Blizzard assets или `Wow.exe`.
|
||||
|
||||
Binary file not shown.
+27
-136
@@ -8,73 +8,6 @@ param(
|
||||
[switch]$NativeRenderer
|
||||
)
|
||||
|
||||
function Enable-LargeAddressAware {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
||||
|
||||
if ($bytes.Length -lt 64) {
|
||||
throw "File is too small to be a valid PE executable: $Path"
|
||||
}
|
||||
|
||||
# DOS header: MZ
|
||||
if ($bytes[0] -ne 0x4D -or $bytes[1] -ne 0x5A) {
|
||||
throw "DOS MZ signature was not found: $Path"
|
||||
}
|
||||
|
||||
# IMAGE_DOS_HEADER.e_lfanew
|
||||
$peOffset = [BitConverter]::ToInt32($bytes, 0x3C)
|
||||
|
||||
if ($peOffset -lt 0 -or ($peOffset + 24) -gt $bytes.Length) {
|
||||
throw "Invalid PE header offset in: $Path"
|
||||
}
|
||||
|
||||
# PE\0\0 signature
|
||||
$peSignature = [BitConverter]::ToUInt32($bytes, $peOffset)
|
||||
|
||||
if ($peSignature -ne 0x00004550) {
|
||||
throw "PE signature was not found: $Path"
|
||||
}
|
||||
|
||||
# IMAGE_FILE_HEADER starts after the 4-byte PE signature.
|
||||
# Characteristics is at offset 18 inside IMAGE_FILE_HEADER.
|
||||
$characteristicsOffset = $peOffset + 4 + 18
|
||||
$characteristics = [BitConverter]::ToUInt16(
|
||||
$bytes,
|
||||
$characteristicsOffset
|
||||
)
|
||||
|
||||
$newCharacteristics = [uint16](
|
||||
$characteristics -bor 0x0020
|
||||
)
|
||||
|
||||
if ($newCharacteristics -ne $characteristics) {
|
||||
$encoded = [BitConverter]::GetBytes($newCharacteristics)
|
||||
|
||||
$bytes[$characteristicsOffset] = $encoded[0]
|
||||
$bytes[$characteristicsOffset + 1] = $encoded[1]
|
||||
|
||||
[System.IO.File]::WriteAllBytes($Path, $bytes)
|
||||
|
||||
Write-Host (
|
||||
"LAA enabled for {0}: 0x{1:X4} -> 0x{2:X4}" -f
|
||||
$Path,
|
||||
$characteristics,
|
||||
$newCharacteristics
|
||||
) -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host (
|
||||
"LAA is already enabled for {0}: 0x{1:X4}" -f
|
||||
$Path,
|
||||
$characteristics
|
||||
) -ForegroundColor DarkGreen
|
||||
}
|
||||
}
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = $PSScriptRoot
|
||||
$cmake = 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe'
|
||||
@@ -88,6 +21,7 @@ if (-not (Test-Path -LiteralPath $cmake)) {
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to initialize WarcraftXL submodules.' }
|
||||
|
||||
$win32BuildDir = Join-Path $repoRoot 'build\warcraftxl-win32'
|
||||
$hostBuildDir = Join-Path $repoRoot 'build\warcraftxl-host-x64'
|
||||
$stockExe = Join-Path $repoRoot 'Wow_Original.exe'
|
||||
|
||||
if ($Deploy -or -not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
@@ -103,81 +37,37 @@ if ($Deploy -or -not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
|
||||
& $cmake -S $repoRoot -B $win32BuildDir -A Win32 '-DCLIENT_PATH='
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Win32 configure failed.' }
|
||||
& $cmake --build $win32BuildDir --config $Configuration --parallel 4
|
||||
& $cmake --build $win32BuildDir --config $Configuration --target WarcraftXL d3d9 MoonWellLoader
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Win32 build failed.' }
|
||||
|
||||
$win32ArtifactDir = Join-Path $win32BuildDir "vendor\warcraftxl\$Configuration"
|
||||
$warcraftXL = Join-Path $win32ArtifactDir 'WarcraftXL.dll'
|
||||
$selectedProxy = Join-Path $win32ArtifactDir 'd3d9.dll'
|
||||
$recoveryProxy = Join-Path $win32BuildDir "artifacts\$Configuration\d3d9-native.dll"
|
||||
$extensionNames = @(
|
||||
'MoonWell',
|
||||
'wxl-db2',
|
||||
'wxl-fdid-moonwell',
|
||||
'wxl-grasswind',
|
||||
'wxl-modern-adt',
|
||||
'wxl-modern-blp',
|
||||
'wxl-modern-m2',
|
||||
'wxl-modern-wmo',
|
||||
'wxl-moonwell-storage-fallback',
|
||||
'wxl-unit-outline'
|
||||
)
|
||||
& $cmake -S $repoRoot -B $hostBuildDir -A x64 '-DWXL_BUILD_HOST=ON' '-DCLIENT_PATH='
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Host configure failed.' }
|
||||
& $cmake --build $hostBuildDir --config $Configuration --target WarcraftXLHost
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Host build failed.' }
|
||||
|
||||
if ($NativeRenderer) {
|
||||
Write-Warning '-NativeRenderer is no longer needed: WarcraftXL 1.1 uses native D3D9 by default.'
|
||||
}
|
||||
$win32ArtifactDir = Join-Path $win32BuildDir "vendor\warcraftxl\$Configuration"
|
||||
$hostArtifactDir = Join-Path $hostBuildDir "vendor\warcraftxl\$Configuration"
|
||||
$warcraftXL = Join-Path $win32ArtifactDir 'WarcraftXL.dll'
|
||||
$modernProxy = Join-Path $win32ArtifactDir 'd3d9.dll'
|
||||
$nativeProxy = Join-Path $win32BuildDir "artifacts\$Configuration\d3d9-native.dll"
|
||||
$hostExe = Join-Path $hostArtifactDir 'WarcraftXLHost.exe'
|
||||
$selectedProxy = if ($NativeRenderer) { $nativeProxy } else { $modernProxy }
|
||||
$adtResources = Join-Path $repoRoot 'vendor\modules\wxl-modern-adt\_resources'
|
||||
|
||||
function Install-WarcraftXLArtifacts {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Destination
|
||||
)
|
||||
param([Parameter(Mandatory)][string]$Destination)
|
||||
|
||||
$utils = Join-Path $Destination 'Utils'
|
||||
New-Item `
|
||||
-ItemType Directory `
|
||||
-Path $Destination, $utils `
|
||||
-Force |
|
||||
Out-Null
|
||||
$loosePatch = Join-Path $Destination 'Data\Patch-WXL.MPQ'
|
||||
New-Item -ItemType Directory -Path $Destination, $utils, $loosePatch -Force | Out-Null
|
||||
|
||||
$legacyHost = Join-Path $utils 'WarcraftXLHost.exe'
|
||||
if (Test-Path -LiteralPath $legacyHost -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $legacyHost -Force
|
||||
Write-Host "Removed obsolete WarcraftXL 1.0 host: $legacyHost"
|
||||
}
|
||||
|
||||
$destinationExe = Join-Path $Destination 'Wow.exe'
|
||||
|
||||
Copy-Item `
|
||||
-LiteralPath $stockExe `
|
||||
-Destination $destinationExe `
|
||||
-Force
|
||||
|
||||
Enable-LargeAddressAware -Path $destinationExe
|
||||
|
||||
Copy-Item `
|
||||
-LiteralPath $warcraftXL `
|
||||
-Destination (Join-Path $Destination 'WarcraftXL.dll') `
|
||||
-Force
|
||||
|
||||
Copy-Item `
|
||||
-LiteralPath $selectedProxy `
|
||||
-Destination (Join-Path $Destination 'd3d9.dll') `
|
||||
-Force
|
||||
|
||||
Copy-Item `
|
||||
-LiteralPath $recoveryProxy `
|
||||
-Destination (Join-Path $utils 'd3d9-native.dll') `
|
||||
-Force
|
||||
|
||||
foreach ($extensionName in $extensionNames) {
|
||||
$extensionSource = Join-Path $win32BuildDir "$Configuration\$extensionName.dll"
|
||||
$extensionDestination = Join-Path $Destination "Extensions\$extensionName"
|
||||
New-Item -ItemType Directory -Path $extensionDestination -Force | Out-Null
|
||||
Copy-Item `
|
||||
-LiteralPath $extensionSource `
|
||||
-Destination (Join-Path $extensionDestination "$extensionName.dll") `
|
||||
-Force
|
||||
Copy-Item -LiteralPath $stockExe -Destination (Join-Path $Destination 'Wow.exe') -Force
|
||||
Copy-Item -LiteralPath $warcraftXL -Destination (Join-Path $Destination 'WarcraftXL.dll') -Force
|
||||
Copy-Item -LiteralPath $selectedProxy -Destination (Join-Path $Destination 'd3d9.dll') -Force
|
||||
Copy-Item -LiteralPath $nativeProxy -Destination (Join-Path $utils 'd3d9-native.dll') -Force
|
||||
Copy-Item -LiteralPath $hostExe -Destination (Join-Path $utils 'WarcraftXLHost.exe') -Force
|
||||
Get-ChildItem -LiteralPath $adtResources | ForEach-Object {
|
||||
Copy-Item -LiteralPath $_.FullName -Destination $loosePatch -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +93,7 @@ if ($Deploy) {
|
||||
}
|
||||
|
||||
Install-WarcraftXLArtifacts -Destination $ClientPath
|
||||
Write-Host "Installed WarcraftXL 1.1 runtime and extensions in $ClientPath"
|
||||
Write-Host "Installed WarcraftXL runtime, host and modules in $ClientPath"
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
@@ -212,4 +102,5 @@ if (-not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
Write-Host "Packaged stock Wow.exe and WarcraftXL artifacts in $PackagePath"
|
||||
}
|
||||
|
||||
Write-Host "WarcraftXL 1.1 build complete (native D3D9 proxy): $win32ArtifactDir"
|
||||
$renderer = if ($NativeRenderer) { 'native D3D9 recovery proxy' } else { 'WarcraftXL D3D9On12 proxy' }
|
||||
Write-Host "WarcraftXL build complete ($renderer): $win32ArtifactDir"
|
||||
|
||||
@@ -20,7 +20,6 @@ IGNORED_TOP_LEVEL_DIRS = {
|
||||
IGNORED_DIRS_ANYWHERE = {
|
||||
".git",
|
||||
".moonwell_launcher",
|
||||
".vscode"
|
||||
}
|
||||
IGNORED_TOP_LEVEL_FILES = {
|
||||
"Wow.moonwell-patched.backup.exe",
|
||||
|
||||
+27
-1
@@ -5,10 +5,36 @@
|
||||
"creature_entry_id_start": 5000000,
|
||||
"summon_spell_id_start": 110000,
|
||||
"skill_line_ability_id_start": 30000,
|
||||
"spell_icon_id_start": 50000,
|
||||
"defaults": {
|
||||
"mount_template_display_id": 2404,
|
||||
"pet_template_display_id": 5448
|
||||
},
|
||||
"mounts": [],
|
||||
"pets": []
|
||||
"pets": [
|
||||
{
|
||||
"folder": "src/Data/patch-Z/Creature/Catslime",
|
||||
"name": "Cat Slime",
|
||||
"name_ru": "Котослизень",
|
||||
"display_scale": 0.3
|
||||
},
|
||||
{
|
||||
"folder": "src/Data/patch-Z/Creature/MoonkinHatchling_Alliance",
|
||||
"key": "moonkin_hatchling_alliance",
|
||||
"race_mask": 1101,
|
||||
"icon": "inv_misc_petmoonkinne.jpg",
|
||||
"name": "Moonkin Hatchling",
|
||||
"name_ru": "Лунный совушек",
|
||||
"display_scale": 0.3
|
||||
},
|
||||
{
|
||||
"folder": "src/Data/patch-Z/Creature/MoonkinHatchling_Horde",
|
||||
"key": "moonkin_hatchling_horde",
|
||||
"race_mask": 690,
|
||||
"icon": "inv_misc_petmoonkinta.jpg",
|
||||
"name": "Moonkin Hatchling",
|
||||
"name_ru": "Лунный совушек",
|
||||
"display_scale": 0.3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,22 @@
|
||||
"creature_entry": 5000000,
|
||||
"summon_spell_id": 110000,
|
||||
"skill_line_ability_id": 30000
|
||||
},
|
||||
"moonkin_hatchling_alliance": {
|
||||
"model_data_id": 50001,
|
||||
"display_info_id": 50001,
|
||||
"creature_entry": 5000001,
|
||||
"summon_spell_id": 110001,
|
||||
"skill_line_ability_id": 30001,
|
||||
"spell_icon_id": 50000
|
||||
},
|
||||
"moonkin_hatchling_horde": {
|
||||
"model_data_id": 50002,
|
||||
"display_info_id": 50002,
|
||||
"creature_entry": 5000002,
|
||||
"summon_spell_id": 110002,
|
||||
"skill_line_ability_id": 30002,
|
||||
"spell_icon_id": 50001
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -58,6 +58,9 @@ if ($runningClients.Count) {
|
||||
Write-Host 'Stopping the running WoW client...'
|
||||
$runningClients | Stop-Process -Force
|
||||
Start-Sleep -Seconds 2
|
||||
Get-Process WarcraftXLHost -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Path -and $_.Path.StartsWith($ClientPath, [System.StringComparison]::OrdinalIgnoreCase)
|
||||
} | Stop-Process -Force
|
||||
}
|
||||
|
||||
$env:WOW_HOME = $ClientPath
|
||||
@@ -117,12 +120,12 @@ if (-not $SkipManifest) {
|
||||
}
|
||||
|
||||
$wowHash = (Get-FileHash -LiteralPath (Join-Path $ClientPath 'Wow.exe') -Algorithm SHA256).Hash
|
||||
$renderer = 'WarcraftXL 1.1 native D3D9 proxy'
|
||||
$renderer = if ($NativeRenderer) { 'native D3D9 recovery proxy' } else { 'WarcraftXL D3D9On12' }
|
||||
Write-Host ''
|
||||
Write-Host 'Deployment complete.'
|
||||
Write-Host " Renderer: $renderer"
|
||||
Write-Host " Wow.exe SHA-256: $wowHash"
|
||||
Write-Host " Extensions: $(Join-Path $ClientPath 'Extensions')"
|
||||
Write-Host " Host: $(Join-Path $ClientPath 'Utils\WarcraftXLHost.exe')"
|
||||
|
||||
if ($Launch) {
|
||||
Write-Host 'Launching Wow.exe...'
|
||||
|
||||
+3
-2
@@ -50,8 +50,9 @@ extern "C" HRESULT WINAPI Direct3DCreate9Ex(UINT sdkVersion, IDirect3D9Ex** outp
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
// Legacy compatibility exports retained for the recovery proxy. WarcraftXL 1.1
|
||||
// uses native D3D9 and ignores the former D3D9On12 bridge.
|
||||
// Compatibility exports referenced by the current WarcraftXL core. MoonWell
|
||||
// does not enable its D3D9On12 backend, so these intentionally report no D3D12
|
||||
// device and a neutral supersampling factor.
|
||||
extern "C" void* WxlD3D12Device() { return nullptr; }
|
||||
extern "C" void* WxlD3D12Queue() { return nullptr; }
|
||||
extern "C" void WxlD3D12DrainDebug() {}
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// FileDataID overlay for selected retail assets shipped in MoonWell patches.
|
||||
|
||||
#include "game/Io.hpp"
|
||||
#include "wxl/FdidApi.h"
|
||||
#include "wxl/PluginApi.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace moonwell::fdid
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::string_view kFileDataMap = "WXLFileData.csv";
|
||||
|
||||
std::once_flag g_loadOnce;
|
||||
std::unordered_map<uint32_t, std::string> g_paths;
|
||||
const WXL_Api* g_api = nullptr;
|
||||
const char*(__cdecl* g_resolveTexture)(uint32_t) = nullptr;
|
||||
const char*(__cdecl* g_resolveModel)(uint32_t) = nullptr;
|
||||
|
||||
bool ReadAll(const char* path, std::vector<uint8_t>& out)
|
||||
{
|
||||
void* handle = nullptr;
|
||||
if (!wxl::game::io::FileOpen(path, wxl::game::io::kOpenWholeFile, &handle) || !handle)
|
||||
return false;
|
||||
uint32_t high = 0;
|
||||
const uint32_t size = wxl::game::io::FileSize(handle, &high);
|
||||
if (high != 0)
|
||||
{
|
||||
wxl::game::io::FileClose(handle);
|
||||
return false;
|
||||
}
|
||||
out.resize(size);
|
||||
uint32_t read = 0;
|
||||
const bool ok = (size == 0 || wxl::game::io::FileRead(handle, out.data(), size, &read))
|
||||
&& read == size;
|
||||
wxl::game::io::FileClose(handle);
|
||||
if (!ok) out.clear();
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::string_view Trim(std::string_view value)
|
||||
{
|
||||
while (!value.empty() && (value.front() == ' ' || value.front() == '\t' ||
|
||||
value.front() == '\r' || value.front() == '\n'))
|
||||
value.remove_prefix(1);
|
||||
while (!value.empty() && (value.back() == ' ' || value.back() == '\t' ||
|
||||
value.back() == '\r' || value.back() == '\n'))
|
||||
value.remove_suffix(1);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Load()
|
||||
{
|
||||
std::vector<uint8_t> bytes;
|
||||
if (!ReadAll(kFileDataMap.data(), bytes))
|
||||
{
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_WARN, "MoonWellFdid", "FileDataID map '%s' was not found",
|
||||
kFileDataMap.data());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view text(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
size_t lineStart = 0;
|
||||
while (lineStart < text.size())
|
||||
{
|
||||
size_t lineEnd = text.find('\n', lineStart);
|
||||
if (lineEnd == std::string_view::npos) lineEnd = text.size();
|
||||
std::string_view line = Trim(text.substr(lineStart, lineEnd - lineStart));
|
||||
lineStart = lineEnd + 1;
|
||||
|
||||
if (line.empty() || line.front() == '#') continue;
|
||||
const size_t comma = line.find(',');
|
||||
if (comma == std::string_view::npos) continue;
|
||||
|
||||
const std::string_view idText = Trim(line.substr(0, comma));
|
||||
std::string_view pathText = Trim(line.substr(comma + 1));
|
||||
uint32_t fileDataId = 0;
|
||||
const auto parsed = std::from_chars(idText.data(), idText.data() + idText.size(), fileDataId);
|
||||
if (parsed.ec != std::errc{} || parsed.ptr != idText.data() + idText.size() ||
|
||||
fileDataId == 0 || pathText.empty())
|
||||
continue;
|
||||
|
||||
std::string path(pathText);
|
||||
for (char& c : path) if (c == '/') c = '\\';
|
||||
g_paths[fileDataId] = std::move(path);
|
||||
}
|
||||
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_INFO, "MoonWellFdid", "loaded %zu custom FileDataID path(s)",
|
||||
g_paths.size());
|
||||
}
|
||||
|
||||
const char* ResolveCustom(uint32_t fileDataId)
|
||||
{
|
||||
std::call_once(g_loadOnce, &Load);
|
||||
const auto found = g_paths.find(fileDataId);
|
||||
return found == g_paths.end() ? nullptr : found->second.c_str();
|
||||
}
|
||||
|
||||
const char* __cdecl ResolveTexture(uint32_t fileDataId)
|
||||
{
|
||||
if (const char* custom = ResolveCustom(fileDataId)) return custom;
|
||||
return g_resolveTexture ? g_resolveTexture(fileDataId) : nullptr;
|
||||
}
|
||||
|
||||
const char* __cdecl ResolveModel(uint32_t fileDataId)
|
||||
{
|
||||
if (const char* custom = ResolveCustom(fileDataId)) return custom;
|
||||
return g_resolveModel ? g_resolveModel(fileDataId) : nullptr;
|
||||
}
|
||||
|
||||
bool PatchResolver(WXL_FdidApi* fdid)
|
||||
{
|
||||
if (!fdid || fdid->apiVersion != WXL_FDID_API_VERSION) return false;
|
||||
g_resolveTexture = fdid->ResolveTexture;
|
||||
g_resolveModel = fdid->ResolveModel;
|
||||
|
||||
DWORD oldProtection = 0;
|
||||
if (!VirtualProtect(fdid, sizeof(*fdid), PAGE_READWRITE, &oldProtection)) return false;
|
||||
fdid->ResolveTexture = &ResolveTexture;
|
||||
fdid->ResolveModel = &ResolveModel;
|
||||
DWORD ignored = 0;
|
||||
VirtualProtect(fdid, sizeof(*fdid), oldProtection, &ignored);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "MoonWellFdid", 1, WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION) return 0;
|
||||
moonwell::fdid::g_api = api;
|
||||
auto* fdid = static_cast<WXL_FdidApi*>(api->GetInterface("wxl.fdid", WXL_FDID_API_VERSION));
|
||||
if (!moonwell::fdid::PatchResolver(fdid))
|
||||
{
|
||||
api->Log(WXL_LOG_ERROR, "MoonWellFdid", "wxl.fdid v1 is unavailable");
|
||||
return 0;
|
||||
}
|
||||
api->Log(WXL_LOG_INFO, "MoonWellFdid", "custom FileDataID overlay installed");
|
||||
return 1;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// MoonWell's specific-archive fallback, kept outside the WarcraftXL core.
|
||||
|
||||
#include "wxl/PluginApi.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace
|
||||
{
|
||||
const WXL_Api* g_api = nullptr;
|
||||
using FileOpenFn = int(__stdcall*)(void* archive, const char* name, uint32_t flags, void** out);
|
||||
FileOpenFn g_nextOpen = nullptr;
|
||||
std::atomic<uint32_t> g_logCount{0};
|
||||
|
||||
int __stdcall FileOpenHook(void* archive, const char* name, uint32_t flags, void** out)
|
||||
{
|
||||
if (!g_nextOpen) return 0;
|
||||
|
||||
const int nativeResult = g_nextOpen(archive, name, flags, out);
|
||||
if (nativeResult || archive == nullptr || !name || !*name) return nativeResult;
|
||||
|
||||
// A dependency requested through one concrete MPQ may live in a higher-priority loose
|
||||
// Patch-*.MPQ directory. Retry only after that exact archive misses, using the client's
|
||||
// normal global search path. This is the MoonWell-only behavior that used to patch core.
|
||||
if (out) *out = nullptr;
|
||||
const int fallbackResult = g_nextOpen(nullptr, name, flags, out);
|
||||
if (fallbackResult && g_api && g_api->Log)
|
||||
{
|
||||
const uint32_t index = g_logCount.fetch_add(1, std::memory_order_relaxed);
|
||||
if (index < 32)
|
||||
g_api->Log(WXL_LOG_INFO, "MoonWellStorageFallback",
|
||||
"specific archive miss resolved globally: '%s'", name);
|
||||
}
|
||||
return fallbackResult;
|
||||
}
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "MoonWellStorageFallback", 1,
|
||||
WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION || !api->HookAttachByName) return 0;
|
||||
g_api = api;
|
||||
const int installed = api->HookAttachByName(
|
||||
"Io.FileOpen", reinterpret_cast<void*>(&FileOpenHook),
|
||||
reinterpret_cast<void**>(&g_nextOpen), WXL_HOOK_DEFAULT_PRIORITY);
|
||||
api->Log(installed ? WXL_LOG_INFO : WXL_LOG_ERROR, "MoonWellStorageFallback", "%s",
|
||||
installed ? "specific-archive fallback installed" : "Io.FileOpen hook failed");
|
||||
return installed;
|
||||
}
|
||||
+128
-84
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Appends compact MoonWell spell overrides to stock 3.3.5 DBC bytes.
|
||||
// Appends compact MoonWell spell overrides to the stock 3.3.5 DBCs at open time.
|
||||
|
||||
#include "game/Io.hpp"
|
||||
#include "wxl/PluginApi.h"
|
||||
#include "wxl/StorageApi.h"
|
||||
#include "Host.hpp"
|
||||
#include "core/Logger.hpp"
|
||||
#include "mpq/MpqStore.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
@@ -15,17 +15,22 @@
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace moonwell::spells
|
||||
namespace moonwell::host
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::string_view kOverridesPath = "WXLSpellOverrides.tsv";
|
||||
constexpr std::string_view kSpellPath = "DBFilesClient\\Spell.dbc";
|
||||
constexpr std::string_view kSkillPath = "DBFilesClient\\SkillLineAbility.dbc";
|
||||
constexpr std::string_view kSpellIconPath = "DBFilesClient\\SpellIcon.dbc";
|
||||
constexpr uint32_t kSpellFields = 234;
|
||||
constexpr uint32_t kSpellRecordSize = kSpellFields * 4;
|
||||
constexpr uint32_t kSkillFields = 14;
|
||||
constexpr uint32_t kSkillRecordSize = kSkillFields * 4;
|
||||
constexpr uint32_t kSpellIconFields = 2;
|
||||
constexpr uint32_t kSpellIconRecordSize = kSpellIconFields * 4;
|
||||
constexpr char8_t kDescriptionPrefixUtf8[] =
|
||||
u8"\u041F\u0440\u0438\u0437\u044B\u0432\u0430\u0435\u0442 \u0438\u043B\u0438 \u043E\u0442\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0441\u043F\u0443\u0442\u043D\u0438\u043A\u0430: ";
|
||||
|
||||
struct Override
|
||||
{
|
||||
@@ -33,39 +38,17 @@ namespace moonwell::spells
|
||||
uint32_t templateSpellId = 0;
|
||||
uint32_t creatureEntry = 0;
|
||||
uint32_t skillLineAbilityId = 0;
|
||||
uint32_t raceMask = 0;
|
||||
uint32_t spellIconId = 0;
|
||||
std::string iconPath;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::once_flag g_loadOnce;
|
||||
bool g_ready = false;
|
||||
const WXL_Api* g_api = nullptr;
|
||||
std::vector<Override> g_overrides;
|
||||
|
||||
void Log(int level, const char* message)
|
||||
{
|
||||
if (g_api && g_api->Log) g_api->Log(level, "MoonWellSpells", "%s", message);
|
||||
}
|
||||
|
||||
bool ReadAll(const char* path, std::vector<uint8_t>& out)
|
||||
{
|
||||
void* handle = nullptr;
|
||||
if (!wxl::game::io::FileOpen(path, wxl::game::io::kOpenWholeFile, &handle) || !handle)
|
||||
return false;
|
||||
uint32_t high = 0;
|
||||
const uint32_t size = wxl::game::io::FileSize(handle, &high);
|
||||
if (high != 0)
|
||||
{
|
||||
wxl::game::io::FileClose(handle);
|
||||
return false;
|
||||
}
|
||||
out.resize(size);
|
||||
uint32_t read = 0;
|
||||
const bool ok = (size == 0 || wxl::game::io::FileRead(handle, out.data(), size, &read))
|
||||
&& read == size;
|
||||
wxl::game::io::FileClose(handle);
|
||||
if (!ok) out.clear();
|
||||
return ok;
|
||||
}
|
||||
std::vector<uint8_t> g_spellDbc;
|
||||
std::vector<uint8_t> g_skillDbc;
|
||||
std::vector<uint8_t> g_spellIconDbc;
|
||||
|
||||
uint32_t ReadU32(const uint8_t* data)
|
||||
{
|
||||
@@ -119,26 +102,50 @@ namespace moonwell::spells
|
||||
lineStart = lineEnd + 1;
|
||||
if (line.empty() || line.front() == '#') continue;
|
||||
|
||||
std::string_view fields[5];
|
||||
std::vector<std::string_view> fields;
|
||||
size_t fieldStart = 0;
|
||||
bool valid = true;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
while (true)
|
||||
{
|
||||
const size_t tab = line.find('\t', fieldStart);
|
||||
if (tab == std::string_view::npos) { valid = false; break; }
|
||||
fields[i] = line.substr(fieldStart, tab - fieldStart);
|
||||
if (tab == std::string_view::npos)
|
||||
{
|
||||
fields.push_back(Trim(line.substr(fieldStart)));
|
||||
break;
|
||||
}
|
||||
fields.push_back(Trim(line.substr(fieldStart, tab - fieldStart)));
|
||||
fieldStart = tab + 1;
|
||||
}
|
||||
if (!valid) return false;
|
||||
fields[4] = Trim(line.substr(fieldStart));
|
||||
|
||||
Override item;
|
||||
if (!ParseU32(fields[0], item.spellId) ||
|
||||
!ParseU32(fields[1], item.templateSpellId) ||
|
||||
!ParseU32(fields[2], item.creatureEntry) ||
|
||||
!ParseU32(fields[3], item.skillLineAbilityId) || fields[4].empty())
|
||||
if (fields.size() == 5)
|
||||
{
|
||||
if (!ParseU32(fields[0], item.spellId) ||
|
||||
!ParseU32(fields[1], item.templateSpellId) ||
|
||||
!ParseU32(fields[2], item.creatureEntry) ||
|
||||
!ParseU32(fields[3], item.skillLineAbilityId) || fields[4].empty())
|
||||
return false;
|
||||
item.name.assign(fields[4]);
|
||||
}
|
||||
else if (fields.size() == 8)
|
||||
{
|
||||
if (!ParseU32(fields[0], item.spellId) ||
|
||||
!ParseU32(fields[1], item.templateSpellId) ||
|
||||
!ParseU32(fields[2], item.creatureEntry) ||
|
||||
!ParseU32(fields[3], item.skillLineAbilityId) ||
|
||||
!ParseU32(fields[4], item.raceMask) ||
|
||||
!ParseU32(fields[5], item.spellIconId) || fields[7].empty())
|
||||
return false;
|
||||
if (item.spellIconId != 0)
|
||||
{
|
||||
if (fields[6].empty() || fields[6] == "-") return false;
|
||||
item.iconPath.assign(fields[6]);
|
||||
}
|
||||
item.name.assign(fields[7]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
item.name.assign(fields[4]);
|
||||
}
|
||||
out.push_back(std::move(item));
|
||||
}
|
||||
return !out.empty();
|
||||
@@ -196,11 +203,13 @@ namespace moonwell::spells
|
||||
uint8_t* row = records.data() + recordStart;
|
||||
WriteU32(row, item.spellId);
|
||||
WriteU32(row + 110 * 4, item.creatureEntry);
|
||||
if (item.spellIconId != 0) WriteU32(row + 133 * 4, item.spellIconId);
|
||||
|
||||
const uint32_t nameOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||
strings.insert(strings.end(), item.name.begin(), item.name.end());
|
||||
strings.push_back(0);
|
||||
const std::string description = "Призывает или отпускает спутника: " + item.name + ".";
|
||||
const std::string description =
|
||||
std::string(reinterpret_cast<const char*>(kDescriptionPrefixUtf8)) + item.name + ".";
|
||||
const uint32_t descriptionOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||
strings.insert(strings.end(), description.begin(), description.end());
|
||||
strings.push_back(0);
|
||||
@@ -239,6 +248,7 @@ namespace moonwell::spells
|
||||
uint8_t* row = records.data() + recordStart;
|
||||
WriteU32(row, item.skillLineAbilityId);
|
||||
WriteU32(row + 8, item.spellId);
|
||||
WriteU32(row + 12, item.raceMask);
|
||||
}
|
||||
|
||||
out.reserve(base.size() + records.size());
|
||||
@@ -250,61 +260,95 @@ namespace moonwell::spells
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildSpellIconDbc(const std::vector<uint8_t>& base, const std::vector<Override>& overrides,
|
||||
std::vector<uint8_t>& out)
|
||||
{
|
||||
uint32_t count = 0, stringSize = 0;
|
||||
size_t recordsEnd = 0;
|
||||
if (!ValidateWdbc(base, kSpellIconFields, kSpellIconRecordSize, count, stringSize, recordsEnd))
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> records;
|
||||
std::vector<uint8_t> strings;
|
||||
for (const Override& item : overrides)
|
||||
{
|
||||
if (item.spellIconId == 0) continue;
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
const uint8_t* row = base.data() + 20 + static_cast<size_t>(i) * kSpellIconRecordSize;
|
||||
if (ReadU32(row) == item.spellIconId) return false;
|
||||
}
|
||||
for (size_t i = 0; i < records.size(); i += kSpellIconRecordSize)
|
||||
if (ReadU32(records.data() + i) == item.spellIconId) return false;
|
||||
|
||||
const uint32_t pathOffset = stringSize + static_cast<uint32_t>(strings.size());
|
||||
const size_t recordStart = records.size();
|
||||
records.resize(recordStart + kSpellIconRecordSize);
|
||||
WriteU32(records.data() + recordStart, item.spellIconId);
|
||||
WriteU32(records.data() + recordStart + 4, pathOffset);
|
||||
strings.insert(strings.end(), item.iconPath.begin(), item.iconPath.end());
|
||||
strings.push_back(0);
|
||||
}
|
||||
|
||||
if (records.empty()) return true;
|
||||
out.reserve(base.size() + records.size() + strings.size());
|
||||
out.insert(out.end(), base.begin(), base.begin() + 20);
|
||||
WriteU32(out.data() + 4, count + static_cast<uint32_t>(records.size() / kSpellIconRecordSize));
|
||||
WriteU32(out.data() + 16, stringSize + static_cast<uint32_t>(strings.size()));
|
||||
out.insert(out.end(), base.begin() + 20, base.begin() + recordsEnd);
|
||||
out.insert(out.end(), records.begin(), records.end());
|
||||
out.insert(out.end(), base.begin() + recordsEnd, base.end());
|
||||
out.insert(out.end(), strings.begin(), strings.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Load()
|
||||
{
|
||||
std::vector<uint8_t> overrideBytes;
|
||||
if (!ReadAll(kOverridesPath.data(), overrideBytes))
|
||||
const std::string root = wxl::host::ClientRoot();
|
||||
wxl::host::mpq::MpqStore store;
|
||||
std::vector<uint8_t> overrideBytes, spellBase, skillBase, spellIconBase;
|
||||
if (root.empty() || !store.Mount(root) || !store.ReadAll(kOverridesPath, overrideBytes))
|
||||
return;
|
||||
|
||||
if (!ParseOverrides(overrideBytes, g_overrides))
|
||||
std::vector<Override> overrides;
|
||||
if (!ParseOverrides(overrideBytes, overrides) ||
|
||||
!store.ReadAll(kSpellPath, spellBase) || !store.ReadAll(kSkillPath, skillBase) ||
|
||||
!store.ReadAll(kSpellIconPath, spellIconBase) ||
|
||||
!BuildSpellDbc(spellBase, overrides, g_spellDbc) ||
|
||||
!BuildSkillDbc(skillBase, overrides, g_skillDbc) ||
|
||||
!BuildSpellIconDbc(spellIconBase, overrides, g_spellIconDbc))
|
||||
{
|
||||
Log(WXL_LOG_ERROR, "failed to parse WXLSpellOverrides.tsv");
|
||||
WLOG_ERROR("moonwell-spells: failed to build custom companion DBCs");
|
||||
return;
|
||||
}
|
||||
g_ready = true;
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_INFO, "MoonWellSpells", "loaded %zu companion spell override(s)",
|
||||
g_overrides.size());
|
||||
WLOG_INFO("moonwell-spells: appended %zu companion spell(s)", overrides.size());
|
||||
}
|
||||
|
||||
int __cdecl Transform(const char* rawName, const uint8_t* raw, uint32_t rawLen,
|
||||
const WXL_ByteSink* sink)
|
||||
bool Provide(std::string_view name, std::vector<uint8_t>& out)
|
||||
{
|
||||
if (!rawName || !raw || !sink || !sink->Write) return 0;
|
||||
const std::string_view name(rawName);
|
||||
if (!SamePath(name, kSpellPath) && !SamePath(name, kSkillPath)) return false;
|
||||
if (!SamePath(name, kSpellPath) && !SamePath(name, kSkillPath) &&
|
||||
!SamePath(name, kSpellIconPath))
|
||||
return false;
|
||||
std::call_once(g_loadOnce, &Load);
|
||||
if (!g_ready) return false;
|
||||
|
||||
const std::vector<uint8_t> base(raw, raw + rawLen);
|
||||
std::vector<uint8_t> out;
|
||||
const bool built = SamePath(name, kSpellPath)
|
||||
? BuildSpellDbc(base, g_overrides, out)
|
||||
: BuildSkillDbc(base, g_overrides, out);
|
||||
if (!built)
|
||||
if (SamePath(name, kSpellPath))
|
||||
out = g_spellDbc;
|
||||
else if (SamePath(name, kSkillPath))
|
||||
out = g_skillDbc;
|
||||
else
|
||||
{
|
||||
Log(WXL_LOG_ERROR, "failed to build custom companion DBC");
|
||||
return 0;
|
||||
if (g_spellIconDbc.empty()) return false;
|
||||
out = g_spellIconDbc;
|
||||
}
|
||||
sink->Write(sink->ctx, out.data(), static_cast<uint32_t>(out.size()));
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Install(const WXL_Api* api)
|
||||
{
|
||||
g_api = api;
|
||||
if (!api || !api->GetInterface) return false;
|
||||
auto* storage = static_cast<WXL_StorageApi*>(
|
||||
api->GetInterface("wxl.storage", WXL_STORAGE_API_VERSION));
|
||||
if (!storage || storage->apiVersion != WXL_STORAGE_API_VERSION ||
|
||||
!storage->RegisterClientTransform)
|
||||
struct Registrar
|
||||
{
|
||||
Log(WXL_LOG_ERROR, "wxl.storage v1 is unavailable");
|
||||
return false;
|
||||
}
|
||||
storage->RegisterClientTransform(".dbc", &Transform);
|
||||
Log(WXL_LOG_INFO, "DBC override transform registered");
|
||||
return true;
|
||||
Registrar() { wxl::host::RegisterProvider("moonwell-companion-spells", &Provide); }
|
||||
};
|
||||
|
||||
Registrar g_registrar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// FileDataID resolver for selected retail assets shipped in MoonWell patches.
|
||||
|
||||
#include "Host.hpp"
|
||||
#include "core/Logger.hpp"
|
||||
#include "mpq/MpqStore.hpp"
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace moonwell::host
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::string_view kFileDataMap = "WXLFileData.csv";
|
||||
|
||||
std::once_flag g_loadOnce;
|
||||
std::unordered_map<uint32_t, std::string> g_paths;
|
||||
|
||||
std::string_view Trim(std::string_view value)
|
||||
{
|
||||
while (!value.empty() && (value.front() == ' ' || value.front() == '\t' ||
|
||||
value.front() == '\r' || value.front() == '\n'))
|
||||
value.remove_prefix(1);
|
||||
while (!value.empty() && (value.back() == ' ' || value.back() == '\t' ||
|
||||
value.back() == '\r' || value.back() == '\n'))
|
||||
value.remove_suffix(1);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Load()
|
||||
{
|
||||
const std::string root = wxl::host::ClientRoot();
|
||||
wxl::host::mpq::MpqStore store;
|
||||
std::vector<uint8_t> bytes;
|
||||
if (root.empty() || !store.Mount(root) || !store.ReadAll(kFileDataMap, bytes))
|
||||
{
|
||||
WLOG_WARN("moonwell: FileDataID map '%.*s' was not found",
|
||||
int(kFileDataMap.size()), kFileDataMap.data());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view text(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
size_t lineStart = 0;
|
||||
while (lineStart < text.size())
|
||||
{
|
||||
size_t lineEnd = text.find('\n', lineStart);
|
||||
if (lineEnd == std::string_view::npos) lineEnd = text.size();
|
||||
std::string_view line = Trim(text.substr(lineStart, lineEnd - lineStart));
|
||||
lineStart = lineEnd + 1;
|
||||
|
||||
if (line.empty() || line.front() == '#') continue;
|
||||
const size_t comma = line.find(',');
|
||||
if (comma == std::string_view::npos) continue;
|
||||
|
||||
const std::string_view idText = Trim(line.substr(0, comma));
|
||||
std::string_view pathText = Trim(line.substr(comma + 1));
|
||||
uint32_t fileDataId = 0;
|
||||
const auto parsed = std::from_chars(idText.data(), idText.data() + idText.size(), fileDataId);
|
||||
if (parsed.ec != std::errc{} || parsed.ptr != idText.data() + idText.size() ||
|
||||
fileDataId == 0 || pathText.empty())
|
||||
continue;
|
||||
|
||||
std::string path(pathText);
|
||||
for (char& c : path) if (c == '/') c = '\\';
|
||||
g_paths[fileDataId] = std::move(path);
|
||||
}
|
||||
|
||||
WLOG_INFO("moonwell: loaded %zu FileDataID path(s) from %.*s",
|
||||
g_paths.size(), int(kFileDataMap.size()), kFileDataMap.data());
|
||||
}
|
||||
|
||||
bool Resolve(uint32_t fileDataId, std::string& outPath)
|
||||
{
|
||||
std::call_once(g_loadOnce, &Load);
|
||||
const auto found = g_paths.find(fileDataId);
|
||||
if (found == g_paths.end()) return false;
|
||||
outPath = found->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct Registrar
|
||||
{
|
||||
Registrar() { wxl::host::RegisterResolver("moonwell-filedata", &Resolve); }
|
||||
};
|
||||
|
||||
Registrar g_registrar;
|
||||
}
|
||||
}
|
||||
@@ -5,71 +5,28 @@
|
||||
// changes are verified and applied to the process image during WarcraftXL's
|
||||
// boot phase, before GlueXML is loaded.
|
||||
|
||||
#include "game/Script.hpp"
|
||||
#include "wxl/PluginApi.h"
|
||||
#include "SpellOverrides.hpp"
|
||||
#include "core/Logger.hpp"
|
||||
#include "core/Hook.hpp"
|
||||
#include "core/Mem.hpp"
|
||||
#include "runtime/LuaBindings.hpp"
|
||||
#include "runtime/ModuleInstall.hpp"
|
||||
#include "offsets/engine/Gx.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
|
||||
namespace moonwell
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const WXL_Api* g_api = nullptr;
|
||||
|
||||
void Log(int level, const char* format, ...)
|
||||
{
|
||||
if (!g_api || !g_api->Log) return;
|
||||
char message[1024]{};
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf_s(message, sizeof(message), _TRUNCATE, format, args);
|
||||
va_end(args);
|
||||
g_api->Log(level, "MoonWell", "%s", message);
|
||||
}
|
||||
|
||||
#define WLOG_INFO(...) Log(WXL_LOG_INFO, __VA_ARGS__)
|
||||
#define WLOG_ERROR(...) Log(WXL_LOG_ERROR, __VA_ARGS__)
|
||||
|
||||
bool PatchMemory(void* destination, const void* source, size_t size)
|
||||
{
|
||||
DWORD oldProtection = 0;
|
||||
if (!VirtualProtect(destination, size, PAGE_EXECUTE_READWRITE, &oldProtection))
|
||||
return false;
|
||||
std::memcpy(destination, source, size);
|
||||
FlushInstructionCache(GetCurrentProcess(), destination, size);
|
||||
DWORD ignored = 0;
|
||||
VirtualProtect(destination, size, oldProtection, &ignored);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Fn>
|
||||
bool Hook(const char* name, uintptr_t address, Fn* detour, Fn** original)
|
||||
{
|
||||
return g_api && g_api->HookAttach && g_api->HookAttach(
|
||||
name, address, reinterpret_cast<void*>(detour),
|
||||
reinterpret_cast<void**>(original), WXL_HOOK_DEFAULT_PRIORITY) != 0;
|
||||
}
|
||||
|
||||
template <class Fn>
|
||||
bool HookByName(const char* point, Fn* detour, Fn** original)
|
||||
{
|
||||
return g_api && g_api->HookAttachByName && g_api->HookAttachByName(
|
||||
point, reinterpret_cast<void*>(detour), reinterpret_cast<void**>(original),
|
||||
WXL_HOOK_DEFAULT_PRIORITY) != 0;
|
||||
}
|
||||
|
||||
constexpr uint32_t kTraitorFlag = 0x40000000u;
|
||||
bool g_loginCharacterIsTraitor = false;
|
||||
|
||||
using GxSetProjectionFn = void(__fastcall*)(void* self, void* edx, const void* projection);
|
||||
using GxSetProjectionFn = wxl::offsets::engine::gx::GxSetProjectionFn;
|
||||
GxSetProjectionFn g_nextSetProjection = nullptr;
|
||||
|
||||
// FrameXML's stock SetCreature only accepts a creature entry and waits
|
||||
@@ -107,10 +64,10 @@ namespace moonwell
|
||||
int __cdecl SetCreatureDisplayInfoHook(void* state)
|
||||
{
|
||||
const int result = g_nextSetCreature ? g_nextSetCreature(state) : 0;
|
||||
if (!state || !wxl::game::script::IsNumber(state, 3))
|
||||
if (!state || !wxl::runtime::lua::IsNumber(state, 3))
|
||||
return result;
|
||||
|
||||
const double requestedDisplayInfo = wxl::game::script::ToNumber(state, 3);
|
||||
const double requestedDisplayInfo = wxl::runtime::lua::ToNumber(state, 3);
|
||||
if (requestedDisplayInfo <= 0.0 || requestedDisplayInfo > 4294967295.0)
|
||||
return result;
|
||||
|
||||
@@ -145,8 +102,8 @@ namespace moonwell
|
||||
|
||||
void InstallEncounterJournalModelPreview()
|
||||
{
|
||||
if (!Hook("MoonWellSetCreatureDisplayInfo", kSetCreature,
|
||||
&SetCreatureDisplayInfoHook, &g_nextSetCreature))
|
||||
if (!wxl::core::hook::Install("MoonWellSetCreatureDisplayInfo", kSetCreature,
|
||||
&SetCreatureDisplayInfoHook, &g_nextSetCreature))
|
||||
{
|
||||
WLOG_ERROR("moonwell: encounter journal model hook installation failed");
|
||||
return;
|
||||
@@ -194,14 +151,14 @@ namespace moonwell
|
||||
|
||||
int __cdecl SetCharacterCreateCamera(void* state)
|
||||
{
|
||||
const bool enabled = state && wxl::game::script::IsNumber(state, 1)
|
||||
&& wxl::game::script::ToNumber(state, 1) != 0.0;
|
||||
const float faceZoom = state && wxl::game::script::IsNumber(state, 2)
|
||||
? static_cast<float>(wxl::game::script::ToNumber(state, 2)) : 2.0f;
|
||||
const float faceVerticalOffset = state && wxl::game::script::IsNumber(state, 3)
|
||||
? static_cast<float>(wxl::game::script::ToNumber(state, 3)) : -0.65f;
|
||||
double requestedDuration = state && wxl::game::script::IsNumber(state, 4)
|
||||
? wxl::game::script::ToNumber(state, 4) : 500.0;
|
||||
const bool enabled = state && wxl::runtime::lua::IsNumber(state, 1)
|
||||
&& wxl::runtime::lua::ToNumber(state, 1) != 0.0;
|
||||
const float faceZoom = state && wxl::runtime::lua::IsNumber(state, 2)
|
||||
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 2)) : 2.0f;
|
||||
const float faceVerticalOffset = state && wxl::runtime::lua::IsNumber(state, 3)
|
||||
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 3)) : -0.65f;
|
||||
double requestedDuration = state && wxl::runtime::lua::IsNumber(state, 4)
|
||||
? wxl::runtime::lua::ToNumber(state, 4) : 500.0;
|
||||
if (requestedDuration < 0.0) requestedDuration = 0.0;
|
||||
if (requestedDuration > 2000.0) requestedDuration = 2000.0;
|
||||
|
||||
@@ -245,16 +202,15 @@ namespace moonwell
|
||||
|
||||
void InstallCharacterCreateCamera()
|
||||
{
|
||||
constexpr uintptr_t kGxDeviceVTable = 0x00A2E718;
|
||||
constexpr unsigned kGxSetProjectionSlot = 0xA0 / 4;
|
||||
void** vtable = reinterpret_cast<void**>(kGxDeviceVTable);
|
||||
void** slot = &vtable[kGxSetProjectionSlot];
|
||||
namespace gx = wxl::offsets::engine::gx;
|
||||
void** vtable = reinterpret_cast<void**>(gx::kGxDeviceVTable);
|
||||
void** slot = &vtable[gx::kGxSetProjectionSlot];
|
||||
if (*slot == reinterpret_cast<void*>(&CharacterCreateProjectionHook))
|
||||
return;
|
||||
|
||||
g_nextSetProjection = reinterpret_cast<GxSetProjectionFn>(*slot);
|
||||
void* replacement = reinterpret_cast<void*>(&CharacterCreateProjectionHook);
|
||||
if (!g_nextSetProjection || !PatchMemory(slot, &replacement, sizeof(replacement)))
|
||||
if (!g_nextSetProjection || !wxl::core::mem::Patch(slot, &replacement, sizeof(replacement)))
|
||||
{
|
||||
g_nextSetProjection = nullptr;
|
||||
WLOG_ERROR("moonwell: character-create projection hook installation failed");
|
||||
@@ -266,17 +222,17 @@ namespace moonwell
|
||||
int __cdecl SetLoginCharacterFlags(void* state)
|
||||
{
|
||||
uint32_t flags = 0;
|
||||
if (state && wxl::game::script::IsNumber(state, 1))
|
||||
flags = static_cast<uint32_t>(wxl::game::script::ToNumber(state, 1));
|
||||
if (state && wxl::runtime::lua::IsNumber(state, 1))
|
||||
flags = static_cast<uint32_t>(wxl::runtime::lua::ToNumber(state, 1));
|
||||
|
||||
g_loginCharacterIsTraitor = (flags & kTraitorFlag) != 0;
|
||||
wxl::game::script::PushBoolean(state, g_loginCharacterIsTraitor);
|
||||
wxl::runtime::lua::PushBoolean(state, g_loginCharacterIsTraitor ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __cdecl IsTraitor(void* state)
|
||||
{
|
||||
wxl::game::script::PushBoolean(state, g_loginCharacterIsTraitor);
|
||||
wxl::runtime::lua::PushBoolean(state, g_loginCharacterIsTraitor ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -303,7 +259,7 @@ namespace moonwell
|
||||
name, reinterpret_cast<void*>(address));
|
||||
return false;
|
||||
}
|
||||
if (!PatchMemory(reinterpret_cast<void*>(address), replacement.data(), N))
|
||||
if (!wxl::core::mem::Patch(reinterpret_cast<void*>(address), replacement.data(), N))
|
||||
{
|
||||
WLOG_ERROR("moonwell: '%s' patch failed at %p", name,
|
||||
reinterpret_cast<void*>(address));
|
||||
@@ -415,7 +371,7 @@ namespace moonwell
|
||||
const auto caveRel = static_cast<int32_t>(reinterpret_cast<uintptr_t>(cave) -
|
||||
(patchAddress + jump.size()));
|
||||
std::memcpy(jump.data() + 1, &caveRel, sizeof(caveRel));
|
||||
if (!PatchMemory(reinterpret_cast<void*>(patchAddress), jump.data(), jump.size()))
|
||||
if (!wxl::core::mem::Patch(reinterpret_cast<void*>(patchAddress), jump.data(), jump.size()))
|
||||
{
|
||||
VirtualFree(cave, 0, MEM_RELEASE);
|
||||
WLOG_ERROR("moonwell: GetCharacterInfo jump patch failed");
|
||||
@@ -434,84 +390,40 @@ namespace moonwell
|
||||
WLOG_ERROR("moonwell: compatibility module incomplete; see mismatches above");
|
||||
else
|
||||
WLOG_INFO("moonwell: compatibility module ready (stock Wow.exe remains untouched)");
|
||||
wxl::core::log::Flush();
|
||||
}
|
||||
|
||||
using RegisterFunctionFn = void(__cdecl*)(const char*, wxl::game::script::Function);
|
||||
using ValidateCallbackFn = void(__cdecl*)(uintptr_t);
|
||||
using GetContextFn = void*(__cdecl*)();
|
||||
RegisterFunctionFn g_nextRegisterFunction = nullptr;
|
||||
ValidateCallbackFn g_nextValidateCallback = nullptr;
|
||||
void* g_registeredState = nullptr;
|
||||
bool g_registeringMoonWell = false;
|
||||
|
||||
bool IsMoonWellCallback(uintptr_t callback)
|
||||
DWORD WINAPI FlushRuntimeLog(LPVOID)
|
||||
{
|
||||
return callback == reinterpret_cast<uintptr_t>(&SetLoginCharacterFlags)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&IsTraitor)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&SetCharacterCreateCamera);
|
||||
// Let the remainder of RunAll() and the core-ready line reach the
|
||||
// shared buffered logger, then make startup diagnostics durable.
|
||||
Sleep(1000);
|
||||
wxl::core::log::Flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __cdecl ValidateCallbackHook(uintptr_t callback)
|
||||
void InstallRuntimeLogFlush()
|
||||
{
|
||||
if (!IsMoonWellCallback(callback) && g_nextValidateCallback)
|
||||
g_nextValidateCallback(callback);
|
||||
HANDLE thread = CreateThread(nullptr, 0, &FlushRuntimeLog, nullptr, 0, nullptr);
|
||||
if (thread) CloseHandle(thread);
|
||||
}
|
||||
|
||||
void RegisterLuaFunctionsForCurrentState()
|
||||
struct Registration
|
||||
{
|
||||
if (!g_nextRegisterFunction || g_registeringMoonWell) return;
|
||||
constexpr uintptr_t kGetContext = 0x00817DB0;
|
||||
void* state = reinterpret_cast<GetContextFn>(kGetContext)();
|
||||
if (!state || state == g_registeredState) return;
|
||||
|
||||
g_registeringMoonWell = true;
|
||||
g_nextRegisterFunction("MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags);
|
||||
g_nextRegisterFunction("MoonWellIsTraitor", &IsTraitor);
|
||||
g_nextRegisterFunction("MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
|
||||
g_registeringMoonWell = false;
|
||||
g_registeredState = state;
|
||||
WLOG_INFO("moonwell: Lua functions registered for state %p", state);
|
||||
}
|
||||
|
||||
void __cdecl RegisterFunctionHook(const char* name, wxl::game::script::Function function)
|
||||
{
|
||||
if (g_nextRegisterFunction) g_nextRegisterFunction(name, function);
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
}
|
||||
|
||||
bool InstallLuaBridge()
|
||||
{
|
||||
const bool validator = HookByName("Lua.ValidateFunctionPointer", &ValidateCallbackHook,
|
||||
&g_nextValidateCallback);
|
||||
const bool registrar = HookByName("Lua.RegisterFunction", &RegisterFunctionHook,
|
||||
&g_nextRegisterFunction);
|
||||
if (!validator || !registrar)
|
||||
WLOG_ERROR("moonwell: Lua bridge hook installation failed");
|
||||
return validator && registrar;
|
||||
}
|
||||
Registration()
|
||||
{
|
||||
wxl::runtime::lua::RegisterFunction(
|
||||
"MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags);
|
||||
wxl::runtime::lua::RegisterFunction("MoonWellIsTraitor", &IsTraitor);
|
||||
wxl::runtime::lua::RegisterFunction(
|
||||
"MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
|
||||
wxl::runtime::modules::RegisterBoot("moonwell", &InstallBoot);
|
||||
wxl::runtime::modules::Register(
|
||||
"moonwell-character-create-camera", &InstallCharacterCreateCamera);
|
||||
wxl::runtime::modules::Register(
|
||||
"moonwell-encounter-journal-models", &InstallEncounterJournalModelPreview);
|
||||
wxl::runtime::modules::Register("moonwell-log-flush", &InstallRuntimeLogFlush);
|
||||
}
|
||||
} g_registration;
|
||||
}
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "MoonWell", 1, WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION) return 0;
|
||||
moonwell::g_api = api;
|
||||
|
||||
moonwell::InstallBoot();
|
||||
const bool lua = moonwell::InstallLuaBridge();
|
||||
moonwell::InstallCharacterCreateCamera();
|
||||
moonwell::InstallEncounterJournalModelPreview();
|
||||
const bool spells = moonwell::spells::Install(api);
|
||||
const bool ready = lua && spells;
|
||||
api->Log(ready ? WXL_LOG_INFO : WXL_LOG_ERROR, "MoonWell", "%s",
|
||||
ready ? "MoonWell extension loaded" : "MoonWell extension loaded with errors");
|
||||
return ready ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
struct WXL_Api;
|
||||
|
||||
namespace moonwell::spells
|
||||
{
|
||||
bool Install(const WXL_Api* api);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
file(GLOB_RECURSE WXL_EXT_SHARED_SRC CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/engine/assets/shared/textures/blp/*.cpp")
|
||||
@@ -1,77 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// WarcraftXL 1.1 BLP transform extension.
|
||||
|
||||
#include "engine/assets/shared/textures/blp/BlpTranscode.hpp"
|
||||
#include "wxl/ModernBlpApi.h"
|
||||
#include "wxl/PluginApi.h"
|
||||
#include "wxl/StorageApi.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
const WXL_Api* g_api = nullptr;
|
||||
constexpr uint32_t kMaxTextureEdge = 1024;
|
||||
|
||||
int __cdecl Transcode(const char* name, const uint8_t* raw, uint32_t rawLen,
|
||||
const WXL_ByteSink* sink)
|
||||
{
|
||||
if (!raw || !sink || !sink->Write) return 0;
|
||||
namespace blp = wxl::modern::assets::textures::blp;
|
||||
|
||||
const std::span<const uint8_t> input(raw, rawLen);
|
||||
std::vector<uint8_t> capped;
|
||||
const bool didCap = blp::CapBlpMips(input, capped, kMaxTextureEdge);
|
||||
const std::span<const uint8_t> source = didCap
|
||||
? std::span<const uint8_t>(capped.data(), capped.size()) : input;
|
||||
|
||||
std::vector<uint8_t> transcoded;
|
||||
if (blp::TranscodeBlp(source, transcoded))
|
||||
{
|
||||
sink->Write(sink->ctx, transcoded.data(), static_cast<uint32_t>(transcoded.size()));
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_DEBUG, "wxl-modern-blp", "%s%s BGRA->DXT5",
|
||||
name ? name : "?", didCap ? " capped" : "");
|
||||
return 1;
|
||||
}
|
||||
if (!didCap) return 0;
|
||||
|
||||
sink->Write(sink->ctx, capped.data(), static_cast<uint32_t>(capped.size()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const WXL_ModernBlpApi g_blpApi = {
|
||||
sizeof(WXL_ModernBlpApi), WXL_MODERN_BLP_API_VERSION, &Transcode,
|
||||
};
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "wxl-modern-blp", 1, WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION) return 0;
|
||||
g_api = api;
|
||||
auto* storage = static_cast<WXL_StorageApi*>(
|
||||
api->GetInterface("wxl.storage", WXL_STORAGE_API_VERSION));
|
||||
if (!storage || storage->apiVersion != WXL_STORAGE_API_VERSION ||
|
||||
!storage->RegisterClientTransform)
|
||||
{
|
||||
api->Log(WXL_LOG_ERROR, "wxl-modern-blp", "wxl.storage v1 is unavailable");
|
||||
return 0;
|
||||
}
|
||||
|
||||
storage->RegisterClientTransform(".blp", &Transcode);
|
||||
api->PublishInterface("wxl.modern-blp", WXL_MODERN_BLP_API_VERSION,
|
||||
const_cast<WXL_ModernBlpApi*>(&g_blpApi));
|
||||
api->Log(WXL_LOG_INFO, "wxl-modern-blp", "BLP transcode and mip cap active");
|
||||
return 1;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ function Stop-WowRuntime {
|
||||
param([Parameter(Mandatory=$true)][string]$ClientPath)
|
||||
|
||||
$clientRoot = [System.IO.Path]::GetFullPath($ClientPath).TrimEnd('\') + '\'
|
||||
$runtimeProcesses = @(Get-Process -Name "Wow" -ErrorAction SilentlyContinue | Where-Object {
|
||||
$runtimeProcesses = @(Get-Process -Name "Wow", "WarcraftXLHost" -ErrorAction SilentlyContinue | Where-Object {
|
||||
try {
|
||||
$_.Path -and $_.Path.StartsWith($clientRoot, [System.StringComparison]::OrdinalIgnoreCase)
|
||||
} catch {
|
||||
@@ -96,7 +96,7 @@ function Stop-WowRuntime {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Stopping the running WoW client..."
|
||||
Write-Host "Stopping the running WoW client and WarcraftXL host..."
|
||||
$runtimeProcesses | Stop-Process -Force
|
||||
|
||||
try {
|
||||
@@ -110,11 +110,19 @@ function Stop-WowRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Ensure tool exists
|
||||
if (!(Test-Path $TOOL)) {
|
||||
Write-Host "Building Rust tool..."
|
||||
Push-Location (Join-Path $ROOT "tool")
|
||||
cargo build --release
|
||||
# --- Always rebuild the tool so generated DBC/SQL formats match the current sources.
|
||||
$cargo = Get-Command cargo -ErrorAction SilentlyContinue
|
||||
if (-not $cargo) {
|
||||
throw "Cargo was not found; the MPQ/DBC builder cannot be refreshed."
|
||||
}
|
||||
Write-Host "Building Rust tool..."
|
||||
Push-Location (Join-Path $ROOT "tool")
|
||||
try {
|
||||
& $cargo.Source build --release
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Rust tool build failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
@@ -135,6 +143,11 @@ if (!(Test-Path (Join-Path $DIST_DIR "Data"))) {
|
||||
# --- Build/package/deploy WarcraftXL before the dist sync. The packaged files
|
||||
# are uploaded by upload_to_s3.py and consumed by the launcher manifest.
|
||||
Stop-WowRuntime -ClientPath $WOW_HOME
|
||||
$creatureCache = Join-Path $WOW_HOME "Cache\WDB\ruRU\creaturecache.wdb"
|
||||
if (Test-Path -LiteralPath $creatureCache -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $creatureCache -Force
|
||||
Write-Host "Cleared stale creature cache: $creatureCache"
|
||||
}
|
||||
Write-Host "Building and packaging WarcraftXL..."
|
||||
& $WXL_BUILD_SCRIPT -Configuration Release -ClientPath $WOW_HOME `
|
||||
-PackagePath $DIST_DIR -Deploy
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.3 MiB |
Binary file not shown.
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"fileDataID": 3730958,
|
||||
"textures": [
|
||||
{
|
||||
"fileDataID": 3732460,
|
||||
"file": "catslime_3732460.blp"
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732461,
|
||||
"file": "catslime_3732461.blp"
|
||||
}
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"fileDataID": 3732472,
|
||||
"file": "catslime00.skin"
|
||||
}
|
||||
],
|
||||
"lodSkins": [
|
||||
{
|
||||
"fileDataID": 3732473,
|
||||
"file": "catslime_lod01.skin"
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732474,
|
||||
"file": "catslime_lod02.skin"
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732475,
|
||||
"file": "catslime_lod03.skin"
|
||||
}
|
||||
],
|
||||
"anims": [
|
||||
{
|
||||
"fileDataID": 3732462,
|
||||
"file": "catslime0097-00.anim",
|
||||
"animID": 97,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732463,
|
||||
"file": "catslime0096-00.anim",
|
||||
"animID": 96,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732464,
|
||||
"file": "catslime0098-00.anim",
|
||||
"animID": 98,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732465,
|
||||
"file": "catslime0100-00.anim",
|
||||
"animID": 100,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732466,
|
||||
"file": "catslime0099-00.anim",
|
||||
"animID": 99,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732467,
|
||||
"file": "catslime0101-00.anim",
|
||||
"animID": 101,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732468,
|
||||
"file": "catslime0069-00.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732469,
|
||||
"file": "catslime0123-00.anim",
|
||||
"animID": 123,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732470,
|
||||
"file": "catslime0061-00.anim",
|
||||
"animID": 61,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732471,
|
||||
"file": "catslime0075-00.anim",
|
||||
"animID": 75,
|
||||
"subAnimID": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"fileDataID": 467885,
|
||||
"textures": [
|
||||
{
|
||||
"fileDataID": 467884,
|
||||
"file": "babymoonkinne.blp"
|
||||
},
|
||||
{
|
||||
"fileDataID": 944915,
|
||||
"file": "armorreflect4.blp"
|
||||
}
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"fileDataID": 473055,
|
||||
"file": "babymoonkin_ne00.skin"
|
||||
}
|
||||
],
|
||||
"lodSkins": [],
|
||||
"anims": [
|
||||
{
|
||||
"fileDataID": 473051,
|
||||
"file": "babymoonkin_ne0074-00.anim",
|
||||
"animID": 74,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473072,
|
||||
"file": "babymoonkin_ne0062-00.anim",
|
||||
"animID": 62,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473068,
|
||||
"file": "babymoonkin_ne0060-00.anim",
|
||||
"animID": 60,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473061,
|
||||
"file": "babymoonkin_ne0070-00.anim",
|
||||
"animID": 70,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473038,
|
||||
"file": "babymoonkin_ne0097-00.anim",
|
||||
"animID": 97,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473067,
|
||||
"file": "babymoonkin_ne0096-00.anim",
|
||||
"animID": 96,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473047,
|
||||
"file": "babymoonkin_ne0098-00.anim",
|
||||
"animID": 98,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473040,
|
||||
"file": "babymoonkin_ne0100-00.anim",
|
||||
"animID": 100,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473035,
|
||||
"file": "babymoonkin_ne0099-00.anim",
|
||||
"animID": 99,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473041,
|
||||
"file": "babymoonkin_ne0101-00.anim",
|
||||
"animID": 101,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473031,
|
||||
"file": "babymoonkin_ne0069-00.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473050,
|
||||
"file": "babymoonkin_ne0069-01.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 1
|
||||
},
|
||||
{
|
||||
"fileDataID": 473046,
|
||||
"file": "babymoonkin_ne0069-02.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 2
|
||||
},
|
||||
{
|
||||
"fileDataID": 473042,
|
||||
"file": "babymoonkin_ne0069-03.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 3
|
||||
},
|
||||
{
|
||||
"fileDataID": 473060,
|
||||
"file": "babymoonkin_ne0061-00.anim",
|
||||
"animID": 61,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473030,
|
||||
"file": "babymoonkin_ne0067-00.anim",
|
||||
"animID": 67,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473037,
|
||||
"file": "babymoonkin_ne0075-00.anim",
|
||||
"animID": 75,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473069,
|
||||
"file": "babymoonkin_ne0133-00.anim",
|
||||
"animID": 133,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473057,
|
||||
"file": "babymoonkin_ne0134-00.anim",
|
||||
"animID": 134,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473074,
|
||||
"file": "babymoonkin_ne0123-00.anim",
|
||||
"animID": 123,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473056,
|
||||
"file": "babymoonkin_ne0128-00.anim",
|
||||
"animID": 128,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473029,
|
||||
"file": "babymoonkin_ne0129-00.anim",
|
||||
"animID": 129,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473073,
|
||||
"file": "babymoonkin_ne0079-00.anim",
|
||||
"animID": 79,
|
||||
"subAnimID": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"fileDataID": 467887,
|
||||
"textures": [
|
||||
{
|
||||
"fileDataID": 467886,
|
||||
"file": "babymoonkinta.blp"
|
||||
},
|
||||
{
|
||||
"fileDataID": 249237,
|
||||
"file": "..\\..\\item\\objectcomponents\\shield\\armorreflect4.blp"
|
||||
}
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"fileDataID": 473058,
|
||||
"file": "babymoonkin_ta00.skin"
|
||||
}
|
||||
],
|
||||
"lodSkins": [],
|
||||
"anims": [
|
||||
{
|
||||
"fileDataID": 473066,
|
||||
"file": "babymoonkin_ta0074-00.anim",
|
||||
"animID": 74,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473039,
|
||||
"file": "babymoonkin_ta0062-00.anim",
|
||||
"animID": 62,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473064,
|
||||
"file": "babymoonkin_ta0060-00.anim",
|
||||
"animID": 60,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473075,
|
||||
"file": "babymoonkin_ta0070-00.anim",
|
||||
"animID": 70,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473053,
|
||||
"file": "babymoonkin_ta0097-00.anim",
|
||||
"animID": 97,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473032,
|
||||
"file": "babymoonkin_ta0096-00.anim",
|
||||
"animID": 96,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473076,
|
||||
"file": "babymoonkin_ta0098-00.anim",
|
||||
"animID": 98,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473071,
|
||||
"file": "babymoonkin_ta0100-00.anim",
|
||||
"animID": 100,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473036,
|
||||
"file": "babymoonkin_ta0099-00.anim",
|
||||
"animID": 99,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473048,
|
||||
"file": "babymoonkin_ta0101-00.anim",
|
||||
"animID": 101,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473065,
|
||||
"file": "babymoonkin_ta0069-00.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473044,
|
||||
"file": "babymoonkin_ta0069-01.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 1
|
||||
},
|
||||
{
|
||||
"fileDataID": 473070,
|
||||
"file": "babymoonkin_ta0069-02.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 2
|
||||
},
|
||||
{
|
||||
"fileDataID": 473052,
|
||||
"file": "babymoonkin_ta0069-03.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 3
|
||||
},
|
||||
{
|
||||
"fileDataID": 473062,
|
||||
"file": "babymoonkin_ta0061-00.anim",
|
||||
"animID": 61,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473043,
|
||||
"file": "babymoonkin_ta0067-00.anim",
|
||||
"animID": 67,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473045,
|
||||
"file": "babymoonkin_ta0075-00.anim",
|
||||
"animID": 75,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473034,
|
||||
"file": "babymoonkin_ta0133-00.anim",
|
||||
"animID": 133,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473063,
|
||||
"file": "babymoonkin_ta0134-00.anim",
|
||||
"animID": 134,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473054,
|
||||
"file": "babymoonkin_ta0123-00.anim",
|
||||
"animID": 123,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473049,
|
||||
"file": "babymoonkin_ta0128-00.anim",
|
||||
"animID": 128,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473033,
|
||||
"file": "babymoonkin_ta0129-00.anim",
|
||||
"animID": 129,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 473059,
|
||||
"file": "babymoonkin_ta0079-00.anim",
|
||||
"animID": 79,
|
||||
"subAnimID": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
@@ -4,7 +4,6 @@
|
||||
## Author: Rochet2 <https://github.com/Rochet2>
|
||||
## SavedVariables: AIO_sv, AIO_sv_Addons
|
||||
## SavedVariablesPerCharacter: AIO_sv_char
|
||||
## X-Hidden: 1
|
||||
|
||||
#dependencies
|
||||
Dep_LibWindow-1.1\LibStub.lua
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
|
||||
-- This file is only there in standalone Ace3 and provides handy dev tool stuff I guess
|
||||
-- for now only /rl to reload your UI :)
|
||||
-- note the complete overkill use of AceAddon and console, ain't it cool?
|
||||
|
||||
-- GLOBALS: next, loadstring, ReloadUI, geterrorhandler
|
||||
-- GLOBALS: BINDING_HEADER_ACE3, BINDING_NAME_RELOADUI, Ace3, LibStub
|
||||
|
||||
-- BINDINGs labels
|
||||
BINDING_HEADER_ACE3 = "Ace3"
|
||||
BINDING_NAME_RELOADUI = "ReloadUI"
|
||||
--
|
||||
|
||||
local gui = LibStub("AceGUI-3.0")
|
||||
local reg = LibStub("AceConfigRegistry-3.0")
|
||||
local dialog = LibStub("AceConfigDialog-3.0")
|
||||
|
||||
Ace3 = LibStub("AceAddon-3.0"):NewAddon("Ace3", "AceConsole-3.0")
|
||||
local Ace3 = Ace3
|
||||
|
||||
local selectedgroup
|
||||
local frame
|
||||
local select
|
||||
local status = {}
|
||||
local configs = {}
|
||||
|
||||
local function frameOnClose()
|
||||
gui:Release(frame)
|
||||
frame = nil
|
||||
end
|
||||
|
||||
local function RefreshConfigs()
|
||||
for name in reg:IterateOptionsTables() do
|
||||
configs[name] = name
|
||||
end
|
||||
end
|
||||
|
||||
local function ConfigSelected(widget, event, value)
|
||||
selectedgroup = value
|
||||
dialog:Open(value, widget)
|
||||
end
|
||||
|
||||
local old_CloseSpecialWindows
|
||||
|
||||
-- GLOBALS: CloseSpecialWindows, next
|
||||
function Ace3:Open()
|
||||
if not old_CloseSpecialWindows then
|
||||
old_CloseSpecialWindows = CloseSpecialWindows
|
||||
CloseSpecialWindows = function()
|
||||
local found = old_CloseSpecialWindows()
|
||||
if frame then
|
||||
frame:Hide()
|
||||
return true
|
||||
end
|
||||
return found
|
||||
end
|
||||
end
|
||||
RefreshConfigs()
|
||||
if next(configs) == nil then
|
||||
self:Print("No Configs are Registered")
|
||||
return
|
||||
end
|
||||
|
||||
if not frame then
|
||||
frame = gui:Create("Frame")
|
||||
frame:ReleaseChildren()
|
||||
frame:SetTitle("Ace3 Options")
|
||||
frame:SetLayout("FILL")
|
||||
frame:SetCallback("OnClose", frameOnClose)
|
||||
|
||||
select = gui:Create("DropdownGroup")
|
||||
select:SetGroupList(configs)
|
||||
select:SetCallback("OnGroupSelected", ConfigSelected)
|
||||
frame:AddChild(select)
|
||||
end
|
||||
if not selectedgroup then
|
||||
selectedgroup = next(configs)
|
||||
end
|
||||
select:SetGroup(selectedgroup)
|
||||
frame:Show()
|
||||
end
|
||||
|
||||
local function RefreshOnUpdate(this)
|
||||
select:SetGroup(selectedgroup)
|
||||
this:SetScript("OnUpdate", nil)
|
||||
end
|
||||
|
||||
function Ace3:ConfigTableChanged(event, appName)
|
||||
if selectedgroup == appName and frame then
|
||||
frame.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
||||
end
|
||||
end
|
||||
|
||||
reg.RegisterCallback(Ace3, "ConfigTableChange", "ConfigTableChanged")
|
||||
|
||||
function Ace3:PrintCmd(input)
|
||||
input = input:trim():match("^(.-);*$")
|
||||
local func, err = loadstring("LibStub(\"AceConsole-3.0\"):Print(" .. input .. ")")
|
||||
if not func then
|
||||
LibStub("AceConsole-3.0"):Print("Error: " .. err)
|
||||
else
|
||||
func()
|
||||
end
|
||||
end
|
||||
|
||||
function Ace3:OnInitialize()
|
||||
self:RegisterChatCommand("ace3", function() self:Open() end)
|
||||
self:RegisterChatCommand("rl", function() ReloadUI() end)
|
||||
self:RegisterChatCommand("print", "PrintCmd")
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user