Fix release packaging and macOS stack overflow crash

- Fix StormLib package name: libstormlib-dev → libstorm-dev (correct
  Ubuntu package name) across all CI workflows and extract_assets.sh
- Build StormLib from source on Windows CI (no MSYS2 package exists),
  ensuring asset_extract.exe is included in release archives
- Update extract_assets.sh/.ps1 to prefer pre-built asset_extract
  binary next to the script (release archives) before trying build dir
- Move ADTTerrain allocations from stack to heap in prepareTile() to
  fix stack overflow on macOS (worker threads default to 512 KB stack,
  two ADTTerrain structs ≈ 560 KB exceeded that)
This commit is contained in:
Kelsi 2026-02-25 01:55:16 -08:00
parent 624744da86
commit 8fe53171eb
6 changed files with 76 additions and 37 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -269,9 +269,11 @@ std::shared_ptr<PendingTile> 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<MapChunk,256> ≈ 280 KB; macOS worker
// threads default to 512 KB stack, so two on-stack copies would overflow)
auto terrainPtr = std::make_unique<pipeline::ADTTerrain>(pipeline::ADTLoader::load(adtData));
if (!terrainPtr->isLoaded()) {
LOG_ERROR("Failed to parse ADT terrain: ", adtPath);
return nullptr;
}
@ -282,47 +284,47 @@ std::shared_ptr<PendingTile> 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<uint32_t>(terrain.doodadNames.size());
const uint32_t wmoNameBase = static_cast<uint32_t>(terrain.wmoNames.size());
auto objTerrain = std::make_unique<pipeline::ADTTerrain>(pipeline::ADTLoader::load(objData));
if (objTerrain->isLoaded()) {
const uint32_t doodadNameBase = static_cast<uint32_t>(terrainPtr->doodadNames.size());
const uint32_t wmoNameBase = static_cast<uint32_t>(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<uint32_t> 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<uint32_t> 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<PendingTile> 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<PendingTile> TerrainManager::prepareTile(int x, int y) {
auto pending = std::make_shared<PendingTile>();
pending->coord = coord;
pending->terrain = std::move(terrain);
pending->terrain = std::move(*terrainPtr);
pending->mesh = std::move(mesh);
std::unordered_set<uint32_t> preparedModelIds;