Skip to main content

nwnrs_erf/
types.rs

1use std::{
2    collections::{BTreeMap, hash_map::RandomState},
3    fmt, io,
4    time::SystemTime,
5};
6
7use indexmap::IndexMap;
8use nwnrs_compressedbuf::prelude::*;
9use nwnrs_encoding::prelude::*;
10use nwnrs_io::prelude::*;
11use nwnrs_resman::prelude::*;
12use nwnrs_resref::prelude::*;
13
14pub(crate) const HEADER_SIZE: u64 = 160;
15pub(crate) const VALID_ERF_TYPES: [&str; 4] = ["NWM ", "MOD ", "ERF ", "HAK "];
16
17#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ErfError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            ErfError::Io(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Io",
                    &__self_0),
            ErfError::ResMan(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "ResMan",
                    &__self_0),
            ErfError::ResRef(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "ResRef",
                    &__self_0),
            ErfError::Compression(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Compression", &__self_0),
            ErfError::Expectation(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Expectation", &__self_0),
            ErfError::Encoding(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Encoding", &__self_0),
            ErfError::Message(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Message", &__self_0),
        }
    }
}Debug)]
18/// Errors returned while reading or writing ERF-family archives.
19pub enum ErfError {
20    /// An underlying IO operation failed.
21    Io(io::Error),
22    /// Resource manager setup failed while constructing archive-backed [`Res`]
23    /// entries.
24    ResMan(ResManError),
25    /// A resource reference inside the archive was invalid.
26    ResRef(ResRefError),
27    /// A compressed payload could not be decoded or encoded.
28    Compression(CompressedBufError),
29    /// A format invariant was violated.
30    Expectation(ExpectationError),
31    /// Text could not be converted using the configured NWN encoding.
32    Encoding(EncodingConversionError),
33    /// The archive contents were otherwise invalid.
34    Message(String),
35}
36
37impl ErfError {
38    pub(crate) fn msg(message: impl Into<String>) -> Self {
39        Self::Message(message.into())
40    }
41}
42
43impl fmt::Display for ErfError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::Io(error) => error.fmt(f),
47            Self::ResMan(error) => error.fmt(f),
48            Self::ResRef(error) => error.fmt(f),
49            Self::Compression(error) => error.fmt(f),
50            Self::Expectation(error) => error.fmt(f),
51            Self::Encoding(error) => error.fmt(f),
52            Self::Message(message) => f.write_str(message),
53        }
54    }
55}
56
57impl std::error::Error for ErfError {}
58
59impl From<io::Error> for ErfError {
60    fn from(value: io::Error) -> Self {
61        Self::Io(value)
62    }
63}
64
65impl From<ResManError> for ErfError {
66    fn from(value: ResManError) -> Self {
67        Self::ResMan(value)
68    }
69}
70
71impl From<ResRefError> for ErfError {
72    fn from(value: ResRefError) -> Self {
73        Self::ResRef(value)
74    }
75}
76
77impl From<CompressedBufError> for ErfError {
78    fn from(value: CompressedBufError) -> Self {
79        Self::Compression(value)
80    }
81}
82
83impl From<ExpectationError> for ErfError {
84    fn from(value: ExpectationError) -> Self {
85        Self::Expectation(value)
86    }
87}
88
89impl From<EncodingConversionError> for ErfError {
90    fn from(value: EncodingConversionError) -> Self {
91        Self::Encoding(value)
92    }
93}
94
95/// Result type for ERF operations.
96pub type ErfResult<T> = Result<T, ErfError>;
97
98#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ErfVersion {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self { ErfVersion::V1 => "V1", ErfVersion::E1 => "E1", })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ErfVersion {
    #[inline]
    fn clone(&self) -> ErfVersion { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ErfVersion { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for ErfVersion {
    #[inline]
    fn eq(&self, other: &ErfVersion) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ErfVersion {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
99/// Supported ERF-family versions.
100pub enum ErfVersion {
101    /// Legacy archive layout without per-entry compression metadata.
102    V1,
103    /// Enhanced-edition layout with optional per-entry compression and an
104    /// archive OID.
105    E1,
106}
107
108#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ErfWriteOptions {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "ErfWriteOptions", "resource_list_padding",
            &&self.resource_list_padding)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ErfWriteOptions {
    #[inline]
    fn clone(&self) -> ErfWriteOptions {
        let _: ::core::clone::AssertParamIsClone<u64>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ErfWriteOptions { }Copy, #[automatically_derived]
impl ::core::default::Default for ErfWriteOptions {
    #[inline]
    fn default() -> ErfWriteOptions {
        ErfWriteOptions {
            resource_list_padding: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl ::core::cmp::PartialEq for ErfWriteOptions {
    #[inline]
    fn eq(&self, other: &ErfWriteOptions) -> bool {
        self.resource_list_padding == other.resource_list_padding
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ErfWriteOptions {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u64>;
    }
}Eq)]
109/// Optional layout controls for ERF-family archive writes.
110pub struct ErfWriteOptions {
111    /// Explicit padding to preserve between the key list and resource list.
112    pub resource_list_padding: u64,
113}
114
115#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Erf {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["mtime", "file_type", "file_version", "filename", "build_year",
                        "build_day", "str_ref", "loc_strings", "entries", "oid",
                        "resource_list_padding"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.mtime, &self.file_type, &self.file_version,
                        &self.filename, &self.build_year, &self.build_day,
                        &self.str_ref, &self.loc_strings, &self.entries, &self.oid,
                        &&self.resource_list_padding];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "Erf", names,
            values)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for Erf {
    #[inline]
    fn clone(&self) -> Erf {
        Erf {
            mtime: ::core::clone::Clone::clone(&self.mtime),
            file_type: ::core::clone::Clone::clone(&self.file_type),
            file_version: ::core::clone::Clone::clone(&self.file_version),
            filename: ::core::clone::Clone::clone(&self.filename),
            build_year: ::core::clone::Clone::clone(&self.build_year),
            build_day: ::core::clone::Clone::clone(&self.build_day),
            str_ref: ::core::clone::Clone::clone(&self.str_ref),
            loc_strings: ::core::clone::Clone::clone(&self.loc_strings),
            entries: ::core::clone::Clone::clone(&self.entries),
            oid: ::core::clone::Clone::clone(&self.oid),
            resource_list_padding: ::core::clone::Clone::clone(&self.resource_list_padding),
        }
    }
}Clone)]
116/// A decoded ERF-family archive.
117///
118/// The entry map preserves archive order and the archive itself implements
119/// [`nwnrs_resman::ResContainer`] for use with [`nwnrs_resman::ResMan`].
120///
121/// The typed value preserves archive identity, stored entry order, and archive
122/// header metadata without requiring callers to choose between inspection and
123/// container-style lookup.
124pub struct Erf {
125    /// The archive modification time when known.
126    pub mtime: SystemTime,
127    /// The four-byte archive type tag such as `ERF `, `MOD `, `HAK `, or `NWM
128    /// `.
129    pub file_type: String,
130    /// The archive version.
131    pub file_version: ErfVersion,
132    pub(crate) filename: String,
133    /// Build year stored in the archive header.
134    pub build_year: i32,
135    /// Build day stored in the archive header.
136    pub build_day: i32,
137    /// Localized string reference stored in the archive header.
138    pub str_ref: i32,
139    pub(crate) loc_strings: BTreeMap<i32, String>,
140    pub(crate) entries: IndexMap<ResRef, Res, RandomState>,
141    pub(crate) oid: Option<String>,
142    pub(crate) resource_list_padding: u64,
143}
144
145impl Erf {
146    /// Returns the display filename associated with this archive.
147    #[must_use]
148    pub fn filename(&self) -> &str {
149        &self.filename
150    }
151
152    /// Returns the localized strings stored in the archive header.
153    #[must_use]
154    pub fn loc_strings(&self) -> &BTreeMap<i32, String> {
155        &self.loc_strings
156    }
157
158    /// Returns the archive entries in stored order.
159    #[must_use]
160    pub fn entries(&self) -> &IndexMap<ResRef, Res, RandomState> {
161        &self.entries
162    }
163
164    /// Returns the enhanced-edition archive OID when present.
165    #[must_use]
166    pub fn oid(&self) -> Option<&str> {
167        self.oid.as_deref()
168    }
169
170    /// Returns the preserved padding between the key list and resource list.
171    #[must_use]
172    pub fn resource_list_padding(&self) -> u64 {
173        self.resource_list_padding
174    }
175}
176
177impl fmt::Display for Erf {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        f.write_fmt(format_args!("Erf:{0}", self.filename))write!(f, "Erf:{}", self.filename)
180    }
181}
182
183impl ResContainer for Erf {
184    fn contains(&self, rr: &ResRef) -> bool {
185        self.entries.contains_key(rr)
186    }
187
188    fn demand(&self, rr: &ResRef) -> ResManResult<Res> {
189        self.entries
190            .get(rr)
191            .cloned()
192            .ok_or_else(|| ResManError::Message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("not found: {0}", rr))
    })format!("not found: {rr}")))
193    }
194
195    fn count(&self) -> usize {
196        self.entries.len()
197    }
198
199    fn contents(&self) -> Vec<ResRef> {
200        self.entries.keys().cloned().collect()
201    }
202}
203
204#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ErfResMeta {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field4_finish(f, "ErfResMeta",
            "offset", &self.offset, "disk_size", &self.disk_size,
            "uncompressed_size", &self.uncompressed_size, "compression",
            &&self.compression)
    }
}Debug)]
205pub(crate) struct ErfResMeta {
206    pub offset:            u64,
207    pub disk_size:         usize,
208    pub uncompressed_size: usize,
209    pub compression:       nwnrs_exo::ExoResFileCompressionType,
210}