use std::path::Path;
use mpq::{Archive, File};
use crate::error::{Error, Result};
use crate::formats::{War3MapImp, War3MapMmp, War3MapW3i, War3MapWts};
use crate::model::{War3Image, War3MapHeader};
use crate::reader::ByteReader;
const HM3W_MAGIC: &[u8; 4] = b"HM3W";
const SCRIPT_PATHS: &[&str] = &[
"war3map.j",
"scripts\\war3map.j",
"war3map.lua",
"scripts\\war3map.lua",
];
pub struct War3MapW3x {
pub header: War3MapHeader,
pub archive: Archive,
pub files: Option<Vec<String>>,
}
impl War3MapW3x {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
Self::from_vec(std::fs::read(path)?)
}
pub fn from_buffer(buffer: &[u8]) -> Result<Self> {
Self::from_vec(buffer.to_vec())
}
pub fn from_vec(buffer: Vec<u8>) -> Result<Self> {
let header = Self::parse_header(&buffer)?;
let mut archive = Archive::load(buffer)?;
let files = Self::read_listfile(&mut archive).ok();
Ok(Self {
header,
archive,
files,
})
}
fn parse_header(buffer: &[u8]) -> Result<War3MapHeader> {
let r = &mut ByteReader::new(buffer);
let magic: [u8; 4] = r.bytes()?;
if &magic != HM3W_MAGIC {
return Ok(War3MapHeader::default());
}
Ok(War3MapHeader {
has_hm3w: true,
u1: Some(r.u32()?),
name: Some(r.cstr_lossy()?),
flags: Some(r.u32()?),
max_players: Some(r.u32()?),
})
}
fn read_listfile(archive: &mut Archive) -> Result<Vec<String>> {
let file = archive.open_file("(listfile)")?;
let mut data = vec![0; file.size() as usize];
file.read(archive, &mut data)?;
let listfile = String::from_utf8_lossy(&data);
Ok(listfile
.split(['\r', '\n'])
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect())
}
pub fn get(&mut self, filename: &str) -> Result<File> {
self.archive
.open_file(filename)
.map_err(|_| Error::FileNotFound(filename.to_string()))
}
pub fn has(&mut self, filename: &str) -> bool {
self.archive.open_file(filename).is_ok()
}
pub fn read_file(&mut self, filename: &str) -> Result<Vec<u8>> {
let file = self.get(filename)?;
let mut data = vec![0; file.size() as usize];
file.read(&mut self.archive, &mut data)?;
Ok(data)
}
pub fn get_script_file(&mut self) -> Option<File> {
SCRIPT_PATHS.iter().find_map(|&path| self.get(path).ok())
}
pub fn read_map_info(&mut self) -> Result<War3MapW3i> {
War3MapW3i::parse(&self.read_file("war3map.w3i")?)
}
pub fn read_imports(&mut self) -> Result<War3MapImp> {
War3MapImp::parse(&self.read_file("war3map.imp")?)
}
pub fn read_string_table(&mut self) -> Result<War3MapWts> {
let data = self.read_file("war3map.wts")?;
War3MapWts::parse(&String::from_utf8_lossy(&data))
}
pub fn read_minimap_icons(&mut self) -> Result<War3MapMmp> {
War3MapMmp::parse(&self.read_file("war3map.mmp")?)
}
pub fn read_minimap(&mut self) -> Result<War3Image> {
self.read_image_variants(
"war3mapMap",
&[
"war3mapMap.tga",
"war3mapMap.blp",
"war3mapmap.tga",
"war3mapmap.blp",
],
)
}
pub fn read_preview(&mut self) -> Result<War3Image> {
self.read_image_variants(
"war3mapPreview",
&[
"war3mapPreview.tga",
"war3mapPreview.blp",
"war3mappreview.tga",
"war3mappreview.blp",
],
)
}
fn read_image_variants(&mut self, stem: &str, candidates: &[&str]) -> Result<War3Image> {
let filename = candidates
.iter()
.find(|&&name| self.has(name))
.ok_or_else(|| Error::FileNotFound(stem.to_string()))?;
let data = self.read_file(filename)?;
War3Image::from_buffer(&data, filename)
}
}