Initial commit: wowee native WoW 3.3.5a client

This commit is contained in:
Kelsi 2026-02-02 12:24:50 -08:00
commit ce6cb8f38e
147 changed files with 32347 additions and 0 deletions

View file

@ -0,0 +1,210 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include <array>
namespace wowee {
namespace pipeline {
/**
* ADT chunk coordinates
*/
struct ADTCoord {
int32_t x;
int32_t y;
};
/**
* Heightmap for a map chunk (9x9 + 8x8 grid)
*/
struct HeightMap {
std::array<float, 145> heights; // 9x9 outer + 8x8 inner vertices
float getHeight(int x, int y) const;
bool isLoaded() const { return heights[0] != 0.0f || heights[1] != 0.0f; }
};
/**
* Texture layer for a map chunk
*/
struct TextureLayer {
uint32_t textureId; // Index into MTEX array
uint32_t flags; // Layer flags
uint32_t offsetMCAL; // Offset to alpha map in MCAL chunk
uint32_t effectId; // Effect ID (optional)
bool useAlpha() const { return (flags & 0x100) != 0; }
bool compressedAlpha() const { return (flags & 0x200) != 0; }
};
/**
* Map chunk (256x256 units, 1/16 of ADT)
*/
struct MapChunk {
uint32_t flags;
uint32_t indexX;
uint32_t indexY;
uint16_t holes; // 4x4 bitmask for terrain holes (cave entrances, etc.)
float position[3]; // World position (X, Y, Z)
HeightMap heightMap;
std::vector<TextureLayer> layers;
std::vector<uint8_t> alphaMap; // Alpha blend maps for layers
// Normals (compressed)
std::array<int8_t, 145 * 3> normals; // X, Y, Z per vertex
bool hasHeightMap() const { return heightMap.isLoaded(); }
bool hasLayers() const { return !layers.empty(); }
// Check if a quad has a hole (y and x are quad indices 0-7)
bool isHole(int y, int x) const {
int column = y / 2;
int row = x / 2;
int bit = 1 << (column * 4 + row);
return (bit & holes) != 0;
}
};
/**
* Complete ADT terrain tile (16x16 map chunks)
*/
struct ADTTerrain {
bool loaded = false;
uint32_t version = 0;
ADTCoord coord; // ADT coordinates (e.g., 32, 49 for Azeroth)
// 16x16 map chunks (256 total)
std::array<MapChunk, 256> chunks;
// Texture filenames
std::vector<std::string> textures;
// Doodad definitions (M2 models)
std::vector<std::string> doodadNames;
std::vector<uint32_t> doodadIds;
// WMO definitions (buildings)
std::vector<std::string> wmoNames;
std::vector<uint32_t> wmoIds;
// Doodad placement data (from MDDF chunk)
struct DoodadPlacement {
uint32_t nameId; // Index into doodadNames
uint32_t uniqueId;
float position[3]; // X, Y, Z
float rotation[3]; // Rotation in degrees
uint16_t scale; // 1024 = 1.0
uint16_t flags;
};
std::vector<DoodadPlacement> doodadPlacements;
// WMO placement data (from MODF chunk)
struct WMOPlacement {
uint32_t nameId; // Index into wmoNames
uint32_t uniqueId;
float position[3]; // X, Y, Z
float rotation[3]; // Rotation in degrees
float extentLower[3]; // Bounding box
float extentUpper[3];
uint16_t flags;
uint16_t doodadSet;
};
std::vector<WMOPlacement> wmoPlacements;
// Water/liquid data (from MH2O chunk)
struct WaterLayer {
uint16_t liquidType; // Type of liquid (0=water, 1=ocean, 2=magma, 3=slime)
uint16_t flags;
float minHeight;
float maxHeight;
uint8_t x; // X offset within chunk (0-7)
uint8_t y; // Y offset within chunk (0-7)
uint8_t width; // Width in vertices (1-9)
uint8_t height; // Height in vertices (1-9)
std::vector<float> heights; // Height values (width * height)
std::vector<uint8_t> mask; // Render mask (which tiles to render)
};
struct ChunkWater {
std::vector<WaterLayer> layers;
bool hasWater() const { return !layers.empty(); }
};
std::array<ChunkWater, 256> waterData; // Water for each chunk
MapChunk& getChunk(int x, int y) { return chunks[y * 16 + x]; }
const MapChunk& getChunk(int x, int y) const { return chunks[y * 16 + x]; }
bool isLoaded() const { return loaded; }
size_t getTextureCount() const { return textures.size(); }
};
/**
* ADT terrain loader
*
* Loads WoW 3.3.5a ADT (Azeroth Data Tile) terrain files
*/
class ADTLoader {
public:
/**
* Load ADT terrain from byte data
* @param adtData Raw ADT file data
* @return Loaded terrain (check isLoaded())
*/
static ADTTerrain load(const std::vector<uint8_t>& adtData);
private:
// Chunk identifiers (as they appear in file when read as little-endian uint32)
static constexpr uint32_t MVER = 0x4D564552; // Version (ASCII "MVER")
static constexpr uint32_t MHDR = 0x4D484452; // Header (ASCII "MHDR")
static constexpr uint32_t MCIN = 0x4D43494E; // Chunk info (ASCII "MCIN")
static constexpr uint32_t MTEX = 0x4D544558; // Textures (ASCII "MTEX")
static constexpr uint32_t MMDX = 0x4D4D4458; // Doodad names (ASCII "MMDX")
static constexpr uint32_t MMID = 0x4D4D4944; // Doodad IDs (ASCII "MMID")
static constexpr uint32_t MWMO = 0x4D574D4F; // WMO names (ASCII "MWMO")
static constexpr uint32_t MWID = 0x4D574944; // WMO IDs (ASCII "MWID")
static constexpr uint32_t MDDF = 0x4D444446; // Doodad placement (ASCII "MDDF")
static constexpr uint32_t MODF = 0x4D4F4446; // WMO placement (ASCII "MODF")
static constexpr uint32_t MH2O = 0x4D48324F; // Water/liquid (ASCII "MH2O")
static constexpr uint32_t MCNK = 0x4D434E4B; // Map chunk (ASCII "MCNK")
// Sub-chunks within MCNK
static constexpr uint32_t MCVT = 0x4D435654; // Height values (ASCII "MCVT")
static constexpr uint32_t MCNR = 0x4D434E52; // Normals (ASCII "MCNR")
static constexpr uint32_t MCLY = 0x4D434C59; // Layers (ASCII "MCLY")
static constexpr uint32_t MCRF = 0x4D435246; // References (ASCII "MCRF")
static constexpr uint32_t MCSH = 0x4D435348; // Shadow map (ASCII "MCSH")
static constexpr uint32_t MCAL = 0x4D43414C; // Alpha maps (ASCII "MCAL")
static constexpr uint32_t MCLQ = 0x4D434C51; // Liquid (deprecated) (ASCII "MCLQ")
struct ChunkHeader {
uint32_t magic;
uint32_t size;
};
static bool readChunkHeader(const uint8_t* data, size_t offset, size_t dataSize, ChunkHeader& header);
static uint32_t readUInt32(const uint8_t* data, size_t offset);
static uint16_t readUInt16(const uint8_t* data, size_t offset);
static float readFloat(const uint8_t* data, size_t offset);
static void parseMVER(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMTEX(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMMDX(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMWMO(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMDDF(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMODF(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMCNK(const uint8_t* data, size_t size, int chunkIndex, ADTTerrain& terrain);
static void parseMCVT(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMCNR(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMCLY(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMCAL(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMH2O(const uint8_t* data, size_t size, ADTTerrain& terrain);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,107 @@
#pragma once
#include "pipeline/mpq_manager.hpp"
#include "pipeline/blp_loader.hpp"
#include "pipeline/dbc_loader.hpp"
#include <memory>
#include <string>
#include <map>
#include <mutex>
namespace wowee {
namespace pipeline {
/**
* AssetManager - Unified interface for loading WoW assets
*
* Coordinates MPQ archives, texture loading, and database files
*/
class AssetManager {
public:
AssetManager();
~AssetManager();
/**
* Initialize asset manager
* @param dataPath Path to WoW Data directory
* @return true if initialization succeeded
*/
bool initialize(const std::string& dataPath);
/**
* Shutdown and cleanup
*/
void shutdown();
/**
* Check if asset manager is initialized
*/
bool isInitialized() const { return initialized; }
/**
* Load a BLP texture
* @param path Virtual path to BLP file (e.g., "Textures\\Minimap\\Background.blp")
* @return BLP image (check isValid())
*/
BLPImage loadTexture(const std::string& path);
/**
* Load a DBC file
* @param name DBC file name (e.g., "Map.dbc")
* @return Loaded DBC file (check isLoaded())
*/
std::shared_ptr<DBCFile> loadDBC(const std::string& name);
/**
* Get a cached DBC file
* @param name DBC file name
* @return Cached DBC or nullptr if not loaded
*/
std::shared_ptr<DBCFile> getDBC(const std::string& name) const;
/**
* Check if a file exists in MPQ archives
* @param path Virtual file path
* @return true if file exists
*/
bool fileExists(const std::string& path) const;
/**
* Read raw file data from MPQ archives
* @param path Virtual file path
* @return File contents (empty if not found)
*/
std::vector<uint8_t> readFile(const std::string& path) const;
/**
* Get MPQ manager for direct access
*/
MPQManager& getMPQManager() { return mpqManager; }
const MPQManager& getMPQManager() const { return mpqManager; }
/**
* Get loaded DBC count
*/
size_t getLoadedDBCCount() const { return dbcCache.size(); }
/**
* Clear all cached resources
*/
void clearCache();
private:
bool initialized = false;
std::string dataPath;
MPQManager mpqManager;
mutable std::mutex readMutex;
std::map<std::string, std::shared_ptr<DBCFile>> dbcCache;
/**
* Normalize path for case-insensitive lookup
*/
std::string normalizePath(const std::string& path) const;
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,110 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
namespace wowee {
namespace pipeline {
/**
* BLP image format (Blizzard Picture)
*/
enum class BLPFormat {
UNKNOWN = 0,
BLP0 = 1, // Alpha channel only
BLP1 = 2, // DXT compression or uncompressed
BLP2 = 3 // DXT compression with mipmaps
};
/**
* BLP compression type
*/
enum class BLPCompression {
NONE = 0,
PALETTE = 1, // 256-color palette
DXT1 = 2, // DXT1 compression (no alpha or 1-bit alpha)
DXT3 = 3, // DXT3 compression (4-bit alpha)
DXT5 = 4, // DXT5 compression (interpolated alpha)
ARGB8888 = 5 // Uncompressed 32-bit ARGB
};
/**
* Loaded BLP image data
*/
struct BLPImage {
int width = 0;
int height = 0;
int channels = 4;
int mipLevels = 1;
BLPFormat format = BLPFormat::UNKNOWN;
BLPCompression compression = BLPCompression::NONE;
std::vector<uint8_t> data; // RGBA8 pixel data (decompressed)
std::vector<std::vector<uint8_t>> mipmaps; // Mipmap levels
bool isValid() const { return width > 0 && height > 0 && !data.empty(); }
};
/**
* BLP texture loader
*
* Supports BLP0, BLP1, BLP2 formats
* Handles DXT1/3/5 compression and palette formats
*/
class BLPLoader {
public:
/**
* Load BLP image from byte data
* @param blpData Raw BLP file data
* @return Loaded image (check isValid())
*/
static BLPImage load(const std::vector<uint8_t>& blpData);
/**
* Get format name for debugging
*/
static const char* getFormatName(BLPFormat format);
static const char* getCompressionName(BLPCompression compression);
private:
// BLP1 file header — all fields after magic are uint32
// Used by classic WoW through WotLK for many textures
struct BLP1Header {
char magic[4]; // 'BLP1'
uint32_t compression; // 0=JPEG, 1=palette (uncompressed/indexed)
uint32_t alphaBits; // 0, 1, 4, or 8
uint32_t width;
uint32_t height;
uint32_t extra; // Flags/unknown (often 4 or 5)
uint32_t hasMips; // 0 or 1
uint32_t mipOffsets[16];
uint32_t mipSizes[16];
uint32_t palette[256]; // 256-color BGRA palette (for compression=1)
};
// BLP2 file header — compression fields are uint8
// Used by WoW from TBC onwards (coexists with BLP1 in WotLK)
struct BLP2Header {
char magic[4]; // 'BLP2'
uint32_t version; // Always 1
uint8_t compression; // 1=uncompressed/palette, 2=DXTC, 3=A8R8G8B8
uint8_t alphaDepth; // 0, 1, 4, or 8
uint8_t alphaEncoding; // 0=DXT1, 1=DXT3, 7=DXT5
uint8_t hasMips; // Has mipmaps
uint32_t width;
uint32_t height;
uint32_t mipOffsets[16];
uint32_t mipSizes[16];
uint32_t palette[256]; // 256-color BGRA palette (for compression=1)
};
static BLPImage loadBLP1(const uint8_t* data, size_t size);
static BLPImage loadBLP2(const uint8_t* data, size_t size);
static void decompressDXT1(const uint8_t* src, uint8_t* dst, int width, int height);
static void decompressDXT3(const uint8_t* src, uint8_t* dst, int width, int height);
static void decompressDXT5(const uint8_t* src, uint8_t* dst, int width, int height);
static void decompressPalette(const uint8_t* src, uint8_t* dst, const uint32_t* palette, int width, int height, uint8_t alphaDepth = 8);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,135 @@
#pragma once
#include <vector>
#include <map>
#include <string>
#include <cstdint>
#include <memory>
namespace wowee {
namespace pipeline {
/**
* DBC File - WoW Database Client file
*
* DBC files store game database tables (spells, items, maps, creatures, etc.)
* Format: Fixed header + fixed-size records + string block
*/
class DBCFile {
public:
DBCFile();
~DBCFile();
/**
* Load DBC file from byte data
* @param dbcData Raw DBC file data
* @return true if loaded successfully
*/
bool load(const std::vector<uint8_t>& dbcData);
/**
* Check if DBC is loaded
*/
bool isLoaded() const { return loaded; }
/**
* Get record count
*/
uint32_t getRecordCount() const { return recordCount; }
/**
* Get field count (number of 32-bit fields per record)
*/
uint32_t getFieldCount() const { return fieldCount; }
/**
* Get record size in bytes
*/
uint32_t getRecordSize() const { return recordSize; }
/**
* Get string block size
*/
uint32_t getStringBlockSize() const { return stringBlockSize; }
/**
* Get a record by index
* @param index Record index (0 to recordCount-1)
* @return Pointer to record data (recordSize bytes) or nullptr
*/
const uint8_t* getRecord(uint32_t index) const;
/**
* Get a 32-bit integer field from a record
* @param recordIndex Record index
* @param fieldIndex Field index (0 to fieldCount-1)
* @return Field value
*/
uint32_t getUInt32(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get a 32-bit signed integer field from a record
* @param recordIndex Record index
* @param fieldIndex Field index
* @return Field value
*/
int32_t getInt32(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get a float field from a record
* @param recordIndex Record index
* @param fieldIndex Field index
* @return Field value
*/
float getFloat(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get a string field from a record
* @param recordIndex Record index
* @param fieldIndex Field index (contains string offset)
* @return String value
*/
std::string getString(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get string by offset in string block
* @param offset Offset into string block
* @return String value
*/
std::string getStringByOffset(uint32_t offset) const;
/**
* Find a record by ID (assumes first field is ID)
* @param id Record ID to find
* @return Record index or -1 if not found
*/
int32_t findRecordById(uint32_t id) const;
private:
// DBC file header (20 bytes)
struct DBCHeader {
char magic[4]; // 'WDBC'
uint32_t recordCount; // Number of records
uint32_t fieldCount; // Number of fields per record
uint32_t recordSize; // Size of each record in bytes
uint32_t stringBlockSize; // Size of string block
};
bool loaded = false;
uint32_t recordCount = 0;
uint32_t fieldCount = 0;
uint32_t recordSize = 0;
uint32_t stringBlockSize = 0;
std::vector<uint8_t> recordData; // All record data
std::vector<uint8_t> stringBlock; // String block
// Cache for record ID -> index lookup
mutable std::map<uint32_t, uint32_t> idToIndexCache;
mutable bool idCacheBuilt = false;
void buildIdCache() const;
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,187 @@
#pragma once
#include <vector>
#include <string>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
namespace wowee {
namespace pipeline {
/**
* M2 Model Format (WoW Character/Creature Models)
*
* M2 files contain:
* - Skeletal animated meshes
* - Multiple texture units and materials
* - Animation sequences
* - Bone hierarchy
* - Particle emitters, ribbon emitters, etc.
*
* Reference: https://wowdev.wiki/M2
*/
// Animation sequence data
struct M2Sequence {
uint32_t id; // Animation ID
uint32_t variationIndex; // Sub-animation index
uint32_t duration; // Length in milliseconds
float movingSpeed; // Speed during animation
uint32_t flags; // Animation flags
int16_t frequency; // Probability weight
uint32_t replayMin; // Minimum replay delay
uint32_t replayMax; // Maximum replay delay
uint32_t blendTime; // Blend time in ms
glm::vec3 boundMin; // Bounding box
glm::vec3 boundMax;
float boundRadius; // Bounding sphere radius
int16_t nextAnimation; // Next animation in chain
uint16_t aliasNext; // Alias for next animation
};
// Animation track with per-sequence keyframe data
struct M2AnimationTrack {
uint16_t interpolationType = 0; // 0=none, 1=linear, 2=hermite, 3=bezier
int16_t globalSequence = -1; // -1 if not a global sequence
struct SequenceKeys {
std::vector<uint32_t> timestamps; // Milliseconds
std::vector<glm::vec3> vec3Values; // For translation/scale tracks
std::vector<glm::quat> quatValues; // For rotation tracks
};
std::vector<SequenceKeys> sequences; // One per animation sequence
bool hasData() const { return !sequences.empty(); }
};
// Bone data for skeletal animation
struct M2Bone {
int32_t keyBoneId; // Bone ID (-1 = not key bone)
uint32_t flags; // Bone flags
int16_t parentBone; // Parent bone index (-1 = root)
uint16_t submeshId; // Submesh ID
glm::vec3 pivot; // Pivot point
M2AnimationTrack translation; // Position keyframes per sequence
M2AnimationTrack rotation; // Rotation keyframes per sequence
M2AnimationTrack scale; // Scale keyframes per sequence
};
// Vertex with skinning data
struct M2Vertex {
glm::vec3 position;
uint8_t boneWeights[4]; // Bone weights (0-255)
uint8_t boneIndices[4]; // Bone indices
glm::vec3 normal;
glm::vec2 texCoords[2]; // Two UV sets
};
// Texture unit
struct M2Texture {
uint32_t type; // Texture type
uint32_t flags; // Texture flags
std::string filename; // Texture filename (from FileData or embedded)
};
// Render batch (submesh)
struct M2Batch {
uint8_t flags;
int8_t priorityPlane;
uint16_t shader; // Shader ID
uint16_t skinSectionIndex; // Submesh index
uint16_t colorIndex; // Color animation index
uint16_t materialIndex; // Material index
uint16_t materialLayer; // Material layer
uint16_t textureCount; // Number of textures
uint16_t textureIndex; // First texture lookup index
uint16_t textureUnit; // Texture unit
uint16_t transparencyIndex; // Transparency animation index
uint16_t textureAnimIndex; // Texture animation index
// Render data
uint32_t indexStart; // First index
uint32_t indexCount; // Number of indices
uint32_t vertexStart; // First vertex
uint32_t vertexCount; // Number of vertices
// Geoset info (from submesh)
uint16_t submeshId = 0; // Submesh/geoset ID (determines body part group)
uint16_t submeshLevel = 0; // Submesh level (0=base, 1+=LOD/alternate mesh)
};
// Attachment point (bone-anchored position for weapons, effects, etc.)
struct M2Attachment {
uint32_t id; // 0=Head, 1=RightHand, 2=LeftHand, etc.
uint16_t bone; // Bone index
glm::vec3 position; // Offset from bone pivot
};
// Complete M2 model structure
struct M2Model {
// Model metadata
std::string name;
uint32_t version;
glm::vec3 boundMin; // Model bounding box
glm::vec3 boundMax;
float boundRadius; // Bounding sphere
// Geometry data
std::vector<M2Vertex> vertices;
std::vector<uint16_t> indices;
// Skeletal animation
std::vector<M2Bone> bones;
std::vector<M2Sequence> sequences;
// Rendering
std::vector<M2Batch> batches;
std::vector<M2Texture> textures;
std::vector<uint16_t> textureLookup; // Batch texture index lookup
// Attachment points (for weapon/effect anchoring)
std::vector<M2Attachment> attachments;
std::vector<uint16_t> attachmentLookup; // attachment ID → index
// Flags
uint32_t globalFlags;
bool isValid() const {
return !vertices.empty() && !indices.empty();
}
};
class M2Loader {
public:
/**
* Load M2 model from raw file data
*
* @param m2Data Raw M2 file bytes
* @return Parsed M2 model
*/
static M2Model load(const std::vector<uint8_t>& m2Data);
/**
* Load M2 skin file (contains submesh/batch data)
*
* @param skinData Raw M2 skin file bytes
* @param model Model to populate with skin data
* @return True if successful
*/
static bool loadSkin(const std::vector<uint8_t>& skinData, M2Model& model);
/**
* Load external .anim file data into model bone tracks
*
* @param m2Data Original M2 file bytes (contains track headers)
* @param animData Raw .anim file bytes
* @param sequenceIndex Which sequence index this .anim file provides data for
* @param model Model to patch with animation data
*/
static void loadAnimFile(const std::vector<uint8_t>& m2Data,
const std::vector<uint8_t>& animData,
uint32_t sequenceIndex,
M2Model& model);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,109 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
#include <map>
// Forward declare StormLib handle
typedef void* HANDLE;
namespace wowee {
namespace pipeline {
/**
* MPQManager - Manages MPQ archive loading and file reading
*
* WoW 3.3.5a stores all game assets in MPQ archives.
* This manager loads multiple archives and provides unified file access.
*/
class MPQManager {
public:
MPQManager();
~MPQManager();
/**
* Initialize the MPQ system
* @param dataPath Path to WoW Data directory
* @return true if initialization succeeded
*/
bool initialize(const std::string& dataPath);
/**
* Shutdown and close all archives
*/
void shutdown();
/**
* Load a single MPQ archive
* @param path Full path to MPQ file
* @param priority Priority for file resolution (higher = checked first)
* @return true if archive loaded successfully
*/
bool loadArchive(const std::string& path, int priority = 0);
/**
* Check if a file exists in any loaded archive
* @param filename Virtual file path (e.g., "World\\Maps\\Azeroth\\Azeroth.wdt")
* @return true if file exists
*/
bool fileExists(const std::string& filename) const;
/**
* Read a file from MPQ archives
* @param filename Virtual file path
* @return File contents as byte vector (empty if not found)
*/
std::vector<uint8_t> readFile(const std::string& filename) const;
/**
* Get file size without reading it
* @param filename Virtual file path
* @return File size in bytes (0 if not found)
*/
uint32_t getFileSize(const std::string& filename) const;
/**
* Check if MPQ system is initialized
*/
bool isInitialized() const { return initialized; }
/**
* Get list of loaded archives
*/
const std::vector<std::string>& getLoadedArchives() const { return archiveNames; }
private:
struct ArchiveEntry {
HANDLE handle;
std::string path;
int priority;
};
bool initialized = false;
std::string dataPath;
std::vector<ArchiveEntry> archives;
std::vector<std::string> archiveNames;
/**
* Find archive containing a file
* @param filename File to search for
* @return Archive handle or nullptr if not found
*/
HANDLE findFileArchive(const std::string& filename) const;
/**
* Load patch archives (e.g., patch.MPQ, patch-2.MPQ, etc.)
*/
bool loadPatchArchives();
/**
* Load locale-specific archives
* @param locale Locale string (e.g., "enUS")
*/
bool loadLocaleArchives(const std::string& locale);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,136 @@
#pragma once
#include "pipeline/adt_loader.hpp"
#include <vector>
#include <cstdint>
namespace wowee {
namespace pipeline {
/**
* Vertex format for terrain rendering
*/
struct TerrainVertex {
float position[3]; // X, Y, Z
float normal[3]; // Normal vector
float texCoord[2]; // Base texture coordinates
float layerUV[2]; // Layer texture coordinates
uint8_t chunkIndex; // Which chunk this vertex belongs to
TerrainVertex() : chunkIndex(0) {
position[0] = position[1] = position[2] = 0.0f;
normal[0] = normal[1] = normal[2] = 0.0f;
texCoord[0] = texCoord[1] = 0.0f;
layerUV[0] = layerUV[1] = 0.0f;
}
};
/**
* Triangle index (3 vertices)
*/
using TerrainIndex = uint32_t;
/**
* Renderable terrain mesh for a single map chunk
*/
struct ChunkMesh {
std::vector<TerrainVertex> vertices;
std::vector<TerrainIndex> indices;
// Chunk position in world space
float worldX;
float worldY;
float worldZ;
// Chunk grid coordinates
int chunkX;
int chunkY;
// Texture layer info
struct LayerInfo {
uint32_t textureId;
uint32_t flags;
std::vector<uint8_t> alphaData; // 64x64 alpha map
};
std::vector<LayerInfo> layers;
bool isValid() const { return !vertices.empty() && !indices.empty(); }
size_t getVertexCount() const { return vertices.size(); }
size_t getTriangleCount() const { return indices.size() / 3; }
};
/**
* Complete terrain tile mesh (16x16 chunks)
*/
struct TerrainMesh {
std::array<ChunkMesh, 256> chunks; // 16x16 grid
std::vector<std::string> textures; // Texture filenames
int validChunkCount = 0;
const ChunkMesh& getChunk(int x, int y) const { return chunks[y * 16 + x]; }
ChunkMesh& getChunk(int x, int y) { return chunks[y * 16 + x]; }
};
/**
* Terrain mesh generator
*
* Converts ADT heightmap data into renderable triangle meshes
*/
class TerrainMeshGenerator {
public:
/**
* Generate terrain mesh from ADT data
* @param terrain Loaded ADT terrain data
* @return Generated mesh (check validChunkCount)
*/
static TerrainMesh generate(const ADTTerrain& terrain);
private:
/**
* Generate mesh for a single map chunk
*/
static ChunkMesh generateChunkMesh(const MapChunk& chunk, int chunkX, int chunkY, int tileX, int tileY);
/**
* Generate vertices from heightmap
* WoW heightmap layout: 9x9 outer + 8x8 inner vertices (145 total)
*/
static std::vector<TerrainVertex> generateVertices(const MapChunk& chunk, int chunkX, int chunkY, int tileX, int tileY);
/**
* Generate triangle indices
* Creates triangles that connect the heightmap vertices
* Skips quads that are marked as holes in the chunk
*/
static std::vector<TerrainIndex> generateIndices(const MapChunk& chunk);
/**
* Calculate texture coordinates for vertex
*/
static void calculateTexCoords(TerrainVertex& vertex, int x, int y);
/**
* Convert WoW's compressed normals to float
*/
static void decompressNormal(const int8_t* compressedNormal, float* normal);
/**
* Get height at grid position from WoW's 9x9+8x8 layout
*/
static float getHeightAt(const HeightMap& heightMap, int x, int y);
/**
* Convert grid coordinates to vertex index
*/
static int getVertexIndex(int x, int y);
// Terrain constants
// WoW terrain: 64x64 tiles, each tile = 533.33 yards, each chunk = 33.33 yards
static constexpr float TILE_SIZE = 533.33333f; // One ADT tile = 533.33 yards
static constexpr float CHUNK_SIZE = TILE_SIZE / 16.0f; // One chunk = 33.33 yards (16 chunks per tile)
static constexpr float GRID_STEP = CHUNK_SIZE / 8.0f; // 8 quads per chunk = 4.17 yards per vertex
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,222 @@
#pragma once
#include <vector>
#include <string>
#include <unordered_map>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
namespace wowee {
namespace pipeline {
/**
* WMO (World Model Object) Format
*
* WMO files contain buildings, dungeons, and large structures.
* Structure:
* - Root WMO file: Contains groups, materials, doodad sets
* - Group WMO files: Individual rooms/sections (_XXX.wmo)
*
* Reference: https://wowdev.wiki/WMO
*/
// WMO Material
struct WMOMaterial {
uint32_t flags;
uint32_t shader;
uint32_t blendMode;
uint32_t texture1; // Diffuse texture index
uint32_t color1;
uint32_t texture2; // Environment/detail texture
uint32_t color2;
uint32_t texture3;
uint32_t color3;
float runtime[4]; // Runtime data
};
// WMO Group Info
struct WMOGroupInfo {
uint32_t flags;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
int32_t nameOffset; // Group name in MOGN chunk
};
// WMO Light
struct WMOLight {
uint32_t type; // 0=omni, 1=spot, 2=directional, 3=ambient
uint8_t useAttenuation;
uint8_t pad[3];
glm::vec4 color;
glm::vec3 position;
float intensity;
float attenuationStart;
float attenuationEnd;
float unknown[4];
};
// WMO Doodad Set (collection of M2 models placed in WMO)
struct WMODoodadSet {
char name[20];
uint32_t startIndex; // First doodad in MODD
uint32_t count; // Number of doodads
uint32_t padding;
};
// WMO Doodad Instance
struct WMODoodad {
uint32_t nameIndex; // Index into MODN (doodad names)
glm::vec3 position;
glm::quat rotation; // Quaternion rotation
float scale;
glm::vec4 color; // BGRA color
};
// WMO Fog
struct WMOFog {
uint32_t flags;
glm::vec3 position;
float smallRadius;
float largeRadius;
float endDist;
float startFactor;
glm::vec4 color1; // End fog color
float endDist2;
float startFactor2;
glm::vec4 color2; // Start fog color (blend with color1)
};
// WMO Portal
struct WMOPortal {
uint16_t startVertex;
uint16_t vertexCount;
uint16_t planeIndex;
uint16_t padding;
};
// WMO Portal Plane
struct WMOPortalPlane {
glm::vec3 normal;
float distance;
};
// WMO Group Vertex
struct WMOVertex {
glm::vec3 position;
glm::vec3 normal;
glm::vec2 texCoord;
glm::vec4 color; // Vertex color
};
// WMO Batch (render batch)
struct WMOBatch {
uint32_t startIndex; // First index (this is uint32 in file format)
uint16_t indexCount; // Number of indices
uint16_t startVertex;
uint16_t lastVertex;
uint8_t flags;
uint8_t materialId;
};
// WMO Group (individual room/section)
struct WMOGroup {
uint32_t flags;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
uint16_t portalStart;
uint16_t portalCount;
uint16_t batchCountA;
uint16_t batchCountB;
uint32_t fogIndices[4]; // Fog references
uint32_t liquidType;
uint32_t groupId;
// Geometry
std::vector<WMOVertex> vertices;
std::vector<uint16_t> indices;
std::vector<WMOBatch> batches;
// Portals
std::vector<WMOPortal> portals;
std::vector<glm::vec3> portalVertices;
// BSP tree (for collision - optional)
std::vector<uint8_t> bspNodes;
std::string name;
std::string description;
};
// Complete WMO Model
struct WMOModel {
// Root WMO data
uint32_t version;
uint32_t nGroups;
uint32_t nPortals;
uint32_t nLights;
uint32_t nDoodadNames;
uint32_t nDoodadDefs;
uint32_t nDoodadSets;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
// Materials and textures
std::vector<WMOMaterial> materials;
std::vector<std::string> textures;
std::unordered_map<uint32_t, uint32_t> textureOffsetToIndex; // MOTX offset -> texture array index
// Groups (rooms/sections)
std::vector<WMOGroupInfo> groupInfo;
std::vector<WMOGroup> groups;
// Portals (visibility culling)
std::vector<WMOPortal> portals;
std::vector<WMOPortalPlane> portalPlanes;
std::vector<glm::vec3> portalVertices;
// Lights
std::vector<WMOLight> lights;
// Doodads (M2 models placed in WMO)
// Keyed by byte offset into MODN chunk (nameIndex in MODD references these offsets)
std::unordered_map<uint32_t, std::string> doodadNames;
std::vector<WMODoodad> doodads;
std::vector<WMODoodadSet> doodadSets;
// Fog
std::vector<WMOFog> fogs;
// Group names
std::vector<std::string> groupNames;
bool isValid() const {
return nGroups > 0 && !groups.empty();
}
};
class WMOLoader {
public:
/**
* Load root WMO file
*
* @param wmoData Raw WMO file bytes
* @return Parsed WMO model (without group geometry)
*/
static WMOModel load(const std::vector<uint8_t>& wmoData);
/**
* Load WMO group file
*
* @param groupData Raw WMO group file bytes
* @param model Model to populate with group data
* @param groupIndex Group index to load
* @return True if successful
*/
static bool loadGroup(const std::vector<uint8_t>& groupData,
WMOModel& model,
uint32_t groupIndex);
};
} // namespace pipeline
} // namespace wowee