1pub mod load;
7pub mod messaging;
8pub mod object;
9pub mod papyrus;
10pub mod query;
11pub mod scaleform;
12pub mod serialization;
13pub mod task;
14pub mod trampoline;
15
16use crate::{rel::version::Version, rex::kernel32::get_current_module};
17
18#[repr(C)]
19#[derive(Debug)]
20pub struct PluginVersionData {
21 pub data_version: u32,
22 pub plugin_version: u32,
23 pub plugin_name: [u8; 256],
24 pub author: [u8; 256],
25 pub support_email: [u8; 252],
26 pub version_independence_ex: u32,
27 pub version_independence: u32,
28 pub compatible_versions: [u32; 16],
29 pub xse_minimum: u32,
33}
34
35const _: () = {
36 use core::mem::offset_of;
37
38 assert!(offset_of!(PluginVersionData, data_version) == 0x000);
39 assert!(offset_of!(PluginVersionData, plugin_version) == 0x004);
40 assert!(offset_of!(PluginVersionData, plugin_name) == 0x008);
41 assert!(offset_of!(PluginVersionData, author) == 0x108);
42 assert!(offset_of!(PluginVersionData, support_email) == 0x208);
43 assert!(offset_of!(PluginVersionData, version_independence_ex) == 0x304);
44 assert!(offset_of!(PluginVersionData, version_independence) == 0x308);
45 assert!(offset_of!(PluginVersionData, compatible_versions) == 0x30C);
46 assert!(offset_of!(PluginVersionData, xse_minimum) == 0x34C);
47 assert!(size_of::<PluginVersionData>() == 0x350);
48};
49
50impl PluginVersionData {
51 pub const VERSION: u32 = 1;
52
53 pub const VERSION_INDEPENDENT_ADDRESS_LIBRARY_POST_AE: u32 = 1;
54 pub const VERSION_INDEPENDENT_SIGNATURES: u32 = 1 << 1;
55 pub const VERSION_INDEPENDENT_STRUCTS_POST_629: u32 = 1 << 2;
56
57 pub const VERSION_INDEPENDENT_EX_NO_STRUCT_USE: u32 = 1;
58
59 pub const fn set_plugin_version(&mut self, version: u32) {
60 self.plugin_version = version;
61 }
62
63 pub const fn get_plugin_version(&self) -> u32 {
64 self.plugin_version
65 }
66
67 pub fn set_plugin_name(&mut self, name: &str) {
68 Self::set_char_buffer(name, &mut self.plugin_name);
69 }
70
71 pub fn get_plugin_name(&self) -> &str {
72 Self::get_char_buffer(&self.plugin_name)
73 }
74
75 pub fn set_author_name(&mut self, name: &str) {
76 Self::set_char_buffer(name, &mut self.author);
77 }
78
79 pub fn get_author_name(&self) -> &str {
80 Self::get_char_buffer(&self.author)
81 }
82
83 pub fn set_author_email(&mut self, email: &str) {
84 Self::set_char_buffer(email, &mut self.support_email);
85 }
86
87 pub fn get_author_email(&self) -> &str {
88 Self::get_char_buffer(&self.support_email)
89 }
90
91 fn set_char_buffer(input: &str, buffer: &mut [u8]) {
92 let bytes = input.as_bytes();
93 let len = bytes.len().min(buffer.len() - 1);
94 buffer[..len].copy_from_slice(&bytes[..len]);
95 buffer[len] = 0;
96 }
97
98 fn get_char_buffer(buffer: &[u8]) -> &str {
99 let end = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
100 core::str::from_utf8(&buffer[..end]).unwrap_or("")
101 }
102
103 pub fn get_singleton() -> Option<&'static Self> {
104 use windows::Win32::System::LibraryLoader::GetProcAddress;
105 use windows::core::s;
106
107 let f = unsafe { GetProcAddress(get_current_module(), s!("SKSEPlugin_Version")) };
108 #[allow(clippy::fn_to_numeric_cast_any)]
109 f.map(|f| unsafe { &*(f as *const Self) })
110 }
111}
112
113#[repr(u32)]
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum StructCompatibility {
116 Dependent = 0,
117 Independent = 1,
118}
119
120#[repr(transparent)]
121#[derive(Debug, Clone, Copy)]
122pub struct VersionNumber {
123 packed: u32,
124}
125
126impl VersionNumber {
127 #[inline]
128 pub const fn new(major: u16, minor: u16, patch: u16, build: u16) -> Self {
129 Self {
130 packed: ((major as u32) << 24)
131 | ((minor as u32) << 16)
132 | ((patch as u32) << 8)
133 | (build as u32),
134 }
135 }
136
137 #[inline]
138 pub const fn default_const() -> Self {
139 Self::new(0, 0, 0, 0)
140 }
141
142 #[inline]
143 pub const fn from_version(version: Version) -> Self {
144 Self { packed: version.pack() }
145 }
146
147 #[inline]
148 pub const fn from_packed(packed: u32) -> Self {
149 Self { packed }
150 }
151
152 #[inline]
153 pub const fn to_packed(self) -> u32 {
154 self.packed
155 }
156}
157
158pub const fn to_fixed_str<const N: usize>(s: &str) -> [u8; N] {
159 let bytes = s.as_bytes();
160 let bytes_len = bytes.len();
161
162 assert!(bytes_len < N, "The length of the input string is too large for the specified size.");
163
164 let mut buf = [0_u8; N];
165
166 let mut i = 0;
167 while i < bytes_len {
168 let b = bytes[i];
169 assert!(b != 0, "The input string contains a null byte.");
170 assert!(b.is_ascii(), "The input string contains non-ASCII characters.");
171 buf[i] = b;
172 i += 1;
173 }
174
175 buf
176}
177
178pub const fn new_cstr(s: &'static str) -> &'static std::ffi::CStr {
196 let mut bytes = s.as_bytes();
197 loop {
198 match bytes {
199 [0, _, ..] => panic!("C strings cannot contain null bytes"),
200 [] => panic!("C strings must be null-terminated"),
201 [0] => break,
202 [_, remaining @ ..] => bytes = remaining,
203 }
204 }
205
206 unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(s.as_bytes()) }
208}
209
210#[repr(transparent)]
211#[derive(Debug, Clone)]
212pub struct String256([u8; 256]);
213
214impl String256 {
215 pub const fn new(s: &str) -> Self {
220 Self(to_fixed_str(s))
221 }
222
223 pub const fn default_const() -> Self {
225 Self([0; 256])
226 }
227}
228
229#[repr(transparent)]
230#[derive(Debug, Clone)]
231pub struct String252([u8; 252]);
232
233impl String252 {
234 pub const fn new(s: &str) -> Self {
239 Self(to_fixed_str(s))
240 }
241
242 pub const fn default_const() -> Self {
244 Self([0; 252])
245 }
246}
247
248#[repr(C)]
249#[derive(Debug, Clone)]
250pub struct RuntimeCompatibility {
251 pub address_library: bool,
253 pub signature_scanning: bool,
254 pub structs_post_629: bool,
255
256 pub _pad0: u8,
259 pub compatible_versions: [VersionNumber; 16],
262}
263
264const _: () = {
265 assert!(core::mem::size_of::<RuntimeCompatibility>() == 0x44);
266};
267
268impl RuntimeCompatibility {
269 pub const fn new() -> Self {
270 Self {
271 address_library: true,
272 signature_scanning: false,
273 structs_post_629: false,
274 _pad0: 0,
275 compatible_versions: [VersionNumber::new(0, 0, 0, 0); 16],
278 }
279 }
280}
281
282impl Default for RuntimeCompatibility {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288#[repr(C)]
289#[derive(Debug, Clone)]
290pub struct PluginDeclarationInfo {
291 pub version: VersionNumber,
293 pub name: String256,
295 pub author: String256,
297 pub support_email: String252,
299 pub struct_compatibility: StructCompatibility,
308 pub runtime_compatibility: RuntimeCompatibility,
314 pub minimum_skse_version: VersionNumber,
316}
317
318const _: () = {
319 use std::mem::offset_of;
320
321 assert!(0x000 == offset_of!(PluginDeclarationInfo, version));
322 assert!(0x004 == offset_of!(PluginDeclarationInfo, name));
323 assert!(0x104 == offset_of!(PluginDeclarationInfo, author));
324 assert!(0x204 == offset_of!(PluginDeclarationInfo, support_email));
325 assert!(0x300 == offset_of!(PluginDeclarationInfo, struct_compatibility));
326 assert!(0x304 == offset_of!(PluginDeclarationInfo, runtime_compatibility));
327 assert!(0x348 == offset_of!(PluginDeclarationInfo, minimum_skse_version));
328};
329
330#[repr(C)]
332#[derive(Debug, Clone)]
333pub struct PluginDeclaration {
334 pub data_version: u32,
335 pub data: PluginDeclarationInfo,
336}
337const _: () = assert!(0x350 == core::mem::size_of::<PluginDeclaration>());
338
339impl PluginDeclaration {
340 pub fn get_singleton() -> Option<&'static mut Self> {
341 use windows::Win32::System::LibraryLoader::GetProcAddress;
342 use windows::core::s;
343
344 let f = unsafe { GetProcAddress(get_current_module(), s!("SKSEPlugin_Version")) };
345
346 #[allow(clippy::fn_to_numeric_cast_any)]
347 f.map(|f| unsafe { &mut *(f as *mut Self) })
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use core::mem;
355
356 #[test]
357 fn test_plugin_version_data_matches_plugin_declaration_info() {
358 const PLUGIN_VERSION_DATA: PluginVersionData = PluginVersionData {
359 data_version: 1,
360 plugin_version: 2,
361 plugin_name: [0; 256],
362 author: [0; 256],
363 support_email: [0; 252],
364 version_independence_ex: 1,
365 version_independence: 1,
366 compatible_versions: [0; 16],
367 xse_minimum: 0,
368 };
369
370 const PLUGIN_DECLARATION: PluginDeclaration = PluginDeclaration {
371 data_version: 1,
372 data: PluginDeclarationInfo {
373 version: VersionNumber::new(0, 0, 0, 2),
374 name: String256::default_const(),
375 author: String256::default_const(),
376 support_email: String252::default_const(),
377 struct_compatibility: StructCompatibility::Independent,
378 runtime_compatibility: RuntimeCompatibility {
379 address_library: true,
380 signature_scanning: false,
381 structs_post_629: false,
382 _pad0: 0,
383 compatible_versions: [VersionNumber::default_const(); 16],
384 },
385 minimum_skse_version: VersionNumber::default_const(),
386 },
387 };
388
389 const PLUGIN_VERSION_BYTES: [u8; 848] = unsafe { mem::transmute(PLUGIN_VERSION_DATA) };
391 const PLUGIN_DECLARATION_BYTES: [u8; 848] = unsafe { mem::transmute(PLUGIN_DECLARATION) };
392
393 pretty_assertions::assert_eq!(PLUGIN_VERSION_BYTES, PLUGIN_DECLARATION_BYTES); }
395}