use crate::error::{Result, WdlError};
use crate::types::{
MLDD_MAGIC, MLDX_MAGIC, MLMD_MAGIC, MLMX_MAGIC, MODF_MAGIC, MWID_MAGIC, MWMO_MAGIC, WdlFile,
};
pub fn validate_wdl_file(file: &WdlFile) -> Result<()> {
file.validate()?;
validate_map_tiles(file)?;
validate_model_references(file)?;
validate_version_specific(file)?;
Ok(())
}
fn validate_map_tiles(file: &WdlFile) -> Result<()> {
for &(x, y) in file.heightmap_tiles.keys() {
if x >= 64 || y >= 64 {
return Err(WdlError::ValidationError(format!(
"Map tile coordinates out of bounds: ({x}, {y})"
)));
}
}
for &(x, y) in file.holes_data.keys() {
if x >= 64 || y >= 64 {
return Err(WdlError::ValidationError(format!(
"Holes data coordinates out of bounds: ({x}, {y})"
)));
}
}
for y in 0..64 {
for x in 0..64 {
let index = y * 64 + x;
let has_offset = file.map_tile_offsets[index] != 0;
let has_heightmap = file.heightmap_tiles.contains_key(&(x as u32, y as u32));
if has_offset != has_heightmap {
return Err(WdlError::ValidationError(format!(
"Map tile offset mismatch at ({x}, {y}): offset={has_offset}, heightmap={has_heightmap}"
)));
}
}
}
Ok(())
}
fn validate_model_references(file: &WdlFile) -> Result<()> {
for placement in &file.wmo_placements {
if (placement.wmo_id as usize) >= file.wmo_indices.len() {
return Err(WdlError::ValidationError(format!(
"WMO placement references invalid MWID index: {} (max: {})",
placement.wmo_id,
file.wmo_indices.len().saturating_sub(1)
)));
}
}
if file.version.has_ml_chunks() {
if file.m2_visibility.len() != file.m2_placements.len() {
return Err(WdlError::ValidationError(format!(
"M2 visibility count ({}) doesn't match M2 placement count ({})",
file.m2_visibility.len(),
file.m2_placements.len()
)));
}
if file.wmo_legion_visibility.len() != file.wmo_legion_placements.len() {
return Err(WdlError::ValidationError(format!(
"WMO visibility count ({}) doesn't match WMO placement count ({})",
file.wmo_legion_visibility.len(),
file.wmo_legion_placements.len()
)));
}
}
Ok(())
}
fn validate_version_specific(file: &WdlFile) -> Result<()> {
if !file.version.has_maho_chunk() && !file.holes_data.is_empty() {
return Err(WdlError::ValidationError(
"Pre-WotLK WDL files should not have MAHO chunks".to_string(),
));
}
if !file.version.has_ml_chunks()
&& (!file.m2_placements.is_empty()
|| !file.m2_visibility.is_empty()
|| !file.wmo_legion_placements.is_empty()
|| !file.wmo_legion_visibility.is_empty())
{
return Err(WdlError::ValidationError(
"Pre-Legion WDL files should not have MLDD, MLDX, MLMD, or MLMX chunks".to_string(),
));
}
if file.version.has_ml_chunks()
&& (!file.wmo_filenames.is_empty()
|| !file.wmo_indices.is_empty()
|| !file.wmo_placements.is_empty())
{
return Err(WdlError::ValidationError(
"Legion+ WDL files should not have MWMO, MWID, or MODF chunks".to_string(),
));
}
if !file.version.has_ml_chunks()
&& file.chunks.iter().any(|c| {
c.magic == MLDD_MAGIC
|| c.magic == MLDX_MAGIC
|| c.magic == MLMD_MAGIC
|| c.magic == MLMX_MAGIC
})
{
return Err(WdlError::ValidationError(
"Pre-Legion WDL files should not have MLDD, MLDX, MLMD, or MLMX chunks".to_string(),
));
}
if file.version.has_ml_chunks()
&& file
.chunks
.iter()
.any(|c| c.magic == MWMO_MAGIC || c.magic == MWID_MAGIC || c.magic == MODF_MAGIC)
{
return Err(WdlError::ValidationError(
"Legion+ WDL files should not have MWMO, MWID, or MODF chunks".to_string(),
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{HeightMapTile, HolesData};
use crate::version::WdlVersion;
#[test]
fn test_validate_map_tiles() {
let mut file = WdlFile::new();
file.heightmap_tiles.insert((5, 10), HeightMapTile::new());
file.map_tile_offsets[10 * 64 + 5] = 1;
assert!(validate_map_tiles(&file).is_ok());
file.heightmap_tiles.insert((65, 10), HeightMapTile::new());
assert!(validate_map_tiles(&file).is_err());
file.heightmap_tiles.remove(&(65, 10));
file.map_tile_offsets[64 + 1] = 1;
assert!(validate_map_tiles(&file).is_err());
}
#[test]
fn test_validate_version_specific() {
let mut wotlk_file = WdlFile::with_version(WdlVersion::Wotlk);
wotlk_file.holes_data.insert((1, 1), HolesData::new());
assert!(validate_version_specific(&wotlk_file).is_ok());
let mut vanilla_file = WdlFile::with_version(WdlVersion::Vanilla);
vanilla_file.holes_data.insert((1, 1), HolesData::new());
assert!(validate_version_specific(&vanilla_file).is_err());
}
}