commonlibsse_ng\rel\id\id_database/
header.rs

1// C++ Original code
2// - https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/REL/ID.h
3// - header_t::read: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/REL/ID.cpp
4// SPDX-FileCopyrightText: (C) 2018 Ryan-rsm-McKenzie
5// SPDX-License-Identifier: MIT
6//
7// SPDX-FileCopyrightText: (C) 2025 SARDONYX
8// SPDX-License-Identifier: Apache-2.0 OR MIT
9//! AddressLibrary header parser
10
11use crate::rel::version::Version;
12
13/// AddressLibrary header information
14#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Header {
16    /// The version information of the address library.
17    pub version: Version,
18
19    /// The size of pointers in the address library, typically 8 bytes for 64-bit systems.
20    pointer_size: u32,
21
22    /// The number of addresses contained in the address library.
23    address_count: u32,
24}
25
26impl Header {
27    /// Parses a `Header` from a reader.
28    ///
29    /// Reads the format version, the address library version, name length, pointer size, and address count.
30    ///
31    /// # Errors
32    ///
33    /// Returns a `HeaderError` if any step in the reading process fails, such as:
34    /// - Reading format version
35    /// - Unsupported address format
36    /// - Reading version, name length, pointer size, or address count
37    pub fn from_reader<R>(reader: &mut R, expected_fmt_ver: u8) -> Result<Self, HeaderError>
38    where
39        R: std::io::Read + std::io::Seek,
40    {
41        use snafu::ResultExt as _;
42
43        // Read format version: 4bytes(1..=4 bytes)
44        {
45            let mut format = [0_u8; 4];
46            reader
47                .read_exact(&mut format)
48                .context(ReadFormatVersionSnafu)?;
49            let format = i32::from_le_bytes(format);
50
51            if format != expected_fmt_ver as i32 {
52                return Err(HeaderError::UnexpectedFormat {
53                    expected: expected_fmt_ver,
54                    actual_format: format,
55                });
56            }
57        }
58
59        // Read version: next 16bytes(5..=20 bytes nth)
60        let version = {
61            let mut version = [0_u8; 16];
62            reader.read_exact(&mut version).context(ReadVersionSnafu)?;
63            let version = u32_to_u16_array(u8_to_le_u32_array(version));
64            Version::new(version[0], version[1], version[2], version[3])
65        };
66
67        // Read name length: next 4bytes(20..=23 bytes nth)
68        // This value is usually `0x0c` -> 12bytes.
69        {
70            let mut name_len = [0_u8; 4];
71            reader
72                .read_exact(&mut name_len)
73                .context(ReadNameLengthSnafu)?;
74            let name_len = i32::from_le_bytes(name_len) as i64;
75            reader
76                .seek(std::io::SeekFrom::Current(name_len))
77                .context(SeekAfterNameLengthSnafu)?;
78        }
79
80        // Read pointer size: next 4bytes(usually 0x24..=0x27 bytes nth)
81        // This value is almost always 8(bytes) -> 64bit
82        let pointer_size = {
83            let mut pointer_size = [0_u8; 4];
84            reader
85                .read_exact(&mut pointer_size)
86                .context(ReadPointerSizeSnafu)?;
87            u32::from_le_bytes(pointer_size)
88        };
89
90        // Read address count: next 4bytes(usually 0x28..=0x2b bytes nth)
91        let address_count = {
92            let mut address_count = [0_u8; 4];
93            reader
94                .read_exact(&mut address_count)
95                .context(ReadAddressCountSnafu)?;
96            u32::from_le_bytes(address_count)
97        };
98
99        Ok(Self {
100            version,
101            address_count,
102            pointer_size,
103        })
104    }
105
106    /// Returns the number of addresses in the address library.
107    pub const fn address_count(&self) -> usize {
108        self.address_count as usize
109    }
110
111    /// Returns the pointer size in bytes, typically 8 bytes for 64-bit systems.
112    pub const fn pointer_size(&self) -> u64 {
113        self.pointer_size as u64
114    }
115}
116
117/// Type of error that occurs when reading the AddressLibrary header.
118#[derive(Debug, snafu::Snafu)] // Derive Snafu error enum
119pub enum HeaderError {
120    /// Failed to read format version
121    #[snafu(display("Failed to read format version: {}", source))]
122    ReadFormatVersion { source: std::io::Error },
123
124    /// Expected address library format {expected}, but got {actual_format}
125    UnexpectedFormat { expected: u8, actual_format: i32 },
126
127    #[snafu(display("Failed to read version: {}", source))]
128    ReadVersion { source: std::io::Error },
129
130    /// Failed to read name length
131    #[snafu(display("Failed to read name length: {}", source))]
132    ReadNameLength { source: std::io::Error },
133
134    /// Failed to seek after name length
135    #[snafu(display("Failed to seek after name length: {}", source))]
136    SeekAfterNameLength { source: std::io::Error },
137
138    /// Failed to read pointer size
139    #[snafu(display("Failed to read pointer size: {}", source))]
140    ReadPointerSize { source: std::io::Error },
141
142    /// Failed to read address count
143    #[snafu(display("Failed to read address count: {}", source))]
144    ReadAddressCount { source: std::io::Error },
145}
146
147// io::Error doesn't have `Clone`. Therefore, implement manually.
148impl Clone for HeaderError {
149    fn clone(&self) -> Self {
150        match self {
151            Self::ReadFormatVersion { source } => Self::ReadFormatVersion {
152                source: std::io::Error::new(source.kind(), source.to_string()),
153            },
154            Self::UnexpectedFormat {
155                expected,
156                actual_format,
157            } => Self::UnexpectedFormat {
158                expected: *expected,
159                actual_format: *actual_format,
160            },
161            Self::ReadVersion { source } => Self::ReadVersion {
162                source: std::io::Error::new(source.kind(), source.to_string()),
163            },
164            Self::ReadNameLength { source } => Self::ReadNameLength {
165                source: std::io::Error::new(source.kind(), source.to_string()),
166            },
167            Self::SeekAfterNameLength { source } => Self::SeekAfterNameLength {
168                source: std::io::Error::new(source.kind(), source.to_string()),
169            },
170            Self::ReadPointerSize { source } => Self::ReadPointerSize {
171                source: std::io::Error::new(source.kind(), source.to_string()),
172            },
173            Self::ReadAddressCount { source } => Self::ReadAddressCount {
174                source: std::io::Error::new(source.kind(), source.to_string()),
175            },
176        }
177    }
178}
179
180// Helper functions for version parsing
181const fn u8_to_le_u32_array(input: [u8; 16]) -> [u32; 4] {
182    [
183        u32::from_le_bytes([input[0], input[1], input[2], input[3]]),
184        u32::from_le_bytes([input[4], input[5], input[6], input[7]]),
185        u32::from_le_bytes([input[8], input[9], input[10], input[11]]),
186        u32::from_le_bytes([input[12], input[13], input[14], input[15]]),
187    ]
188}
189
190const fn u32_to_u16_array(input: [u32; 4]) -> [u16; 4] {
191    [
192        input[0] as u16,
193        input[1] as u16,
194        input[2] as u16,
195        input[3] as u16,
196    ]
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use std::io::Cursor;
203    /// use commonlibsse_ng::rel::id::Header;
204    /// use commonlibsse_ng::rel::version::Version;
205
206    #[test]
207    fn test_parse_header() {
208        #[rustfmt::skip]
209        let binary_data: &[u8] = &[
210            0x01, 0x00, 0x00, 0x00, // 00000000:  u32_le: Format version => 0x00000001
211
212            // Skyrim version(1.5.97.0)
213            0x01, 0x00, 0x00, 0x00, // 00000004:  u32_le: Major -> 1
214            0x05, 0x00, 0x00, 0x00, // 00000008:  u32_le: Minor -> 5
215            0x61, 0x00, 0x00, 0x00, // 0000000C:  u32_le: Patch -> 97
216            0x00, 0x00, 0x00, 0x00, // 00000010:  u32_le: build -> 0
217
218            0x0C, 0x00, 0x00, 0x00, // 00000014:  u32_le: name length -> 0xc -> 12bytes
219
220            // The string "SkyrimSE.exe"(12bytes len) is being read here in ASCII
221            0x53, 0x6B, 0x79, 0x72, // 00000018:  The string (0x53 = 'S', 0x6B = 'k', 0x79 = 'y', 0x72 = 'r')
222            0x69, 0x6D, 0x53, 0x45, // 0000001C:  The string  (0x69 = 'i', 0x6D = 'm', 0x53 = 'S', 0x45 = 'E')
223            0x2E, 0x65, 0x78, 0x65, // 00000020:  The string ".exe" (0x2E = '.', 0x65 = 'e', etc.)
224
225            0x08, 0x00, 0x00, 0x00, // 00000024:  u32_le: The pointer size (this should be the pointer size, which is 8 bytes in this case)
226
227            0xB2, 0xE1, 0x0B, 0x00, // 00000028:  u32_le: Address count (0xbe1b2 -> 778_674)
228        ];
229
230        let mut cursor = Cursor::new(binary_data);
231        let header = Header::from_reader(&mut cursor, 1).expect("Failed to read header");
232        assert_eq!(header.version, Version::new(1, 5, 97, 0));
233        assert_eq!(header.pointer_size(), 8);
234        assert_eq!(header.address_count(), 778674);
235    }
236}