-
Notifications
You must be signed in to change notification settings - Fork 16
Add events support #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Over the course of two days, I managed to learn more about SAMP with Chat-GPT and finally achieve public calls similar to PySAMP (or Zeex). However, for now, this is a raw Rust project, not a fully-fledged SDK. The next plan is to transfer this to the SDK. But maybe I need a break. Here are some preview logs:
rust code: // src/lib.rs
#![allow(non_camel_case_types, non_snake_case)]
use core::ffi::{c_char, c_int, c_void};
use std::{ffi::{CStr, CString}, mem, ptr};
// ---- AMX / SDK ----
#[repr(C)] pub struct AMX { _priv: [u8; 0] }
pub type cell = i32;
// plugincommon.h
const SUPPORTS_VERSION: u32 = 0x0200;
const SUPPORTS_AMX_NATIVES: u32 = 0x10000;
const PLUGIN_DATA_LOGPRINTF: usize = 0x00;
const PLUGIN_DATA_AMX_EXPORTS: usize = 0x10;
const PLUGIN_AMX_EXPORT_FindPublic: usize = 9;
const PLUGIN_AMX_EXPORT_Exec: usize = 7;
const AMX_EXEC_MAIN: c_int = -1;
const AMX_EXEC_CONT: c_int = -2;
// ---- types of functions ----
type logprintf_t = unsafe extern "C" fn(*const c_char, ...);
type amx_FindPublic_t = unsafe extern "C" fn(*mut AMX, *const c_char, *mut c_int) -> c_int;
type amx_Exec_t = unsafe extern "C" fn(*mut AMX, *mut cell, c_int) -> c_int;
// ---- globals ----
static mut LOGPRINTF: Option<logprintf_t> = None;
static mut ORIG_FINDPUBLIC: Option<amx_FindPublic_t> = None;
static mut ORIG_EXEC: Option<amx_Exec_t> = None;
static mut HOOK_FINDPUBLIC: Detour = Detour::new();
static mut HOOK_EXEC: Detour = Detour::new();
static mut CURRENT_PUBLIC: Option<String> = None;
// ---- Detour like JumpX86 ----
struct Detour {
target: *mut u8,
saved: [u8; 5],
installed: bool,
}
impl Detour {
pub const fn new() -> Self { Self { target: ptr::null_mut(), saved: [0;5], installed: false } }
unsafe fn unprotect(page: *mut u8, len: usize) {
// mprotect on pages
let pagesize = libc::sysconf(libc::_SC_PAGESIZE) as usize;
let base = (page as usize & !(pagesize - 1)) as *mut c_void;
let span = ((len + (page as usize & (pagesize-1)) + pagesize - 1)/pagesize)*pagesize;
libc::mprotect(base, span, libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC);
}
pub unsafe fn install(&mut self, target: *mut u8, hook: *const u8) {
if self.installed { return; }
self.target = target;
Self::unprotect(target, 5);
// save the original
self.saved.copy_from_slice(std::slice::from_raw_parts(target,5));
// put as E9 rel32
*target = 0xE9; // JMP rel32
let next = target.add(5) as usize;
let rel = (hook as usize).wrapping_sub(next) as u32;
ptr::write_unaligned(target.add(1) as *mut u32, rel);
self.installed = true;
}
pub unsafe fn remove(&mut self) {
if !self.installed { return; }
Self::unprotect(self.target, 5);
std::ptr::copy_nonoverlapping(self.saved.as_ptr(), self.target, 5);
self.installed = false;
}
}
unsafe fn log(s: &str) {
if let Some(lp) = LOGPRINTF {
if let Ok(c) = CString::new(s) { lp(c.as_ptr()); }
}
}
// ---- Hooks ----
#[no_mangle]
pub unsafe extern "C" fn amx_FindPublic_hook(amx: *mut AMX, name: *const c_char, index: *mut c_int) -> c_int {
// temporarily remove the hook, call the original
HOOK_FINDPUBLIC.remove();
let ret = (ORIG_FINDPUBLIC.unwrap())(amx, name, index);
// save name (for Exec)
if !name.is_null() {
let s = CStr::from_ptr(name).to_string_lossy().into_owned();
CURRENT_PUBLIC = Some(s.clone());
log(&format!("rust: FindPublic -> {}", s));
}
HOOK_FINDPUBLIC.install(HOOK_FINDPUBLIC.target, amx_FindPublic_hook as *const u8);
ret
}
#[no_mangle]
pub unsafe extern "C" fn amx_Exec_hook(amx: *mut AMX, retval: *mut cell, index: c_int) -> c_int {
HOOK_EXEC.remove();
// (optional) you can do something BEFORE
if let Some(ref name) = CURRENT_PUBLIC {
log(&format!("rust: Exec idx={} name={}", index, name));
} else {
log(&format!("rust: Exec idx={}", index));
}
let ret = (ORIG_EXEC.unwrap())(amx, retval, index);
// (optional) AFTER
HOOK_EXEC.install(HOOK_EXEC.target, amx_Exec_hook as *const u8);
ret
}
// ---- Export plugin ----
#[no_mangle]
pub extern "C" fn Supports() -> u32 {
SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES
}
#[no_mangle]
pub extern "C" fn Load(ppData: *mut *mut c_void) -> bool {
unsafe {
LOGPRINTF = Some(mem::transmute(*ppData.add(PLUGIN_DATA_LOGPRINTF)));
log("rust: Plugin is loaded");
let amx_exports = *ppData.add(PLUGIN_DATA_AMX_EXPORTS) as *mut *mut c_void;
// addresses of target functions
let findp_addr = *amx_exports.add(PLUGIN_AMX_EXPORT_FindPublic) as *mut u8;
let exec_addr = *amx_exports.add(PLUGIN_AMX_EXPORT_Exec) as *mut u8;
ORIG_FINDPUBLIC = Some(mem::transmute(findp_addr));
ORIG_EXEC = Some(mem::transmute(exec_addr));
// we put up detour
HOOK_FINDPUBLIC.install(findp_addr, amx_FindPublic_hook as *const u8);
log("rust: hook installed -> amx_FindPublic");
HOOK_EXEC.install(exec_addr, amx_Exec_hook as *const u8);
log("rust: hook installed -> amx_Exec");
}
true
}
#[no_mangle]
pub extern "C" fn Unload() {
unsafe {
HOOK_FINDPUBLIC.remove();
HOOK_EXEC.remove();
log("rust: Plugin is unloaded");
}
} [package]
name = "samp-hooks-test"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
libc = "0.2"
native print(const string[]);
main()
{
print("pawn: main");
return 1;
}
forward OnGameModeInit();
public OnGameModeInit() {
print("pawn: OnGameModeInit");
return 1;
}
|
At this stage of development, I've successfully managed to retrieve events and event arguments from SAMP. During development, an alignment issue was discovered. So, I added a C-language intermediary. This prevents Rust from getting a segment fault error, for example, when initializing a HashTable. I've added logging levels to my library. I think adding a logging system to the SDK with the same pull request where events are implemented is a questionable idea. However, logging has been a big help to me, and perhaps it will help someone in the future. I haven't yet studied how the current SDK implementation writes logs. If one exists, I'll adapt the code to it. Logging levels are definitely necessary, as excessive logging causes the server to lag even when only one player is playing. Ideally, logs should be sent async, but for now, I've solved the problem by keeping the Perhaps I'm making a mistake by developing with There's a lot of work to do to bring this functionality to the SDK. But I think the hardest part (learning hooks) is behind me. I'll also attach the source code of my work in case I can't implement I'm showing an example of the logs when:
I removed the duplicate lines with "OnPlayerUpdate" from the output. Content of the file
Content of the file [package]
name = "samp-hooks-test"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
libc = "0.2"
ilhook = { version = "2.1.3", default-features = false, features = ["x86"] }
[build-dependencies]
cc = "1.0" Content of the file fn main() {
println!("cargo:rerun-if-changed=src/shim.c");
cc::Build::new()
.file("src/shim.c")
.flag_if_supported("-m32") // harmless if native i686 build
.compile("align_shims"); // produces libalign_shims.a and links it in
} Content of the file #include <stdint.h>
struct Registers {
uint32_t eflags, edi, esi, ebp, esp, ebx, edx, ecx, eax;
};
// Rust bodies we'll call (provided below)
void on_push_rust (struct Registers* regs, uintptr_t user);
void on_pushstring_rust(struct Registers* regs, uintptr_t user);
uintptr_t on_findpublic_rust(struct Registers* regs, uintptr_t ori, uintptr_t user);
uintptr_t on_exec_rust (struct Registers* regs, uintptr_t ori, uintptr_t user);
// Align stack on entry and forward to Rust.
// cdecl is the default ABI on i686-unknown-linux-gnu.
__attribute__((force_align_arg_pointer))
void on_push_shim(struct Registers* regs, uintptr_t user) {
on_push_rust(regs, user);
}
__attribute__((force_align_arg_pointer))
void on_pushstring_shim(struct Registers* regs, uintptr_t user) {
on_pushstring_rust(regs, user);
}
__attribute__((force_align_arg_pointer))
uintptr_t on_findpublic_shim(struct Registers* regs, uintptr_t ori, uintptr_t user) {
return on_findpublic_rust(regs, ori, user);
}
__attribute__((force_align_arg_pointer))
uintptr_t on_exec_shim(struct Registers* regs, uintptr_t ori, uintptr_t user) {
return on_exec_rust(regs, ori, user);
} Content of the file #![allow(non_camel_case_types, non_snake_case)]
use std::{alloc::GlobalAlloc, panic};
use core::ffi::{c_char, c_int, c_void};
use ilhook::x86::{CallbackOption, HookFlags, HookPoint, HookType, Hooker, Registers};
use std::{
collections::{HashMap, VecDeque},
ffi::{CStr, CString},
mem,
ptr,
sync::{
atomic::{AtomicPtr, Ordering},
Mutex, OnceLock,
},
};
// ---------------- Consts ----------------
const SUPPORTS_VERSION: u32 = 0x0200;
const SUPPORTS_AMX_NATIVES: u32 = 0x10000;
const PLUGIN_DATA_LOGPRINTF: usize = 0x00;
const PLUGIN_DATA_AMX_EXPORTS: usize = 0x10;
const PLUGIN_AMX_EXPORT_Exec: usize = 7;
const PLUGIN_AMX_EXPORT_FindPublic: usize = 9;
const PLUGIN_AMX_EXPORT_Push: usize = 29;
const PLUGIN_AMX_EXPORT_PushString: usize = 31;
const AMX_EXEC_MAIN: c_int = -1;
const AMX_EXEC_CONT: c_int = -2;
const AMX_EXEC_FAKE: c_int = -13331;
// ---------------- Types ----------------
#[repr(C)]
pub struct AMX {
_priv: [u8; 0],
}
pub type cell = i32;
type logprintf_t = unsafe extern "C" fn(*const c_char, ...);
// ---------------- Thread-safe globals ----------------
static LOGPRINTF: OnceLock<logprintf_t> = OnceLock::new();
// Store raw pointers atomically (type-safe) → no Send/Sync issues.
static HOOK_FINDPUBLIC: AtomicPtr<HookPoint> = AtomicPtr::new(ptr::null_mut());
static HOOK_EXEC: AtomicPtr<HookPoint> = AtomicPtr::new(ptr::null_mut());
static HOOK_PUSH: AtomicPtr<HookPoint> = AtomicPtr::new(ptr::null_mut());
static HOOK_PUSHSTR: AtomicPtr<HookPoint> = AtomicPtr::new(ptr::null_mut());
// State is fine behind a Mutex since it holds `usize` keys and `AmxState` is Send.
static STATES: OnceLock<Mutex<HashMap<usize, AmxState>>> = OnceLock::new();
static GAMEMODE_AMX: OnceLock<Mutex<Option<usize>>> = OnceLock::new();
// ---------------- State ----------------
#[derive(Debug, Clone)]
enum Arg {
Cell(cell),
Str(String),
}
#[derive(Default, Debug)]
struct AmxState {
current_public: Option<String>,
args: VecDeque<Arg>,
}
// ---------------- Logging ----------------
fn is_enough_level(level: &str) -> bool {
// let env_level = std::env::var("RUST_LOG_LEVEL").unwrap_or_default().trim().to_lowercase();
let env_level = String::from("debug");
let level_enum = match level.to_lowercase().as_str() {
"error" => 0,
"warn" => 1,
"info" => 2,
"debug" => 3,
"trace" => 4,
_ => 2,
};
let env_level_enum = match env_level.as_str() {
"error" => 0,
"warn" => 1,
"info" => 2,
"debug" => 3,
"trace" => 4,
_ => 2,
};
level_enum <= env_level_enum
}
fn log(level: &str, msg: &str) {
if is_enough_level(level) {
let msg = format!("[{level}] {msg}");
let msg = msg.as_str();
let panic_result = panic::catch_unwind(|| {
if let Some(lp) = LOGPRINTF.get() {
if let Ok(cmsg) = CString::new(msg) {
const FMT: &[u8] = b"%s\0";
unsafe {
// UNSAFE: calling varargs function from SDK
lp(FMT.as_ptr() as *const c_char, cmsg.as_ptr());
}
return;
}
}
println!("[println] {}", msg);
});
match panic_result {
Ok(_) => {}
Err(e) => {
println!("[ERROR] rust: panic occurred while logging");
if let Some(s) = e.downcast_ref::<&str>() {
panic!("rust: panic occurred while logging {}", s);
}
panic!("rust: panic occurred while logging");
}
}
}
}
fn fmt_args(v: &VecDeque<Arg>) -> String {
log("trace", "fn fmt_args(v: &VecDeque<Arg>) -> String {{");
let mut out = String::new();
for (i, a) in v.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
match a {
Arg::Cell(x) => out.push_str(&format!("i:{x}")),
Arg::Str(s) => out.push_str(&format!("s:\"{s}\"")),
}
}
out
}
// ---------------- Safe accessors ----------------
fn states_map() -> &'static Mutex<HashMap<usize, AmxState>> {
log("trace", "fn states_map() -> &'static Mutex<HashMap<usize, AmxState>> {{");
STATES.get_or_init(|| {
log("trace", "rust: initializing STATES map");
log("trace", "rust: init hashMap...");
let hashMap = HashMap::new();
log("trace", &format!("rust: hashMap is inited. New Mutex... {:?}", hashMap));
log("trace", "rust: hashMap is inited. New Mutex...");
let mutex = Mutex::new(hashMap);
log("trace", "rust: mutex is inited.");
mutex
})
}
fn gamemode_cell() -> &'static Mutex<Option<usize>> {
log("trace", "fn gamemode_cell() -> &'static Mutex<Option<usize>> {{");
GAMEMODE_AMX.get_or_init(|| {
log("trace", "rust: initializing GAMEMODE_AMX cell");
Mutex::new(None)
})
}
// Read stack arg safely with logging
fn stack_get_u32(reg: &Registers, idx: usize) -> Option<u32> {
log("trace", "fn stack_get_u32(reg: &Registers, idx: usize) -> Option<u32> {{");
let base = reg.esp as usize;
log("trace", &format!("rust: stack_get_u32 idx={} base=0x{:x}", idx, base));
let addr = base.checked_add(idx * 4)?;
let p = addr as *const u32;
if p.is_null() {
log("trace", "rust: stack_get_u32 null pointer");
return None;
}
unsafe {
log("trace", "read stack arg begin");
let v = *p;
log("trace", "read stack arg end");
Some(v)
}
}
fn stack_get_addr(reg: &Registers, idx: usize) -> Option<usize> {
log("trace", "fn stack_get_addr(reg: &Registers, idx: usize) -> Option<usize> {{");
stack_get_u32(reg, idx).map(|v| v as usize)
}
fn cstr_from_ptr<'a>(p: *const c_char, label: &str) -> Option<&'a CStr> {
log("trace", "fn cstr_from_ptr<'a>(p: *const c_char, label: &str) -> Option<&'a CStr> {");
if p.is_null() {
log("debug", &format!("rust: cstr_from_ptr null for {}", label));
return None;
}
unsafe {
log("trace", &format!("CStr::from_ptr {} begin", label));
let s = CStr::from_ptr(p);
log("trace", &format!("CStr::from_ptr {} end", label));
Some(s)
}
}
fn read_export(amx_exports: *mut *mut c_void, idx: usize, label: &str) -> Option<usize> {
// I don't know why, but the code gets a segment fault error if the log below is disabled.
// For example, set the debug level for the logger that's one line below.
// But the code works if you install the `apt install tzdata` package.
// This means you either need to log "fn read_export..." or install the tzdata package.
// Fortunately, the tzdata package is usually already installed.
// I currently have tzdata installed, so the logger on the line
// below is set to the "debug" level, not the "info" level.
// I replaced iconv with cat in my dockerfile, but the segment fault still
// occurs without the tzdata package. I conclude that iconv doesn't contribute to the problem.
log("trace", "fn read_export(amx_exports: *mut *mut c_void, idx: usize, label: &str) -> Option<usize> {");
if amx_exports.is_null() {
log("debug", "rust: read_export amx_exports is null");
return None;
}
unsafe {
log("trace", &format!("read_export {}: compute ptr begin", label));
let p = amx_exports.add(idx);
log("trace", &format!("read_export {}: compute ptr end", label));
if p.is_null() {
log("debug", &format!("rust: read_export {}: slot is null", label));
return None;
}
log("trace", &format!("read_export {}: deref begin", label));
let val = *p as usize;
log("trace", &format!("read_export {}: deref end -> 0x{:x}", label, val));
Some(val)
}
}
// ---------------- Typed casts ----------------
type OrigFindPublic = unsafe extern "C" fn(*mut AMX, *const c_char, *mut c_int) -> c_int;
type OrigExec = unsafe extern "C" fn(*mut AMX, *mut cell, c_int) -> c_int;
fn cast_findpublic(addr: usize) -> Option<OrigFindPublic> {
log("trace", "fn cast_findpublic(addr: usize) -> Option<OrigFindPublic> {{");
if addr == 0 {
log("trace", "rust: cast_findpublic addr is 0");
return None;
}
unsafe {
log("trace", "transmute function pointer for amx_FindPublic begin");
let f = mem::transmute::<usize, OrigFindPublic>(addr);
log("trace", "transmute function pointer for amx_FindPublic end");
Some(f)
}
}
fn cast_exec(addr: usize) -> Option<OrigExec> {
log("trace", "fn cast_exec(addr: usize) -> Option<OrigExec> {{");
if addr == 0 {
log("trace", "rust: cast_exec addr is 0");
return None;
}
unsafe {
log("trace", "transmute function pointer for amx_Exec begin");
let f = mem::transmute::<usize, OrigExec>(addr);
log("trace", "transmute function pointer for amx_Exec end");
Some(f)
}
}
extern "cdecl" {
fn on_push_shim(regs: *mut Registers, user: usize);
fn on_pushstring_shim(regs: *mut Registers, user: usize);
fn on_findpublic_shim(regs: *mut Registers, ori: usize, user: usize) -> usize;
fn on_exec_shim(regs: *mut Registers, ori: usize, user: usize) -> usize;
}
// ---------------- Hooks ----------------
// Push: amx_Push(AMX* amx, cell value)
#[no_mangle]
pub unsafe extern "C" fn on_push_rust(reg: *mut Registers, _user: usize) {
log("trace", "pub unsafe extern \"C\" fn on_push_rust(reg: *mut Registers, _user: usize) {{");
let reg = &*reg;
if let (Some(amx_addr), Some(v)) = (stack_get_addr(reg, 1), stack_get_u32(reg, 2)) {
if amx_addr != 0 {
let mut states = states_map().lock().unwrap();
states.entry(amx_addr).or_default().args.push_front(Arg::Cell(v as cell));
}
}
}
// PushString: amx_PushString(AMX*, cell*, cell**, const char*, int, int)
#[no_mangle]
pub unsafe extern "C" fn on_pushstring_rust(reg: *mut Registers, _user: usize) {
log("trace", "pub unsafe extern \"C\" fn on_pushstring_rust(reg: *mut Registers, _user: usize) {{");
let reg = &*reg;
if let (Some(amx_addr), Some(cptr_u)) = (stack_get_addr(reg, 1), stack_get_u32(reg, 4)) {
let cptr = cptr_u as *const c_char;
if amx_addr != 0 && !cptr.is_null() {
if let Some(s) = cstr_from_ptr(cptr, "PushString.str") {
let mut states = states_map().lock().unwrap();
states
.entry(amx_addr)
.or_default()
.args
.push_front(Arg::Str(s.to_string_lossy().into_owned()));
}
}
}
}
// FindPublic (Retn): int(AMX*, const char*, int*)
#[no_mangle]
pub unsafe extern "C" fn on_findpublic_rust(
reg: *mut Registers,
ori: usize,
user: usize,
) -> usize {
log("trace", "pub unsafe extern \"C\" fn on_findpublic_rust(");
let regref = &*reg;
let amx_addr = stack_get_addr(regref, 1).unwrap_or(0);
let name_ptr = stack_get_u32(regref, 2).unwrap_or(0) as *const c_char;
let idxp = stack_get_u32(regref, 3).unwrap_or(0) as *mut c_int;
let orig = match cast_findpublic(ori) {
Some(f) => f,
None => return 1usize, // safer: say "not found"
};
let amx_ptr = amx_addr as *mut AMX;
let mut ret = unsafe {
log("trace", "call original amx_FindPublic begin");
let r = orig(amx_ptr, name_ptr, idxp);
log("trace", &format!("call original amx_FindPublic end ret={r}"));
r
};
if amx_addr != 0 {
if let Some(s) = cstr_from_ptr(name_ptr, "FindPublic.name") {
let name = s.to_string_lossy().into_owned();
// Update state
{
let mut states = states_map().lock().unwrap();
let st = states.entry(amx_addr).or_default();
st.current_public = Some(name.clone());
st.args.clear();
}
if ret != 0 {
// Not found
let gm = *gamemode_cell().lock().unwrap();
if gm.is_some() && gm.unwrap() == amx_addr && !idxp.is_null() {
unsafe {
log("trace", "write *index = AMX_EXEC_FAKE begin");
*idxp = AMX_EXEC_FAKE;
log("trace", "write *index = AMX_EXEC_FAKE end");
}
ret = 0; // fake success
log("debug", &format!(
"rust: FindPublic miss -> forcing Exec (public={})",
name
));
} else {
log("debug", &format!("rust: FindPublic miss (public={})", name));
}
} else {
log("trace", &format!("rust: FindPublic ok -> {}", name));
}
}
}
ret as usize
}
// Exec (Retn): int(AMX*, cell* retval, int index)
#[no_mangle]
pub unsafe extern "C" fn on_exec_rust(reg: *mut Registers, ori: usize, user: usize) -> usize {
log("trace", "pub unsafe extern \"C\" fn on_exec_rust(reg: *mut Registers, ori: usize, user: usize) -> usize {{");
let regref = &*reg;
let amx_addr = stack_get_addr(regref, 1).unwrap_or(0);
let retval = stack_get_u32(regref, 2).unwrap_or(0) as *mut cell;
let index = stack_get_u32(regref, 3).unwrap_or(0) as c_int;
log("trace", &format!("on_exec_rust: amx_addr={amx_addr} retval={retval:?} index={index:?}"));
let orig = match cast_exec(ori) {
Some(f) => f,
None => return 0,
};
if amx_addr != 0 {
if index == AMX_EXEC_MAIN {
let mut gm = gamemode_cell().lock().unwrap();
*gm = Some(amx_addr);
log("trace", "rust: Exec MAIN -> set GAMEMODE_AMX");
} else {
let mut states = states_map().lock().unwrap();
let st = states.entry(amx_addr).or_default();
match &st.current_public {
Some(name) => {
if st.args.is_empty() {
log("debug", &format!("rust: Exec idx={index} public={name} (no args)"));
} else {
log("debug", &format!(
"rust: Exec idx={index} public={name} args=[{}]",
fmt_args(&st.args)
));
}
}
None => log("trace", &format!("rust: Exec idx={index} (no current public)")),
}
st.args.clear();
}
}
let amx_ptr = amx_addr as *mut AMX;
let ret = unsafe {
log("trace", "call original amx_Exec begin");
let r = orig(amx_ptr, retval, index);
log("trace", &format!("call original amx_Exec end. ret={r}"));
r
};
ret as usize
}
// ---------------- Exports ----------------
#[no_mangle]
pub extern "C" fn Supports() -> u32 {
log("trace", "pub extern \"C\" fn Supports() -> u32 {{");
SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES
}
// Helper: store a HookPoint in an AtomicPtr (leak Box; drop later on Unload)
fn install_hook(slot: &AtomicPtr<HookPoint>, hp: HookPoint, name: &str) {
log("trace", "fn install_hook(slot: &AtomicPtr<HookPoint>, hp: HookPoint, name: &str) {{");
let b = Box::new(hp);
let raw = Box::into_raw(b);
slot.store(raw, Ordering::SeqCst);
log("debug", &format!("rust: hook {name} ok"));
}
// Helper: take and drop a HookPoint from an AtomicPtr (auto-unhook via Drop)
fn uninstall_hook(slot: &AtomicPtr<HookPoint>, name: &str) {
log("trace", "fn uninstall_hook(slot: &AtomicPtr<HookPoint>, name: &str) {");
let ptr = slot.swap(ptr::null_mut(), Ordering::SeqCst);
if !ptr.is_null() {
log("debug", &format!("rust: unhook {name} begin"));
unsafe {
log("trace", "Box::from_raw(HookPoint) begin");
let _ = Box::from_raw(ptr); // drop => unhook
log("trace", "Box::from_raw(HookPoint) end");
}
log("debug", &format!("rust: unhook {name} end"));
}
}
#[no_mangle]
pub extern "C" fn Load(ppData: *mut *mut c_void) -> bool {
log("trace", "pub extern \"C\" fn Load(ppData: *mut *mut c_void) -> bool {");
// Get logprintf
if !ppData.is_null() {
unsafe {
log("trace", "Load: read ppData[LOGPRINTF] begin");
let lp = *ppData.add(PLUGIN_DATA_LOGPRINTF) as *const ();
log("trace", "Load: read ppData[LOGPRINTF] end");
if !lp.is_null() {
let f = mem::transmute::<*const (), logprintf_t>(lp);
LOGPRINTF.set(f).ok();
}
}
}
// Get exports table
let amx_exports = unsafe {
log("trace", "Load: read ppData[AMX_EXPORTS] begin");
let p = if ppData.is_null() {
ptr::null_mut()
} else {
*ppData.add(PLUGIN_DATA_AMX_EXPORTS) as *mut *mut c_void
};
log("trace", "Load: read ppData[AMX_EXPORTS] end");
p
};
// Resolve addresses
let findp_addr = read_export(amx_exports, PLUGIN_AMX_EXPORT_FindPublic, "FindPublic");
let push_addr = read_export(amx_exports, PLUGIN_AMX_EXPORT_Push, "Push");
let pstr_addr = read_export(amx_exports, PLUGIN_AMX_EXPORT_PushString, "PushString");
let exec_addr = read_export(amx_exports, PLUGIN_AMX_EXPORT_Exec, "Exec");
// Install hooks (fail-soft with logs)
if let Some(addr) = push_addr {
let res = unsafe {
Hooker::new(addr, HookType::JmpBack(on_push_shim), CallbackOption::None, 0, HookFlags::empty()).hook()
};
match res {
Ok(hp) => install_hook(&HOOK_PUSH, hp, "amx_Push"),
Err(_) => log("error", "rust: hook amx_Push FAILED"),
}
}
if let Some(addr) = pstr_addr {
let res = unsafe {
Hooker::new(
addr,
HookType::JmpBack(on_pushstring_shim),
CallbackOption::None,
0,
HookFlags::empty(),
)
.hook()
};
match res {
Ok(hp) => install_hook(&HOOK_PUSHSTR, hp, "amx_PushString"),
Err(_) => log("error", "rust: hook amx_PushString FAILED"),
}
}
if let Some(addr) = findp_addr {
let res = unsafe {
Hooker::new(
addr,
HookType::Retn(0, on_findpublic_shim),
CallbackOption::None,
0,
HookFlags::empty(),
)
.hook()
};
match res {
Ok(hp) => install_hook(&HOOK_FINDPUBLIC, hp, "amx_FindPublic"),
Err(_) => log("error", "rust: hook amx_FindPublic FAILED"),
}
}
if let Some(addr) = exec_addr {
let res = unsafe {
Hooker::new(addr, HookType::Retn(0, on_exec_shim), CallbackOption::None, 0, HookFlags::empty()).hook()
};
match res {
Ok(hp) => install_hook(&HOOK_EXEC, hp, "amx_Exec"),
Err(_) => log("error", "rust: hook amx_Exec FAILED"),
}
}
log("info", "rust: Plugin is loaded");
true
}
#[no_mangle]
pub extern "C" fn Unload() {
log("trace", "pub extern \"C\" fn Unload() {{");
uninstall_hook(&HOOK_FINDPUBLIC, "FindPublic");
uninstall_hook(&HOOK_PUSH, "Push");
uninstall_hook(&HOOK_PUSHSTR, "PushString");
uninstall_hook(&HOOK_EXEC, "Exec");
if let Some(m) = GAMEMODE_AMX.get() {
*m.lock().unwrap() = None;
}
log("info", "rust: Plugin is unloaded");
} |
This pull request is for adding support for events from the SAMP server, such as
OnPlayerSpawn
orOnPlayerConnect
. I mentioned yesterday that I needed this #3 (comment) because I want to create gamemodes in Rust, not plugins.At the time of this pull request, it's completely untested and created using an AI language model. Later, outside of work hours, I need to try creating a gamemode and see if I can receive events.
I created the pull request in advance to keep you updated (in case anyone has already done this) and in case I don't succeed, someone can take my work.
Yesterday was my first experience with Rust. After the tests are finished, I really need a human perspective on the work I've done 🙏
Below is an example of using events: