commonlibsse_ng\rel\id\id_database/
bin_loader.rs

1use std::path::Path;
2
3use crate::rel::id::Mapping;
4use crate::rel::id::id_database::header::Header;
5use crate::rel::id::id_database::unpack::unpack_file;
6use crate::rel::id::id_database::{DataBaseError, FailedUnpackFileSnafu};
7use crate::rel::version::Version;
8use shared_rwlock::SharedRwLock;
9use snafu::ResultExt as _;
10
11/// Reads, parses, and writes binary database files into memory.
12/// Then returns the written memory.
13///
14/// - `expected_fmt_ver`: Expected AddressLibrary format version. SE/VR: 1, AE: 2
15///
16/// # Errors
17/// - If the specified path does not exist.
18/// - If the version without bin file mismatches with the runtime
19/// - If parsing of the data in the bin file fails.
20/// - Failure to allocate memory for bin file storage.
21pub(super) fn load_bin_file<P>(
22    path: P,
23    version: Version,
24    expected_fmt_ver: u8,
25) -> Result<SharedRwLock<Mapping>, DataBaseError>
26where
27    P: AsRef<Path>,
28{
29    load_bin_file_inner(path.as_ref(), version, expected_fmt_ver)
30}
31
32fn load_bin_file_inner(
33    path: &Path,
34    version: Version,
35    expected_fmt_ver: u8,
36) -> Result<SharedRwLock<Mapping>, DataBaseError> {
37    use std::fs::File;
38    use std::io;
39
40    let mut reader = {
41        let file = File::open(path)
42            .map_err(|_| DataBaseError::AddressLibraryNotFound { path: path.to_path_buf() })?;
43        io::BufReader::new(file)
44    };
45
46    let header = Header::from_reader(&mut reader, expected_fmt_ver)?;
47    if header.version != version {
48        return Err(DataBaseError::VersionMismatch { expected: version, actual: header.version });
49    }
50
51    let (mem_map, is_created) = {
52        let shared_id =
53            windows::core::HSTRING::from(format!("CommonLibSSEOffsets-rs-v2-{version}"));
54        SharedRwLock::new(&shared_id, header.address_count())
55    }
56    .map_err(|err| DataBaseError::MemoryMapError { source: err })?;
57
58    if is_created {
59        let mut mem_map = mem_map.write().map_err(|_| DataBaseError::Poisoned)?;
60        unpack_file(&mut mem_map, &mut reader, header.pointer_size())
61            .context(FailedUnpackFileSnafu)?;
62    }
63
64    Ok(mem_map)
65}
66
67#[cfg(feature = "test_on_local")]
68#[cfg(test)]
69mod local_tests {
70    use core::ffi::c_void;
71    use core::num::NonZeroUsize;
72    use core::ptr::NonNull;
73
74    use super::*;
75    use crate::rel::ResolvableAddress;
76    use crate::rel::id::IDDatabase;
77    use crate::rel::module::{ModuleStateError, Runtime, get_skyrim_dir};
78    use crate::rel::version::Version;
79
80    // ---- Config ---------------------------------------------------------------
81    // const VERSION: Version = Version::new(1, 6, 1170, 0);
82    const VERSION: Version = Version::new(1, 6, 353, 0);
83    const MODULE_BASE: usize = 0x1000;
84    // ---------------------------------------------------------------------------
85
86    #[derive(Debug)]
87    struct TestRelocation {
88        db: IDDatabase,
89        current_id: u64,
90    }
91
92    impl TestRelocation {
93        const fn new(mem_map: SharedRwLock<Mapping>) -> Self {
94            Self { db: IDDatabase { mem_map }, current_id: 0 }
95        }
96
97        const fn set_id(&mut self, id: u64) {
98            self.current_id = id;
99        }
100    }
101
102    impl ResolvableAddress for TestRelocation {
103        fn offset(&self) -> Result<NonZeroUsize, DataBaseError> {
104            self.db.id_to_offset(self.current_id)
105        }
106
107        fn address(&self) -> Result<NonNull<c_void>, DataBaseError> {
108            let offset = self.offset()?;
109            Ok(unsafe { Self::base()?.byte_add(offset.get()) })
110        }
111
112        // Set dummy module base(SkyrimSE.exe address)
113        fn base() -> Result<NonNull<c_void>, ModuleStateError> {
114            NonNull::new(core::ptr::without_provenance_mut(MODULE_BASE))
115                .ok_or(ModuleStateError::ModuleLockIsPoisoned)
116        }
117    }
118
119    #[allow(unused)]
120    fn write_debug_value(value: impl core::fmt::Debug) -> std::io::Result<()> {
121        const TARGET: &str = env!("CARGO_MANIFEST_DIR");
122        std::fs::write(format!("{TARGET}/address_dump.log"), format!("{value:#?}"))
123    }
124
125    // REQUIREMENT: We need the version of AddressLibrary specified in Skyrim's Data on Steam.
126    #[test]
127    fn test_load_bin() {
128        let mut test_rel = {
129            let runtime = Runtime::from_version(&VERSION);
130            let ver_suffix = if runtime.is_ae() { "lib" } else { "" };
131            let path = get_skyrim_dir(runtime).unwrap().join(format!(
132                "Data/SKSE/Plugins/version{ver_suffix}-{}.bin",
133                VERSION.to_address_library_string(),
134            ));
135            TestRelocation::new(load_bin_file(&path, VERSION, 2).unwrap())
136        };
137
138        // write_debug_value(&test_rel).unwrap();
139        test_rel.set_id(11483);
140        assert_eq!(test_rel.offset().unwrap().get(), 0x10f7a0);
141        assert_eq!(test_rel.address().unwrap().addr().get(), MODULE_BASE + 0x10f7a0);
142    }
143}