use crate::cell::{AmxCell, AmxPrimitive, AmxString, Buffer, Ref};
use crate::consts::{AmxExecIdx, AmxFlags};
use crate::error::{AmxError, AmxResult};
#[allow(clippy::wildcard_imports)]
use crate::exports::*;
use crate::raw::types::{AMX, AMX_HEADER, AMX_NATIVE_INFO};
#[cfg(feature = "encoding")]
use crate::encoding;
use std::borrow::Cow;
use std::ffi::CString;
use std::ptr::NonNull;
macro_rules! amx_try {
($call:expr) => {
let result = $call;
if result > 0 {
return Err(result.into());
}
};
}
#[derive(Debug)]
pub struct Amx {
ptr: *mut AMX,
fn_table: usize,
}
impl Amx {
pub fn new(ptr: *mut AMX, fn_table: usize) -> Amx {
Amx { ptr, fn_table }
}
pub fn register(&self, natives: &[AMX_NATIVE_INFO]) -> AmxResult<()> {
let register = Register::from_table(self.fn_table);
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let len = natives.len() as i32;
let ptr = natives.as_ptr();
amx_try!(register(self.ptr, ptr, len));
Ok(())
}
pub(crate) fn allot<T: Sized + AmxPrimitive>(&self, cells: usize) -> AmxResult<Ref<'_, T>> {
if cells > i32::MAX as usize {
return Err(AmxError::Memory);
}
let allot = Allot::from_table(self.fn_table);
let mut amx_addr = 0;
let mut phys_addr = 0;
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let cells_i32 = cells as i32;
amx_try!(allot(
self.ptr,
cells_i32,
&raw mut amx_addr,
&raw mut phys_addr
));
if phys_addr == 0 {
return Err(AmxError::Memory);
}
unsafe { Ok(Ref::new(amx_addr, phys_addr as *mut T)) }
}
pub fn exec(&self, index: AmxExecIdx) -> AmxResult<i32> {
let exec = Exec::from_table(self.fn_table);
let mut retval = 0;
amx_try!(exec(self.ptr, &raw mut retval, index.into()));
Ok(retval)
}
pub fn find_native(&self, name: &str) -> AmxResult<i32> {
let find_native = FindNative::from_table(self.fn_table);
let c_str = CString::new(name).map_err(|_| AmxError::NotFound)?;
let mut index = -1;
amx_try!(find_native(self.ptr, c_str.as_ptr(), &raw mut index));
Ok(index)
}
pub fn find_public(&self, name: &str) -> AmxResult<AmxExecIdx> {
let find_public = FindPublic::from_table(self.fn_table);
let c_str = CString::new(name).map_err(|_| AmxError::NotFound)?;
let mut index = -1;
amx_try!(find_public(self.ptr, c_str.as_ptr(), &raw mut index));
Ok(AmxExecIdx::from(index))
}
pub fn find_pubvar<T: Sized + AmxPrimitive>(&self, name: &str) -> AmxResult<Ref<'_, T>> {
let find_pubvar = FindPubVar::from_table(self.fn_table);
let c_str = CString::new(name).map_err(|_| AmxError::NotFound)?;
let mut cell_ptr = 0;
amx_try!(find_pubvar(self.ptr, c_str.as_ptr(), &raw mut cell_ptr));
self.get_ref(cell_ptr)
}
pub fn flags(&self) -> AmxResult<AmxFlags> {
let flags = Flags::from_table(self.fn_table);
let mut value: u16 = 0;
amx_try!(flags(self.ptr, &raw mut value));
Ok(AmxFlags::from_bits_truncate(value))
}
pub fn get_ref<T: Sized + AmxPrimitive>(&self, address: i32) -> AmxResult<Ref<'_, T>> {
let get_addr = GetAddr::from_table(self.fn_table);
let mut dest = 0;
let mut dest_addr = std::ptr::addr_of_mut!(dest);
amx_try!(get_addr(self.ptr, address, &raw mut dest_addr));
if dest_addr.is_null() {
return Err(AmxError::MemoryAccess);
}
unsafe { Ok(Ref::new(address, dest_addr.cast::<T>())) }
}
#[inline]
pub(crate) fn release(&self, address: i32) {
if let Some(mut amx) = self.amx() {
let amx = unsafe { amx.as_mut() };
if address >= 0 && amx.hea > address {
amx.hea = address;
}
}
}
pub fn push<'a, T: AmxCell<'a>>(&'a self, value: T) -> AmxResult<()> {
let push = Push::from_table(self.fn_table);
amx_try!(push(self.ptr, value.as_cell()));
Ok(())
}
pub fn strlen(&self, value: *const i32) -> AmxResult<usize> {
let strlen = StrLen::from_table(self.fn_table);
let mut len = 0;
amx_try!(strlen(value, &raw mut len));
#[allow(clippy::cast_sign_loss)]
Ok(len as usize)
}
#[must_use]
pub fn allocator(&self) -> Allocator<'_> {
Allocator::new(self)
}
#[must_use]
pub fn amx(&self) -> Option<NonNull<AMX>> {
NonNull::new(self.ptr)
}
#[must_use]
pub fn header(&self) -> Option<NonNull<AMX_HEADER>> {
let amx = NonNull::new(self.ptr)?;
NonNull::new(unsafe { (*amx.as_ptr()).base.cast::<AMX_HEADER>() })
}
}
pub struct Allocator<'amx> {
amx: &'amx Amx,
release_addr: i32,
}
impl<'amx> Allocator<'amx> {
pub(crate) fn new(amx: &'amx Amx) -> Allocator<'amx> {
let amx_ptr = amx
.amx()
.expect("Allocator::new() received Amx with null pointer")
.as_ptr();
let release_addr = unsafe { (*amx_ptr).hea };
Allocator { amx, release_addr }
}
pub fn allot<T: Sized + AmxPrimitive>(&self, init_value: T) -> AmxResult<Ref<'_, T>> {
let mut cell = self.amx.allot(1)?;
*cell = init_value;
Ok(cell)
}
pub fn allot_buffer(&self, size: usize) -> AmxResult<Buffer<'_>> {
let buffer = self.amx.allot(size)?;
Ok(Buffer::new(buffer, size))
}
pub fn allot_array<T>(&self, array: &[T]) -> AmxResult<Buffer<'_>>
where
T: AmxCell<'amx> + AmxPrimitive,
{
let mut buffer = self.allot_buffer(array.len())?;
let slice = buffer.as_mut_slice();
for (idx, item) in array.iter().enumerate() {
slice[idx] = item.as_cell();
}
Ok(buffer)
}
pub fn allot_string(&self, string: &str) -> AmxResult<AmxString<'_>> {
let bytes = Allocator::string_bytes(string);
let buffer = self.allot_buffer(bytes.len() + 1)?;
Ok(unsafe { AmxString::new(buffer, bytes.as_ref()) })
}
fn string_bytes(string: &str) -> Cow<'_, [u8]> {
#[cfg(feature = "encoding")]
return encoding::get().encode(string).0;
#[cfg(not(feature = "encoding"))]
return Cow::from(string.as_bytes());
}
}
impl Drop for Allocator<'_> {
fn drop(&mut self) {
self.amx.release(self.release_addr);
}
}