use std::{
collections::{BTreeMap, hash_map::RandomState},
fmt, io,
time::SystemTime,
};
use indexmap::IndexMap;
use nwnrs_compressedbuf::prelude::*;
use nwnrs_encoding::prelude::*;
use nwnrs_io::prelude::*;
use nwnrs_resman::prelude::*;
use nwnrs_resref::prelude::*;
pub(crate) const HEADER_SIZE: u64 = 160;
pub(crate) const VALID_ERF_TYPES: [&str; 4] = ["NWM ", "MOD ", "ERF ", "HAK "];
#[derive(Debug)]
pub enum ErfError {
Io(io::Error),
ResMan(ResManError),
ResRef(ResRefError),
Compression(CompressedBufError),
Expectation(ExpectationError),
Encoding(EncodingConversionError),
Message(String),
}
impl ErfError {
pub(crate) fn msg(message: impl Into<String>) -> Self {
Self::Message(message.into())
}
}
impl fmt::Display for ErfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => error.fmt(f),
Self::ResMan(error) => error.fmt(f),
Self::ResRef(error) => error.fmt(f),
Self::Compression(error) => error.fmt(f),
Self::Expectation(error) => error.fmt(f),
Self::Encoding(error) => error.fmt(f),
Self::Message(message) => f.write_str(message),
}
}
}
impl std::error::Error for ErfError {}
impl From<io::Error> for ErfError {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl From<ResManError> for ErfError {
fn from(value: ResManError) -> Self {
Self::ResMan(value)
}
}
impl From<ResRefError> for ErfError {
fn from(value: ResRefError) -> Self {
Self::ResRef(value)
}
}
impl From<CompressedBufError> for ErfError {
fn from(value: CompressedBufError) -> Self {
Self::Compression(value)
}
}
impl From<ExpectationError> for ErfError {
fn from(value: ExpectationError) -> Self {
Self::Expectation(value)
}
}
impl From<EncodingConversionError> for ErfError {
fn from(value: EncodingConversionError) -> Self {
Self::Encoding(value)
}
}
pub type ErfResult<T> = Result<T, ErfError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErfVersion {
V1,
E1,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ErfWriteOptions {
pub resource_list_padding: u64,
}
#[derive(Debug, Clone)]
pub struct Erf {
pub mtime: SystemTime,
pub file_type: String,
pub file_version: ErfVersion,
pub(crate) filename: String,
pub build_year: i32,
pub build_day: i32,
pub str_ref: i32,
pub(crate) loc_strings: BTreeMap<i32, String>,
pub(crate) entries: IndexMap<ResRef, Res, RandomState>,
pub(crate) oid: Option<String>,
pub(crate) resource_list_padding: u64,
}
impl Erf {
#[must_use]
pub fn filename(&self) -> &str {
&self.filename
}
#[must_use]
pub fn loc_strings(&self) -> &BTreeMap<i32, String> {
&self.loc_strings
}
#[must_use]
pub fn entries(&self) -> &IndexMap<ResRef, Res, RandomState> {
&self.entries
}
#[must_use]
pub fn oid(&self) -> Option<&str> {
self.oid.as_deref()
}
#[must_use]
pub fn resource_list_padding(&self) -> u64 {
self.resource_list_padding
}
}
impl fmt::Display for Erf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Erf:{}", self.filename)
}
}
impl ResContainer for Erf {
fn contains(&self, rr: &ResRef) -> bool {
self.entries.contains_key(rr)
}
fn demand(&self, rr: &ResRef) -> ResManResult<Res> {
self.entries
.get(rr)
.cloned()
.ok_or_else(|| ResManError::Message(format!("not found: {rr}")))
}
fn count(&self) -> usize {
self.entries.len()
}
fn contents(&self) -> Vec<ResRef> {
self.entries.keys().cloned().collect()
}
}
#[derive(Debug)]
pub(crate) struct ErfResMeta {
pub offset: u64,
pub disk_size: usize,
pub uncompressed_size: usize,
pub compression: nwnrs_exo::ExoResFileCompressionType,
}