from_singleton/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    any,
5    borrow::Cow,
6    ptr::{self, NonNull},
7    sync::{LazyLock, OnceLock},
8};
9
10use find::{FD4SingletonMap, FD4SingletonPartialResult};
11use fxhash::FxHashMap;
12use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
13
14pub mod find;
15
16/// Assigns a type a name that can be used for Dantelion2 singleton reflection.
17///
18/// The default implementation trims the type name down to its base, but it
19/// can also be overriden.
20pub trait FromSingleton {
21    /// Returns the name to look up the singleton by.
22    #[inline]
23    fn name() -> Cow<'static, str> {
24        let type_name = any::type_name::<Self>();
25
26        let end = type_name.find('<').unwrap_or(type_name.len());
27
28        let start = type_name[..end]
29            .rfind(':')
30            .unwrap_or(usize::MAX)
31            .wrapping_add(1);
32
33        Cow::Borrowed(&type_name[start..end])
34    }
35}
36
37/// Returns a reference to the singleton map for the process, where
38/// keys are singleton names and values are pointers to the static singleton pointers.
39///
40/// This function is safe, but may not contain all singletons if it is called
41/// before Dantelion2 reflection is initialized by the process.
42pub fn map() -> &'static FxHashMap<String, NonNull<*mut u8>> {
43    if let Some(map) = ALL_SINGLETON_MAP.get() {
44        return map;
45    }
46
47    let derived = &*DERIVED_SINGLETON_MAP;
48    let partial = &*PARTIAL_SINGLETON_MAP;
49
50    if !partial.all_null() || !derived.all_null() {
51        ALL_SINGLETON_MAP.get_or_init(|| {
52            // SAFETY: if any singletons are not null initialization has surely happened
53            let mut new_map = unsafe { partial.clone().finish() };
54
55            new_map.extend(derived.iter().map(|(k, v)| (k.clone(), *v)));
56
57            new_map
58        })
59    } else {
60        derived
61    }
62}
63
64/// Returns a pointer to a singleton instance using
65/// Dantelion2 reflection. May return [`None`] if the singleton
66/// was not found.
67///
68/// This function is safe, but it may not find all singletons if it is called
69/// before Dantelion2 reflection is initialized by the process.
70///
71/// Ensure the return value is convertible to a reference before dereferencing it.
72#[inline]
73pub fn address_of<T>() -> Option<NonNull<T>>
74where
75    T: FromSingleton + Sized,
76{
77    let static_ptr = static_of::<T>()?;
78
79    // SAFETY: pointer is valid for the read as insured by `static_of`.
80    unsafe { NonNull::new(static_ptr.read()) }
81}
82
83/// Returns a pointer to the pointer to a singleton instance using
84/// Dantelion2 reflection. May return [`None`] if the singleton
85/// was not found.
86///
87/// This function is safe, but it may not find all singletons if it is called
88/// before Dantelion2 reflection is initialized by the process.
89///
90/// Ensure the return value is convertible to a reference before dereferencing it.
91#[inline]
92pub fn static_of<T>() -> Option<NonNull<*mut T>>
93where
94    T: FromSingleton + Sized,
95{
96    let name = <T as FromSingleton>::name();
97    map().get(&*name).cloned().map(NonNull::cast)
98}
99
100static DERIVED_SINGLETON_MAP: LazyLock<FD4SingletonMap> = LazyLock::new(|| unsafe {
101    let image_base = GetModuleHandleW(ptr::null());
102    assert!(!image_base.is_null(), "GetModuleHandleW failed");
103    let pe = pelite::pe::PeView::module(image_base as _);
104    find::derived_singletons(pe)
105});
106
107static PARTIAL_SINGLETON_MAP: LazyLock<FD4SingletonPartialResult> = LazyLock::new(|| unsafe {
108    let image_base = GetModuleHandleW(ptr::null());
109    assert!(!image_base.is_null(), "GetModuleHandleW failed");
110    let pe = pelite::pe::PeView::module(image_base as _);
111    find::fd4_singletons(pe)
112});
113
114static ALL_SINGLETON_MAP: OnceLock<FD4SingletonMap> = OnceLock::new();
115
116#[cfg(test)]
117mod tests {
118    use crate::FromSingleton;
119
120    mod fd4 {
121        use std::{borrow::Cow, marker::PhantomData};
122
123        use crate::FromSingleton;
124
125        pub struct FD4PadManager;
126        pub struct FD4HkEzDrawRigidBodyDispBufferManager<T>(PhantomData<T>);
127        pub struct FD4FileManager;
128
129        impl FromSingleton for FD4PadManager {}
130
131        impl<T> FromSingleton for FD4HkEzDrawRigidBodyDispBufferManager<T> {}
132
133        impl FromSingleton for FD4FileManager {
134            fn name() -> Cow<'static, str> {
135                Cow::Borrowed("CSFile")
136            }
137        }
138    }
139
140    mod cs {
141        use crate::FromSingleton;
142
143        pub struct CSFile;
144
145        impl FromSingleton for CSFile {}
146    }
147
148    impl<T> FromSingleton for Option<T> {}
149
150    #[test]
151    fn correct_names() {
152        type LongType = fd4::FD4HkEzDrawRigidBodyDispBufferManager<Option<Option<i32>>>;
153
154        assert_eq!(fd4::FD4PadManager::name(), "FD4PadManager");
155
156        assert_eq!(LongType::name(), "FD4HkEzDrawRigidBodyDispBufferManager");
157
158        assert_eq!(fd4::FD4FileManager::name(), cs::CSFile::name());
159
160        assert_eq!(Option::<Result<LongType, LongType>>::name(), "Option");
161    }
162}