1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
pub mod ffi_extern;
pub mod ffi_intern;

use libloading::Symbol;
use std::{
    ffi::{CStr, OsStr},
    sync::Arc,
};
use zsplg_core::Wrapper as FFIWrapper;

pub struct Plugin {
    user_data: FFIWrapper,
    modname: Vec<u8>,
    dlh: libloading::Library,
}

pub struct Handle {
    user_data: FFIWrapper,
    parent: Arc<Plugin>,
}

impl Drop for Plugin {
    fn drop(&mut self) {
        self.user_data.call_dtor();
    }
}

impl Drop for Handle {
    fn drop(&mut self) {
        self.user_data.call_dtor();
    }
}

impl Plugin {
    fn get_fn<T>(&self, prefix: &[u8], name: &[u8]) -> Result<Symbol<'_, T>, std::io::Error> {
        let mut real_name: Vec<u8> =
            Vec::with_capacity(self.modname.len() + prefix.len() + name.len() + 2);
        real_name.extend(self.modname.iter().copied());
        real_name.push(b'_');
        real_name.extend(prefix.iter().copied());
        real_name.extend(name.iter().copied());
        real_name.push(b'\0');
        unsafe { self.dlh.get(&real_name[..]) }
    }

    pub fn new(file: Option<&OsStr>, modname: &CStr) -> Result<Plugin, std::io::Error> {
        let mut ret = Plugin {
            user_data: FFIWrapper::null(),
            modname: modname.to_bytes().to_owned(),
            dlh: match file {
                Some(file) => libloading::Library::new(file)?,
                None => {
                    #[cfg(not(unix))]
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::InvalidInput,
                        "selfexe-referential plugins aren't supported on this platform",
                    ));
                    #[cfg(unix)]
                    libloading::os::unix::Library::this().into()
                }
            },
        };
        // call initialization function
        ret.user_data = (ret.get_fn::<extern "C" fn() -> FFIWrapper>(b"", b"init")?)();
        Ok(ret)
    }

    pub fn create_handle(this: &Arc<Self>, args: &[FFIWrapper]) -> Result<Handle, std::io::Error> {
        let hcfn: Symbol<extern "C" fn(*const FFIWrapper, usize, *const FFIWrapper) -> FFIWrapper> =
            this.get_fn(b"", b"hcreate")?;

        Ok(Handle {
            user_data: hcfn(&this.user_data, args.len(), args.as_ptr()),
            parent: Arc::clone(this),
        })
    }

    fn call_intern(
        &self,
        hsel: Option<&FFIWrapper>,
        fname: &CStr,
        args: &[FFIWrapper],
    ) -> Result<FFIWrapper, std::io::Error> {
        let xfn: Symbol<extern "C" fn(*const FFIWrapper, usize, *const FFIWrapper) -> FFIWrapper> =
            self.get_fn(if hsel.is_some() { b"h_" } else { b"_" }, fname.to_bytes())?;

        Ok(xfn(
            hsel.unwrap_or(&self.user_data),
            args.len(),
            args.as_ptr(),
        ))
    }
}

pub trait RTMultiFn {
    fn call(&self, fname: &CStr, args: &[FFIWrapper]) -> Result<FFIWrapper, std::io::Error>;
}

impl RTMultiFn for Plugin {
    #[inline]
    fn call(&self, fname: &CStr, args: &[FFIWrapper]) -> Result<FFIWrapper, std::io::Error> {
        self.call_intern(None, fname, args)
    }
}

impl RTMultiFn for Handle {
    #[inline]
    fn call(&self, fname: &CStr, args: &[FFIWrapper]) -> Result<FFIWrapper, std::io::Error> {
        self.parent.call_intern(Some(&self.user_data), fname, args)
    }
}

impl<T> RTMultiFn for zsplg_core::WrapSized<T>
where
    T: RTMultiFn,
{
    #[inline]
    fn call(&self, fname: &CStr, args: &[FFIWrapper]) -> Result<FFIWrapper, std::io::Error> {
        self.0.call(fname, args)
    }
}