commonlibsse_ng\skse\interfaces/
messaging.rs

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
9
10//! Rust bindings for the SKSE Messaging Interface
11//!
12//! This module provides Rust representations of the SKSE messaging system, which allows plugins to communicate with each other.
13//! It includes message types, dispatchers, and the main `MessagingInterface` wrapper.
14//!
15//! # References
16//! - [Original C++ Code](https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/SKSE/Interfaces.h)
17//! - [C++ Implementation](https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/SKSE/Interfaces.cpp)
18use crate::skse::{
19    api::{ApiStorageError, get_plugin_handle},
20    impls::stab::SKSEMessagingInterface,
21};
22use std::{
23    borrow::Cow,
24    ffi::{CStr, c_char, c_void},
25};
26
27/// Represents the different types of messages that can be sent or received through SKSE's messaging system.
28#[commonlibsse_ng_derive_internal::ffi_enum]
29#[repr(u32)]
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub enum MessageType {
32    /// Fired after all plugins are loaded.
33    PostLoad,
34    /// Fired after all `PostLoad` events have completed.
35    PostPostLoad,
36    /// Fired before loading a game save.
37    PreLoadGame,
38    /// Fired after loading a game save.
39    PostLoadGame,
40    /// Fired before saving a game.
41    SaveGame,
42    /// Fired before deleting a game save.
43    DeleteGame,
44    /// Fired when the input system is loaded.
45    InputLoaded,
46    /// Fired when starting a new game.
47    NewGame,
48    /// Fired after all game data has loaded.
49    DataLoaded,
50}
51
52/// Represents the different event dispatchers that SKSE provides.
53#[commonlibsse_ng_derive_internal::ffi_enum]
54#[repr(u32)]
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub enum Dispatcher {
57    /// Dispatcher for mod events.
58    ModEvent = 0,
59    /// Dispatcher for camera events.
60    CameraEvent,
61    /// Dispatcher for crosshair events.
62    CrosshairEvent,
63    /// Dispatcher for action events.
64    ActionEvent,
65    /// Dispatcher for NiNode update events.
66    NiNodeUpdateEvent,
67}
68
69/// Represents a message sent through the SKSE messaging system.
70#[derive(Clone)]
71#[repr(C)]
72pub struct Message {
73    /// The name of the sender as a C string.
74    pub sender: *const c_char,
75    /// The type of message.
76    pub msg_type: MessageType_CEnum,
77    /// The length of the data buffer.
78    pub data_len: u32,
79    /// Pointer to the message data.
80    ///
81    /// # Note
82    /// The pointer may be invalid, even if `data_len` is non-zero.
83    /// Always use [`Message::get_valid_data`] to safely access the data.
84    pub data: *mut c_void,
85}
86
87impl Message {
88    /// Returns a valid memory slice of the message data, if accessible.
89    ///
90    /// # Safety
91    /// - This function checks if the pointer is non-null and properly aligned.
92    /// - It also ensures that the memory range is valid before returning a reference.
93    ///
94    /// # When `None` is returned
95    /// - If `data_len` is 0.
96    /// - If the data pointer is null, unaligned, or invalid.
97    pub fn get_valid_data(&self) -> Option<&[u8]> {
98        use crate::rex::win32::is_valid_range;
99
100        if self.data_len == 0 {
101            return None;
102        }
103
104        let data = self.data.cast::<u8>();
105        if data.is_null() && !data.is_aligned() {
106            return None;
107        }
108
109        let len = self.data_len as usize;
110        is_valid_range(data, len).then_some(unsafe { core::slice::from_raw_parts(data, len) })
111    }
112}
113
114// # Why does this struct need to implement Debug manually?
115// In the case of `*const c_char`, Debug is a memory address, **which is difficult to debug**.
116// Therefore, implement it manually and display the string.
117impl core::fmt::Debug for Message {
118    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119        let data: Cow<'static, str> = if self.data_len == 0 {
120            "".into()
121        } else if !self.data.is_null() && self.data.cast::<u8>().is_aligned() {
122            if crate::rex::win32::is_valid_range(self.data.cast::<u8>(), self.data_len as usize) {
123                String::from_utf8_lossy(unsafe {
124                    core::slice::from_raw_parts(self.data.cast::<u8>(), self.data_len as usize)
125                })
126            } else {
127                "inaccessible ptr".into()
128            }
129        } else {
130            "null/unaligned ptr".into()
131        };
132
133        f.debug_struct("Message")
134            .field("sender", &unsafe { CStr::from_ptr(self.sender) })
135            .field("msg_type", &self.msg_type)
136            .field("data_len", &self.data_len)
137            .field("data_ptr", &self.data)
138            .field("data", &data)
139            .finish()
140    }
141}
142
143/// APIs that enable data to be sent and received between plugins.
144#[derive(Debug, Clone)]
145#[repr(transparent)]
146pub struct MessagingInterface(&'static SKSEMessagingInterface);
147
148impl MessagingInterface {
149    /// The version number of the messaging interface.
150    pub const VERSION: u32 = 2;
151
152    /// Creates a new `MessagingInterface` instance from the raw SKSE interface.
153    #[inline]
154    pub(crate) const fn new(interface: &'static SKSEMessagingInterface) -> Self {
155        Self(interface)
156    }
157
158    /// Returns the version number of the messaging interface.
159    #[inline]
160    pub const fn version(&self) -> u32 {
161        self.0.interfaceVersion
162    }
163
164    /// Dispatches a message to SKSE listeners.
165    ///
166    /// # Errors
167    /// If the internal global API storage is uninitialized because forgot to call `skse::init`
168    #[inline]
169    pub fn dispatch<T>(
170        &self,
171        message_type: MessageType,
172        data: &mut T,
173        data_len: u32,
174        receiver: Option<&CStr>,
175    ) -> Result<(), MessagingError> {
176        unsafe { self.dispatch_raw(message_type, (data as *mut T).cast(), data_len, receiver) }
177    }
178
179    /// Dispatches a message to SKSE listeners.
180    ///
181    /// # Errors
182    /// If the internal global API storage is uninitialized because forgot to call `skse::init`
183    ///
184    /// # Safety
185    /// If the reference to the pointer pointing to data is valid.
186    pub unsafe fn dispatch_raw(
187        &self,
188        message_type: MessageType,
189        data: *mut c_void,
190        data_len: u32,
191        receiver: Option<&CStr>,
192    ) -> Result<(), MessagingError> {
193        let result = unsafe {
194            (self.0.Dispatch)(
195                get_plugin_handle()?,
196                message_type as u32,
197                data,
198                data_len,
199                receiver.map_or(core::ptr::null_mut(), |cstr| cstr.as_ptr()),
200            )
201        };
202        if !result {
203            return Err(MessagingError::DispatchFailed {
204                message_type,
205                receiver: receiver
206                    .map_or("all listeners", |receiver| receiver.to_str().unwrap_or_default())
207                    .to_string(),
208            });
209        }
210
211        Ok(())
212    }
213
214    /// Gets the event dispatcher for a specific dispatcher id.
215    #[inline]
216    pub fn get_event_dispatcher(&self, dispatcher_id: Dispatcher) -> *mut c_void {
217        unsafe { (self.0.GetEventDispatcher)(dispatcher_id as u32) }
218    }
219
220    /// Registers a listener for SKSE's in-game events (e.g., loading saves).
221    ///
222    /// # Errors
223    /// If the internal global API storage is uninitialized because forgot to call `skse::init`
224    ///
225    /// # Event Data
226    /// - `PreLoadGame`:  The name of the save data
227    /// - `PostLoadGame`: Invalid ptr(data length 1)
228    ///
229    /// # Example
230    ///
231    /// ```rust
232    /// if let Ok(messaging) = commonlibsse_ng::skse::api::get_messaging_interface() {
233    ///     messaging.register_skse_listener(|message| {
234    ///         #[cfg(feature = "tracing")]
235    ///         tracing::info!("SKSE event: {message:#?}");
236    ///     });
237    /// }
238    /// ```
239    #[inline]
240    pub fn register_skse_listener(&self, f: fn(msg: &Message)) -> Result<(), MessagingError> {
241        self.register_listener(c"SKSE", f)
242    }
243
244    /// Registers a listener for a specific plugin's in-game events.
245    ///
246    /// # Errors
247    /// If the internal global API storage is uninitialized because forgot to call `skse::init`
248    pub fn register_listener(
249        &self,
250        sender: &CStr,
251        f: fn(msg: &Message),
252    ) -> Result<(), MessagingError> {
253        #[allow(clippy::fn_to_numeric_cast_any)]
254        let void_callback = (f as *mut fn(msg: &Message)).cast::<c_void>();
255        let result = unsafe {
256            (self.0.RegisterListener)(get_plugin_handle()?, sender.as_ptr(), void_callback)
257        };
258
259        if !result {
260            return Err(MessagingError::RegisterListenerFailed {
261                sender_name: sender.to_string_lossy().to_string(),
262            });
263        }
264
265        Ok(())
266    }
267}
268
269#[derive(Debug, Clone, PartialEq, snafu::Snafu)]
270pub enum MessagingError {
271    /// Failed to dispatch message to {receiver}, kind: {message_type:?}
272    DispatchFailed { message_type: MessageType, receiver: String },
273
274    /// Failed to register listener for sender: {sender_name}
275    RegisterListenerFailed { sender_name: String },
276
277    #[snafu(transparent)]
278    ApiStorageError { source: ApiStorageError },
279}