Skip to main content

samp_sdk/omp/
server.rs

1//! Vtables for objects managed by the Open Multiplayer server.
2//!
3//! The vtable indices and signatures were derived from the public
4//! specification of the Open Multiplayer SDK (<https://github.com/openmultiplayer/open.mp-sdk>).
5//! No SDK code was copied.
6//!
7//! Unlike `component.rs` (where WE implement the vtable), here we define
8//! vtables for objects created by the SERVER so we can call methods on
9//! them from Rust.
10//!
11//! ## Indices per ABI
12//!
13//! **Itanium ABI** — two destructor slots (D1 + D0) interleaved after the
14//! virtuals of each base class, in declaration order.
15//!
16//! **MSVC ABI** — a single destructor (scalar deleting) at the end of the
17//! virtuals of the class that declared it (e.g. `~IExtensible` at slot [4],
18//! after `removeExtension`).
19//!
20//! The practical difference is that on MSVC the vtable is 1 slot smaller per
21//! destructor (no separate D0 slot), which shifts all subsequent methods.
22
23use crate::raw::types::AMX;
24
25use super::events::PawnEventHandler;
26use super::types::UID;
27
28/// Number of AMX functions exported by `IPawnComponent` (`NUM_AMX_FUNCS` in the SDK).
29pub const NUM_AMX_FUNCS: usize = 52;
30
31/// UID of the Open Multiplayer Pawn component (`PawnComponent_UID` in the SDK).
32pub const PAWN_COMPONENT_UID: UID = 0x7890_6cd9_f19c_36a6;
33
34// ---------------------------------------------------------------------------
35// IComponentList — list of loaded components (server-owned object)
36// ---------------------------------------------------------------------------
37//
38// Inheritance: IComponentList : public IExtensible
39//
40// Primary vtable (Itanium ABI):
41//   [0-3] IExtensible (get/add/remove/remove)
42//   [4]   ~destructor D1
43//   [5]   ~destructor D0
44//   [6]   IComponentList::queryComponent(UID) -> IComponent*
45//
46// Primary vtable (MSVC ABI):
47//   [0-3] IExtensible (get/add/remove/remove)
48//   [4]   ~destructor (single scalar deleting)
49//   [5]   IComponentList::queryComponent(UID) -> IComponent*
50
51/// Opaque handle for the server's `IComponentList*`.
52#[repr(C)]
53pub struct ServerComponentList {
54    vtable: *const ServerComponentListVTable,
55}
56
57// IComponentList vtable layout — only the count of opaque destructor slots
58// differs (Itanium: D1+D0 = 2 slots; MSVC: single scalar deleting = 1 slot).
59// The calling convention also differs (Itanium "C" vs MSVC "thiscall").
60//
61// Opaque slots (in order):
62//   [0] IExtensible::getExtension
63//   [1] IExtensible::addExtension
64//   [2] IExtensible::removeExtension(ptr)
65//   [3] IExtensible::removeExtension(uid)
66//   [4] ~destructor (D1 on Itanium, scalar deleting on MSVC)
67//   [5] ~destructor D0 (Itanium-only — does not exist on MSVC)
68//
69// Useful slot:
70//   [6 Itanium / 5 MSVC] queryComponent
71#[cfg(not(target_env = "msvc"))]
72type QueryComponentFn = unsafe extern "C" fn(*mut ServerComponentList, UID) -> *mut ServerComponent;
73#[cfg(target_env = "msvc")]
74type QueryComponentFn =
75    unsafe extern "thiscall" fn(*mut ServerComponentList, UID) -> *mut ServerComponent;
76
77#[cfg(not(target_env = "msvc"))]
78const COMPONENT_LIST_PREFIX_SLOTS: usize = 6;
79#[cfg(target_env = "msvc")]
80const COMPONENT_LIST_PREFIX_SLOTS: usize = 5;
81
82#[repr(C)]
83struct ServerComponentListVTable {
84    _prefix: [*const (); COMPONENT_LIST_PREFIX_SLOTS],
85    query_component: QueryComponentFn,
86}
87
88/// Opaque handle for the `IComponent*` returned by queryComponent.
89#[repr(C)]
90pub struct ServerComponent {
91    vtable: *const (),
92}
93
94/// Queries a component by UID in the list provided by the server.
95///
96/// # Safety
97/// `list` must be a valid pointer to an Open Multiplayer server `IComponentList`.
98pub unsafe fn query_component(list: *mut ServerComponentList, uid: UID) -> *mut ServerComponent {
99    unsafe { ((*(*list).vtable).query_component)(list, uid) }
100}
101
102// ---------------------------------------------------------------------------
103// IPawnComponent — access to the PAWN/AMX subsystem (server-owned object)
104// ---------------------------------------------------------------------------
105//
106// Inheritance: IPawnComponent : public IComponent : public IExtensible, IUIDProvider
107//
108// Primary vtable (Itanium ABI) — confirmed by runtime dump (Open Multiplayer 1.5.8):
109//   [0-4]  IExtensible + server-internal slots
110//   [5]    ~PawnComponent D1
111//   [6]    ~PawnComponent D0 (deleting)
112//   [7]    componentName
113//   [8]    (unknown)
114//   [9]    componentVersion
115//   [10]   onLoad
116//   [11]   (unknown)
117//   [12]   onReady
118//   [13]   onFree
119//   [14]   (unknown)
120//   [15]   free
121//   [16]   reset
122//   [17]   (unknown)
123//   [18]   IPawnComponent::getEventDispatcher  <- confirmed at runtime
124//   [19]   IPawnComponent::getAmxFunctions     <- confirmed at runtime
125//
126// Primary vtable (MSVC ABI):
127//   [0-3]  IExtensible (get/add/remove/remove)
128//   [4]    ~destructor (single scalar deleting)
129//   [5-15] IComponent (supportedVersion..reset)
130//   [16]   IPawnComponent::getEventDispatcher
131//   [17]   IPawnComponent::getAmxFunctions
132
133/// Opaque handle for the server's `IPawnComponent*`.
134#[repr(C)]
135pub struct ServerPawnComponent {
136    vtable: *const ServerPawnComponentVTable,
137    // IUIDProvider secondary vtable — we do not access it directly
138    _uid_vtable: *const (),
139}
140
141// IPawnComponent vtable layout — useful slots ([18-19] Itanium / [16-17] MSVC)
142// and shared trailing opaques. The prefix difference comes from how each ABI
143// emits destructors (Itanium D1+D0 + unknown slots confirmed in the dump).
144//
145// Opaque prefix slots (Itanium ABI, 18 slots — confirmed by runtime dump):
146//   [0-4]   IExtensible (4 methods + 1 unknown slot)
147//   [5]     ~PawnComponent D1
148//   [6]     ~PawnComponent D0 (deleting)
149//   [7]     componentName
150//   [8]     (unknown)
151//   [9]     componentVersion
152//   [10]    onLoad
153//   [11]    (unknown)
154//   [12]    onReady
155//   [13]    onFree
156//   [14]    (unknown)
157//   [15]    free
158//   [16]    reset
159//   [17]    (unknown)
160//
161// Opaque prefix slots (MSVC ABI, 16 slots):
162//   [0-3]   IExtensible (get/add/removeExt(ptr)/removeExt(uid))
163//   [4]     ~destructor (single scalar deleting)
164//   [5-15]  IComponent (supportedVersion, componentName, componentType,
165//           componentVersion, onLoad, onInit, onReady, onFree,
166//           provideConfiguration, free, reset)
167//
168// Useful slots (in both):
169//   [18-19 Itanium / 16-17 MSVC] getEventDispatcher, getAmxFunctions
170//
171// Trailing opaques (4 slots, identical in both ABIs):
172//   getScript(const), getScript(mut), mainScript, sideScripts
173#[cfg(not(target_env = "msvc"))]
174type GetEventDispatcherFn =
175    unsafe extern "C" fn(*mut ServerPawnComponent) -> *mut IEventDispatcherPawn;
176#[cfg(target_env = "msvc")]
177type GetEventDispatcherFn =
178    unsafe extern "thiscall" fn(*mut ServerPawnComponent) -> *mut IEventDispatcherPawn;
179
180#[cfg(not(target_env = "msvc"))]
181type GetAmxFunctionsFn =
182    unsafe extern "C" fn(*const ServerPawnComponent) -> *const AmxFunctionTable;
183#[cfg(target_env = "msvc")]
184type GetAmxFunctionsFn =
185    unsafe extern "thiscall" fn(*const ServerPawnComponent) -> *const AmxFunctionTable;
186
187#[cfg(not(target_env = "msvc"))]
188const PAWN_COMPONENT_PREFIX_SLOTS: usize = 18;
189#[cfg(target_env = "msvc")]
190const PAWN_COMPONENT_PREFIX_SLOTS: usize = 16;
191
192#[repr(C)]
193struct ServerPawnComponentVTable {
194    _prefix: [*const (); PAWN_COMPONENT_PREFIX_SLOTS],
195    get_event_dispatcher: GetEventDispatcherFn,
196    get_amx_functions: GetAmxFunctionsFn,
197    // Trailing opaques common to both ABIs.
198    _get_script_const: *const (),
199    _get_script_mut: *const (),
200    _main_script: *const (),
201    _side_scripts: *const (),
202}
203
204/// Table of 52 AMX function pointers (`StaticArray<void*, NUM_AMX_FUNCS>`).
205pub type AmxFunctionTable = [*mut (); NUM_AMX_FUNCS];
206
207// ---------------------------------------------------------------------------
208// IPawnScript — opaque handle; we only use GetAMX() at index [57]
209// ---------------------------------------------------------------------------
210//
211// IPawnScript does not declare a virtual destructor.
212// Methods [0..56] are opaque; [57] is GetAMX().
213// Index 57 is identical on Itanium and MSVC (no virtual destructor = no shift).
214
215/// Opaque handle for the server's `IPawnScript*`.
216#[repr(C)]
217pub struct IPawnScript {
218    vtable: *const IPawnScriptVTable,
219}
220
221// IPawnScript does not declare a virtual destructor — identical layout on
222// Itanium and MSVC. Slot [57] is GetAMX(); only the calling convention differs.
223#[cfg(not(target_env = "msvc"))]
224type GetAmxFn = unsafe extern "C" fn(*mut IPawnScript) -> *mut AMX;
225#[cfg(target_env = "msvc")]
226type GetAmxFn = unsafe extern "thiscall" fn(*mut IPawnScript) -> *mut AMX;
227
228#[repr(C)]
229struct IPawnScriptVTable {
230    _prefix: [*const (); 57],
231    get_amx: GetAmxFn,
232}
233
234/// Extracts the `AMX*` pointer from an `IPawnScript*`.
235///
236/// # Safety
237/// `script` must be a valid pointer to an Open Multiplayer server `IPawnScript`.
238pub unsafe fn get_amx_from_script(script: *mut IPawnScript) -> *mut AMX {
239    unsafe { ((*(*script).vtable).get_amx)(script) }
240}
241
242// ---------------------------------------------------------------------------
243// IEventDispatcher<PawnEventHandler> — server-side vtable
244// ---------------------------------------------------------------------------
245//
246// IEventDispatcher<T> does not declare a virtual destructor.
247// Vtable:
248//   [0] addEventHandler(handler*, priority: i8) -> bool
249//   [1] removeEventHandler(handler*) -> bool
250//   [2] hasEventHandler (unused)
251//   [3] count (unused)
252//
253// No virtual destructor = no shift between Itanium and MSVC.
254// Only the calling convention differs.
255
256/// Opaque handle for the server's `IEventDispatcher<PawnEventHandler>*`.
257#[repr(C)]
258pub struct IEventDispatcherPawn {
259    vtable: *const IEventDispatcherPawnVTable,
260}
261
262// IEventDispatcher<T> does not declare a virtual destructor — identical layout
263// on both ABIs. Slots [0..3]: addEventHandler, removeEventHandler,
264// hasEventHandler, count. Only the first two are used; only the calling
265// convention differs.
266#[cfg(not(target_env = "msvc"))]
267type AddEventHandlerFn =
268    unsafe extern "C" fn(*mut IEventDispatcherPawn, *mut PawnEventHandler, i8) -> bool;
269#[cfg(target_env = "msvc")]
270type AddEventHandlerFn =
271    unsafe extern "thiscall" fn(*mut IEventDispatcherPawn, *mut PawnEventHandler, i8) -> bool;
272
273#[cfg(not(target_env = "msvc"))]
274type RemoveEventHandlerFn =
275    unsafe extern "C" fn(*mut IEventDispatcherPawn, *mut PawnEventHandler) -> bool;
276#[cfg(target_env = "msvc")]
277type RemoveEventHandlerFn =
278    unsafe extern "thiscall" fn(*mut IEventDispatcherPawn, *mut PawnEventHandler) -> bool;
279
280#[repr(C)]
281struct IEventDispatcherPawnVTable {
282    add_event_handler: AddEventHandlerFn,
283    remove_event_handler: RemoveEventHandlerFn,
284    _has_event_handler: *const (),
285    _count: *const (),
286}
287
288/// Registers a Pawn event handler in the dispatcher.
289///
290/// # Safety
291/// Both pointers must be valid. `handler` must outlive the dispatcher.
292pub unsafe fn add_pawn_event_handler(
293    dispatcher: *mut IEventDispatcherPawn,
294    handler: *mut PawnEventHandler,
295) {
296    unsafe { ((*(*dispatcher).vtable).add_event_handler)(dispatcher, handler, 0) };
297}
298
299/// Removes a Pawn event handler from the dispatcher.
300///
301/// # Safety
302/// Both pointers must be valid.
303pub unsafe fn remove_pawn_event_handler(
304    dispatcher: *mut IEventDispatcherPawn,
305    handler: *mut PawnEventHandler,
306) {
307    unsafe { ((*(*dispatcher).vtable).remove_event_handler)(dispatcher, handler) };
308}
309
310/// Gets the Pawn event dispatcher from the `IPawnComponent`.
311///
312/// # Safety
313/// `pawn` must be a valid pointer to an Open Multiplayer server `IPawnComponent`.
314pub unsafe fn get_pawn_event_dispatcher(pawn: *mut ServerComponent) -> *mut IEventDispatcherPawn {
315    let pawn = pawn.cast::<ServerPawnComponent>();
316    unsafe { ((*(*pawn).vtable).get_event_dispatcher)(pawn) }
317}
318
319/// Gets the pointer to the AMX function table from the `IPawnComponent`.
320///
321/// # Safety
322/// `pawn` must be a valid pointer to an Open Multiplayer server `IPawnComponent`.
323pub unsafe fn get_amx_functions(pawn: *mut ServerComponent) -> usize {
324    let pawn = pawn as *const ServerPawnComponent;
325    let table_ptr = unsafe { ((*(*pawn).vtable).get_amx_functions)(pawn) };
326    table_ptr as usize
327}
328
329// ---------------------------------------------------------------------------
330// PawnComponent — high-level typed wrapper
331// ---------------------------------------------------------------------------
332
333use super::component_api::OmpComponentHandle;
334use std::ptr::NonNull;
335
336/// Typed wrapper for the Open Multiplayer server's `IPawnComponent`.
337///
338/// Obtained via `samp::plugin::omp_query::<PawnComponent>()`. Exposes the
339/// Pawn-specific methods (event dispatcher, AMX functions) in addition to the
340/// generic `IComponent` ones (`name()`, `version()` via `component_api`).
341#[derive(Debug, Clone, Copy)]
342pub struct PawnComponent {
343    ptr: NonNull<ServerComponent>,
344}
345
346impl OmpComponentHandle for PawnComponent {
347    const UID: UID = PAWN_COMPONENT_UID;
348
349    unsafe fn from_raw(ptr: NonNull<ServerComponent>) -> Self {
350        Self { ptr }
351    }
352
353    fn as_raw(&self) -> NonNull<ServerComponent> {
354        self.ptr
355    }
356}
357
358impl PawnComponent {
359    /// Returns the component name — equivalent to `component_name(&self)`.
360    #[must_use]
361    pub fn name(&self) -> Option<String> {
362        super::component_api::component_name(self)
363    }
364
365    /// Returns the component version — equivalent to `component_version(&self)`.
366    #[must_use]
367    pub fn version(&self) -> Option<super::types::SemanticVersion> {
368        super::component_api::component_version(self)
369    }
370
371    /// Returns the component's `IEventDispatcher<PawnEventHandler>`.
372    ///
373    /// Use it to register AMX event handlers (load/unload).
374    #[must_use]
375    pub fn event_dispatcher(&self) -> *mut IEventDispatcherPawn {
376        unsafe { get_pawn_event_dispatcher(self.ptr.as_ptr()) }
377    }
378
379    /// Returns the AMX function table as `usize` (raw pointer).
380    ///
381    /// Available only after `on_omp_ready` — before that callback,
382    /// `getAmxFunctions()` returns 0 (server behavior).
383    #[must_use]
384    pub fn amx_functions(&self) -> usize {
385        unsafe { get_amx_functions(self.ptr.as_ptr()) }
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn pawn_component_uid_is_nonzero() {
395        assert_ne!(PAWN_COMPONENT_UID, 0);
396    }
397
398    #[test]
399    fn pawn_component_uid_matches_known_value() {
400        // Value derived from the Open Multiplayer SDK (PawnComponent_UID).
401        // If it changes, the vtable indices and the entire Open Multiplayer integration break.
402        assert_eq!(PAWN_COMPONENT_UID, 0x7890_6cd9_f19c_36a6);
403    }
404
405    #[test]
406    fn num_amx_funcs_is_52() {
407        assert_eq!(NUM_AMX_FUNCS, 52);
408    }
409
410    #[test]
411    fn pawn_component_uid_via_trait_matches_constant() {
412        assert_eq!(
413            <PawnComponent as OmpComponentHandle>::UID,
414            PAWN_COMPONENT_UID
415        );
416    }
417
418    #[test]
419    fn pawn_component_is_copy() {
420        // Sanity: the wrapper should be Copy so it can be used freely in closures.
421        fn assert_copy<T: Copy>() {}
422        assert_copy::<PawnComponent>();
423    }
424
425    #[test]
426    fn pawn_component_size_is_one_pointer() {
427        // Only stores a pointer — no overhead vs `*mut ServerComponent`.
428        assert_eq!(
429            std::mem::size_of::<PawnComponent>(),
430            std::mem::size_of::<*const ()>()
431        );
432    }
433}