commonlibsse_ng\rel\module/module_handle.rs
1// C++ Original code
2// - https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/REL/Module.h
3// - load_segments, clear: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/REL/Module.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 MI
9
10//! Module handling library for Skyrim SE/AE/VR .
11//!
12//! This module provides functionality to interact with loaded modules (executables and DLLs),
13//! extract segment information, and parse NT headers.
14
15use core::ffi::c_void;
16use core::ptr::NonNull;
17
18/// A handle that obtains and holds the address of the surviving dll/exe until the end of program execution.
19///
20/// # undefined behavior
21/// If `Self::new` specifies a dll/exe that does not live until the end of program execution
22#[repr(transparent)]
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct ModuleHandle(pub(crate) NonNull<c_void>);
25
26unsafe impl Send for ModuleHandle {}
27unsafe impl Sync for ModuleHandle {}
28
29impl Default for ModuleHandle {
30 #[inline]
31 fn default() -> Self {
32 Self::const_default()
33 }
34}
35
36impl ModuleHandle {
37 #[inline]
38 pub(crate) const fn const_default() -> Self {
39 Self(NonNull::dangling())
40 }
41
42 /// Gets the module handle of a module (exe, dll, etc.) that is being loaded by the calling process.
43 ///
44 /// # Example
45 /// ```
46 /// use commonlibsse_ng::rel::module::ModuleHandle;
47 /// use windows::core::h; // `h!` is utf-16 str macro.
48 ///
49 /// let handle = unsafe { ModuleHandle::new(h!("kernel32.dll")) };
50 /// assert!(handle.is_ok());
51 ///
52 /// // If there is no extension, a `.dll` is automatically specified.(This is the behavior of `GetModuleHandleW` function.)
53 /// let handle = unsafe { ModuleHandle::new(h!("kernel32")) };
54 /// assert!(handle.is_ok());
55 /// ```
56 ///
57 /// # Errors
58 /// - Errors if a module is specified that is not loaded by the calling process.
59 /// - If the specified module handle could not be obtained.
60 ///
61 /// # Safety
62 /// It is safe as long as specify a dll/exe that survives the `'static` life time.
63 pub unsafe fn new<H>(module_name: H) -> Result<Self, ModuleHandleError>
64 where
65 H: windows::core::Param<windows::core::PCWSTR>,
66 {
67 use snafu::ResultExt as _;
68 use windows::Win32::System::LibraryLoader::GetModuleHandleW;
69
70 // GetModuleHandleW: https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew
71 let handle =
72 unsafe { GetModuleHandleW(module_name) }.with_context(|_| HandleNotFoundSnafu)?;
73
74 // TODO: size of module(However, it incurs the overhead of a function call.
75 // let _module_size = get_module_size(handle).with_context(|_| HandleNotFoundSnafu)?;
76
77 // If it is null, it is not null because of an error in the previous Result.
78 Ok(Self(unsafe { NonNull::new_unchecked(handle.0) }))
79 }
80
81 /// Attempt to parse NT Header part.
82 ///
83 /// # Errors
84 /// When fail to parse as valid header.
85 pub const fn try_as_nt_header(
86 &self,
87 ) -> Result<&windows::Win32::System::Diagnostics::Debug::IMAGE_NT_HEADERS64, ModuleHandleError>
88 {
89 use windows::Win32::System::Diagnostics::Debug::IMAGE_NT_HEADERS64;
90 use windows::Win32::System::SystemServices::{
91 IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE,
92 };
93
94 let dos_header = self.0.cast::<IMAGE_DOS_HEADER>();
95
96 let e_lfanew_offset = {
97 let dos_header = unsafe { dos_header.as_ref() };
98 // If it is a valid exe or dll, the first two bytes are the letters `MZ`
99 // (inverted with little endian by u16 and containing 0x5a4d) from the designer's name.
100 let dos_magic = dos_header.e_magic;
101 if dos_magic != IMAGE_DOS_SIGNATURE {
102 return Err(ModuleHandleError::InvalidDosHeaderSignature { actual: dos_magic });
103 }
104
105 dos_header.e_lfanew as usize
106 };
107
108 // The nt_header exists at the position e_lfanew from the start of the dos_header, i.e., the binary data of the exe.
109 let nt_header = unsafe {
110 dos_header
111 .byte_add(e_lfanew_offset) // Be careful not to mistakenly use `.add` or `.offset`.
112 .cast::<IMAGE_NT_HEADERS64>()
113 .as_ref()
114 };
115
116 let nt_signature = nt_header.Signature;
117 if nt_signature == IMAGE_NT_SIGNATURE {
118 Ok(nt_header)
119 } else {
120 Err(ModuleHandleError::InvalidNtHeader64Signature { actual: nt_signature })
121 }
122 }
123}
124
125/// Error types for module handle operations.
126#[derive(Debug, Clone, PartialEq, Eq, snafu::Snafu)]
127pub enum ModuleHandleError {
128 /// Invalid module handle.
129 NullHandle,
130
131 /// Failed to get module handle for '{source}'
132 HandleNotFound { source: windows::core::Error },
133 /// Invalid dos header of this exe/dll. Expected `0x5a4d`, but got `{actual}`
134 InvalidDosHeaderSignature { actual: u16 },
135 /// Invalid NT header64. Expected `PE\0\0`(0x4550), but got `{actual:X}`
136 InvalidNtHeader64Signature { actual: u32 },
137}
138
139// pub fn get_module_size(handle: windows::Win32::Foundation::HMODULE) -> windows::core::Result<u32> {
140// use windows::Win32::System::ProcessStatus::GetModuleInformation;
141// use windows::Win32::System::ProcessStatus::MODULEINFO;
142// use windows::Win32::System::Threading::GetCurrentProcess;
143
144// const MODULEINFO_SIZE: u32 = core::mem::size_of::<MODULEINFO>() as u32;
145
146// let mut module_info = MODULEINFO::default();
147// unsafe {
148// GetModuleInformation(GetCurrentProcess(), handle, &mut module_info, MODULEINFO_SIZE)?;
149// }
150
151// Ok(module_info.SizeOfImage)
152// }
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use windows::core::h;
158
159 #[test]
160 fn test_module_handle_nt_header() {
161 let handle =
162 unsafe { ModuleHandle::new(h!("msvcrt.dll")).unwrap_or_else(|err| panic!("{err}")) };
163 let nt_header = handle.try_as_nt_header().unwrap_or_else(|err| panic!("{err}"));
164 assert_ne!(nt_header.Signature, 0);
165 }
166}