Skip to main content

nwnrs_key/
types.rs

1use std::{
2    collections::hash_map::RandomState,
3    fmt, io,
4    sync::{Arc, Mutex},
5    time::SystemTime,
6};
7
8use indexmap::IndexMap;
9use nwnrs_checksums::prelude::*;
10use nwnrs_compressedbuf::prelude::*;
11use nwnrs_exo::prelude::*;
12use nwnrs_resman::prelude::*;
13use nwnrs_resref::prelude::*;
14
15pub(crate) const HEADER_SIZE: u64 = 64;
16
17/// Packed KEY resource id.
18///
19/// The upper bits identify the owning BIF and the lower bits identify the
20/// variable resource within that BIF.
21pub type ResId = u32;
22/// Callback used to open a referenced BIF by filename.
23pub type BifResolver =
24    Arc<dyn Fn(&str) -> io::Result<Option<SharedReadSeek>> + Send + Sync + 'static>;
25
26#[derive(#[automatically_derived]
impl ::core::fmt::Debug for KeyError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            KeyError::Io(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Io",
                    &__self_0),
            KeyError::ResMan(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "ResMan",
                    &__self_0),
            KeyError::ResRef(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "ResRef",
                    &__self_0),
            KeyError::Compression(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Compression", &__self_0),
            KeyError::Message(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Message", &__self_0),
        }
    }
}Debug)]
27/// Errors returned while reading or writing KEY/BIF data.
28pub enum KeyError {
29    /// An underlying IO operation failed.
30    Io(io::Error),
31    /// Resource manager setup failed while constructing entries.
32    ResMan(ResManError),
33    /// A resource reference or filename could not be interpreted.
34    ResRef(ResRefError),
35    /// A compressed payload could not be decoded or encoded.
36    Compression(CompressedBufError),
37    /// The KEY or BIF contents were otherwise invalid.
38    Message(String),
39}
40
41impl KeyError {
42    pub(crate) fn msg(message: impl Into<String>) -> Self {
43        Self::Message(message.into())
44    }
45}
46
47impl fmt::Display for KeyError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::Io(error) => error.fmt(f),
51            Self::ResMan(error) => error.fmt(f),
52            Self::ResRef(error) => error.fmt(f),
53            Self::Compression(error) => error.fmt(f),
54            Self::Message(message) => f.write_str(message),
55        }
56    }
57}
58
59impl std::error::Error for KeyError {}
60
61impl From<io::Error> for KeyError {
62    fn from(value: io::Error) -> Self {
63        Self::Io(value)
64    }
65}
66
67impl From<ResManError> for KeyError {
68    fn from(value: ResManError) -> Self {
69        Self::ResMan(value)
70    }
71}
72
73impl From<ResRefError> for KeyError {
74    fn from(value: ResRefError) -> Self {
75        Self::ResRef(value)
76    }
77}
78
79impl From<CompressedBufError> for KeyError {
80    fn from(value: CompressedBufError) -> Self {
81        Self::Compression(value)
82    }
83}
84
85/// Result type for KEY/BIF operations.
86pub type KeyResult<T> = Result<T, KeyError>;
87
88#[derive(#[automatically_derived]
impl ::core::fmt::Debug for KeyBifVersion {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                KeyBifVersion::V1 => "V1",
                KeyBifVersion::E1 => "E1",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for KeyBifVersion {
    #[inline]
    fn clone(&self) -> KeyBifVersion { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for KeyBifVersion { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for KeyBifVersion {
    #[inline]
    fn eq(&self, other: &KeyBifVersion) -> 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 KeyBifVersion {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq)]
89/// Supported KEY/BIF versions.
90pub enum KeyBifVersion {
91    /// Legacy KEY/BIF layout.
92    V1,
93    /// Enhanced-edition KEY/BIF layout with optional compression metadata and
94    /// OID support.
95    E1,
96}
97
98#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VariableResource {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field5_finish(f,
            "VariableResource", "id", &self.id, "io_offset", &self.io_offset,
            "io_size", &self.io_size, "compression_type",
            &self.compression_type, "uncompressed_size",
            &&self.uncompressed_size)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VariableResource {
    #[inline]
    fn clone(&self) -> VariableResource {
        VariableResource {
            id: ::core::clone::Clone::clone(&self.id),
            io_offset: ::core::clone::Clone::clone(&self.io_offset),
            io_size: ::core::clone::Clone::clone(&self.io_size),
            compression_type: ::core::clone::Clone::clone(&self.compression_type),
            uncompressed_size: ::core::clone::Clone::clone(&self.uncompressed_size),
        }
    }
}Clone)]
99/// Metadata for a variable resource entry stored inside a BIF.
100pub struct VariableResource {
101    pub(crate) id:                ResId,
102    pub(crate) io_offset:         u64,
103    pub(crate) io_size:           usize,
104    pub(crate) compression_type:  ExoResFileCompressionType,
105    pub(crate) uncompressed_size: usize,
106}
107
108impl VariableResource {
109    /// Returns the packed KEY resource id for this entry.
110    #[must_use]
111    pub fn id(&self) -> ResId {
112        self.id
113    }
114
115    /// Returns the byte offset of the payload inside the BIF stream.
116    #[must_use]
117    pub fn io_offset(&self) -> u64 {
118        self.io_offset
119    }
120
121    /// Returns the stored payload size on disk.
122    #[must_use]
123    pub fn io_size(&self) -> usize {
124        self.io_size
125    }
126
127    /// Returns the compression marker stored for the payload.
128    #[must_use]
129    pub fn compression_type(&self) -> ExoResFileCompressionType {
130        self.compression_type
131    }
132
133    /// Returns the expected size after decompression.
134    #[must_use]
135    pub fn uncompressed_size(&self) -> usize {
136        self.uncompressed_size
137    }
138}
139
140pub(crate) struct LoadedBif {
141    pub stream:             SharedReadSeek,
142    pub file_type:          String,
143    pub file_version:       KeyBifVersion,
144    pub variable_resources: IndexMap<ResId, VariableResource, RandomState>,
145    pub oid:                Option<String>,
146    pub raw_oid:            Option<String>,
147}
148
149impl fmt::Debug for LoadedBif {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        f.debug_struct("LoadedBif")
152            .field("file_type", &self.file_type)
153            .field("file_version", &self.file_version)
154            .field("variable_resources", &self.variable_resources.len())
155            .field("oid", &self.oid)
156            .finish_non_exhaustive()
157    }
158}
159
160pub(crate) struct BifHandle {
161    pub filename:          String,
162    pub resolver_filename: String,
163    pub expected_version:  KeyBifVersion,
164    pub expected_oid:      Option<String>,
165    pub drives:            u16,
166    pub resolver:          BifResolver,
167    pub loaded:            Mutex<Option<Arc<LoadedBif>>>,
168}
169
170impl fmt::Debug for BifHandle {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        f.debug_struct("BifHandle")
173            .field("filename", &self.filename)
174            .field("expected_version", &self.expected_version)
175            .field("expected_oid", &self.expected_oid)
176            .finish_non_exhaustive()
177    }
178}
179
180#[derive(#[automatically_derived]
impl ::core::fmt::Debug for KeyEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "KeyEntry",
            "res_id", &self.res_id, "sha1", &&self.sha1)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for KeyEntry {
    #[inline]
    fn clone(&self) -> KeyEntry {
        KeyEntry {
            res_id: ::core::clone::Clone::clone(&self.res_id),
            sha1: ::core::clone::Clone::clone(&self.sha1),
        }
    }
}Clone)]
181pub(crate) struct KeyEntry {
182    pub res_id: ResId,
183    pub sha1:   SecureHash,
184}
185
186/// Decoded contents of a KEY file together with lazy BIF resolvers.
187///
188/// The table implements [`nwnrs_resman::ResContainer`], so it can be placed
189/// directly inside a layered [`nwnrs_resman::ResMan`].
190/// A decoded KEY table together with its referenced BIF handles.
191///
192/// The table preserves the KEY-level lookup structure explicitly: typed
193/// resource references map to KEY entries, which in turn identify one BIF and
194/// one variable resource id. The same typed value also implements
195/// [`nwnrs_resman::ResContainer`] so callers may use it directly in layered
196/// resource resolution.
197pub struct KeyTable {
198    pub(crate) version:          KeyBifVersion,
199    pub(crate) label:            String,
200    pub(crate) build_year:       u32,
201    pub(crate) build_day:        u32,
202    pub(crate) bifs:             Vec<BifHandle>,
203    pub(crate) resref_id_lookup: IndexMap<ResRef, KeyEntry, RandomState>,
204    pub(crate) oid:              Option<String>,
205    pub(crate) raw_oid:          Option<String>,
206}
207
208#[derive(#[automatically_derived]
impl ::core::fmt::Debug for KeyBifContents {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "KeyBifContents", "filename", &self.filename, "resources",
            &&self.resources)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for KeyBifContents {
    #[inline]
    fn clone(&self) -> KeyBifContents {
        KeyBifContents {
            filename: ::core::clone::Clone::clone(&self.filename),
            resources: ::core::clone::Clone::clone(&self.resources),
        }
    }
}Clone)]
209/// The resources stored in a single BIF referenced by a [`KeyTable`].
210pub struct KeyBifContents {
211    /// The BIF filename as recorded by the KEY file.
212    pub filename:  String,
213    /// The resources stored in that BIF, in table order.
214    pub resources: Vec<ResRef>,
215}
216
217impl fmt::Debug for KeyTable {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        f.debug_struct("KeyTable")
220            .field("version", &self.version)
221            .field("label", &self.label)
222            .field("build_year", &self.build_year)
223            .field("build_day", &self.build_day)
224            .field("oid", &self.oid)
225            .field(
226                "bifs",
227                &self
228                    .bifs
229                    .iter()
230                    .map(|bif| bif.filename.clone())
231                    .collect::<Vec<_>>(),
232            )
233            .field("entry_count", &self.resref_id_lookup.len())
234            .finish_non_exhaustive()
235    }
236}
237
238impl fmt::Display for KeyTable {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        f.write_fmt(format_args!("KeyTable:{0}", self.label))write!(f, "KeyTable:{}", self.label)
241    }
242}
243
244impl ResContainer for KeyTable {
245    fn contains(&self, rr: &ResRef) -> bool {
246        self.resref_id_lookup.contains_key(rr)
247    }
248
249    fn demand(&self, rr: &ResRef) -> ResManResult<Res> {
250        let entry = self
251            .resref_id_lookup
252            .get(rr)
253            .ok_or_else(|| ResManError::Message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("not found: {0}", rr))
    })format!("not found: {rr}")))?;
254        let bif_idx = (entry.res_id >> 20) as usize;
255        let variable_id = entry.res_id & 0x000f_ffff;
256        let bif = self.bifs.get(bif_idx).ok_or_else(|| {
257            ResManError::Message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid bif index for {0}: {1}",
                rr, bif_idx))
    })format!("invalid bif index for {rr}: {bif_idx}"))
258        })?;
259        let loaded = bif
260            .load()
261            .map_err(|error| ResManError::Message(error.to_string()))?;
262        let variable = loaded.variable_resources.get(&variable_id).ok_or_else(|| {
263            ResManError::Message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("keytable references non-existent id: {0}",
                entry.res_id))
    })format!(
264                "keytable references non-existent id: {}",
265                entry.res_id
266            ))
267        })?;
268
269        Ok(Res::new_with_stream(
270            new_res_origin(
271                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("KeyTable:{0}", self.label))
    })format!("KeyTable:{}", self.label),
272                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("id={0} in {1}", entry.res_id,
                bif.filename))
    })format!("id={} in {}", entry.res_id, bif.filename),
273            ),
274            rr.clone(),
275            SystemTime::UNIX_EPOCH,
276            loaded.stream.clone(),
277            i64::try_from(variable.io_size).map_err(|e| {
278                ResManError::Message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("KEY resource size exceeds i64 range: {0}",
                e))
    })format!("KEY resource size exceeds i64 range: {e}"))
279            })?,
280            variable.io_offset,
281            variable.compression_type,
282            None,
283            variable.uncompressed_size,
284            entry.sha1,
285        ))
286    }
287
288    fn count(&self) -> usize {
289        self.resref_id_lookup.len()
290    }
291
292    fn contents(&self) -> Vec<ResRef> {
293        self.resref_id_lookup.keys().cloned().collect()
294    }
295}
296
297impl KeyTable {
298    /// Returns the KEY/BIF version expected by this table.
299    #[must_use]
300    pub fn version(&self) -> KeyBifVersion {
301        self.version
302    }
303
304    /// Returns the build year stored in the KEY header.
305    #[must_use]
306    pub fn build_year(&self) -> u32 {
307        self.build_year
308    }
309
310    /// Returns the build day stored in the KEY header.
311    #[must_use]
312    pub fn build_day(&self) -> u32 {
313        self.build_day
314    }
315
316    /// Returns the enhanced-edition OID when present.
317    #[must_use]
318    pub fn oid(&self) -> Option<&str> {
319        self.oid.as_deref()
320    }
321
322    /// Returns the raw enhanced-edition OID bytes as stored in the KEY header.
323    #[must_use]
324    pub fn raw_oid(&self) -> Option<&str> {
325        self.raw_oid.as_deref()
326    }
327
328    /// Returns the referenced BIF filenames in table order.
329    #[must_use]
330    pub fn bifs(&self) -> Vec<String> {
331        self.bifs.iter().map(|bif| bif.filename.clone()).collect()
332    }
333
334    /// Returns the resources grouped by BIF.
335    ///
336    /// Calling this may lazily open referenced BIF files through the configured
337    /// resolver.
338    ///
339    /// # Errors
340    ///
341    /// Returns [`KeyError`] if any referenced BIF file cannot be loaded.
342    pub fn bif_contents(&self) -> KeyResult<Vec<KeyBifContents>> {
343        let mut by_bif = Vec::with_capacity(self.bifs.len());
344
345        for (bif_idx, bif) in self.bifs.iter().enumerate() {
346            let loaded = bif.load()?;
347            let mut resources = Vec::with_capacity(loaded.variable_resources.len());
348
349            for local_id in loaded.variable_resources.keys() {
350                let full_id = (u32::try_from(bif_idx)
351                    .map_err(|_error| KeyError::msg("bif index exceeds 32-bit range"))?
352                    << 20)
353                    | *local_id;
354                let rr = self
355                    .resref_id_lookup
356                    .iter()
357                    .find_map(|(rr, entry)| (entry.res_id == full_id).then(|| rr.clone()))
358                    .ok_or_else(|| {
359                        KeyError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("bif {0} references unknown variable resource id {1}",
                bif.filename, full_id))
    })format!(
360                            "bif {} references unknown variable resource id {}",
361                            bif.filename, full_id
362                        ))
363                    })?;
364                resources.push(rr);
365            }
366
367            by_bif.push(KeyBifContents {
368                filename: bif.filename.clone(),
369                resources,
370            });
371        }
372
373        Ok(by_bif)
374    }
375}
376
377impl BifHandle {
378    pub(crate) fn load(&self) -> KeyResult<Arc<LoadedBif>> {
379        {
380            let loaded = self
381                .loaded
382                .lock()
383                .map_err(|error| KeyError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("bif lock poisoned: {0}", error))
    })format!("bif lock poisoned: {error}")))?;
384            if let Some(loaded) = loaded.as_ref() {
385                return Ok(loaded.clone());
386            }
387        }
388
389        let stream = (self.resolver)(&self.resolver_filename)?.ok_or_else(|| {
390            KeyError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("key file referenced file {0} but cannot open",
                self.filename))
    })format!(
391                "key file referenced file {} but cannot open",
392                self.filename
393            ))
394        })?;
395        let loaded = Arc::new(crate::io::read_bif(
396            stream.clone(),
397            &self.filename,
398            self.expected_version,
399            self.expected_oid.as_deref(),
400        )?);
401        *self
402            .loaded
403            .lock()
404            .map_err(|error| KeyError::msg(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("bif lock poisoned: {0}", error))
    })format!("bif lock poisoned: {error}")))? =
405            Some(loaded.clone());
406        Ok(loaded)
407    }
408}
409
410#[derive(#[automatically_derived]
impl ::core::fmt::Debug for KeyBifEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["directory", "name", "recorded_filename", "drives", "bif_oid",
                        "entries"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.directory, &self.name, &self.recorded_filename,
                        &self.drives, &self.bif_oid, &&self.entries];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "KeyBifEntry",
            names, values)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for KeyBifEntry {
    #[inline]
    fn clone(&self) -> KeyBifEntry {
        KeyBifEntry {
            directory: ::core::clone::Clone::clone(&self.directory),
            name: ::core::clone::Clone::clone(&self.name),
            recorded_filename: ::core::clone::Clone::clone(&self.recorded_filename),
            drives: ::core::clone::Clone::clone(&self.drives),
            bif_oid: ::core::clone::Clone::clone(&self.bif_oid),
            entries: ::core::clone::Clone::clone(&self.entries),
        }
    }
}Clone)]
411/// Specification for a BIF to be written by [`crate::write_key_and_bif`].
412pub struct KeyBifEntry {
413    /// Optional directory component to prepend to the emitted BIF path.
414    pub directory:         String,
415    /// Basename of the emitted BIF, without the `.bif` suffix.
416    pub name:              String,
417    /// Exact filename/path spelling to emit into the KEY file and on disk.
418    pub recorded_filename: Option<String>,
419    /// Raw file-table drive flags.
420    pub drives:            u16,
421    /// Exact 24-byte BIF OID to emit for E1 outputs.
422    pub bif_oid:           Option<String>,
423    /// Resource entries that should be written into the BIF.
424    pub entries:           Vec<ResRef>,
425}
426
427pub(crate) type WriteBifResult = (usize, Vec<(ResRef, SecureHash)>);