Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADT Format πŸ”οΈ

ADT (Area Data Terrain) files contain the terrain and object information for a single map tile in World of Warcraft. The world is divided into a grid of 64x64 maps, with each map consisting of 16x16 chunks. Each ADT file represents one map tile, which is 533.33333 yards (1600 feet) on each side.

Overview

  • Extension: .adt
  • Purpose: Terrain geometry, textures, water, and object placement
  • Grid Size: 16x16 chunks per ADT, 64x64 ADTs per continent
  • Chunk Size: 33.33333 yards (100 feet) per chunk
  • Format: Chunk-based binary format

File Types

Since Cataclysm (4.x), ADT data is split across multiple files. Confirmed in Cataclysm Preservation Project TrinityCore 4.3.4:

File PatternDescriptionContentChunks Present
MapName_XX_YY.adtRoot fileTerrain structure and headerMVER, MHDR, MCNK, MFBO
MapName_XX_YY_tex0.adtTexture fileTexture data and amplitudeMVER, MTEX, MAMP, MCNK, MTXP (MoP+)
MapName_XX_YY_tex1.adtTexture fileAdditional texture layersMVER, MTEX, MAMP, MCNK, MTXP (MoP+)
MapName_XX_YY_obj0.adtObject fileM2 and WMO placement dataMVER, MMDX, MMID, MWMO, MWID, MDDF, MODF, MCNK
MapName_XX_YY_obj1.adtObject fileAdditional object dataMVER, MMDX, MMID, MWMO, MWID, MDDF, MODF, MCNK
MapName_XX_YY_lod.adtLOD fileLevel of detail information(Not analyzed)

Where XX and YY are the tile coordinates (0-63).

Coordinate System

World of Warcraft uses a right-handed coordinate system:

  • X-axis: North (positive) to South (negative)
  • Y-axis: West (positive) to East (negative)
  • Z-axis: Up (positive) to Down (negative)

Map coordinates:

  • One ADT tile = 533.33333 yards = 1600 feet
  • One chunk = 33.33333 yards = 100 feet
  • One unit = 0.33333 yards = 1 foot

The world origin (0,0) is at the center of map (32,32).

Chunk Structure

All chunks follow this format:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct ChunkHeader {
    /// Four-character chunk identifier
    magic: [u8; 4],

    /// Size of chunk data (excluding this header)
    size: u32,
}
}

Main ADT File Structure

MVER - Version

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MVERChunk {
    header: ChunkHeader,  // Magic: "MVER"
    version: u32,         // ADT version number (consistently 18)
}
}

Version history (based on analysis of original MPQ files):

  • 18 = All versions analyzed (1.12.1 through 5.4.8) use version 18
  • Note: Earlier documentation suggesting version 17 for TBC appears to be incorrect

MHDR - Header

Contains offsets to all other chunks in the file:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MHDRChunk {
    header: ChunkHeader,  // Magic: "MHDR"

    /// All offsets are relative to start of file (0 if chunk not present)
    flags: u32,           // Always 0 in ADT files
    mcin_offset: u32,     // Offset to MCIN chunk
    mtex_offset: u32,     // Offset to MTEX chunk
    mmdx_offset: u32,     // Offset to MMDX chunk
    mmid_offset: u32,     // Offset to MMID chunk
    mwmo_offset: u32,     // Offset to MWMO chunk
    mwid_offset: u32,     // Offset to MWID chunk
    mddf_offset: u32,     // Offset to MDDF chunk
    modf_offset: u32,     // Offset to MODF chunk
    mfbo_offset: u32,     // Offset to MFBO chunk (TBC+)
    mh2o_offset: u32,     // Offset to MH2O chunk (TBC+)
    mtxf_offset: u32,     // Offset to MTXF chunk (TBC+)
    reserved: [u32; 4],   // Padding
}
}

MCIN - Chunk Information

Contains offsets and sizes for all 256 (16x16) map chunks:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCINEntry {
    /// Absolute offset to MCNK chunk
    mcnk_offset: u32,

    /// Size of MCNK chunk including header
    size: u32,

    /// Flags (usually 0)
    flags: u32,

    /// Async object id (0 if none)
    async_id: u32,
}

#[repr(C, packed)]
struct MCINChunk {
    header: ChunkHeader,  // Magic: "MCIN"
    entries: [MCINEntry; 256],  // 16x16 grid
}
}

MTEX - Texture List

Contains null-terminated texture filenames:

#![allow(unused)]
fn main() {
struct MTEXChunk {
    header: ChunkHeader,  // Magic: "MTEX"
    /// Concatenated null-terminated strings
    /// Example: "Tileset\\Elwynn\\ElwynnGrass01.blp\0"
    texture_names: Vec<u8>,
}
}

MMDX/MMID - Model List

  • MMDX: Contains null-terminated M2 model filenames
  • MMID: Maps model instances to their filename offsets in MMDX

MWMO/MWID - WMO List

  • MWMO: Contains null-terminated WMO filenames
  • MWID: Maps WMO instances to their filename offsets in MWMO

MDDF - Model (Doodad) Placement

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MDDFEntry {
    /// Index into MMID
    mmid_entry: u32,

    /// Unique instance ID
    unique_id: u32,

    /// Position in world coordinates
    position: [f32; 3],  // x, y, z

    /// Rotation in degrees
    rotation: [f32; 3],  // x, y, z

    /// Scale factor (1024 = 1.0)
    scale: u16,

    /// Flags
    flags: u16,
}

// MDDF flags
const MDDF_BIODOME: u16 = 0x0001;     // Use for biodome in WMO
const MDDF_SHRUBBERY: u16 = 0x0002;   // Shrubbery scale factor
}

MODF - WMO Placement

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MODFEntry {
    /// Index into MWID
    mwid_entry: u32,

    /// Unique instance ID
    unique_id: u32,

    /// Position in world coordinates
    position: [f32; 3],

    /// Rotation in degrees
    rotation: [f32; 3],

    /// Bounding box
    extent_lower: [f32; 3],
    extent_upper: [f32; 3],

    /// Flags (same as MDDF)
    flags: u16,

    /// Doodad set index
    doodad_set: u16,

    /// Name set index
    name_set: u16,

    /// Scale (Legion+)
    scale: u16,
}
}

MH2O - Water Information (WotLK+)

First appeared in Wrath of the Lich King (3.3.5a). Contains water levels and types, replacing the legacy MCLQ system.

Structural Analysis: Complex variable-size chunks with 16Γ—16 grid structure. Validated across multiple TrinityCore versions:

  • TrinityCore 3.3.5a (detailed implementation)
  • SkyFire 5.4.8 (simplified MoP implementation)

Both versions confirm the 16Γ—16 grid structure with variable liquid instances and attributes.

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MH2OHeader {
    /// Offset to MH2OInformation for this chunk
    offset_information: u32,

    /// Number of water layers
    layer_count: u32,

    /// Offset to render mask
    offset_render_mask: u32,
}

#[repr(C, packed)]
struct MH2OInformation {
    /// Water type (ocean, lake, etc)
    liquid_type: u16,

    /// Flags
    flags: u16,

    /// Height levels
    height_level1: f32,
    height_level2: f32,

    /// Position within chunk
    x_offset: u8,
    y_offset: u8,
    width: u8,
    height: u8,

    /// Offset to vertex data
    offset_vertex_data: u32,
}

// MH2O Flags
const MH2O_OCEAN: u16 = 0x0002;
const MH2O_DEEP: u16 = 0x0004;
const MH2O_FISHABLE: u16 = 0x0008;
}

MFBO - Flight Bounds (TBC+)

Contains flight ceiling information. First appeared in The Burning Crusade (2.4.3):

Structural Analysis: All analyzed MFBO chunks are consistently 36 bytes. Validated against TrinityCore implementation which defines the structure as two planes with 9 int16 coordinates each.

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MFBOPlane {
    /// 9 coordinate values defining a plane
    coords: [i16; 9],  // 18 bytes
}

#[repr(C, packed)]
struct MFBOChunk {
    header: ChunkHeader,  // Magic: "MFBO"
    
    /// Maximum flight bounds plane
    max: MFBOPlane,  // 18 bytes
    
    /// Minimum flight bounds plane  
    min: MFBOPlane,  // 18 bytes
}
}

Total data size: 36 bytes (18 + 18), confirmed by empirical analysis and validated across multiple TrinityCore versions:

  • TrinityCore 3.3.5a (WotLK)
  • Cataclysm Preservation Project TrinityCore 4.3.4

Usage: Both TrinityCore versions extract these as int16 flight_box_max[3][3] and int16 flight_box_min[3][3] arrays for flight ceiling calculations.

Validation: Structure confirmed across multiple production WoW server emulator implementations spanning TBC through Cataclysm.

Note: SkyFire 5.4.8 (MoP) does not implement MFBO chunks in their map extractor, suggesting flight bounds may be handled differently in later expansions or not required for server-side processing.

MAMP - Amplitude Map (Cataclysm+)

First appeared in Cataclysm (4.3.4). Contains amplitude or deformation data for terrain:

Structural Analysis: Always 4 bytes, appears to be flags or simple values. Structure based on empirical analysis only.

Server Implementation Status:

  • Not implemented in Cataclysm Preservation Project TrinityCore 4.3.4
  • Not implemented in SkyFire 5.4.8 (MoP)
  • Suggests this chunk is texture-file specific or client-rendering optimization only
#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MAMPChunk {
    header: ChunkHeader,  // Magic: "MAMP"
    
    /// Amplitude value or flags
    /// Common values: 0x00000000, 0x00000001
    value: u32,
}
}

MTXP - Texture Parameters (MoP+)

First appeared in Mists of Pandaria (5.4.8). Contains texture parameters for advanced material properties:

Structural Analysis: Variable size, contains arrays of 16-byte entries. Average size 154 bytes. Structure based on empirical analysis only.

Server Implementation Status:

  • Not implemented in SkyFire 5.4.8 (MoP)
  • Suggests this chunk is client-rendering specific for advanced texture material properties
#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MTXPEntry {
    /// Texture parameter data (16 bytes per entry)
    /// Structure appears to be 4 u32 values
    params: [u32; 4],
}

#[repr(C, packed)]
struct MTXPChunk {
    header: ChunkHeader,  // Magic: "MTXP"
    entries: Vec<MTXPEntry>,  // Variable count
}
}

Note: The exact meaning of the parameter values requires further analysis.

MTXF - Texture Flags (Legacy)

Rarely found in analyzed files. May be deprecated in favor of MTXP:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MTXFChunk {
    header: ChunkHeader,  // Magic: "MTXF"
    flags: Vec<u32>,      // One per texture
}

// Texture flags
const MTXF_DISABLE_ALPHA: u32 = 0x0001;
const MTXF_USE_CUBE_MAP: u32 = 0x0002;
}

Map Chunk (MCNK) Structure

Each MCNK chunk represents a 33.33x33.33 yard square of terrain:

MCNK Header

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCNKHeader {
    /// Flags for this chunk
    flags: u32,

    /// Index of this chunk
    index_x: u32,
    index_y: u32,

    /// Number of texture layers (max 4)
    n_layers: u32,

    /// Number of doodad references
    n_doodad_refs: u32,

    /// High-res holes (8x8 grid)
    holes_high_res: u64,

    /// Offsets to sub-chunks (relative to MCNK data start)
    offset_mcly: u32,  // Texture layers
    offset_mcrf: u32,  // References
    offset_mcal: u32,  // Alpha maps
    size_mcal: u32,    // Alpha map size
    offset_mcsh: u32,  // Shadow map
    size_mcsh: u32,    // Shadow map size

    /// Area ID
    area_id: u32,

    /// Number of WMO references
    n_map_obj_refs: u32,

    /// Low-res holes (4x4 grid)
    holes_low_res: u16,

    /// Unknown
    unknown_0x3C: u16,

    /// Low-res texture map (8x8 grid)
    low_res_texture_map: [u16; 8],

    /// No effect doodad
    no_effect_doodad: u32,

    /// Sound emitters
    offset_mcse: u32,
    n_sound_emitters: u32,

    /// Liquid
    offset_mclq: u32,
    size_mclq: u32,

    /// Position of chunk
    position: [f32; 3],

    /// Vertex colors
    offset_mccv: u32,

    /// Vertex lighting (unused)
    offset_mclv: u32,

    /// Unused
    unused: u32,
}

// MCNK flags
const MCNK_HAS_MCSH: u32 = 0x0001;          // Has shadow map
const MCNK_IMPASS: u32 = 0x0002;            // Impassable
const MCNK_LQ_RIVER: u32 = 0x0004;          // River
const MCNK_LQ_OCEAN: u32 = 0x0008;          // Ocean
const MCNK_LQ_MAGMA: u32 = 0x0010;          // Magma
const MCNK_LQ_SLIME: u32 = 0x0020;          // Slime
const MCNK_HAS_MCCV: u32 = 0x0040;          // Has vertex colors
const MCNK_DO_NOT_FIX_ALPHA_MAP: u32 = 0x8000;   // Don't fix alpha map
const MCNK_HIGH_RES_HOLES: u32 = 0x10000;   // Use high-res holes
}

MCVT - Vertex Heights

Contains 145 height values (9x9 outer + 8x8 inner vertices):

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCVTChunk {
    header: ChunkHeader,  // Magic: "MCVT"
    /// Height values relative to MCNK position
    /// Order: 9x9 outer vertices, then 8x8 inner vertices
    heights: [f32; 145],  // 9*9 + 8*8 = 145
}
}

Vertex layout:

Outer vertices (9x9): Grid corners
Inner vertices (8x8): Center of each quad

MCCV - Vertex Colors

Optional vertex colors for terrain shading:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCCVColor {
    r: u8,
    g: u8,
    b: u8,
    a: u8,  // Usually 127 or 255
}

#[repr(C, packed)]
struct MCCVChunk {
    header: ChunkHeader,  // Magic: "MCCV"
    colors: [MCCVColor; 145],  // Same layout as MCVT
}
}

MCNR - Normals

Normal vectors for lighting:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCNREntry {
    normal: [i8; 3],    // X, Y, Z components (-127 to 127)
}

#[repr(C, packed)]
struct MCNRChunk {
    header: ChunkHeader,  // Magic: "MCNR"
    normals: [MCNREntry; 145],  // Same layout as MCVT
    padding: [u8; 13],   // Unknown padding
}
}

MCLY - Texture Layers

Defines up to 4 texture layers:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCLYEntry {
    /// Texture index in MTEX
    texture_id: u32,

    /// Flags for this layer
    flags: u32,

    /// Offset to alpha map in MCAL
    offset_in_mcal: u32,

    /// Effect ID (usually 0)
    effect_id: u32,
}

// MCLY flags
const MCLY_ANIMATION_ROTATION: u32 = 0x040;      // 45Β° rotation
const MCLY_ANIMATION_SPEED_FAST: u32 = 0x080;    // Faster animation
const MCLY_ANIMATION_SPEED_FASTER: u32 = 0x100;  // Even faster
const MCLY_ANIMATION_SPEED_FASTEST: u32 = 0x200; // Fastest
const MCLY_ANIMATE: u32 = 0x400;                 // Enable animation
const MCLY_USE_ALPHA_MAP: u32 = 0x800;           // Use alpha map
const MCLY_USE_CUBE_MAP_REFLECTION: u32 = 0x1000; // Skybox reflection
}

MCRF - References

Lists doodad and object references for this chunk:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCRFChunk {
    header: ChunkHeader,  // Magic: "MCRF"
    /// Indices into MDDF and MODF
    doodad_refs: Vec<u32>,
    object_refs: Vec<u32>,
}
}

MCSH - Shadow Map

64x64 bit shadow map:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCSHChunk {
    header: ChunkHeader,  // Magic: "MCSH"
    /// 1 bit per terrain quad
    shadow_map: [u8; 512],  // 64 * 64 / 8
}
}

MCAL - Alpha Maps

Alpha maps control texture blending. The first texture layer has no alpha map (it’s the base):

#![allow(unused)]
fn main() {
enum AlphaMapFormat {
    /// Uncompressed 64x64 (4096 bytes)
    Uncompressed4096,

    /// Uncompressed 32x32 (2048 bytes)
    Uncompressed2048,

    /// Compressed (variable size)
    Compressed,
}
}

Compression algorithm:

#![allow(unused)]
fn main() {
fn decompress_alpha_map(compressed: &[u8], output: &mut [u8]) {
    let mut input_pos = 0;
    let mut output_pos = 0;

    while output_pos < output.len() && input_pos < compressed.len() {
        let count = compressed[input_pos] & 0x7F;
        let fill = (compressed[input_pos] & 0x80) != 0;
        input_pos += 1;

        for _ in 0..count {
            if fill {
                // Repeat single value
                output[output_pos] = compressed[input_pos];
                output_pos += 1;
            } else {
                // Copy values
                output[output_pos] = compressed[input_pos];
                input_pos += 1;
                output_pos += 1;
            }
        }

        if fill {
            input_pos += 1;
        }
    }
}
}

MCLQ - Liquid (Legacy, removed in WotLK)

Deprecated: Removed in Wrath of the Lich King (3.3.5a+), replaced by MH2O chunk.

Pre-WotLK liquid data:

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCLQHeader {
    min_height: f32,
    max_height: f32,
}

#[repr(C, packed)]
struct MCLQVertex {
    /// Water height
    height: f32,

    /// Flow data
    flow: [u8; 4],
}

#[repr(C, packed)]
struct MCLQChunk {
    header: ChunkHeader,  // Magic: "MCLQ"
    liquid_header: MCLQHeader,

    /// 9x9 vertex heights
    vertices: [MCLQVertex; 81],

    /// 8x8 flags
    flags: [u8; 64],
}

// MCLQ flags
const MCLQ_HIDDEN: u8 = 0x01;
const MCLQ_FISHABLE: u8 = 0x02;
const MCLQ_SHARED: u8 = 0x04;
}

MCSE - Sound Emitters

#![allow(unused)]
fn main() {
#[repr(C, packed)]
struct MCSEEntry {
    /// Sound ID
    sound_id: u32,

    /// Position relative to chunk
    position: [f32; 3],

    /// Size/radius
    size: [f32; 3],
}
}

Height Calculation Algorithm

To calculate terrain height at any position:

#![allow(unused)]
fn main() {
/// Get interpolated height at position within chunk
pub fn get_height_at_position(
    mcvt: &MCVTChunk,
    x: f32,  // 0.0 to 33.33333
    y: f32,  // 0.0 to 33.33333
) -> f32 {
    // Convert to cell coordinates
    let cell_x = (x / (33.33333 / 8.0)).min(7.999);
    let cell_y = (y / (33.33333 / 8.0)).min(7.999);

    let ix = cell_x as usize;
    let iy = cell_y as usize;

    let fx = cell_x - ix as f32;
    let fy = cell_y - iy as f32;

    // Get the four corner heights
    let h00 = mcvt.get_outer(ix, iy);
    let h01 = mcvt.get_outer(ix, iy + 1);
    let h10 = mcvt.get_outer(ix + 1, iy);
    let h11 = mcvt.get_outer(ix + 1, iy + 1);

    // Get center height
    let hc = mcvt.get_inner(ix, iy);

    // Determine which triangle and interpolate
    if fx + fy < 1.0 {
        // Lower triangle
        if fx < fy {
            // Left triangle
            h00 * (1.0 - fx - fy) + h01 * fy + hc * fx
        } else {
            // Bottom triangle
            h00 * (1.0 - fx - fy) + h10 * fx + hc * fy
        }
    } else {
        // Upper triangle
        if fx > fy {
            // Right triangle
            h11 * (fx + fy - 1.0) + h10 * (1.0 - fy) + hc * (1.0 - fx)
        } else {
            // Top triangle
            h11 * (fx + fy - 1.0) + h01 * (1.0 - fx) + hc * (1.0 - fy)
        }
    }
}
}

Coordinate Transformations

#![allow(unused)]
fn main() {
/// World position to ADT tile coordinates
pub fn world_to_adt(world_x: f32, world_y: f32) -> (i32, i32) {
    // World origin is at center of map (32, 32)
    let adt_x = 32 - (world_x / 533.33333);
    let adt_y = 32 - (world_y / 533.33333);

    (adt_x as i32, adt_y as i32)
}

/// ADT tile to world coordinates (tile corner)
pub fn adt_to_world(adt_x: i32, adt_y: i32) -> (f32, f32) {
    let world_x = (32 - adt_x) as f32 * 533.33333;
    let world_y = (32 - adt_y) as f32 * 533.33333;

    (world_x, world_y)
}

/// Position within ADT to chunk index
pub fn position_to_chunk(x: f32, y: f32) -> (usize, usize) {
    let chunk_x = (x / 33.33333) as usize;
    let chunk_y = (y / 33.33333) as usize;

    (chunk_x.min(15), chunk_y.min(15))
}
}

Usage Examples

Loading and Parsing ADT (Current Implementation)

Implementation Status: ⚠️ Basic Parsing Only - Advanced terrain processing not implemented

#![allow(unused)]
fn main() {
use wow_adt::{Adt, AdtVersion};

// βœ… Load ADT file with correct API
let adt = Adt::from_path("World/Maps/Azeroth/Azeroth_32_48.adt")?;

// βœ… Check detected version
println!("Detected version: {}", adt.version());

// βœ… Access terrain chunk data
println!("MCNK chunks: {}", adt.mcnk_chunks.len());
if adt.mfbo.is_some() {
    println!("Has flight boundaries (TBC+)");
}
if adt.mh2o.is_some() {
    println!("Has water data (WotLK+)");
}

// ❌ Height interpolation algorithms not implemented
// ❌ export_heightmap() method does not exist
// ❌ High-level texture/doodad access APIs not implemented
// ❌ Advanced terrain processing not available
}

Texture Blending (Format Specification)

Implementation Status: ⚠️ Format Specification Only - Mathematical blending approach documented

ADT files support up to 4 texture layers per chunk with alpha map blending. The official format specification describes a complex blending algorithm involving:

  1. Layer Weight Calculation - Based on alpha map values
  2. Height-based Scaling - Terrain height influences blending
  3. Vertex Color Integration - MCCV chunk data affects final output
// From Legion terrain shader (format specification):
// layer_pct = layer_pct / vec4(sum(layer_pct));
// 
// Current implementation provides access to raw texture and alpha data
// through parsed MCNK chunks, but blending logic must be implemented separately

Liquid Rendering

#![allow(unused)]
fn main() {
// MH2O water (WotLK+)
if let Some(water_chunk) = &chunk.water {
    for layer in &water_chunk.layers {
        let water_type = layer.liquid_type;
        let height = layer.height_level1;

        // Render water surface
        render_water_layer(layer, water_type);
    }
}

// Legacy MCLQ water
if let Some(liquid) = &chunk.liquid {
    for x in 0..9 {
        for y in 0..9 {
            let vertex = &liquid.vertices[y * 9 + x];
            let depth = vertex.height;
            let flow = vertex.flow;

            // Render water vertex
        }
    }
}
}

Implementation Notes

Reading ADT Files

#![allow(unused)]
fn main() {
pub struct ADTReader {
    data: Vec<u8>,
    position: usize,
}

impl ADTReader {
    pub fn read_chunk(&mut self) -> Result<(ChunkHeader, &[u8]), ADTError> {
        if self.position + 8 > self.data.len() {
            return Err(ADTError::UnexpectedEof);
        }

        // Read header
        let header = unsafe {
            *(self.data.as_ptr().add(self.position) as *const ChunkHeader)
        };

        self.position += 8;

        // Get chunk data
        let chunk_size = header.size as usize;
        if self.position + chunk_size > self.data.len() {
            return Err(ADTError::InvalidChunkSize);
        }

        let chunk_data = &self.data[self.position..self.position + chunk_size];
        self.position += chunk_size;

        Ok((header, chunk_data))
    }
}
}

Performance Optimizations

  1. Memory mapping: Use memory-mapped files for large ADT files
  2. Lazy loading: Only parse chunks when needed
  3. Caching: Cache frequently accessed data like height maps
  4. LOD: Use WDL files for distant terrain
  5. Frustum culling: Only render visible chunks
  6. Texture atlasing: Combine texture layers to reduce draw calls

Common Pitfalls

  1. Byte order: All values are little-endian
  2. Chunk alignment: Chunks are not always aligned to 4-byte boundaries
  3. String parsing: Strings in MTEX/MMDX/MWMO are null-terminated
  4. Coordinate systems: Y-axis is north/south (not up/down)
  5. Height interpolation: Must use the center vertices for proper interpolation
  6. Alpha map compression: Check MCLY flags to determine format
  7. Scale values: MDDF scale is 1024 = 1.0, not a float

Chunk Version Evolution

Based on analysis of original ADT files across World of Warcraft versions:

Core Chunks (1.12.1+)

These chunks are present in all analyzed versions:

  • MVER - Version information
  • MHDR - File header with offsets
  • MCIN - Chunk index table (pre-Cataclysm) / distributed in split files (Cataclysm+)
  • MTEX - Texture filename list
  • MMDX - M2 model filename list
  • MMID - M2 model indices
  • MWMO - WMO filename list
  • MWID - WMO indices
  • MDDF - M2 model placement data
  • MODF - WMO placement data
  • MCNK - Map chunk data (terrain)

Legacy Chunks (Removed)

  • MCLQ - Legacy liquid data (1.12.1 - 2.4.3, replaced by MH2O in WotLK)

The Burning Crusade Additions (2.4.3+)

  • MFBO - Flight bounds data (appears in ~34% of TBC ADT files)

Wrath of the Lich King Additions (3.3.5a+)

  • MH2O - New water system (replaces legacy MCLQ)

Cataclysm Additions (4.3.4+)

  • MAMP - Amplitude map data for terrain deformation (4 bytes, flag/value)
  • MTXF - Enhanced texture flags (evolution from earlier texture systems)

Mists of Pandaria Additions (5.4.8+)

  • MTXP - Texture parameters for advanced material properties (16-byte entries, variable count)

Structural Changes Across Versions

Analysis of chunk sizes and structures reveals several evolution patterns:

MHDR Structure Evolution

  • Pre-Cataclysm: All files have MCIN offsets (centralized chunk index)
  • Cataclysm+: MCIN offsets removed from many files (distributed across split files)
  • MFBO Support: 90% of TBC files, 75% of WotLK files, 67% of Cataclysm+ files
  • MH2O Support: 0% in TBC, 63% in WotLK, variable in later versions

Chunk Size Patterns

  • MVER: Consistently 4 bytes across all versions
  • MHDR: Consistently 64 bytes across all versions
  • MCIN: Consistently 4096 bytes (256 entries Γ— 16 bytes) when present
  • MCNK: Highly variable sizes, trend toward larger chunks in later versions
    • 1.12.1: avg 4905 bytes
    • 2.4.3: avg 2892 bytes
    • 3.3.5a: avg 5428 bytes
    • 4.3.4+: avg 383-940 bytes (split file architecture)

Split File Architecture Impact (Cataclysm+)

  • Root ADT files become much smaller (mainly MHDR + MCNK structure)
  • Texture files contain MAMP, MTEX, MTXP chunks
  • Object files contain model/WMO placement data
  • Average MCNK size drops significantly due to data redistribution

Analysis Results

  • βœ… WoW 1.12.1: 11 chunk types, monolithic files (avg 4905 byte MCNK)
  • βœ… WoW 2.4.3: 12 chunk types (+MFBO 36 bytes), MFBO in 90% of files
  • βœ… WoW 3.3.5a: 13 chunk types (+MH2O variable size), MH2O in 63% of files
  • βœ… WoW 4.3.4: 14 chunk types (+MAMP 4 bytes), split file architecture introduced
  • βœ… WoW 5.4.8: 14 chunk types (+MTXP 16-byte entries), MTXP in texture files

Server Implementation Validation

Our analysis has been cross-validated against production WoW server emulator implementations:

Validation Sources

  • TrinityCore 3.3.5a (WotLK) - Reference implementation
  • Cataclysm Preservation Project TrinityCore 4.3.4 - Cataclysm support
  • SkyFire 5.4.8 - Mists of Pandaria support

Implementation Confidence Levels

ChunkConfidenceServer ValidationUsage
MFBOVery HighTrinityCore 3.3.5a + Cataclysm 4.3.4Flight bounds for gameplay
MH2OVery HighTrinityCore 3.3.5a + SkyFire 5.4.8Water collision/rendering
Split FilesHighCataclysm 4.3.4 + SkyFire 5.4.8Data organization
MAMPMediumNone (empirical only)Client rendering optimization
MTXPMediumNone (empirical only)Client texture enhancement

Key Insight: Server-validated chunks are essential for gameplay mechanics, while empirical-only chunks appear to be client-side rendering optimizations.

Version History

Based on analysis of original MPQ archives:

MVER VersionGame VersionsMajor Changes
18All analyzed versions (1.12.1 - 5.4.8)Consistent version across all expansions

Format Evolution by Game Version

Game VersionMVERChanges
1.12.1 (Vanilla)18Core ADT format established
2.4.3 (The Burning Crusade)18Added MFBO chunk
3.3.5a (Wrath of the Lich King)18Added MH2O water, replaced MCLQ
4.3.4 (Cataclysm)18Split files (_tex0,_obj0, _obj1), added MAMP
5.4.8 (Mists of Pandaria)18Added MTXP chunk

References

See Also