wow-wmo 0.6.4

Parser, editor, and converter for World of Warcraft WMO (World Model Object) files
Documentation

wow-wmo

A Rust library for parsing, editing, validating, and converting World of Warcraft WMO (World Model Object) files.

Crates.io Version docs.rs License

Status

Production Ready - WMO parser with full format support

Overview

WMO files represent buildings, dungeons, and other large structures in World of Warcraft. They consist of a root file containing metadata and multiple group files containing geometry data.

Features

Core Functionality

  • Parse WMO files from all World of Warcraft versions (Classic through The War Within)
  • Parse WMO group files with full geometry and rendering data
  • Validate WMO files with detailed error reporting
  • Convert WMO files between different versions (upgrading and downgrading)
  • Edit WMO files programmatically
  • Write WMO files with proper chunk formatting
  • Builder API for creating WMO files from scratch
  • Tree visualization for inspecting WMO structure

Supported Chunks

  • Root file chunks: MVER, MOHD, MOTX, MOMT, MOGN, MOGI, MOSB, MOPV, MOPT, MOPR, MOVV, MOVB, MOLT, MODS, MODN, MODD, MFOG, MCVP, GFID
  • Group file chunks: MOGP, MOPY, MOVI, MOVT, MONR, MOTV, MOBA, MOLR, MODR, MOBN, MOBR, MOCV, MLIQ, MORI, MORB, MOTA, MOBS

Installation

Add to your Cargo.toml:

[dependencies]
wow-wmo = "0.6"

Or use cargo add:

cargo add wow-wmo

Usage

Parsing a WMO file

use wow_wmo::{WmoParser, WmoGroupParser};
use std::fs::File;
use std::io::BufReader;

// Parse root file
let file = File::open("building.wmo")?;
let mut reader = BufReader::new(file);
let wmo = WmoParser::new().parse_root(&mut reader)?;

println!("WMO Version: v{}", wmo.version.to_raw());
println!("Groups: {}", wmo.groups.len());
println!("Materials: {}", wmo.materials.len());
println!("Textures: {:?}", wmo.textures);

// Note: Group file parsing uses the chunk discovery pipeline.
// See the parse_wmo example for group file handling.

Validating a WMO file

use wow_wmo::{WmoValidator, WmoParser};
use std::fs::File;
use std::io::BufReader;

let file = File::open("building.wmo")?;
let mut reader = BufReader::new(file);
let wmo = WmoParser::new().parse_root(&mut reader)?;

// Validate the WMO
let validator = WmoValidator::new();
let report = validator.validate_root(&wmo)?;

if !report.errors.is_empty() {
    for error in &report.errors {
        println!("Error: {:?}", error);
    }
}

for warning in &report.warnings {
    println!("Warning: {:?}", warning);
}

Converting between versions

use wow_wmo::{WmoConverter, WmoVersion, WmoParser, WmoWriter};
use std::fs::File;
use std::io::{BufReader, Cursor};

let file = File::open("classic_building.wmo")?;
let mut reader = BufReader::new(file);
let mut wmo = WmoParser::new().parse_root(&mut reader)?;

// Convert from Classic to Cataclysm
let converter = WmoConverter::new();
converter.convert_root(&mut wmo, WmoVersion::Cataclysm)?;

// Write the converted file
let writer = WmoWriter::new();
let mut output = Vec::new();
let mut cursor = Cursor::new(&mut output);
writer.write_root(&mut cursor, &wmo, WmoVersion::Cataclysm)?;
std::fs::write("cata_building.wmo", output)?;

Building a WMO programmatically

use wow_wmo::{WmoRoot, WmoMaterial, WmoMaterialFlags, WmoVersion, WmoWriter, Vec3, Color, WmoHeader, WmoFlags, BoundingBox};
use std::io::Cursor;

// Create a simple WMO structure
let wmo = WmoRoot {
    version: WmoVersion::Wotlk,
    materials: vec![WmoMaterial {
        flags: WmoMaterialFlags::UNLIT,
        shader: 0,
        blend_mode: 0,
        texture1: 0,
        emissive_color: Color::default(),
        sidn_color: Color::default(),
        framebuffer_blend: Color::default(),
        texture2: u32::MAX,
        diffuse_color: Color::default(),
        ground_type: 0,
    }],
    groups: vec![],
    portals: vec![],
    portal_references: vec![],
    visible_block_lists: vec![],
    lights: vec![],
    doodad_defs: vec![],
    doodad_sets: vec![],
    bounding_box: BoundingBox {
        min: Vec3 { x: -50.0, y: -50.0, z: 0.0 },
        max: Vec3 { x: 50.0, y: 50.0, z: 30.0 },
    },
    textures: vec!["world/generic/stone_floor.blp".to_string()],
    texture_offset_index_map: std::collections::HashMap::new(),
    header: WmoHeader {
        n_materials: 1,
        n_groups: 0,
        n_portals: 0,
        n_lights: 0,
        n_doodad_names: 0,
        n_doodad_defs: 0,
        n_doodad_sets: 0,
        flags: WmoFlags::empty(),
        ambient_color: Color { r: 128, g: 128, b: 128, a: 255 },
    },
    skybox: None,
    convex_volume_planes: None,
};

// Write to file
let writer = WmoWriter::new();
let mut output = Vec::new();
let mut cursor = Cursor::new(&mut output);
writer.write_root(&mut cursor, &wmo, WmoVersion::Wotlk)?;
std::fs::write("custom.wmo", output)?;

CLI Integration

WMO functionality is integrated into the main warcraft-rs CLI:

# Get information about a WMO
warcraft-rs wmo info building.wmo --detailed

# Validate WMO structure
warcraft-rs wmo validate building.wmo --warnings

# Convert between versions
warcraft-rs wmo convert classic.wmo modern.wmo --version Cataclysm

# Visualize WMO structure
warcraft-rs wmo tree building.wmo --show-refs

Supported Versions

Version Expansion Status
17 Classic - Wrath of the Lich King Supported
18 Cataclysm Supported
19 Mists of Pandaria Supported
20 Warlords of Draenor Supported
21 Legion Supported
22 Battle for Azeroth Supported
23 Battle for Azeroth (8.1+) Supported
24 Shadowlands Supported
25 Shadowlands (9.1+) Supported
26 Dragonflight Supported
27 The War Within Supported

Performance

  • Parsing: ~5-50ms for typical WMO files
  • Validation: ~1-10ms depending on strictness level
  • Conversion: ~10-100ms depending on version gap
  • Writing: ~5-20ms for typical files

Examples

The crate includes several examples:

  • parse_wmo - Basic WMO parsing example
  • validate_wmo - Validation with different strictness levels
  • convert_wmo - Version conversion example
  • build_wmo - Creating WMO files programmatically

Run examples with:

cargo run --example parse_wmo

Known Issues

  1. Parser Overflow: Fixed - Group name parsing now handles pointer arithmetic correctly
  2. Header Size: Fixed - MOHD chunk size corrected to 60 bytes
  3. Texture Validation: Fixed - Special marker values (0xFF000000+) are now handled
  4. Light Types: Fixed - Unknown light types default to Omni
  5. Doodad Structure: Fixed - Always uses 40 bytes for proper round-trip conversion

License

Licensed under either of

at your option.