1// C++ Original code
2// - ref: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/SKSE/Interfaces.h
3// - ref: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/SKSE/Interfaces.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 MIT
910use core::ffi::c_void;
1112use crate::re::BSCoreTypes::{FormID, VMHandle};
13use crate::skse::api::{ApiStorageError, get_plugin_handle};
14use crate::skse::impls::stab::SKSESerializationInterface;
1516const U32_MAX: usize = u32::MAX as usize;
1718/// Interface for interacting with SKSE's serialization functions.
19///
20/// This struct provides methods for interacting with serialization operations
21/// such as setting unique IDs, handling callbacks for form deletions and loads,
22/// and reading/writing record data.
23#[derive(Debug, Clone)]
24pub struct SerializationInterface(&'static SKSESerializationInterface);
2526impl SerializationInterface {
27/// The version number of the serialization interface.
28pub const VERSION: u32 = 4;
2930/// Creates a new `SerializationInterface` instance.
31#[inline]
32pub(crate) const fn new(interface: &'static SKSESerializationInterface) -> Self {
33Self(interface)
34 }
3536/// Returns the version of the serialization interface.
37#[inline]
38pub const fn version(&self) -> u32 {
39self.0.version
40 }
4142/// Sets a unique identifier for the serialization interface.
43 ///
44 /// # Errors
45 /// Returns an error if the internal global API storage is uninitialized
46 /// (e.g., if `skse::init` has not been called).
47#[inline]
48pub fn set_unique_id(&self, uid: u32) -> Result<(), ApiStorageError> {
49unsafe { (self.0.SetUniqueId)(get_plugin_handle()?, uid) };
50Ok(())
51 }
5253/// Sets a callback function that will be called when a form is deleted.
54 ///
55 /// # Errors
56 /// Returns an error if the internal global API storage is uninitialized
57 /// (e.g., if `skse::init` has not been called).
58#[inline]
59pub fn set_form_delete_callback(
60&self,
61 callback: fn(handle: VMHandle),
62 ) -> Result<(), ApiStorageError> {
63#[allow(clippy::fn_to_numeric_cast_any)]
64let void_f = (callback as *mut fn(handle: VMHandle)).cast();
65unsafe { ((self.0).SetFormDeleteCallback)(get_plugin_handle()?, void_f) }
6667Ok(())
68 }
6970/// Sets a callback function that will be called when the plugin is loaded.
71 ///
72 /// # Errors
73 /// Returns an error if the internal global API storage is uninitialized
74 /// (e.g., if `skse::init` has not been called).
75#[inline]
76pub fn set_load_callback(&self, callback: fn(&Self)) -> Result<(), ApiStorageError> {
77#[allow(clippy::fn_to_numeric_cast_any)]
78let void_f = (callback as *mut fn(&Self)).cast();
79unsafe { ((self.0).SetLoadCallback)(get_plugin_handle()?, void_f) }
80Ok(())
81 }
8283/// Sets a callback function that will be called when the plugin is reverted.
84 ///
85 /// # Errors
86 /// Returns an error if the internal global API storage is uninitialized
87 /// (e.g., if `skse::init` has not been called).
88#[inline]
89pub fn set_revert_callback(&self, callback: fn(&Self)) -> Result<(), ApiStorageError> {
90#[allow(clippy::fn_to_numeric_cast_any)]
91let void_f = (callback as *mut fn(&Self)).cast();
92unsafe { ((self.0).SetRevertCallback)(get_plugin_handle()?, void_f) }
93Ok(())
94 }
9596/// Sets a callback function that will be called when the plugin is saved.
97 ///
98 /// # Errors
99 /// Returns an error if the internal global API storage is uninitialized
100 /// (e.g., if `skse::init` has not been called).
101#[inline]
102pub fn set_save_callback(&self, callback: fn(&Self)) -> Result<(), ApiStorageError> {
103#[allow(clippy::fn_to_numeric_cast_any)]
104let callback = (callback as *mut fn(&Self)).cast();
105unsafe { ((self.0).SetSaveCallback)(get_plugin_handle()?, callback) }
106Ok(())
107 }
108109/// Writes a record to the serialization interface.
110 ///
111 /// # Errors
112 /// Returns an error if the write operation fails or if the buffer length exceeds `u32::MAX`.
113#[inline]
114pub fn write_record<T>(&self, record_type: u32, version: u32, buf: &T) -> Result<(), Error> {
115let data_size: usize = core::mem::size_of::<T>();
116117if data_size > U32_MAX {
118return Err(Error::TooLargeWriteRecordData { actual: data_size });
119 }
120121let void_buf = (buf as *const T).cast::<c_void>();
122let result =
123unsafe { ((self.0).WriteRecord)(record_type, version, void_buf, data_size as u32) };
124125if result { Ok(()) } else { Err(Error::WriteRecordError) }
126 }
127128/// Opens a record for writing.
129 ///
130 /// # Errors
131 /// Returns an error if the open operation fails.
132#[inline]
133pub fn open_recode(&self, record_type: u32, version: u32) -> Result<(), Error> {
134if unsafe { ((self.0).OpenRecord)(record_type, version) } {
135Ok(())
136 } else {
137Err(Error::OpenRecordError)
138 }
139 }
140141/// Writes record data to the serialization interface.
142 ///
143 /// # Errors
144 /// Returns an error if the write operation fails or if the buffer length exceeds `u32::MAX`.
145#[inline]
146pub fn write_record_data<T>(&self, buf: &[T]) -> Result<(), Error> {
147let buf_len = buf.len();
148149match buf_len {
1500 => Ok(()),
1511..=U32_MAX => {
152let result =
153unsafe { ((self.0).WriteRecordData)(buf.as_ptr().cast(), buf_len as u32) };
154if result { Ok(()) } else { Err(Error::WriteRecordDataError) }
155 }
156 too_large_size => Err(Error::TooLargeWriteRecordData { actual: too_large_size }),
157 }
158 }
159160/// Reads record data into the provided buffer.
161 ///
162 /// # Returns
163 /// The number of bytes read.
164#[inline]
165pub fn read_record_data<T>(&self, buf: &mut [T]) -> u32 {
166unsafe { (self.0.ReadRecordData)(buf.as_mut_ptr().cast(), buf.len() as u32) }
167 }
168169/// Retrieves the next record's type, version, and length.
170 ///
171 /// # Errors
172 /// Returns an error if the operation fails.
173#[inline]
174pub fn get_next_record_info(
175&self,
176 record_type: &mut u32,
177 version: &mut u32,
178 length: &mut u32,
179 ) -> Result<(), Error> {
180unsafe {
181let result = (self.0.GetNextRecordInfo)(record_type, version, length);
182if result { Ok(()) } else { Err(Error::GetNextRecordInfoError) }
183 }
184 }
185186/// Resolves the new form ID based on an old form ID.
187 ///
188 /// # Errors
189 /// Returns an error if the form ID resolution fails.
190#[inline]
191pub fn resolve_form_id(&self, old: FormID, new: &mut FormID) -> Result<(), Error> {
192unsafe {
193let result = (self.0.ResolveFormId)(old.get(), &mut new.get());
194if result { Ok(()) } else { Err(Error::ResolveFormIdError) }
195 }
196 }
197198/// Resolves the new handle based on an old handle.
199 ///
200 /// # Errors
201 /// Returns an error if the handle resolution fails.
202#[inline]
203pub fn resolve_handle(&self, old: VMHandle, new: &mut VMHandle) -> Result<(), Error> {
204let result = unsafe { (self.0.ResolveHandle)(old.get(), &mut new.get()) };
205if result { Ok(()) } else { Err(Error::ResolveHandleError) }
206 }
207}
208209/// Custom error type for serialization-related failures.
210#[derive(Debug, snafu::Snafu)]
211pub enum Error {
212/// Failed to write record.
213WriteRecordError,
214215/// Failed to write record data.
216WriteRecordDataError,
217218/// The buffer size exceeds the maximum allowed (`u32::MAX`).
219TooLargeWriteRecordData { actual: usize },
220221/// Failed to open record.
222OpenRecordError,
223224/// Failed to get next record info.
225GetNextRecordInfoError,
226227/// Failed to resolve form ID.
228ResolveFormIdError,
229230/// Failed to resolve handle.
231ResolveHandleError,
232}