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