Compare commits

..

No commits in common. "60c26a17aa381a6e45ff5bbc1c08a3b50b0a346d" and "22518f0936f04423803d8c78370da7393c204fe3" have entirely different histories.

18 changed files with 106 additions and 4686 deletions

3
.gitignore vendored
View file

@ -89,9 +89,6 @@ Data/expansions/*/overlay/
Data/hd/
ingest/
# Asset pipeline state and texture packs
asset_pipeline/
# Local texture dumps / extracted art should never be committed
assets/textures/
node_modules/

View file

@ -106,9 +106,6 @@ This project requires WoW client data that you extract from your own legally obt
Wowee loads assets via an extracted loose-file tree indexed by `manifest.json` (it does not read MPQs at runtime).
For a cross-platform GUI workflow (extraction + texture pack management + active override state), see:
- [Asset Pipeline GUI](docs/asset-pipeline-gui.md)
#### 1) Extract MPQs into `./Data/`
```bash
@ -199,7 +196,6 @@ make -j$(nproc)
- [Project Status](docs/status.md) -- Current code state, limitations, and near-term direction
- [Quick Start](docs/quickstart.md) -- Installation and first steps
- [Build Instructions](BUILD_INSTRUCTIONS.md) -- Detailed dependency, build, and run guide
- [Asset Pipeline GUI](docs/asset-pipeline-gui.md) -- Python GUI for extraction, pack installs, ordering, and override rebuilds
### Technical Documentation
- [Architecture](docs/architecture.md) -- System design and module overview

View file

@ -1,194 +0,0 @@
# Asset Pipeline GUI
WoWee includes a Python GUI for extraction and texture-pack management:
```bash
python3 tools/asset_pipeline_gui.py
```
The script is also executable directly: `./tools/asset_pipeline_gui.py`
## Supported Platforms
- Linux
- macOS
- Windows
The app uses Python's built-in `tkinter` module. If `tkinter` is missing, install the platform package:
- Linux (Debian/Ubuntu): `sudo apt install python3-tk`
- Fedora: `sudo dnf install python3-tkinter`
- Arch: `sudo pacman -S tk`
- macOS: use the official Python.org installer (includes Tk)
- Windows: use the official Python installer and enable Tcl/Tk support
## What It Does
- Runs `asset_extract` (or shell/PowerShell script fallback) to extract MPQ data
- Saves extraction config in `asset_pipeline/state.json`
- Installs texture packs from ZIP or folders (with zip-slip protection)
- Lets users activate/deactivate packs and reorder active pack priority
- Rebuilds `Data/override` from active pack order (runs in background thread)
- Shows current data state (`manifest.json`, entry count, override file count, last runs)
- Browses extracted assets with inline previews (images, 3D wireframes, data tables, text, hex dumps)
## Configuration Tab
### Path Settings
| Field | Description |
|-------|-------------|
| **WoW Data (MPQ source)** | Path to your WoW client's `Data/` folder containing `.MPQ` files |
| **Output Data directory** | Where extracted assets land. Defaults to `<project root>/Data` |
| **Extractor binary/script** | Optional. Leave blank for auto-detection (see below) |
### Extractor Auto-Detection
When no extractor path is configured, the GUI searches in order:
1. `build/bin/asset_extract` — CMake build with bin subdirectory
2. `build/asset_extract` — CMake build without bin subdirectory
3. `bin/asset_extract` — standalone binary
4. **Windows only**: `extract_assets.ps1` — invoked via `powershell -ExecutionPolicy Bypass -File`
5. **Linux/macOS only**: `extract_assets.sh` — invoked via `bash`
On Windows, `.exe` is appended to binary candidates automatically.
### Extraction Options
| Option | Description |
|--------|-------------|
| **Expansion** | `auto`, `classic`, `turtle`, `tbc`, or `wotlk`. Read-only dropdown. |
| **Locale** | `auto`, `enUS`, `enGB`, `deDE`, `frFR`, etc. Read-only dropdown. |
| **Threads** | Worker thread count. 0 = auto (uses all cores). |
| **Skip DBC extraction** | Skip database client files (faster if you only want textures). |
| **Generate DBC CSV** | Output human-readable CSV alongside binary DBC files. |
| **Verify CRC** | Check file integrity during extraction (slower but safer). |
| **Verbose output** | More detail in the Logs tab. |
### Buttons
| Button | Action |
|--------|--------|
| **Save Configuration** | Writes all settings to `asset_pipeline/state.json`. |
| **Run Extraction** | Starts the extractor in a background thread. Output streams to the Logs tab. |
| **Cancel Extraction** | Terminates a running extraction. Grayed out when idle, active during extraction. |
| **Refresh State** | Reloads the Current State tab. |
## Texture Packs Tab
### Installing Packs
- **Install ZIP**: Opens a file picker for `.zip` archives. Each member path is validated against zip-slip attacks before extraction.
- **Install Folder**: Opens a folder picker and copies the entire folder into the pipeline's internal pack storage.
### Managing Packs
| Button | Action |
|--------|--------|
| **Activate** | Adds the selected pack to the active override list. |
| **Deactivate** | Removes the selected pack from the active list (stays installed). |
| **Move Up / Move Down** | Changes priority order. Pack #1 is the base layer; higher numbers override lower. |
| **Rebuild Override** | Merges all active packs into `Data/override/` in a background thread. UI stays responsive. |
| **Uninstall** | Removes the pack from disk after confirmation. |
Pack list selection is preserved across refreshes — you can activate a pack and immediately reorder it without re-selecting.
## Pack Format
Supported pack layouts:
1. `PackName/Data/...`
2. `PackName/data/...`
3. `PackName/...` where top folders include game folders (`Interface`, `World`, `Character`, `Textures`, `Sound`)
4. Single wrapper directory containing any of the above
When multiple active packs contain the same file path, **later packs in active order win**.
## Asset Browser Tab
Browse and preview every extracted asset visually. Requires a completed extraction with a `manifest.json` in the output directory.
### Layout
- **Top bar**: Search entry, file type filter dropdown, Search/Reset buttons, result count
- **Left panel** (~30%): Directory tree built lazily from `manifest.json`
- **Right panel** (~70%): Preview area that adapts to the selected file type
- **Bottom bar**: File path, size, and CRC from manifest
### Search and Filtering
Type a substring into the search box and/or pick a file type from the dropdown, then click **Search**. The tree repopulates with matching results (capped at 5000 entries). Click **Reset** to restore the full tree.
File type filters: All, BLP, M2, WMO, DBC, ADT, Audio, Text.
### Preview Types
| Type | What You See |
|------|--------------|
| **BLP** | Converted to PNG via `blp_convert --to-png`, displayed as an image. Cached in `asset_pipeline/preview_cache/`. |
| **M2** | Wireframe rendering of model vertices and triangles on a Canvas. Drag to rotate, scroll to zoom. |
| **WMO** | Wireframe of group geometry (MOVT/MOVI chunks). Root WMOs auto-load the `_000` group file. |
| **CSV** (DBC exports) | Scrollable table with column names from `dbc_layouts.json`. First 500 rows loaded, click "Load more" for the rest. |
| **ADT** | Colored heightmap grid parsed from MCNK chunks. |
| **Text** (XML, LUA, JSON, HTML, TOC) | Syntax-highlighted scrollable text view. |
| **Audio** (WAV, MP3, OGG) | Metadata display — format, channels, sample rate, duration (WAV). |
| **Other** | Hex dump of the first 512 bytes. |
### Wireframe Controls
- **Left-click drag**: Rotate the model (azimuth + elevation)
- **Scroll wheel**: Zoom in/out
- Depth coloring: closer geometry renders brighter
### Optional Dependencies
| Dependency | Required For | Fallback |
|------------|-------------|----------|
| [Pillow](https://pypi.org/project/Pillow/) (`pip install Pillow`) | BLP image preview | Shows install instructions |
| `blp_convert` (built with project) | BLP → PNG conversion | Shows "not found" message |
All other previews (wireframe, table, text, hex) work without any extra dependencies.
### Cache
BLP previews are cached as PNG files in `asset_pipeline/preview_cache/` keyed by path and file size. Delete this directory to clear the cache.
## Current State Tab
Shows a summary of pipeline state:
- Output directory existence and `manifest.json` entry count
- Override folder file count and last build timestamp
- Installed and active pack counts with priority order
- Last extraction time, success/failure, and the exact command used
- Paths to the state file and packs directory
Click **Refresh** to reload, or it auto-refreshes after operations.
## Logs Tab
All extraction output, override rebuild messages, cancellations, and errors stream here in real time via a log queue polled every 120ms. Click **Clear Logs** to reset.
## State Files and Folders
| Path | Description |
|------|-------------|
| `asset_pipeline/state.json` | All configuration, pack metadata, and extraction history |
| `asset_pipeline/packs/<pack-id>/` | Installed pack contents (one directory per pack) |
| `asset_pipeline/preview_cache/` | Cached BLP → PNG conversions for the Asset Browser |
| `<Output Data>/override/` | Merged output from active packs |
The `asset_pipeline/` directory is gitignored.
## Typical Workflow
1. Launch: `python3 tools/asset_pipeline_gui.py`
2. **Configuration tab**: Browse to your WoW `Data/` folder, pick expansion, click **Save Configuration**.
3. Click **Run Extraction** — watch progress in the **Logs** tab. Cancel with **Cancel Extraction** if needed.
4. Switch to **Texture Packs** tab. Click **Install ZIP** and pick a texture pack.
5. Select the pack and click **Activate**.
6. (Optional) Install more packs, activate them, and use **Move Up/Down** to set priority.
7. Click **Rebuild Override** — the status bar shows progress, and the result appears in Logs.
8. (Optional) Switch to **Asset Browser** to explore extracted files — preview textures, inspect models, browse DBC tables.
9. Run wowee — it loads override textures on top of the extracted base assets.

View file

@ -92,8 +92,8 @@ inline ProcessHandle spawnProcess(const std::vector<std::string>& args) {
if (pid == 0) {
// Child process
setpgid(0, 0);
if (!freopen("/dev/null", "w", stdout)) { _exit(1); }
if (!freopen("/dev/null", "w", stderr)) { _exit(1); }
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
// Build argv for exec
std::vector<const char*> argv;

View file

@ -91,7 +91,7 @@ void ActivitySoundManager::shutdown() {
assetManager = nullptr;
}
void ActivitySoundManager::update([[maybe_unused]] float deltaTime) {
void ActivitySoundManager::update(float deltaTime) {
reapProcesses();
// Play swimming stroke sounds periodically when swimming and moving
@ -168,7 +168,7 @@ void ActivitySoundManager::rebuildJumpClipsForProfile(const std::string& raceFol
}
}
void ActivitySoundManager::rebuildSwimLoopClipsForProfile([[maybe_unused]] const std::string& raceFolder, [[maybe_unused]] const std::string& raceBase, [[maybe_unused]] bool male) {
void ActivitySoundManager::rebuildSwimLoopClipsForProfile(const std::string& raceFolder, const std::string& raceBase, bool male) {
swimLoopClips.clear();
// WoW 3.3.5a doesn't have dedicated swim loop sounds

View file

@ -117,10 +117,10 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) {
bool forestNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\ForestNormalNight.wav", forestNormalNightSounds_[0], assets);
forestSnowDaySounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\ForestSnowDay.wav", forestSnowDaySounds_[0], assets);
bool forestSnowDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\ForestSnowDay.wav", forestSnowDaySounds_[0], assets);
forestSnowNightSounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\ForestSnowNight.wav", forestSnowNightSounds_[0], assets);
bool forestSnowNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\ForestSnowNight.wav", forestSnowNightSounds_[0], assets);
beachDaySounds_.resize(1);
bool beachDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\BeachDay.wav", beachDaySounds_[0], assets);
@ -129,34 +129,34 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) {
bool beachNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\BeachNight.wav", beachNightSounds_[0], assets);
grasslandsDaySounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\GrasslandsDay.wav", grasslandsDaySounds_[0], assets);
bool grasslandsDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\GrasslandsDay.wav", grasslandsDaySounds_[0], assets);
grasslandsNightSounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\GrassLandsNight.wav", grasslandsNightSounds_[0], assets);
bool grasslandsNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\GrassLandsNight.wav", grasslandsNightSounds_[0], assets);
jungleDaySounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\JungleDay.wav", jungleDaySounds_[0], assets);
bool jungleDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\JungleDay.wav", jungleDaySounds_[0], assets);
jungleNightSounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\JungleNight.wav", jungleNightSounds_[0], assets);
bool jungleNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\JungleNight.wav", jungleNightSounds_[0], assets);
marshDaySounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\MarshDay.wav", marshDaySounds_[0], assets);
bool marshDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\MarshDay.wav", marshDaySounds_[0], assets);
marshNightSounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\MarshNight.wav", marshNightSounds_[0], assets);
bool marshNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\MarshNight.wav", marshNightSounds_[0], assets);
desertCanyonDaySounds_.resize(1);
bool desertCanyonDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\CanyonDesertDay.wav", desertCanyonDaySounds_[0], assets);
desertCanyonNightSounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\CanyonDesertNight.wav", desertCanyonNightSounds_[0], assets);
bool desertCanyonNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\CanyonDesertNight.wav", desertCanyonNightSounds_[0], assets);
desertPlainsDaySounds_.resize(1);
bool desertPlainsDayLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\PlainsDesertDay.wav", desertPlainsDaySounds_[0], assets);
desertPlainsNightSounds_.resize(1);
loadSound("Sound\\Ambience\\ZoneAmbience\\PlainsDesertNight.wav", desertPlainsNightSounds_[0], assets);
bool desertPlainsNightLoaded = loadSound("Sound\\Ambience\\ZoneAmbience\\PlainsDesertNight.wav", desertPlainsNightSounds_[0], assets);
// Load city ambience sounds (day and night where available)
stormwindDaySounds_.resize(1);
@ -169,10 +169,10 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) {
bool ironforgeLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\Ironforge.wav", ironforgeSounds_[0], assets);
darnassusDaySounds_.resize(1);
loadSound("Sound\\Ambience\\WMOAmbience\\DarnassusDay.wav", darnassusDaySounds_[0], assets);
bool darnassusDayLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\DarnassusDay.wav", darnassusDaySounds_[0], assets);
darnassusNightSounds_.resize(1);
loadSound("Sound\\Ambience\\WMOAmbience\\DarnassusNight.wav", darnassusNightSounds_[0], assets);
bool darnassusNightLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\DarnassusNight.wav", darnassusNightSounds_[0], assets);
orgrimmarDaySounds_.resize(1);
bool orgrimmarDayLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\OrgrimmarDay.wav", orgrimmarDaySounds_[0], assets);
@ -181,13 +181,13 @@ bool AmbientSoundManager::initialize(pipeline::AssetManager* assets) {
bool orgrimmarNightLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\OrgrimmarNight.wav", orgrimmarNightSounds_[0], assets);
undercitySounds_.resize(1);
loadSound("Sound\\Ambience\\WMOAmbience\\Undercity.wav", undercitySounds_[0], assets);
bool undercityLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\Undercity.wav", undercitySounds_[0], assets);
thunderbluffDaySounds_.resize(1);
loadSound("Sound\\Ambience\\WMOAmbience\\ThunderBluffDay.wav", thunderbluffDaySounds_[0], assets);
bool thunderbluffDayLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\ThunderBluffDay.wav", thunderbluffDaySounds_[0], assets);
thunderbluffNightSounds_.resize(1);
loadSound("Sound\\Ambience\\WMOAmbience\\ThunderBluffNight.wav", thunderbluffNightSounds_[0], assets);
bool thunderbluffNightLoaded = loadSound("Sound\\Ambience\\WMOAmbience\\ThunderBluffNight.wav", thunderbluffNightSounds_[0], assets);
// Load bell toll sounds
bellAllianceSounds_.resize(1);

View file

@ -295,7 +295,7 @@ void CombatSoundManager::playWeaponMiss(bool twoHanded) {
}
}
void CombatSoundManager::playImpact([[maybe_unused]] WeaponSize weaponSize, ImpactType impactType, bool isCrit) {
void CombatSoundManager::playImpact(WeaponSize weaponSize, ImpactType impactType, bool isCrit) {
// Select appropriate impact sound library
const std::vector<CombatSample>* normalLibrary = nullptr;
const std::vector<CombatSample>* critLibrary = nullptr;

View file

@ -34,16 +34,16 @@ bool UiSoundManager::initialize(pipeline::AssetManager* assets) {
bool charSheetCloseLoaded = loadSound("Sound\\Interface\\iAbilitiesCloseA.wav", characterSheetCloseSounds_[0], assets);
auctionOpenSounds_.resize(1);
loadSound("Sound\\Interface\\AuctionWindowOpen.wav", auctionOpenSounds_[0], assets);
bool auctionOpenLoaded = loadSound("Sound\\Interface\\AuctionWindowOpen.wav", auctionOpenSounds_[0], assets);
auctionCloseSounds_.resize(1);
loadSound("Sound\\Interface\\AuctionWindowClose.wav", auctionCloseSounds_[0], assets);
bool auctionCloseLoaded = loadSound("Sound\\Interface\\AuctionWindowClose.wav", auctionCloseSounds_[0], assets);
guildBankOpenSounds_.resize(1);
loadSound("Sound\\Interface\\GuildVaultOpen.wav", guildBankOpenSounds_[0], assets);
bool guildBankOpenLoaded = loadSound("Sound\\Interface\\GuildVaultOpen.wav", guildBankOpenSounds_[0], assets);
guildBankCloseSounds_.resize(1);
loadSound("Sound\\Interface\\GuildVaultClose.wav", guildBankCloseSounds_[0], assets);
bool guildBankCloseLoaded = loadSound("Sound\\Interface\\GuildVaultClose.wav", guildBankCloseSounds_[0], assets);
// Load button sounds
buttonClickSounds_.resize(1);
@ -63,7 +63,7 @@ bool UiSoundManager::initialize(pipeline::AssetManager* assets) {
bool questFailedLoaded = loadSound("Sound\\Interface\\igQuestFailed.wav", questFailedSounds_[0], assets);
questUpdateSounds_.resize(1);
loadSound("Sound\\Interface\\iQuestUpdate.wav", questUpdateSounds_[0], assets);
bool questUpdateLoaded = loadSound("Sound\\Interface\\iQuestUpdate.wav", questUpdateSounds_[0], assets);
// Load loot sounds
lootCoinSmallSounds_.resize(1);
@ -86,13 +86,13 @@ bool UiSoundManager::initialize(pipeline::AssetManager* assets) {
bool pickupBookLoaded = loadSound("Sound\\Interface\\PickUp\\PickUpBook.wav", pickupBookSounds_[0], assets);
pickupClothSounds_.resize(1);
loadSound("Sound\\Interface\\PickUp\\PickUpCloth_Leather01.wav", pickupClothSounds_[0], assets);
bool pickupClothLoaded = loadSound("Sound\\Interface\\PickUp\\PickUpCloth_Leather01.wav", pickupClothSounds_[0], assets);
pickupFoodSounds_.resize(1);
loadSound("Sound\\Interface\\PickUp\\PickUpFoodGeneric.wav", pickupFoodSounds_[0], assets);
bool pickupFoodLoaded = loadSound("Sound\\Interface\\PickUp\\PickUpFoodGeneric.wav", pickupFoodSounds_[0], assets);
pickupGemSounds_.resize(1);
loadSound("Sound\\Interface\\PickUp\\PickUpGems.wav", pickupGemSounds_[0], assets);
bool pickupGemLoaded = loadSound("Sound\\Interface\\PickUp\\PickUpGems.wav", pickupGemSounds_[0], assets);
// Load eating/drinking sounds
eatingSounds_.resize(1);
@ -107,13 +107,13 @@ bool UiSoundManager::initialize(pipeline::AssetManager* assets) {
// Load error/feedback sounds
errorSounds_.resize(1);
loadSound("Sound\\Interface\\Error.wav", errorSounds_[0], assets);
bool errorLoaded = loadSound("Sound\\Interface\\Error.wav", errorSounds_[0], assets);
selectTargetSounds_.resize(1);
loadSound("Sound\\Interface\\iSelectTarget.wav", selectTargetSounds_[0], assets);
bool selectTargetLoaded = loadSound("Sound\\Interface\\iSelectTarget.wav", selectTargetSounds_[0], assets);
deselectTargetSounds_.resize(1);
loadSound("Sound\\Interface\\iDeselectTarget.wav", deselectTargetSounds_[0], assets);
bool deselectTargetLoaded = loadSound("Sound\\Interface\\iDeselectTarget.wav", deselectTargetSounds_[0], assets);
LOG_INFO("UISoundManager: Window sounds - Bag: ", (bagOpenLoaded && bagCloseLoaded) ? "YES" : "NO",
", QuestLog: ", (questLogOpenLoaded && questLogCloseLoaded) ? "YES" : "NO",

View file

@ -122,7 +122,7 @@ bool WardenEmulator::initialize(const void* moduleCode, size_t moduleSize, uint3
uint32_t WardenEmulator::hookAPI(const std::string& dllName,
const std::string& functionName,
[[maybe_unused]] std::function<uint32_t(WardenEmulator&, const std::vector<uint32_t>&)> handler) {
std::function<uint32_t(WardenEmulator&, const std::vector<uint32_t>&)> handler) {
// Allocate address for this API stub
static uint32_t nextStubAddr = API_STUB_BASE;
uint32_t stubAddr = nextStubAddr;
@ -239,7 +239,7 @@ std::string WardenEmulator::readString(uint32_t address, size_t maxLen) {
return std::string(buffer.data());
}
uint32_t WardenEmulator::allocateMemory(size_t size, [[maybe_unused]] uint32_t protection) {
uint32_t WardenEmulator::allocateMemory(size_t size, uint32_t protection) {
// Align to 4KB
size = (size + 0xFFF) & ~0xFFF;
@ -315,7 +315,7 @@ uint32_t WardenEmulator::apiVirtualFree(WardenEmulator& emu, const std::vector<u
return emu.freeMemory(lpAddress) ? 1 : 0;
}
uint32_t WardenEmulator::apiGetTickCount([[maybe_unused]] WardenEmulator& emu, [[maybe_unused]] const std::vector<uint32_t>& args) {
uint32_t WardenEmulator::apiGetTickCount(WardenEmulator& emu, const std::vector<uint32_t>& args) {
auto now = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
uint32_t ticks = static_cast<uint32_t>(ms & 0xFFFFFFFF);
@ -324,7 +324,7 @@ uint32_t WardenEmulator::apiGetTickCount([[maybe_unused]] WardenEmulator& emu, [
return ticks;
}
uint32_t WardenEmulator::apiSleep([[maybe_unused]] WardenEmulator& emu, const std::vector<uint32_t>& args) {
uint32_t WardenEmulator::apiSleep(WardenEmulator& emu, const std::vector<uint32_t>& args) {
if (args.size() < 1) return 0;
uint32_t dwMilliseconds = args[0];
@ -333,12 +333,12 @@ uint32_t WardenEmulator::apiSleep([[maybe_unused]] WardenEmulator& emu, const st
return 0;
}
uint32_t WardenEmulator::apiGetCurrentThreadId([[maybe_unused]] WardenEmulator& emu, [[maybe_unused]] const std::vector<uint32_t>& args) {
uint32_t WardenEmulator::apiGetCurrentThreadId(WardenEmulator& emu, const std::vector<uint32_t>& args) {
std::cout << "[WinAPI] GetCurrentThreadId() = 1234" << '\n';
return 1234; // Fake thread ID
}
uint32_t WardenEmulator::apiGetCurrentProcessId([[maybe_unused]] WardenEmulator& emu, [[maybe_unused]] const std::vector<uint32_t>& args) {
uint32_t WardenEmulator::apiGetCurrentProcessId(WardenEmulator& emu, const std::vector<uint32_t>& args) {
std::cout << "[WinAPI] GetCurrentProcessId() = 5678" << '\n';
return 5678; // Fake process ID
}
@ -347,7 +347,7 @@ uint32_t WardenEmulator::apiReadProcessMemory(WardenEmulator& emu, const std::ve
// ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead)
if (args.size() < 5) return 0;
[[maybe_unused]] uint32_t hProcess = args[0];
uint32_t hProcess = args[0];
uint32_t lpBaseAddress = args[1];
uint32_t lpBuffer = args[2];
uint32_t nSize = args[3];
@ -377,11 +377,13 @@ uint32_t WardenEmulator::apiReadProcessMemory(WardenEmulator& emu, const std::ve
// Unicorn Callbacks
// ============================================================================
void WardenEmulator::hookCode([[maybe_unused]] uc_engine* uc, uint64_t address, [[maybe_unused]] uint32_t size, [[maybe_unused]] void* userData) {
void WardenEmulator::hookCode(uc_engine* uc, uint64_t address, uint32_t size, void* userData) {
WardenEmulator* emu = static_cast<WardenEmulator*>(userData);
std::cout << "[Trace] 0x" << std::hex << address << std::dec << '\n';
}
void WardenEmulator::hookMemInvalid([[maybe_unused]] uc_engine* uc, int type, uint64_t address, int size, [[maybe_unused]] int64_t value, [[maybe_unused]] void* userData) {
void WardenEmulator::hookMemInvalid(uc_engine* uc, int type, uint64_t address, int size, int64_t value, void* userData) {
WardenEmulator* emu = static_cast<WardenEmulator*>(userData);
const char* typeStr = "UNKNOWN";
switch (type) {

View file

@ -129,7 +129,7 @@ bool WardenModule::load(const std::vector<uint8_t>& moduleData,
}
bool WardenModule::processCheckRequest(const std::vector<uint8_t>& checkData,
[[maybe_unused]] std::vector<uint8_t>& responseOut) {
std::vector<uint8_t>& responseOut) {
if (!loaded_) {
std::cerr << "[WardenModule] Module not loaded, cannot process checks" << '\n';
return false;
@ -198,7 +198,7 @@ bool WardenModule::processCheckRequest(const std::vector<uint8_t>& checkData,
return false;
}
uint32_t WardenModule::tick([[maybe_unused]] uint32_t deltaMs) {
uint32_t WardenModule::tick(uint32_t deltaMs) {
if (!loaded_ || !funcList_.tick) {
return 0; // No tick needed
}
@ -209,7 +209,7 @@ uint32_t WardenModule::tick([[maybe_unused]] uint32_t deltaMs) {
return 0;
}
void WardenModule::generateRC4Keys([[maybe_unused]] uint8_t* packet) {
void WardenModule::generateRC4Keys(uint8_t* packet) {
if (!loaded_ || !funcList_.generateRC4Keys) {
return;
}
@ -633,11 +633,9 @@ bool WardenModule::applyRelocations() {
currentOffset += delta;
if (currentOffset + 4 <= moduleSize_) {
uint8_t* addr = static_cast<uint8_t*>(moduleMemory_) + currentOffset;
uint32_t val;
std::memcpy(&val, addr, sizeof(uint32_t));
val += moduleBase_;
std::memcpy(addr, &val, sizeof(uint32_t));
uint32_t* ptr = reinterpret_cast<uint32_t*>(
static_cast<uint8_t*>(moduleMemory_) + currentOffset);
*ptr += moduleBase_;
relocCount++;
} else {
std::cerr << "[WardenModule] Relocation offset " << currentOffset
@ -757,16 +755,16 @@ bool WardenModule::initializeModule() {
void (*logMessage)(const char* msg);
};
// Setup client callbacks (used when calling module entry point below)
[[maybe_unused]] ClientCallbacks callbacks = {};
// Setup client callbacks
ClientCallbacks callbacks = {};
// Stub callbacks (would need real implementations)
callbacks.sendPacket = []([[maybe_unused]] uint8_t* data, size_t len) {
callbacks.sendPacket = [](uint8_t* data, size_t len) {
std::cout << "[WardenModule Callback] sendPacket(" << len << " bytes)" << '\n';
// TODO: Send CMSG_WARDEN_DATA packet
};
callbacks.validateModule = []([[maybe_unused]] uint8_t* hash) {
callbacks.validateModule = [](uint8_t* hash) {
std::cout << "[WardenModule Callback] validateModule()" << '\n';
// TODO: Validate module hash
};
@ -781,7 +779,7 @@ bool WardenModule::initializeModule() {
free(ptr);
};
callbacks.generateRC4 = []([[maybe_unused]] uint8_t* seed) {
callbacks.generateRC4 = [](uint8_t* seed) {
std::cout << "[WardenModule Callback] generateRC4()" << '\n';
// TODO: Re-key RC4 cipher
};

View file

@ -30,39 +30,34 @@ BLPImage BLPLoader::load(const std::vector<uint8_t>& blpData) {
}
BLPImage BLPLoader::loadBLP1(const uint8_t* data, size_t size) {
// Copy header to stack to avoid unaligned reinterpret_cast (UB on strict platforms)
if (size < sizeof(BLP1Header)) {
LOG_ERROR("BLP1 data too small for header");
return BLPImage();
}
BLP1Header header;
std::memcpy(&header, data, sizeof(BLP1Header));
// BLP1 header has all uint32 fields (different layout from BLP2)
const BLP1Header* header = reinterpret_cast<const BLP1Header*>(data);
BLPImage image;
image.format = BLPFormat::BLP1;
image.width = header.width;
image.height = header.height;
image.width = header->width;
image.height = header->height;
image.channels = 4;
image.mipLevels = header.hasMips ? 16 : 1;
image.mipLevels = header->hasMips ? 16 : 1;
// BLP1 compression: 0=JPEG (not used in WoW), 1=palette/indexed
// BLP1 does NOT support DXT — only palette with optional alpha
if (header.compression == 1) {
if (header->compression == 1) {
image.compression = BLPCompression::PALETTE;
} else if (header.compression == 0) {
} else if (header->compression == 0) {
LOG_WARNING("BLP1 JPEG compression not supported");
return BLPImage();
} else {
LOG_WARNING("BLP1 unknown compression: ", header.compression);
LOG_WARNING("BLP1 unknown compression: ", header->compression);
return BLPImage();
}
LOG_DEBUG("Loading BLP1: ", image.width, "x", image.height, " ",
getCompressionName(image.compression), " alpha=", header.alphaBits);
getCompressionName(image.compression), " alpha=", header->alphaBits);
// Get first mipmap (full resolution)
uint32_t offset = header.mipOffsets[0];
uint32_t mipSize = header.mipSizes[0];
uint32_t offset = header->mipOffsets[0];
uint32_t mipSize = header->mipSizes[0];
if (offset + mipSize > size) {
LOG_ERROR("BLP1 mipmap data out of bounds (offset=", offset, " size=", mipSize, " fileSize=", size, ")");
@ -75,50 +70,45 @@ BLPImage BLPLoader::loadBLP1(const uint8_t* data, size_t size) {
int pixelCount = image.width * image.height;
image.data.resize(pixelCount * 4); // RGBA8
decompressPalette(mipData, image.data.data(), header.palette,
image.width, image.height, static_cast<uint8_t>(header.alphaBits));
decompressPalette(mipData, image.data.data(), header->palette,
image.width, image.height, static_cast<uint8_t>(header->alphaBits));
return image;
}
BLPImage BLPLoader::loadBLP2(const uint8_t* data, size_t size) {
// Copy header to stack to avoid unaligned reinterpret_cast (UB on strict platforms)
if (size < sizeof(BLP2Header)) {
LOG_ERROR("BLP2 data too small for header");
return BLPImage();
}
BLP2Header header;
std::memcpy(&header, data, sizeof(BLP2Header));
// BLP2 header has uint8 fields for compression/alpha/encoding
const BLP2Header* header = reinterpret_cast<const BLP2Header*>(data);
BLPImage image;
image.format = BLPFormat::BLP2;
image.width = header.width;
image.height = header.height;
image.width = header->width;
image.height = header->height;
image.channels = 4;
image.mipLevels = header.hasMips ? 16 : 1;
image.mipLevels = header->hasMips ? 16 : 1;
// BLP2 compression types:
// 1 = palette/uncompressed
// 2 = DXTC (DXT1/DXT3/DXT5 based on alphaDepth + alphaEncoding)
// 3 = plain A8R8G8B8
if (header.compression == 1) {
if (header->compression == 1) {
image.compression = BLPCompression::PALETTE;
} else if (header.compression == 2) {
} else if (header->compression == 2) {
// BLP2 DXTC format selection based on alphaDepth + alphaEncoding:
// alphaDepth=0 → DXT1 (no alpha)
// alphaDepth>0, alphaEncoding=0 → DXT1 (1-bit alpha)
// alphaDepth>0, alphaEncoding=1 → DXT3 (explicit 4-bit alpha)
// alphaDepth>0, alphaEncoding=7 → DXT5 (interpolated alpha)
if (header.alphaDepth == 0 || header.alphaEncoding == 0) {
if (header->alphaDepth == 0 || header->alphaEncoding == 0) {
image.compression = BLPCompression::DXT1;
} else if (header.alphaEncoding == 1) {
} else if (header->alphaEncoding == 1) {
image.compression = BLPCompression::DXT3;
} else if (header.alphaEncoding == 7) {
} else if (header->alphaEncoding == 7) {
image.compression = BLPCompression::DXT5;
} else {
image.compression = BLPCompression::DXT1;
}
} else if (header.compression == 3) {
} else if (header->compression == 3) {
image.compression = BLPCompression::ARGB8888;
} else {
image.compression = BLPCompression::ARGB8888;
@ -126,13 +116,13 @@ BLPImage BLPLoader::loadBLP2(const uint8_t* data, size_t size) {
LOG_DEBUG("Loading BLP2: ", image.width, "x", image.height, " ",
getCompressionName(image.compression),
" (comp=", (int)header.compression, " alphaDepth=", (int)header.alphaDepth,
" alphaEnc=", (int)header.alphaEncoding, " mipOfs=", header.mipOffsets[0],
" mipSize=", header.mipSizes[0], ")");
" (comp=", (int)header->compression, " alphaDepth=", (int)header->alphaDepth,
" alphaEnc=", (int)header->alphaEncoding, " mipOfs=", header->mipOffsets[0],
" mipSize=", header->mipSizes[0], ")");
// Get first mipmap (full resolution)
uint32_t offset = header.mipOffsets[0];
uint32_t mipSize = header.mipSizes[0];
uint32_t offset = header->mipOffsets[0];
uint32_t mipSize = header->mipSizes[0];
if (offset + mipSize > size) {
LOG_ERROR("BLP2 mipmap data out of bounds");
@ -159,8 +149,8 @@ BLPImage BLPLoader::loadBLP2(const uint8_t* data, size_t size) {
break;
case BLPCompression::PALETTE:
decompressPalette(mipData, image.data.data(), header.palette,
image.width, image.height, header.alphaDepth);
decompressPalette(mipData, image.data.data(), header->palette,
image.width, image.height, header->alphaDepth);
break;
case BLPCompression::ARGB8888:

View file

@ -42,20 +42,19 @@ bool DBCFile::load(const std::vector<uint8_t>& dbcData) {
return false;
}
// Read header safely (avoid unaligned reinterpret_cast — UB on strict platforms)
DBCHeader header;
std::memcpy(&header, dbcData.data(), sizeof(DBCHeader));
// Read header
const DBCHeader* header = reinterpret_cast<const DBCHeader*>(dbcData.data());
// Verify magic
if (std::memcmp(header.magic, "WDBC", 4) != 0) {
LOG_ERROR("Invalid DBC magic: ", std::string(header.magic, 4));
if (std::memcmp(header->magic, "WDBC", 4) != 0) {
LOG_ERROR("Invalid DBC magic: ", std::string(header->magic, 4));
return false;
}
recordCount = header.recordCount;
fieldCount = header.fieldCount;
recordSize = header.recordSize;
stringBlockSize = header.stringBlockSize;
recordCount = header->recordCount;
fieldCount = header->fieldCount;
recordSize = header->recordSize;
stringBlockSize = header->stringBlockSize;
// Validate sizes
uint32_t expectedSize = sizeof(DBCHeader) + (recordCount * recordSize) + stringBlockSize;
@ -112,9 +111,8 @@ uint32_t DBCFile::getUInt32(uint32_t recordIndex, uint32_t fieldIndex) const {
return 0;
}
uint32_t value;
std::memcpy(&value, record + (fieldIndex * 4), sizeof(uint32_t));
return value;
const uint32_t* field = reinterpret_cast<const uint32_t*>(record + (fieldIndex * 4));
return *field;
}
int32_t DBCFile::getInt32(uint32_t recordIndex, uint32_t fieldIndex) const {
@ -131,9 +129,8 @@ float DBCFile::getFloat(uint32_t recordIndex, uint32_t fieldIndex) const {
return 0.0f;
}
float value;
std::memcpy(&value, record + (fieldIndex * 4), sizeof(float));
return value;
const float* field = reinterpret_cast<const float*>(record + (fieldIndex * 4));
return *field;
}
std::string DBCFile::getString(uint32_t recordIndex, uint32_t fieldIndex) const {

View file

@ -1456,7 +1456,9 @@ bool M2Loader::loadSkin(const std::vector<uint8_t>& skinData, M2Model& model) {
if (header.nSubmeshes > 0 && header.ofsSubmeshes > 0) {
submeshes = readArray<M2SkinSubmesh>(skinData, header.ofsSubmeshes, header.nSubmeshes);
core::Logger::getInstance().debug(" Submeshes: ", submeshes.size());
(void)submeshes;
for (size_t i = 0; i < submeshes.size(); i++) {
const auto& sm = submeshes[i];
}
}
// Read batches with proper submesh references

View file

@ -1610,7 +1610,7 @@ glm::mat4 CharacterRenderer::getBoneTransform(const pipeline::M2Bone& bone, floa
// --- Rendering ---
void CharacterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, [[maybe_unused]] const Camera& camera) {
void CharacterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera) {
if (instances.empty() || !opaquePipeline_) {
return;
}

View file

@ -2381,11 +2381,11 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
}
}
// Bind material descriptor set (set 1) — skip batch if missing
// to avoid inheriting a stale descriptor set from a prior renderer
if (!batch.materialSet) continue;
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
// Bind material descriptor set (set 1)
if (batch.materialSet) {
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout_, 1, 1, &batch.materialSet, 0, nullptr);
}
// Push constants
M2PushConstants pc;

View file

@ -19,7 +19,7 @@ bool VkRenderTarget::create(VkContext& ctx, uint32_t width, uint32_t height,
// Create color image (multisampled if MSAA)
colorImage_ = createImage(device, allocator, width, height, format,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | (useMSAA ? static_cast<VkImageUsageFlags>(0) : static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_SAMPLED_BIT)),
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | (useMSAA ? VkImageUsageFlags(0) : VK_IMAGE_USAGE_SAMPLED_BIT),
msaaSamples);
if (!colorImage_.image) {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff