commonlibsse_ng\re\t/
TESForm.rs

1mod flags;
2mod vtable;
3
4pub use self::flags::{InGameFormFlag, RecordFlag};
5pub use self::vtable::TESFormVtbl;
6
7use crate::re::BSAtomic::BSReadWriteLock;
8use crate::re::BSCoreTypes::FormID;
9use crate::re::BSFixedString::BSFixedString;
10use crate::re::BSTArray::BSStaticArray;
11use crate::re::BSTHashMap::BSTHashMap;
12use crate::re::BaseFormComponent::BaseFormComponent;
13use crate::re::FormTypes::{FormType, FormType_CEnum};
14use crate::re::TESFile::TESFile;
15use crate::re::offsets_rtti::RTTI_TESForm;
16use crate::re::offsets_vtable::VTABLE_TESForm;
17use crate::rel::ResolvableAddress as _;
18use crate::rel::id::RelocationID;
19use crate::rel::id::{DataBaseError, VariantID};
20use core::ffi::CStr;
21use core::ptr::NonNull;
22use std::sync::LazyLock;
23
24#[repr(C)]
25#[derive(Debug, PartialEq)]
26struct TESFileArray {
27    _base: BSStaticArray<TESFile>,
28}
29const_assert_eq!(core::mem::size_of::<TESFileArray>(), 0x10);
30
31#[repr(C)]
32#[derive(Debug, PartialEq, Eq)]
33pub struct TESFileContainer {
34    array: *mut TESFileArray,
35}
36const_assert_eq!(core::mem::size_of::<TESFileContainer>(), 0x8);
37
38#[repr(C)]
39#[derive(Debug, PartialEq, Eq)]
40pub struct TESForm {
41    pub __base: BaseFormComponent,
42    pub sourceFiles: TESFileContainer,
43    pub formFlags: u32,
44    pub formID: FormID,
45    pub inGameFormFlags: InGameFormFlag,
46    pub formType: FormType_CEnum,
47    pub pad1B: u8,
48    pub pad1C: u32,
49}
50const_assert_eq!(core::mem::size_of::<TESForm>(), 0x20);
51
52struct RawFormMapLock<K>
53where
54    K: core::hash::Hash + Eq,
55{
56    map_ptr: NonNull<*mut BSTHashMap<K, Option<NonNull<TESForm>>>>,
57    lock_ptr: NonNull<BSReadWriteLock>,
58}
59
60impl<K> RawFormMapLock<K>
61where
62    K: core::hash::Hash + Eq,
63{
64    #[inline]
65    pub fn get(&self, key: &K) -> Option<NonNull<TESForm>> {
66        let _guard = unsafe { self.lock_ptr.as_ref().read() };
67
68        let map = unsafe { self.map_ptr.as_ref().as_ref()? };
69        match map.get(key) {
70            Some(form) => *form,
71            None => None,
72        }
73    }
74
75    #[inline]
76    pub fn get_mut(&mut self, key: &K) -> Option<NonNull<TESForm>> {
77        let _guard = unsafe { self.lock_ptr.as_mut().write() };
78
79        let map = unsafe { self.map_ptr.as_mut().as_mut()? };
80        match map.get_mut(key) {
81            Some(form) => *form,
82            None => None,
83        }
84    }
85}
86
87unsafe impl<K> Send for RawFormMapLock<K> where K: core::hash::Hash + Eq {}
88unsafe impl<K> Sync for RawFormMapLock<K> where K: core::hash::Hash + Eq {}
89
90/// Pointer of `BSTHashMap<FormID, *mut TESForm>` & Pointer of `BSReadWriteLock`
91pub struct FormIDMap(RawFormMapLock<FormID>);
92impl FormIDMap {
93    #[inline]
94    pub fn get(&self, form_id: FormID) -> Option<NonNull<TESForm>> {
95        self.0.get(&form_id)
96    }
97
98    #[inline]
99    pub fn get_mut(&mut self, form_id: FormID) -> Option<NonNull<TESForm>> {
100        self.0.get_mut(&form_id)
101    }
102}
103
104/// Pointer of `BSTHashMap<BSFixedString, *mut TESForm>` & Pointer of `BSReadWriteLock`
105pub struct EditorIDMap(RawFormMapLock<BSFixedString>);
106impl EditorIDMap {
107    #[inline]
108    pub fn get(&self, editor_id: &CStr) -> Option<NonNull<TESForm>> {
109        self.0.get(&editor_id.into())
110    }
111
112    #[inline]
113    pub fn get_mut(&mut self, editor_id: &CStr) -> Option<NonNull<TESForm>> {
114        self.0.get_mut(&editor_id.into())
115    }
116}
117
118impl TESForm {
119    pub const RTTI: VariantID = RTTI_TESForm;
120    pub const VTABLE: [VariantID; 1] = VTABLE_TESForm;
121    pub const FORM_TYPE: FormType = FormType::None;
122
123    #[inline]
124    pub const fn vtable(&self) -> &TESFormVtbl {
125        debug_assert!(!self.__base.vtable.is_null(), "TESFormVtbl ptr must not be null ptr");
126        unsafe { &*self.__base.vtable.cast() }
127    }
128
129    #[inline]
130    pub fn vtable_from_addr(&self) -> Option<&'static TESFormVtbl> {
131        Some(unsafe { Self::VTABLE[0].address().ok()?.cast().as_ref() })
132    }
133
134    #[commonlibsse_ng_derive_internal::relocate_fn(se_id = 14509, ae_id = 14667)]
135    pub unsafe fn add_compile_index(id: FormID, file: TESFile) {}
136
137    pub fn get_all_forms() -> &'static Result<FormIDMap, DataBaseError> {
138        static PTR_LOCK: LazyLock<Result<FormIDMap, DataBaseError>> = LazyLock::new(|| {
139            let map_ptr = RelocationID::from_se_ae_id(514351, 400507).address()?.cast();
140            let lock_ptr = RelocationID::from_se_ae_id(514360, 400517).address()?.cast();
141            Ok(FormIDMap(RawFormMapLock { map_ptr, lock_ptr }))
142        });
143        &PTR_LOCK
144    }
145
146    pub fn get_all_forms_by_editor_id() -> &'static Result<EditorIDMap, DataBaseError> {
147        static PTR_LOCK: LazyLock<Result<EditorIDMap, DataBaseError>> = LazyLock::new(|| {
148            Ok(EditorIDMap(RawFormMapLock {
149                map_ptr: RelocationID::from_se_ae_id(514352, 400509).address()?.cast(),
150                lock_ptr: RelocationID::from_se_ae_id(514361, 400518).address()?.cast(),
151            }))
152        });
153        &PTR_LOCK
154    }
155
156    /// Search for TESForm based on FormID.
157    #[inline]
158    pub fn lookup_by_id(form_id: FormID) -> Option<NonNull<Self>> {
159        let all_form_map = Self::get_all_forms().as_ref().ok()?;
160        all_form_map.get(form_id)
161    }
162
163    /// Search for TESForm based on editor ID.
164    #[inline]
165    pub fn lookup_by_editor_id(editor_id: &CStr) -> Option<NonNull<Self>> {
166        let all_form_map = Self::get_all_forms_by_editor_id().as_ref().ok()?;
167        all_form_map.get(editor_id)
168    }
169
170    // #[deprecated = "The NG branch implementation of VR typecasts `TESForm` to `TESFullName`, which is invalid because there is no inheritance relationship."]
171    // #[inline]
172    // #[allow(clippy::missing_const_for_fn)]
173    // pub fn get_name(&self) -> Option<&CStr> {
174    //     // let fullname = unsafe { (self as *const Self).cast::<TESFullName>().as_ref() }?; // <- Invalid cast
175    //     // Some(fullname.fullName.as_c_str())
176    // }
177
178    #[commonlibsse_ng_derive_internal::relocate_fn(se_id = 14809, ae_id = 14988)]
179    pub fn get_weight(&self) -> f32 {}
180
181    /// Dummy yet.
182    #[allow(clippy::missing_const_for_fn)]
183    pub fn is_deleted(&self) -> bool {
184        false
185    }
186
187    #[commonlibsse_ng_derive_internal::relocate_fn(se_id = 14482, ae_id = 14639)]
188    pub fn set_player_knowns(&self, known: bool) {}
189}
190
191pub trait DerivedTESForm {
192    const FORM_TYPE: FormType;
193
194    fn get_form(&self) -> &TESForm;
195}
196
197impl DerivedTESForm for TESForm {
198    const FORM_TYPE: FormType = FormType::None;
199
200    #[inline]
201    fn get_form(&self) -> &TESForm {
202        self
203    }
204}