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}