14 Commits

105 changed files with 21319 additions and 32 deletions
+5
View File
@@ -1,15 +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 = main
[submodule "vendor/modules/wxl-modern-adt"]
path = vendor/modules/wxl-modern-adt
url = https://github.com/WarcraftXL/wxl-modern-adt.git
branch = main
+3
View File
@@ -41,6 +41,8 @@ if(TARGET WarcraftXL)
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")
@@ -53,6 +55,7 @@ if(TARGET WarcraftXLHost)
"${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()
+102
View File
@@ -0,0 +1,102 @@
# Автоматический импорт mount и pet
Список современных моделей находится в `custom-creatures.json`. В нём есть две независимые
секции:
```json
{
"mounts": [
"assets/creatures/my_mount"
],
"pets": [
"assets/creatures/my_pet"
]
}
```
Папка должна быть результатом **Raw M2 export** из wow.export и содержать:
- один основной `.m2`;
- `*.manifest.json`;
- все упомянутые в manifest текстуры, skin, skeleton, bone и animation-файлы.
Разрешены абсолютные пути, например `C:/Users/sindo/wow.export/creature/catslime`, но для
воспроизводимой сборки папку лучше скопировать внутрь репозитория и указать относительный путь.
## Что делает сборщик
При обычном запуске `tool.exe`, `run.ps1` или `deploy.ps1` сборщик:
1. читает manifest каждой модели и размещает retail M2 со всеми зависимостями в loose-каталоге
`Data/Patch-WXL.MPQ`, создавая рядом `.mdx`-алиас для загрузчика WoW 3.3.5, чтобы WarcraftXL всегда
преобразовывал MD21 до загрузки клиентом;
2. генерирует `WXLFileData.csv` для WarcraftXL;
3. дописывает модели в `CreatureModelData.dbc` и `CreatureDisplayInfo.dbc`;
4. сохраняет стабильные ID в `custom-creatures.lock.json`;
5. для `pets` создаёт companion summon spell и строку skill line `778`;
6. кладёт серверные DBC, SQL и таблицу ID в `build/server-dbc`.
Полный `Spell.dbc` в патч не копируется. WarcraftXL читает компактный
`WXLSpellOverrides.tsv` и при открытии DBC добавляет новые spell-записи к штатному файлу клиента.
Перестановка моделей в списке не меняет уже выданные ID. Lock-файл необходимо хранить в Git.
## Дополнительные параметры
Если значений по умолчанию недостаточно, вместо строки используется объект:
```json
{
"folder": "assets/creatures/my_mount",
"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,
"display_scale": 1.0,
"model_scale": 1.0,
"collision_width": 0.6111,
"collision_height": 2.031,
"mount_height": 1.8657,
"flags": 0
}
```
Для `mounts` по умолчанию клонируется верховая лошадь с display ID `2404`, для `pets` —
компаньон-кот с 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` и
`skillLineAbilityID`. Значения находятся в `build/server-dbc/custom-creatures.csv`.
1. Скопировать `CreatureModelData.dbc` и `CreatureDisplayInfo.dbc` из `build/server-dbc` в
серверный каталог `data/dbc`.
2. Применить `build/server-dbc/custom-creatures.sql` к `acore_world`.
3. Перезапустить worldserver: DBC и таблицы `spell_dbc` загружаются только при старте.
4. Для проверки изучить выданный spell командой `.learn <summonSpellID>`.
SQL создаёт `creature_template`, `creature_model_info`, `creature_template_model`, companion spell и строку skill line.
Генерация полноценного mount spell для секции `mounts` остаётся отдельным этапом: mount требует
другой spell-шаблон с аурами скорости и `SPELL_AURA_MOUNTED`.
Binary file not shown.
Binary file not shown.
+40
View File
@@ -0,0 +1,40 @@
{
"base_dbc_dir": "assets/dbc/3.3.5a",
"model_data_id_start": 50000,
"display_info_id_start": 50000,
"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": [
{
"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
}
]
}
+27
View File
@@ -0,0 +1,27 @@
{
"models": {
"catslime": {
"model_data_id": 50000,
"display_info_id": 50000,
"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
}
}
}
+4 -3
View File
@@ -76,11 +76,12 @@ New-Item -ItemType Directory -Path $PackagePath -Force | Out-Null
if (-not $SkipDataBuild) {
Write-Host '[2/5] Building MPQ patches...'
$tool = Join-Path $repoRoot 'tool\target\release\tool.exe'
if (-not (Test-Path -LiteralPath $tool -PathType Leaf)) {
$cargo = Get-Command cargo -ErrorAction SilentlyContinue
if (-not $cargo) { throw 'Cargo was not found and the MPQ builder is not compiled.' }
$cargo = Get-Command cargo -ErrorAction SilentlyContinue
if ($cargo) {
& $cargo.Source build --release --manifest-path (Join-Path $repoRoot 'tool\Cargo.toml')
if ($LASTEXITCODE -ne 0) { throw 'MPQ builder compilation failed.' }
} elseif (-not (Test-Path -LiteralPath $tool -PathType Leaf)) {
throw 'Cargo was not found and the MPQ builder is not compiled.'
}
& $tool (Join-Path $repoRoot 'src') $PackagePath
@@ -0,0 +1,354 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Appends compact MoonWell spell overrides to the stock 3.3.5 DBCs at open time.
#include "Host.hpp"
#include "core/Logger.hpp"
#include "mpq/MpqStore.hpp"
#include <cctype>
#include <charconv>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <string>
#include <string_view>
#include <system_error>
#include <vector>
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
{
uint32_t spellId = 0;
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;
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)
{
uint32_t value = 0;
std::memcpy(&value, data, sizeof(value));
return value;
}
void WriteU32(uint8_t* data, uint32_t value)
{
std::memcpy(data, &value, sizeof(value));
}
std::string_view Trim(std::string_view value)
{
while (!value.empty() && std::isspace(static_cast<unsigned char>(value.front())))
value.remove_prefix(1);
while (!value.empty() && std::isspace(static_cast<unsigned char>(value.back())))
value.remove_suffix(1);
return value;
}
bool ParseU32(std::string_view text, uint32_t& value)
{
text = Trim(text);
const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value);
return parsed.ec == std::errc{} && parsed.ptr == text.data() + text.size();
}
bool SamePath(std::string_view left, std::string_view right)
{
if (left.size() != right.size()) return false;
for (size_t i = 0; i < left.size(); ++i)
{
const unsigned char a = static_cast<unsigned char>(left[i] == '/' ? '\\' : left[i]);
const unsigned char b = static_cast<unsigned char>(right[i] == '/' ? '\\' : right[i]);
if (std::tolower(a) != std::tolower(b)) return false;
}
return true;
}
bool ParseOverrides(const std::vector<uint8_t>& bytes, std::vector<Override>& out)
{
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;
std::vector<std::string_view> fields;
size_t fieldStart = 0;
while (true)
{
const size_t tab = line.find('\t', 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;
}
Override item;
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;
}
out.push_back(std::move(item));
}
return !out.empty();
}
bool ValidateWdbc(const std::vector<uint8_t>& bytes, uint32_t fields, uint32_t recordSize,
uint32_t& count, uint32_t& stringSize, size_t& recordsEnd)
{
if (bytes.size() < 20 || std::memcmp(bytes.data(), "WDBC", 4) != 0) return false;
count = ReadU32(bytes.data() + 4);
if (ReadU32(bytes.data() + 8) != fields || ReadU32(bytes.data() + 12) != recordSize)
return false;
stringSize = ReadU32(bytes.data() + 16);
recordsEnd = 20ull + static_cast<size_t>(count) * recordSize;
return recordsEnd <= bytes.size() && recordsEnd + stringSize == bytes.size();
}
const uint8_t* FindSpell(const std::vector<uint8_t>& bytes, uint32_t count, uint32_t id)
{
for (uint32_t i = 0; i < count; ++i)
{
const uint8_t* row = bytes.data() + 20 + static_cast<size_t>(i) * kSpellRecordSize;
if (ReadU32(row) == id) return row;
}
return nullptr;
}
const uint8_t* FindSkillRow(const std::vector<uint8_t>& bytes, uint32_t count, uint32_t spellId)
{
for (uint32_t i = 0; i < count; ++i)
{
const uint8_t* row = bytes.data() + 20 + static_cast<size_t>(i) * kSkillRecordSize;
if (ReadU32(row + 4) == 778 && ReadU32(row + 8) == spellId) return row;
}
return nullptr;
}
bool BuildSpellDbc(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, kSpellFields, kSpellRecordSize, count, stringSize, recordsEnd))
return false;
std::vector<uint8_t> records;
records.reserve(overrides.size() * kSpellRecordSize);
std::vector<uint8_t> strings;
for (const Override& item : overrides)
{
const uint8_t* source = FindSpell(base, count, item.templateSpellId);
if (!source) return false;
const size_t recordStart = records.size();
records.insert(records.end(), source, source + kSpellRecordSize);
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 =
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);
for (uint32_t field = 136; field <= 151; ++field) WriteU32(row + field * 4, nameOffset);
for (uint32_t field = 170; field <= 185; ++field) WriteU32(row + field * 4, descriptionOffset);
for (uint32_t field = 187; field <= 202; ++field) WriteU32(row + field * 4, 0);
}
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>(overrides.size()));
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;
}
bool BuildSkillDbc(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, kSkillFields, kSkillRecordSize, count, stringSize, recordsEnd))
return false;
std::vector<uint8_t> records;
records.reserve(overrides.size() * kSkillRecordSize);
for (const Override& item : overrides)
{
const uint8_t* source = FindSkillRow(base, count, item.templateSpellId);
if (!source) return false;
const size_t recordStart = records.size();
records.insert(records.end(), source, source + kSkillRecordSize);
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());
out.insert(out.end(), base.begin(), base.begin() + 20);
WriteU32(out.data() + 4, count + static_cast<uint32_t>(overrides.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());
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()
{
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;
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))
{
WLOG_ERROR("moonwell-spells: failed to build custom companion DBCs");
return;
}
g_ready = true;
WLOG_INFO("moonwell-spells: appended %zu companion spell(s)", overrides.size());
}
bool Provide(std::string_view name, std::vector<uint8_t>& out)
{
if (!SamePath(name, kSpellPath) && !SamePath(name, kSkillPath) &&
!SamePath(name, kSpellIconPath))
return false;
std::call_once(g_loadOnce, &Load);
if (!g_ready) return false;
if (SamePath(name, kSpellPath))
out = g_spellDbc;
else if (SamePath(name, kSkillPath))
out = g_skillDbc;
else
{
if (g_spellIconDbc.empty()) return false;
out = g_spellIconDbc;
}
return true;
}
struct Registrar
{
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;
}
}
+2
View File
@@ -420,6 +420,8 @@ namespace moonwell
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;
+85 -5
View File
@@ -37,6 +37,42 @@ if (-not $env:WOW_HOME) {
$WOW_HOME = $env:WOW_HOME
Write-Host "WOW_HOME = $WOW_HOME"
function Test-ExistingFileWriteAccess {
param([Parameter(Mandatory=$true)][string]$Path)
try {
$stream = [System.IO.File]::Open(
$Path,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Write,
[System.IO.FileShare]::ReadWrite
)
$stream.Close()
return $true
} catch [System.UnauthorizedAccessException] {
return $false
} catch {
# Sharing violations are handled later by Stop-WowRuntime. They do not
# indicate that the whole script needs an elevated token.
return $true
}
}
$installedWarcraftXL = Join-Path $WOW_HOME "WarcraftXL.dll"
$isAdministrator = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator
)
if ((Test-Path -LiteralPath $installedWarcraftXL -PathType Leaf) -and
-not (Test-ExistingFileWriteAccess -Path $installedWarcraftXL) -and
-not $isAdministrator) {
Write-Host "WarcraftXL.dll requires administrator access. Requesting elevation..."
$elevatedProcess = Start-Process -FilePath "powershell.exe" -Verb RunAs -PassThru -Wait `
-WorkingDirectory $ROOT `
-ArgumentList "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$($MyInvocation.MyCommand.Path)`"", "-Env", $Env
exit $elevatedProcess.ExitCode
}
# --- Paths
$SRC_DIR = Join-Path $ROOT "src"
$DIST_DIR = Join-Path $ROOT "dist"
@@ -44,11 +80,49 @@ $TOOL = Join-Path $ROOT "tool\target\release\tool.exe"
$RELOAD_SCRIPT = Join-Path $ROOT "reload_wow.bat"
$WXL_BUILD_SCRIPT = Join-Path $ROOT "build-warcraftxl.ps1"
# --- Ensure tool exists
if (!(Test-Path $TOOL)) {
Write-Host "Building Rust tool..."
Push-Location (Join-Path $ROOT "tool")
cargo build --release
function Stop-WowRuntime {
param([Parameter(Mandatory=$true)][string]$ClientPath)
$clientRoot = [System.IO.Path]::GetFullPath($ClientPath).TrimEnd('\') + '\'
$runtimeProcesses = @(Get-Process -Name "Wow", "WarcraftXLHost" -ErrorAction SilentlyContinue | Where-Object {
try {
$_.Path -and $_.Path.StartsWith($clientRoot, [System.StringComparison]::OrdinalIgnoreCase)
} catch {
$false
}
})
if (-not $runtimeProcesses.Count) {
return
}
Write-Host "Stopping the running WoW client and WarcraftXL host..."
$runtimeProcesses | Stop-Process -Force
try {
$runtimeProcesses | Wait-Process -Timeout 10 -ErrorAction Stop
} catch {
$stillRunning = @($runtimeProcesses | Where-Object { -not $_.HasExited })
if ($stillRunning.Count) {
$processNames = ($stillRunning | ForEach-Object { "$($_.ProcessName) (PID $($_.Id))" }) -join ", "
throw "Could not stop processes that lock WarcraftXL files: $processNames"
}
}
}
# --- 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
}
@@ -68,6 +142,12 @@ 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
BIN
View File
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.
@@ -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.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -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.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.
@@ -353,6 +353,80 @@ EnsureColorMethods(HIGHLIGHT_FONT_COLOR)
EnsureColorMethods(RED_FONT_COLOR)
EnsureColorMethods(GREEN_FONT_COLOR)
EnsureColorMethods(DISABLED_FONT_COLOR)
-- The imported Encounter Journal UI uses tooltip helpers introduced after
-- 3.3.5. Keep their retail signatures, but implement them with the tooltip
-- methods available in the 3.3.5 client.
local function TooltipColorRGB(color, fallbackR, fallbackG, fallbackB)
if type(color) == "table" then
if color.GetRGB then
return color:GetRGB()
end
return color.r or fallbackR, color.g or fallbackG, color.b or fallbackB
end
return fallbackR, fallbackG, fallbackB
end
if not GameTooltip_AddColoredLine then
function GameTooltip_AddColoredLine(tooltip, text, color, wrap)
if not tooltip or not tooltip.AddLine then
return
end
local r, g, b = TooltipColorRGB(color, 1, 1, 1)
tooltip:AddLine(text or "", r, g, b, wrap and true or false)
end
end
if not GameTooltip_AddNormalLine then
function GameTooltip_AddNormalLine(tooltip, text, wrap)
GameTooltip_AddColoredLine(tooltip, text, NORMAL_FONT_COLOR, wrap)
end
end
if not GameTooltip_AddHighlightLine then
function GameTooltip_AddHighlightLine(tooltip, text, wrap)
GameTooltip_AddColoredLine(tooltip, text, HIGHLIGHT_FONT_COLOR, wrap)
end
end
if not GameTooltip_AddBlankLineToTooltip then
function GameTooltip_AddBlankLineToTooltip(tooltip)
if tooltip and tooltip.AddLine then
tooltip:AddLine(" ")
end
end
end
if not GameTooltip_SetTitle then
function GameTooltip_SetTitle(tooltip, text, color)
if not tooltip then
return
end
local r, g, b = TooltipColorRGB(color or NORMAL_FONT_COLOR, 1, 0.82, 0)
if tooltip.SetText then
tooltip:SetText(text or "", r, g, b)
elseif tooltip.AddLine then
tooltip:AddLine(text or "", r, g, b)
end
end
end
if not GameTooltip_ShowDisabledTooltip then
function GameTooltip_ShowDisabledTooltip(tooltip, owner, text, anchor, offsetX, offsetY)
if not tooltip or not owner then
return
end
tooltip:SetOwner(owner, anchor or "ANCHOR_RIGHT", offsetX or 0, offsetY or 0)
GameTooltip_AddNormalLine(tooltip, text, true)
tooltip:Show()
end
end
LOOTJOURNAL_SOURCE_TOOLTIP_HEAD = LOOTJOURNAL_SOURCE_TOOLTIP_HEAD or "Способ получения:"
RETURN_TO_DEFAULT = RETURN_TO_DEFAULT or "Настройки по умолчанию"
GUIDEBOOK = GUIDEBOOK or "Путеводитель"
@@ -557,6 +631,23 @@ local function EnsureItemsCache()
end
local itemsCache = type(_G.ItemsCache) == "table" and _G.ItemsCache or nil
local compactItemsCache = _G.MoonWellEncounterJournalItemCache
if type(compactItemsCache) == "table" then
itemsCache = itemsCache or {}
for itemID, compactData in pairs(compactItemsCache) do
if type(itemID) == "number" and type(compactData) == "table" and not itemsCache[itemID] then
itemsCache[itemID] = {
[ITEM_CACHE_FIELD.NAME_ENGB] = compactData[1],
[ITEM_CACHE_FIELD.NAME_RURU] = compactData[2],
[ITEM_CACHE_FIELD.RARITY] = compactData[3],
[ITEM_CACHE_FIELD.TEXTURE] = compactData[4],
}
end
end
_G.ItemsCache = itemsCache
_G.MoonWellEncounterJournalItemCache = nil
end
local fileIndex = 1
local itemCacheTable = _G["ItemsCache" .. fileIndex]
@@ -711,8 +802,8 @@ local function EnsureItemCacheFrame()
end
C_Item.GetItemInfo = function(item, skipClientCache, callback, ...)
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice
if ExistingCItemGetItemInfo then
local name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = C_Item.GetItemInfoCache(item)
if not name and ExistingCItemGetItemInfo then
name, link, quality, itemLevel, reqLevel, itemType, itemSubType, maxStack, equipSlot, icon, vendorPrice = ExistingCItemGetItemInfo(item, skipClientCache, nil, ...)
end
if not name then
@@ -2,11 +2,92 @@
-- The static fallback is loaded from Generated_EncounterJournal.lua, so the journal works
-- without ever calling ApplyEncounterJournalData.
-- These entries belong to the Sirus-specific content pack that was mixed into
-- the production export. They are not MoonWell content and must never reach
-- the client, including when the journal is refreshed through AIO.
local BLOCKED_INSTANCE_IDS = {
[773] = true, -- Bronze Sanctum
[774] = true, -- Tol'Garod Prison
[776] = true, -- Sirus categories
[777] = true, -- Sirus world bosses (Norigorn)
}
local function RemoveBlockedInstances(data)
local instances = data.JOURNALINSTANCE or data.instances
local encounters = data.JOURNALENCOUNTER or data.encounters
local creatures = data.JOURNALENCOUNTERCREATURE or data.creatures
local items = data.JOURNALENCOUNTERITEM or data.items
local sections = data.JOURNALENCOUNTERSECTION or data.sections
local tierInstances = data.JOURNALTIERXINSTANCE or data.tierInstances
local blockedEncounterIDs = {}
if type(encounters) == "table" then
for instanceID in pairs(BLOCKED_INSTANCE_IDS) do
local encounterList = encounters[instanceID]
if type(encounterList) == "table" then
for _, encounterInfo in ipairs(encounterList) do
if type(encounterInfo) == "table" and type(encounterInfo[1]) == "number" then
blockedEncounterIDs[encounterInfo[1]] = true
end
end
end
encounters[instanceID] = nil
end
end
if type(instances) == "table" then
for instanceID in pairs(BLOCKED_INSTANCE_IDS) do
instances[instanceID] = nil
end
end
for encounterID in pairs(blockedEncounterIDs) do
if type(creatures) == "table" then
creatures[encounterID] = nil
end
if type(items) == "table" then
items[encounterID] = nil
end
end
if type(sections) == "table" then
for sectionID, sectionInfo in pairs(sections) do
if type(sectionInfo) == "table" and blockedEncounterIDs[sectionInfo[7]] then
sections[sectionID] = nil
end
end
end
if type(tierInstances) == "table" then
for instanceID in pairs(BLOCKED_INSTANCE_IDS) do
tierInstances[instanceID] = nil
end
for _, instanceList in pairs(tierInstances) do
if type(instanceList) == "table" then
local writeIndex = 1
for readIndex = 1, #instanceList do
local instanceID = instanceList[readIndex]
if not BLOCKED_INSTANCE_IDS[instanceID] then
instanceList[writeIndex] = instanceID
writeIndex = writeIndex + 1
end
end
for index = writeIndex, #instanceList do
instanceList[index] = nil
end
end
end
end
end
local function ApplyEncounterJournalData(data)
if type(data) ~= "table" then
return
end
RemoveBlockedInstances(data)
local function NormalizeTierInstances(tiers, tierInstances)
if type(tiers) ~= "table" or type(tierInstances) ~= "table" then
return tierInstances
@@ -9,6 +9,8 @@
AchievementFactions.lua
MoonWellClient.lua
SoloLFGCompat.lua
EncounterJournal\EncounterJournalItemCache.lua
EncounterJournal\EncounterJournalCompat.lua
EncounterJournal\SharedXML\SoundKitConstants.lua
EncounterJournal\Generated_EncounterJournalData.lua
@@ -0,0 +1,27 @@
local SOLO_RANDOM_DUNGEONS = {
[1258] = true,
[1259] = true,
[1260] = true,
[1261] = true,
[1262] = true,
}
local GetStockTexCoordsForRole = GetTexCoordsForRole
function GetTexCoordsForRole(role)
if role == "UNKNOWN" then
local proposalExists, _, dungeonID = GetLFGProposal()
if proposalExists and SOLO_RANDOM_DUNGEONS[dungeonID] then
local _, tank, healer = GetLFGRoles()
if tank then
role = "TANK"
elseif healer then
role = "HEALER"
else
role = "DAMAGER"
end
end
end
return GetStockTexCoordsForRole(role)
end
+288 -7
View File
@@ -82,6 +82,18 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bit_field"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.0"
@@ -103,6 +115,12 @@ version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytemuck"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -170,6 +188,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -253,6 +277,12 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
@@ -313,12 +343,38 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "exr"
version = "1.74.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3"
dependencies = [
"bit_field",
"half",
"lebe",
"miniz_oxide",
"num-complex",
"pulp",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.1.9"
@@ -381,6 +437,27 @@ dependencies = [
"wasip3",
]
[[package]]
name = "gif"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
@@ -416,6 +493,39 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "image"
version = "0.24.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"jpeg-decoder",
"num-traits",
"png",
"qoi",
"tiff",
]
[[package]]
name = "image-blp"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b863207a84a413c9e3e94909828ac796d24b1a6f53baa88f186d4cf61eca691f"
dependencies = [
"color_quant",
"image",
"log",
"nom",
"spin",
"texpresso",
"thiserror 1.0.69",
]
[[package]]
name = "implode"
version = "0.1.1"
@@ -459,6 +569,15 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "jpeg-decoder"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
dependencies = [
"rayon",
]
[[package]]
name = "js-sys"
version = "0.3.91"
@@ -484,6 +603,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lebe"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "libbz2-rs-sys"
version = "0.2.2"
@@ -558,6 +683,12 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -568,6 +699,16 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@@ -594,6 +735,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"bytemuck",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
@@ -665,6 +816,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -703,7 +860,20 @@ checksum = "5a7ff566c5cb263cf6a9f3b8b8ccbeaf236c0b3516c184134c062863258b3789"
dependencies = [
"clap",
"indicatif",
"thiserror",
"thiserror 2.0.18",
]
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
@@ -740,6 +910,38 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pulp"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a"
dependencies = [
"bytemuck",
"cfg-if",
"libm",
"num-complex",
"paste",
"pulp-wasm-simd-flag",
"raw-cpuid",
"reborrow",
"version_check",
]
[[package]]
name = "pulp-wasm-simd-flag"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740"
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]]
name = "quote"
version = "1.0.45"
@@ -819,6 +1021,15 @@ dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "raw-cpuid"
version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [
"bitflags 2.11.0",
]
[[package]]
name = "rayon"
version = "1.11.0"
@@ -839,13 +1050,19 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "reborrow"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.11.0",
]
[[package]]
@@ -874,7 +1091,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"bitflags 2.11.0",
"errno",
"libc",
"linux-raw-sys",
@@ -915,6 +1132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
@@ -988,6 +1206,9 @@ name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
@@ -1035,13 +1256,43 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "texpresso"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8550677e2259d675a7841cb1403db35f330cc9e58674c8c5caa12dd12c51dc71"
dependencies = [
"libm",
"rayon",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
"thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -1055,10 +1306,25 @@ dependencies = [
"syn",
]
[[package]]
name = "tiff"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
dependencies = [
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]]
name = "tool"
version = "0.1.0"
dependencies = [
"image",
"image-blp",
"serde",
"serde_json",
"walkdir",
"wow-mpq",
]
@@ -1206,7 +1472,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"bitflags 2.11.0",
"hashbrown 0.15.5",
"indexmap",
"semver",
@@ -1222,6 +1488,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -1377,7 +1649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"bitflags 2.11.0",
"indexmap",
"log",
"serde",
@@ -1434,7 +1706,7 @@ dependencies = [
"rsa",
"sha1",
"tempfile",
"thiserror",
"thiserror 2.0.18",
]
[[package]]
@@ -1468,3 +1740,12 @@ name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
+4
View File
@@ -6,3 +6,7 @@ edition = "2024"
[dependencies]
walkdir = "2.5.0"
wow-mpq = "0.6.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
image = { version = "0.24.9", default-features = false, features = ["jpeg", "png", "tga"] }
image-blp = "1.2.0"

Some files were not shown because too many files have changed in this diff Show More