diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34f851f6..29a13e11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: libavutil-dev \ libunicorn-dev \ libx11-dev - sudo apt-get install -y libstormlib-dev || true + sudo apt-get install -y libstorm-dev || true - name: Configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @@ -191,9 +191,16 @@ jobs: mingw-w64-clang-aarch64-shaderc git - - name: Install optional packages + - name: Build StormLib from source shell: msys2 {0} - run: pacman -S --noconfirm --needed mingw-w64-clang-aarch64-stormlib || true + run: | + git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib + cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ + -DBUILD_SHARED_LIBS=OFF + cmake --build /tmp/StormLib/build --parallel $(nproc) + cmake --install /tmp/StormLib/build - name: Configure shell: msys2 {0} @@ -254,9 +261,16 @@ jobs: mingw-w64-x86_64-nsis git - - name: Install optional packages + - name: Build StormLib from source shell: msys2 {0} - run: pacman -S --noconfirm --needed mingw-w64-x86_64-stormlib || true + run: | + git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib + cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ + -DBUILD_SHARED_LIBS=OFF + cmake --build /tmp/StormLib/build --parallel $(nproc) + cmake --install /tmp/StormLib/build - name: Configure shell: msys2 {0} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52579ddc..17fa8a17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,7 +54,7 @@ jobs: libavutil-dev \ libunicorn-dev \ libx11-dev - sudo apt-get install -y libstormlib-dev || true + sudo apt-get install -y libstorm-dev || true - name: Configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release @@ -250,9 +250,19 @@ jobs: - name: Install optional packages shell: msys2 {0} run: | - pacman -S --noconfirm --needed ${{ matrix.prefix }}-stormlib || true pacman -S --noconfirm --needed zip + - name: Build StormLib from source + shell: msys2 {0} + run: | + git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib + cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \ + -DBUILD_SHARED_LIBS=OFF + cmake --build /tmp/StormLib/build --parallel $(nproc) + cmake --install /tmp/StormLib/build + - name: Configure shell: msys2 {0} run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 91296a97..4709225b 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -45,7 +45,7 @@ jobs: libavutil-dev \ libunicorn-dev \ libx11-dev - sudo apt-get install -y libstormlib-dev || true + sudo apt-get install -y libstorm-dev || true - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -117,7 +117,7 @@ jobs: libavutil-dev \ libunicorn-dev \ libx11-dev - sudo apt-get install -y libstormlib-dev || true + sudo apt-get install -y libstorm-dev || true - name: Configure (ASan/UBSan) run: | diff --git a/extract_assets.ps1 b/extract_assets.ps1 index 9bcce87e..68e810b4 100644 --- a/extract_assets.ps1 +++ b/extract_assets.ps1 @@ -29,9 +29,16 @@ $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $BuildDir = Join-Path $ScriptDir "build" -$Binary = Join-Path $BuildDir "bin\asset_extract.exe" $OutputDir = Join-Path $ScriptDir "Data" +# Prefer pre-built binary next to this script (release archives), then build dir +$BinaryLocal = Join-Path $ScriptDir "asset_extract.exe" +if (Test-Path $BinaryLocal) { + $Binary = $BinaryLocal +} else { + $Binary = Join-Path $BuildDir "bin\asset_extract.exe" +} + # --- Validate arguments --- if (-not (Test-Path $MpqDir -PathType Container)) { Write-Error "Error: Directory not found: $MpqDir" diff --git a/extract_assets.sh b/extract_assets.sh index 532ee696..19916ed9 100755 --- a/extract_assets.sh +++ b/extract_assets.sh @@ -17,9 +17,15 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BUILD_DIR="${SCRIPT_DIR}/build" -BINARY="${BUILD_DIR}/bin/asset_extract" OUTPUT_DIR="${SCRIPT_DIR}/Data" +# Prefer pre-built binary next to this script (release archives), then build dir +if [ -x "${SCRIPT_DIR}/asset_extract" ]; then + BINARY="${SCRIPT_DIR}/asset_extract" +else + BINARY="${BUILD_DIR}/bin/asset_extract" +fi + # --- Validate arguments --- if [ $# -lt 1 ]; then echo "Usage: $0 /path/to/WoW/Data [classic|turtle|tbc|wotlk]" @@ -73,7 +79,7 @@ if [ ! -f "$BINARY" ]; then fi if [ "$STORMLIB_FOUND" = false ]; then echo "Error: StormLib not found." - echo " Ubuntu/Debian: sudo apt install libstormlib-dev" + echo " Ubuntu/Debian: sudo apt install libstorm-dev" echo " macOS: brew install stormlib" echo " From source: https://github.com/ladislav-zezula/StormLib" exit 1 diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index 4b5c0b7c..da92981a 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -269,9 +269,11 @@ std::shared_ptr TerrainManager::prepareTile(int x, int y) { return nullptr; } - // Parse ADT - pipeline::ADTTerrain terrain = pipeline::ADTLoader::load(adtData); - if (!terrain.isLoaded()) { + // Parse ADT — allocate on heap to avoid stack overflow on macOS + // (ADTTerrain contains std::array ≈ 280 KB; macOS worker + // threads default to 512 KB stack, so two on-stack copies would overflow) + auto terrainPtr = std::make_unique(pipeline::ADTLoader::load(adtData)); + if (!terrainPtr->isLoaded()) { LOG_ERROR("Failed to parse ADT terrain: ", adtPath); return nullptr; } @@ -282,47 +284,47 @@ std::shared_ptr TerrainManager::prepareTile(int x, int y) { std::to_string(coord.x) + "_" + std::to_string(coord.y) + "_obj0.adt"; auto objData = assetManager->readFile(objPath); if (!objData.empty()) { - pipeline::ADTTerrain objTerrain = pipeline::ADTLoader::load(objData); - if (objTerrain.isLoaded()) { - const uint32_t doodadNameBase = static_cast(terrain.doodadNames.size()); - const uint32_t wmoNameBase = static_cast(terrain.wmoNames.size()); + auto objTerrain = std::make_unique(pipeline::ADTLoader::load(objData)); + if (objTerrain->isLoaded()) { + const uint32_t doodadNameBase = static_cast(terrainPtr->doodadNames.size()); + const uint32_t wmoNameBase = static_cast(terrainPtr->wmoNames.size()); - terrain.doodadNames.insert(terrain.doodadNames.end(), - objTerrain.doodadNames.begin(), objTerrain.doodadNames.end()); - terrain.wmoNames.insert(terrain.wmoNames.end(), - objTerrain.wmoNames.begin(), objTerrain.wmoNames.end()); + terrainPtr->doodadNames.insert(terrainPtr->doodadNames.end(), + objTerrain->doodadNames.begin(), objTerrain->doodadNames.end()); + terrainPtr->wmoNames.insert(terrainPtr->wmoNames.end(), + objTerrain->wmoNames.begin(), objTerrain->wmoNames.end()); std::unordered_set existingDoodadUniqueIds; - existingDoodadUniqueIds.reserve(terrain.doodadPlacements.size()); - for (const auto& p : terrain.doodadPlacements) { + existingDoodadUniqueIds.reserve(terrainPtr->doodadPlacements.size()); + for (const auto& p : terrainPtr->doodadPlacements) { if (p.uniqueId != 0) existingDoodadUniqueIds.insert(p.uniqueId); } size_t mergedDoodads = 0; - for (auto placement : objTerrain.doodadPlacements) { - if (placement.nameId >= objTerrain.doodadNames.size()) continue; + for (auto placement : objTerrain->doodadPlacements) { + if (placement.nameId >= objTerrain->doodadNames.size()) continue; placement.nameId += doodadNameBase; if (placement.uniqueId != 0 && !existingDoodadUniqueIds.insert(placement.uniqueId).second) { continue; } - terrain.doodadPlacements.push_back(placement); + terrainPtr->doodadPlacements.push_back(placement); mergedDoodads++; } std::unordered_set existingWmoUniqueIds; - existingWmoUniqueIds.reserve(terrain.wmoPlacements.size()); - for (const auto& p : terrain.wmoPlacements) { + existingWmoUniqueIds.reserve(terrainPtr->wmoPlacements.size()); + for (const auto& p : terrainPtr->wmoPlacements) { if (p.uniqueId != 0) existingWmoUniqueIds.insert(p.uniqueId); } size_t mergedWmos = 0; - for (auto placement : objTerrain.wmoPlacements) { - if (placement.nameId >= objTerrain.wmoNames.size()) continue; + for (auto placement : objTerrain->wmoPlacements) { + if (placement.nameId >= objTerrain->wmoNames.size()) continue; placement.nameId += wmoNameBase; if (placement.uniqueId != 0 && !existingWmoUniqueIds.insert(placement.uniqueId).second) { continue; } - terrain.wmoPlacements.push_back(placement); + terrainPtr->wmoPlacements.push_back(placement); mergedWmos++; } @@ -334,11 +336,11 @@ std::shared_ptr TerrainManager::prepareTile(int x, int y) { } // Set tile coordinates so mesh knows where to position this tile in world - terrain.coord.x = x; - terrain.coord.y = y; + terrainPtr->coord.x = x; + terrainPtr->coord.y = y; // Generate mesh - pipeline::TerrainMesh mesh = pipeline::TerrainMeshGenerator::generate(terrain); + pipeline::TerrainMesh mesh = pipeline::TerrainMeshGenerator::generate(*terrainPtr); if (mesh.validChunkCount == 0) { LOG_ERROR("Failed to generate terrain mesh: ", adtPath); return nullptr; @@ -346,7 +348,7 @@ std::shared_ptr TerrainManager::prepareTile(int x, int y) { auto pending = std::make_shared(); pending->coord = coord; - pending->terrain = std::move(terrain); + pending->terrain = std::move(*terrainPtr); pending->mesh = std::move(mesh); std::unordered_set preparedModelIds;