//! Readers for MegaMek's two unit file formats. //! //! MegaMek stores a unit design in one of two hand-editable text formats: //! `.mtf` for Meks and `.blk` for everything else. Both are declarative — they //! record what the designer chose, not what the choices add up to. Battle //! value, C-bill cost and the Alpha Strike conversion are computed when //! MegaMek loads the design and appear in neither format; those live in //! [`helm_core::ComputedStats`]. //! //! What these readers give you is everything the designer declared: chassis //! and model, tech base, era, tonnage, engine, movement, armour, the equipment //! list and the quirks. //! //! Nothing is discarded. Fields the typed struct does not name stay in //! [`Unit::fields`] under their original key, so a MegaMek release that adds //! one is readable before this crate knows about it. //! //! Parsing one design is pure and available everywhere. Reading a whole //! library touches the filesystem and a zip, so it is behind the `library` //! feature, which is on by default and off for wasm. pub use helm_core::{Format, Mount, Unit}; mod blk; pub mod mekset; mod mtf; mod mul; pub use blk::parse_blk; pub use mtf::{armor_location_order, parse_mtf, split_pipe_list, split_system_field}; pub use mul::{ ForceNode, ForceRef, Kept, MUL_VERSION, Mul, MulUnit, locations_for, locations_for_config, parse_force_chain, parse_mul, write_mul, }; #[cfg(feature = "library")] mod library; #[cfg(feature = "library")] pub use library::{Library, megamek_version, read_dir, read_mekset, read_zip, read_zip_text}; /// What went wrong reading a unit file. #[derive(Debug)] pub enum Error { #[cfg(feature = "library")] Io(std::io::Error), #[cfg(feature = "library")] Zip(zip::result::ZipError), /// The file parsed but does not identify a unit. Malformed { path: String, reason: String }, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #[cfg(feature = "library")] Error::Io(e) => write!(f, "{e}"), #[cfg(feature = "library")] Error::Zip(e) => write!(f, "{e}"), Error::Malformed { path, reason } => write!(f, "{path}: {reason}"), } } } impl std::error::Error for Error {} #[cfg(feature = "library")] impl From for Error { fn from(e: std::io::Error) -> Self { Error::Io(e) } } #[cfg(feature = "library")] impl From for Error { fn from(e: zip::result::ZipError) -> Self { Error::Zip(e) } } /// Parse a unit from bytes, choosing the reader by file extension. pub fn parse(path: &str, bytes: &[u8]) -> Result { // MegaMek's library is thirty years of files from many editors. Most are // UTF-8; the rest are Latin-1 and would otherwise fail to read at all. let text = match std::str::from_utf8(bytes) { Ok(s) => s.to_string(), Err(_) => bytes.iter().map(|&b| b as char).collect(), }; let lower = path.to_ascii_lowercase(); if lower.ends_with(".mtf") { parse_mtf(path, &text) } else if lower.ends_with(".blk") { parse_blk(path, &text) } else { Err(Error::Malformed { path: path.to_string(), reason: "not a .mtf or .blk file".into(), }) } }