├── .github └── workflows │ └── main.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── examples ├── async.rs ├── basic.rs ├── data.rs ├── forwards.rs ├── handles.rs └── natives.rs ├── rustfmt.toml ├── sm-ext-derive ├── Cargo.toml └── src │ └── lib.rs └── src └── lib.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | name: 12 | - ubuntu-latest-stable 13 | - ubuntu-latest-nightly 14 | - windows-latest-stable 15 | - windows-latest-nightly 16 | include: 17 | - name: ubuntu-latest-stable 18 | os: ubuntu-latest 19 | rust: stable 20 | target: i686-unknown-linux-gnu 21 | rustflags: -D warnings 22 | - name: ubuntu-latest-nightly 23 | os: ubuntu-latest 24 | rust: nightly 25 | target: i686-unknown-linux-gnu 26 | rustflags: -D warnings 27 | - name: windows-latest-stable 28 | os: windows-latest 29 | rust: stable 30 | target: i686-pc-windows-msvc 31 | rustflags: -D warnings -C target-feature=+crt-static 32 | - name: windows-latest-nightly 33 | os: windows-latest 34 | rust: nightly 35 | target: i686-pc-windows-msvc 36 | rustflags: -D warnings -C target-feature=+crt-static 37 | 38 | steps: 39 | - uses: actions/checkout@v2 40 | 41 | - name: Install 32-bit support 42 | if: runner.os == 'Linux' 43 | run: | 44 | sudo dpkg --add-architecture i386 45 | sudo apt-get update 46 | sudo apt-get install -y g++-multilib 47 | 48 | - uses: actions-rs/toolchain@v1 49 | with: 50 | profile: minimal 51 | toolchain: ${{ matrix.rust }} 52 | target: ${{ matrix.target }} 53 | override: true 54 | components: rustfmt, clippy 55 | 56 | - uses: actions-rs/cargo@v1 57 | with: 58 | command: build 59 | args: --all --all-targets --target=${{ matrix.target }} 60 | env: 61 | RUSTFLAGS: ${{ matrix.rustflags }} 62 | 63 | - uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | 68 | - uses: actions-rs/cargo@v1 69 | with: 70 | command: clippy 71 | args: --all --all-targets --target=${{ matrix.target }} -- -D warnings 72 | env: 73 | RUSTFLAGS: ${{ matrix.rustflags }} 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sm-ext" 3 | version = "0.3.0" 4 | authors = ["Asher Baker "] 5 | edition = "2018" 6 | description = "Helpers for writing SourceMod Extensions in Rust" 7 | repository = "https://github.com/asherkin/sm-ext-rs" 8 | readme = "README.md" 9 | keywords = ["sourcemod"] 10 | categories = ["api-bindings"] 11 | license = "GPL-3.0-or-later" 12 | 13 | [features] 14 | default = [] 15 | abi_thiscall = ["sm-ext-derive/abi_thiscall"] 16 | 17 | [dependencies] 18 | libc = "0.2.66" 19 | c_str_macro = "1.0.2" 20 | 21 | [dependencies.sm-ext-derive] 22 | version = "0.3.0" 23 | path = "sm-ext-derive" 24 | 25 | [dev-dependencies] 26 | futures = "0.3" 27 | async-std = "1.4" 28 | 29 | [workspace] 30 | 31 | [[example]] 32 | name = "basic" 33 | crate-type = ["cdylib"] 34 | 35 | [[example]] 36 | name = "natives" 37 | crate-type = ["cdylib"] 38 | 39 | [[example]] 40 | name = "forwards" 41 | crate-type = ["cdylib"] 42 | 43 | [[example]] 44 | name = "handles" 45 | crate-type = ["cdylib"] 46 | 47 | [[example]] 48 | name = "data" 49 | crate-type = ["cdylib"] 50 | 51 | [[example]] 52 | name = "async" 53 | crate-type = ["cdylib"] 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SourceMod + Rust 2 | 3 | FFI wrappers and helpers for writing SourceMod extensions in Rust. 4 | 5 | ## Safety 6 | 7 | This crate makes heavy use of `unsafe` for interoperability with SourceMod but aims to expose safe interfaces with no unsound behaviour. However it may be possible to violate some of Rust's safety constraints (particularly aliasing guarantees) using APIs exposed by SourceMod. The aim of this project is to make it easier to write stable SourceMod extensions than when using C++, not to be 100% safe Rust. 8 | 9 | ## Building 10 | 11 | * Documentation 12 | ``` 13 | cargo +nightly doc --no-deps 14 | ``` 15 | 16 | * Windows Debug Build 17 | ``` 18 | RUSTFLAGS="-C target-feature=+crt-static" cargo build --all --all-targets --target=i686-pc-windows-msvc 19 | ``` 20 | 21 | * Windows Release Build 22 | ``` 23 | RUSTFLAGS="-C target-feature=+crt-static -C codegen-units=1" cargo build --all --all-targets --target=i686-pc-windows-msvc --release 24 | ``` 25 | 26 | * Linux Debug Build 27 | ``` 28 | cargo build --all --all-targets --target=i686-unknown-linux-gnu 29 | ``` 30 | 31 | * Linux Release Build 32 | ``` 33 | cargo build --all --all-targets --target=i686-unknown-linux-gnu --release 34 | ``` 35 | 36 | You'll probably want to wrap something around the cargo build to rename the output binary to match SourceMod's convention, and on Linux you'll want to strip release binaries before distribution with `strip --strip-debug`. 37 | -------------------------------------------------------------------------------- /examples/async.rs: -------------------------------------------------------------------------------- 1 | //! An example showing integration with asynchronous futures. 2 | //! 3 | //! ```sourcepawn 4 | //! methodmap RustHandle < Handle { 5 | //! public native void Read(); 6 | //! } 7 | //! 8 | //! typedef RustCallback = function void(RustHandle a); 9 | //! 10 | //! native void Rust_Test(RustCallback func); 11 | //! 12 | //! public void Callback(RustHandle a) 13 | //! { 14 | //! PrintToServer(">>> %d -> %d", a, a.Read()); 15 | //! 16 | //! delete a; 17 | //! } 18 | //! 19 | //! public void OnPluginStart() 20 | //! { 21 | //! Rust_Test(Callback); 22 | //! } 23 | //! ``` 24 | 25 | use async_std::task; 26 | use futures::executor::{LocalPool, LocalSpawner}; 27 | use futures::task::LocalSpawnExt; 28 | use sm_ext::{native, register_natives, CallableParam, ExecType, Executable, GameFrameHookId, HandleId, HandleType, IExtension, IExtensionInterface, IForwardManager, IHandleSys, IPluginContext, IPluginFunction, IShareSys, ISourceMod, SMExtension, SMInterfaceApi}; 29 | use std::cell::RefCell; 30 | use std::error::Error; 31 | use std::rc::Rc; 32 | use std::time::Duration; 33 | 34 | struct IntHandle(i32); 35 | 36 | #[native] 37 | fn native_handle_read(ctx: &IPluginContext, this: HandleId) -> Result> { 38 | let this = MyExtension::handle_type().read_handle(this, ctx.get_identity())?; 39 | let this = this.try_borrow()?; 40 | 41 | Ok(this.0) 42 | } 43 | 44 | #[native] 45 | fn test_native(ctx: &IPluginContext, mut func: IPluginFunction) -> Result<(), Box> { 46 | let mut forward = MyExtension::forwardsys().create_private_forward(None, ExecType::Ignore, &[HandleId::param_type()])?; 47 | forward.add_function(&mut func); 48 | 49 | let this = Rc::new(RefCell::new(IntHandle(0))); 50 | let handle = MyExtension::handle_type().create_handle(this.clone(), ctx.get_identity(), None)?; 51 | 52 | MyExtension::spawner().spawn_local(async move { 53 | let future = async move { 54 | task::sleep(Duration::from_secs(5)).await; 55 | 56 | { 57 | let mut this = this.try_borrow_mut()?; 58 | this.0 = 42; 59 | } 60 | 61 | forward.push(handle)?; 62 | forward.execute()?; 63 | 64 | Ok(()) 65 | }; 66 | 67 | if let Result::<(), Box>::Err(e) = future.await { 68 | // Call error callback. 69 | println!(">>> Async error: {}", e); 70 | } 71 | })?; 72 | 73 | Ok(()) 74 | } 75 | 76 | extern "C" fn on_game_frame(_simulating: bool) { 77 | // TODO: See if we need to catch panics. 78 | MyExtension::get().pool.borrow_mut().run_until_stalled() 79 | } 80 | 81 | #[derive(Default, SMExtension)] 82 | #[extension(name = "Rusty", description = "Sample extension written in Rust")] 83 | pub struct MyExtension { 84 | myself: Option, 85 | sharesys: Option, 86 | forwardsys: Option, 87 | sourcemod: Option, 88 | frame_hook: Option, 89 | handle_type: Option>>, 90 | pool: RefCell, 91 | } 92 | 93 | impl MyExtension { 94 | /// Helper to get the extension singleton from the global provided by sm-ext. 95 | /// This is implemented here rather than by the SMExtension derive to aid code completion. 96 | fn get() -> &'static Self { 97 | EXTENSION_GLOBAL.with(|ext| unsafe { &(*ext.borrow().unwrap()).delegate }) 98 | } 99 | 100 | fn forwardsys() -> &'static IForwardManager { 101 | Self::get().forwardsys.as_ref().unwrap() 102 | } 103 | 104 | fn handle_type() -> &'static HandleType> { 105 | Self::get().handle_type.as_ref().unwrap() 106 | } 107 | 108 | fn spawner() -> LocalSpawner { 109 | Self::get().pool.borrow().spawner() 110 | } 111 | } 112 | 113 | impl IExtensionInterface for MyExtension { 114 | fn on_extension_load(&mut self, myself: IExtension, sys: IShareSys, late: bool) -> Result<(), Box> { 115 | println!(">>> Rusty extension loaded! me = {:?}, sys = {:?}, late = {:?}", myself, sys, late); 116 | 117 | let forward_manager: IForwardManager = sys.request_interface(&myself)?; 118 | println!(">>> Got interface: {:?} v{:?}", forward_manager.get_interface_name(), forward_manager.get_interface_version()); 119 | 120 | let sourcemod: ISourceMod = sys.request_interface(&myself)?; 121 | println!(">>> Got interface: {:?} v{:?}", sourcemod.get_interface_name(), sourcemod.get_interface_version()); 122 | 123 | let handlesys: IHandleSys = sys.request_interface(&myself)?; 124 | println!(">>> Got interface: {:?} v{:?}", handlesys.get_interface_name(), handlesys.get_interface_version()); 125 | 126 | self.frame_hook = Some(sourcemod.add_game_frame_hook(on_game_frame)); 127 | 128 | self.handle_type = Some(handlesys.create_type("IntHandle", None, myself.get_identity())?); 129 | 130 | register_natives!( 131 | &sys, 132 | &myself, 133 | [ 134 | ("Rust_Test", test_native), // 135 | ("RustHandle.Read", native_handle_read), // 136 | ] 137 | ); 138 | 139 | self.myself = Some(myself); 140 | self.sharesys = Some(sys); 141 | self.forwardsys = Some(forward_manager); 142 | self.sourcemod = Some(sourcemod); 143 | 144 | Ok(()) 145 | } 146 | 147 | fn on_extension_unload(&mut self) { 148 | self.frame_hook = None; 149 | self.handle_type = None; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | //! Minimal example of a valid SourceMod extension. 2 | 3 | use sm_ext::{IExtension, IExtensionInterface, IShareSys, SMExtension}; 4 | 5 | #[derive(Default, SMExtension)] 6 | #[extension(name = "Rusty", description = "Sample extension written in Rust")] 7 | pub struct MyExtension; 8 | 9 | impl IExtensionInterface for MyExtension { 10 | fn on_extension_load(&mut self, myself: IExtension, sys: IShareSys, late: bool) -> Result<(), Box> { 11 | println!(">>> Rusty extension loaded! me = {:?}, sys = {:?}, late = {:?}", myself, sys, late); 12 | 13 | Ok(()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/data.rs: -------------------------------------------------------------------------------- 1 | //! An example showing data storage on the extension singleton. 2 | //! 3 | //! ```sourcepawn 4 | //! typedef RustCallback = function int(int a, const char[] b, float c); 5 | //! 6 | //! native int Rust_Test(RustCallback func); 7 | //! 8 | //! public int Callback(int a, const char[] b, float c) 9 | //! { 10 | //! PrintToServer(">>> %d, \"%s\", %.2f", a, b, c); 11 | //! 12 | //! return 42; 13 | //! } 14 | //! 15 | //! public void OnPluginStart() 16 | //! { 17 | //! int result = Rust_Test(Callback); 18 | //! 19 | //! PrintToServer(">>> Rust_Test() = %d", result); 20 | //! } 21 | //! ``` 22 | 23 | use sm_ext::{c_str, cell_t, native, register_natives, ExecType, Executable, GameFrameHookId, IExtension, IExtensionInterface, IForwardManager, IPluginContext, IPluginFunction, IShareSys, ISourceMod, ParamType, SMExtension, SMInterfaceApi}; 24 | use std::error::Error; 25 | 26 | #[native] 27 | fn test_native(ctx: &IPluginContext, mut func: IPluginFunction) -> Result> { 28 | MyExtension::log_message(format!("Log message from Rust native! {:?} {:?}", ctx, func)); 29 | 30 | println!(">>> test_native, func = {:?}", func); 31 | 32 | func.push(0)?; 33 | func.push(c_str!("Hello"))?; 34 | func.push(5.0)?; 35 | let result = func.execute()?; 36 | println!(">>> func() = {:?}", result); 37 | 38 | // Admittedly, this is a little gross. 39 | let mut forward = MyExtension::get().forwardsys.as_ref().unwrap().create_private_forward(None, ExecType::Single, &[ParamType::Cell, ParamType::String, ParamType::Float])?; 40 | assert_eq!(forward.get_function_count(), 0); 41 | 42 | forward.add_function(&mut func); 43 | assert_eq!(forward.get_function_count(), 1); 44 | 45 | forward.push(0)?; 46 | forward.push(c_str!("Hello"))?; 47 | forward.push(5.0)?; 48 | let result = forward.execute()?; 49 | println!(">>> forward() = {:?}", result); 50 | 51 | Ok(result) 52 | } 53 | 54 | // Just used to avoid spamming stdout. 55 | static mut UNSAFE_COUNTER: i32 = 0; 56 | 57 | unsafe extern "C" fn on_game_frame(_simulating: bool) { 58 | if UNSAFE_COUNTER > 5 { 59 | return; 60 | } 61 | 62 | println!(">>> on_game_frame fired!"); 63 | 64 | UNSAFE_COUNTER += 1; 65 | } 66 | 67 | #[derive(Default, SMExtension)] 68 | #[extension(name = "Rusty", description = "Sample extension written in Rust")] 69 | pub struct MyExtension { 70 | myself: Option, 71 | sharesys: Option, 72 | forwardsys: Option, 73 | sourcemod: Option, 74 | frame_hook: Option, 75 | } 76 | 77 | impl MyExtension { 78 | /// Helper to get the extension singleton from the global provided by sm-ext. 79 | /// This is implemented here rather than by the SMExtension derive to aid code completion. 80 | fn get() -> &'static Self { 81 | EXTENSION_GLOBAL.with(|ext| unsafe { &(*ext.borrow().unwrap()).delegate }) 82 | } 83 | 84 | fn log_message(msg: String) { 85 | Self::get().sourcemod.as_ref().unwrap().log_message(Self::get().myself.as_ref().unwrap(), msg); 86 | } 87 | } 88 | 89 | impl IExtensionInterface for MyExtension { 90 | fn on_extension_load(&mut self, myself: IExtension, sys: IShareSys, late: bool) -> Result<(), Box> { 91 | println!(">>> Rusty extension loaded! me = {:?}, sys = {:?}, late = {:?}", myself, sys, late); 92 | 93 | let forward_manager: IForwardManager = sys.request_interface(&myself)?; 94 | println!(">>> Got interface: {:?} v{:?}", forward_manager.get_interface_name(), forward_manager.get_interface_version()); 95 | 96 | let sourcemod: ISourceMod = sys.request_interface(&myself)?; 97 | println!(">>> Got interface: {:?} v{:?}", sourcemod.get_interface_name(), sourcemod.get_interface_version()); 98 | 99 | sourcemod.add_frame_action(|| { 100 | println!(">>> add_frame_action callback fired!"); 101 | }); 102 | 103 | self.frame_hook = Some(sourcemod.add_game_frame_hook(on_game_frame)); 104 | 105 | register_natives!( 106 | &sys, 107 | &myself, 108 | [ 109 | ("Rust_Test", test_native), // 110 | ] 111 | ); 112 | 113 | self.myself = Some(myself); 114 | self.sharesys = Some(sys); 115 | self.forwardsys = Some(forward_manager); 116 | self.sourcemod = Some(sourcemod); 117 | 118 | Ok(()) 119 | } 120 | 121 | fn on_extension_unload(&mut self) { 122 | self.frame_hook = None; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /examples/forwards.rs: -------------------------------------------------------------------------------- 1 | //! A basic SourceMod extension providing natives and forwards to SourcePawn. 2 | //! 3 | //! ```sourcepawn 4 | //! native int Rust_Call(int a, int b); 5 | //! forward int OnRustCall(int a, int b, const char[] c); 6 | //! 7 | //! public void OnPluginStart() 8 | //! { 9 | //! int result = Rust_Call(5, 4); 10 | //! 11 | //! PrintToServer(">>> Rust_Call(5, 4) = %d", result); 12 | //! } 13 | //! 14 | //! public int OnRustCall(int a, int b, const char[] c) 15 | //! { 16 | //! PrintToServer(">>> OnRustCall(%d, %d, %s)", a, b, c); 17 | //! 18 | //! return a + b; 19 | //! } 20 | //! ``` 21 | 22 | use sm_ext::{c_str, forwards, native, register_natives, ExecType, IExtension, IExtensionInterface, IForwardManager, IPluginContext, IShareSys, SMExtension, SMInterfaceApi}; 23 | use std::ffi::CStr; 24 | 25 | #[forwards] 26 | struct MyForwards { 27 | /// ```sourcepawn 28 | /// forward int OnRustCall(int a, int b, const char[] c); 29 | /// ``` 30 | #[global_forward("OnRustCall", ExecType::Single)] 31 | on_rust_call: fn(a: i32, b: i32, c: &CStr) -> i32, 32 | } 33 | 34 | /// A native that triggers the OnRustCall forward and returns the result. 35 | /// 36 | /// ```sourcepawn 37 | /// native int Rust_Call(int a, int b); 38 | /// ``` 39 | #[native] 40 | fn test_native_call(_ctx: &IPluginContext, a: i32, b: i32) -> Result { 41 | let result = MyForwards::on_rust_call(|fwd| fwd.execute(a, b, c_str!("Hello")))?; 42 | 43 | Ok(result) 44 | } 45 | 46 | #[derive(Default, SMExtension)] 47 | #[extension(name = "Rusty", description = "Sample extension written in Rust")] 48 | pub struct MyExtension; 49 | 50 | impl IExtensionInterface for MyExtension { 51 | fn on_extension_load(&mut self, myself: IExtension, sys: IShareSys, late: bool) -> Result<(), Box> { 52 | println!(">>> Rusty extension loaded! me = {:?}, sys = {:?}, late = {:?}", myself, sys, late); 53 | 54 | let forward_manager: IForwardManager = sys.request_interface(&myself)?; 55 | println!(">>> Got interface: {:?} v{:?}", forward_manager.get_interface_name(), forward_manager.get_interface_version()); 56 | 57 | MyForwards::register(&forward_manager)?; 58 | 59 | register_natives!( 60 | &sys, 61 | &myself, 62 | [ 63 | ("Rust_Call", test_native_call), // 64 | ] 65 | ); 66 | 67 | Ok(()) 68 | } 69 | 70 | fn on_extension_unload(&mut self) { 71 | MyForwards::unregister(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/handles.rs: -------------------------------------------------------------------------------- 1 | //! A basic SourceMod extension using a custom Handle type. 2 | //! 3 | //! ```sourcepawn 4 | //! methodmap Rust < Handle { 5 | //! public native Rust(); 6 | //! public native void Add(int number); 7 | //! property int Result { 8 | //! public native get(); 9 | //! } 10 | //! } 11 | //! 12 | //! public void OnPluginStart() 13 | //! { 14 | //! Rust test = new Rust(); 15 | //! test.Add(5); 16 | //! test.Add(4); 17 | //! int result = test.Result; 18 | //! 19 | //! PrintToServer(">>> 5 + 4 = %d", result); 20 | //! } 21 | //! ``` 22 | 23 | use sm_ext::{cell_t, native, register_natives, HandleError, HandleId, HandleType, IExtension, IExtensionInterface, IHandleSys, IPluginContext, IShareSys, SMExtension, SMInterfaceApi, TryIntoPlugin}; 24 | use std::cell::RefCell; 25 | use std::error::Error; 26 | use std::rc::Rc; 27 | 28 | #[derive(Debug)] 29 | struct RustContext(i32); 30 | 31 | impl<'ctx> TryIntoPlugin<'ctx> for RustContext { 32 | type Error = HandleError; 33 | 34 | fn try_into_plugin(self, ctx: &'ctx IPluginContext) -> Result { 35 | let object = Rc::new(RefCell::new(self)); 36 | let handle = MyExtension::handle_type().create_handle(object, ctx.get_identity(), None)?; 37 | 38 | Ok(handle.into()) 39 | } 40 | } 41 | 42 | #[native] 43 | fn native_obj_new(_ctx: &IPluginContext) -> RustContext { 44 | println!(">>> Rust.Rust()"); 45 | 46 | RustContext(0) 47 | } 48 | 49 | #[native] 50 | fn native_obj_add(ctx: &IPluginContext, this: HandleId, number: i32) -> Result<(), Box> { 51 | println!(">>> Rust.Add({:?}, {:?})", this, number); 52 | 53 | let this = MyExtension::handle_type().read_handle(this, ctx.get_identity())?; 54 | let mut this = this.try_borrow_mut()?; 55 | 56 | this.0 += number; 57 | 58 | Ok(()) 59 | } 60 | 61 | #[native] 62 | fn native_obj_result(ctx: &IPluginContext, this: HandleId) -> Result> { 63 | println!(">>> Rust.Result({:?})", this); 64 | 65 | let this = MyExtension::handle_type().read_handle(this, ctx.get_identity())?; 66 | let this = this.try_borrow()?; 67 | 68 | Ok(this.0) 69 | } 70 | 71 | #[derive(Default, SMExtension)] 72 | #[extension(name = "Rusty", description = "Sample extension written in Rust")] 73 | pub struct MyExtension { 74 | handle_type: Option>>, 75 | } 76 | 77 | impl MyExtension { 78 | /// Helper to get the extension singleton from the global provided by sm-ext. 79 | /// This is implemented here rather than by the SMExtension derive to aid code completion. 80 | fn get() -> &'static Self { 81 | EXTENSION_GLOBAL.with(|ext| unsafe { &(*ext.borrow().unwrap()).delegate }) 82 | } 83 | 84 | fn handle_type() -> &'static HandleType> { 85 | Self::get().handle_type.as_ref().unwrap() 86 | } 87 | } 88 | 89 | impl IExtensionInterface for MyExtension { 90 | fn on_extension_load(&mut self, myself: IExtension, sys: IShareSys, late: bool) -> Result<(), Box> { 91 | println!(">>> Rusty extension loaded! me = {:?}, sys = {:?}, late = {:?}", myself, sys, late); 92 | 93 | let handlesys: IHandleSys = sys.request_interface(&myself)?; 94 | println!(">>> Got interface: {:?} v{:?}", handlesys.get_interface_name(), handlesys.get_interface_version()); 95 | 96 | self.handle_type = Some(handlesys.create_type("RustContext", None, myself.get_identity())?); 97 | 98 | register_natives!( 99 | &sys, 100 | &myself, 101 | [ 102 | ("Rust.Rust", native_obj_new), // 103 | ("Rust.Add", native_obj_add), // 104 | ("Rust.Result.get", native_obj_result), // 105 | ] 106 | ); 107 | 108 | Ok(()) 109 | } 110 | 111 | fn on_extension_unload(&mut self) { 112 | self.handle_type = None; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /examples/natives.rs: -------------------------------------------------------------------------------- 1 | //! A basic SourceMod extension providing natives to SourcePawn. 2 | //! 3 | //! ```sourcepawn 4 | //! native int Rust_Add(int a, int b); 5 | //! 6 | //! public void OnPluginStart() 7 | //! { 8 | //! PrintToServer("Rust_Add(5, 4) = %d", Rust_Add(5, 4)); 9 | //! } 10 | //! ``` 11 | 12 | use sm_ext::{native, register_natives, IExtension, IExtensionInterface, IPluginContext, IShareSys, SMExtension}; 13 | use std::ffi::CStr; 14 | 15 | /// A native that adds two integers. 16 | /// 17 | /// ```sourcepawn 18 | /// native int Rust_Add(int a, int b); 19 | /// ``` 20 | #[native] 21 | fn test_native_add(_ctx: &IPluginContext, a: i32, b: i32) -> i32 { 22 | a + b 23 | } 24 | 25 | /// A native that has no return type. 26 | /// 27 | /// ```sourcepawn 28 | /// native void Rust_Void(); 29 | /// ``` 30 | #[native] 31 | fn test_native_void(_ctx: &IPluginContext) {} 32 | 33 | /// A native that returns an error to SourcePawn. 34 | /// 35 | /// ```sourcepawn 36 | /// native void Rust_Error(); 37 | /// ``` 38 | #[native] 39 | fn test_native_error(_ctx: &IPluginContext) -> Result<(), &'static str> { 40 | Err("This is an error...") 41 | } 42 | 43 | /// A native that uses a number of different argument types. 44 | /// 45 | /// The last 3 arguments are optional to support older plugins that were compiled before they were added. 46 | /// 47 | /// All of these signatures are valid for this native: 48 | /// ```sourcepawn 49 | /// native float Rust_Test(int a, float b, const char[] c, int &d, float &e); 50 | /// native float Rust_Test(int a, float b, const char[] c, int &d, float &e, int f); 51 | /// native float Rust_Test(int a, float b, const char[] c, int &d, float &e, int f, float g); 52 | /// native float Rust_Test(int a, float b, const char[] c, int &d, float &e, int f, float g, const char[] h); 53 | /// ``` 54 | #[native] 55 | #[allow(clippy::too_many_arguments, clippy::many_single_char_names)] 56 | fn test_native(ctx: &IPluginContext, a: i32, b: f32, c: &CStr, d: &mut i32, e: &mut f32, f: Option, g: Option, h: Option<&str>) -> f32 { 57 | println!(">>> {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?}", ctx, a, b, c, d, e, f, g, h); 58 | 59 | *d = 47; 60 | *e = 1.5; 61 | 62 | 5.0 63 | } 64 | 65 | #[derive(Default, SMExtension)] 66 | #[extension(name = "Rusty", description = "Sample extension written in Rust")] 67 | pub struct MyExtension; 68 | 69 | impl IExtensionInterface for MyExtension { 70 | fn on_extension_load(&mut self, myself: IExtension, sys: IShareSys, late: bool) -> Result<(), Box> { 71 | println!(">>> Rusty extension loaded! me = {:?}, sys = {:?}, late = {:?}", myself, sys, late); 72 | 73 | register_natives!( 74 | &sys, 75 | &myself, 76 | [ 77 | ("Rust_Add", test_native_add), // 78 | ("Rust_Void", test_native_void), // 79 | ("Rust_Error", test_native_error), // 80 | ("Rust_Test", test_native), // 81 | ] 82 | ); 83 | 84 | Ok(()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | max_width = 1000 3 | -------------------------------------------------------------------------------- /sm-ext-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sm-ext-derive" 3 | version = "0.3.0" 4 | authors = ["Asher Baker "] 5 | edition = "2018" 6 | workspace = ".." 7 | description = "Procedural macro package for sm-ext helpers" 8 | repository = "https://github.com/asherkin/sm-ext-rs" 9 | license = "GPL-3.0-or-later" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [features] 15 | default = [] 16 | abi_thiscall = [] 17 | 18 | [dependencies] 19 | quote = "1" 20 | syn = { version = "1", features = ["full"] } 21 | proc-macro2 = "1" 22 | -------------------------------------------------------------------------------- /sm-ext-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; 4 | use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt}; 5 | use syn; 6 | use syn::spanned::Spanned; 7 | 8 | /// Creates the entry point for SourceMod to recognise this library as an extension and set the required metadata. 9 | /// 10 | /// The `#[extension]` attribute recognises the following optional keys using the *MetaListNameValueStr* syntax: 11 | /// * `name` 12 | /// * `description` 13 | /// * `url` 14 | /// * `author` 15 | /// * `version` 16 | /// * `tag` 17 | /// * `date` 18 | /// 19 | /// If not overridden, all extension metadata will be set to suitable values using the Cargo package metadata. 20 | /// 21 | /// An instance of the struct this is applied to will be created with [`Default::default()`] to serve 22 | /// as the global singleton instance, and you can implement the [`IExtensionInterface`] trait on the 23 | /// type to handle SourceMod lifecycle callbacks. 24 | #[proc_macro_derive(SMExtension, attributes(extension))] 25 | pub fn derive_extension_metadata(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 26 | let ast: syn::DeriveInput = syn::parse(input).unwrap(); 27 | 28 | let name = &ast.ident; 29 | let input = MetadataInput::new(&ast); 30 | 31 | let extension_name = CStringToken(input.name); 32 | let extension_description = CStringToken(input.description); 33 | let extension_url = CStringToken(input.url); 34 | let extension_author = CStringToken(input.author); 35 | let extension_version = CStringToken(input.version); 36 | let extension_tag = CStringToken(input.tag); 37 | let extension_date = CStringToken(input.date); 38 | 39 | let expanded = quote! { 40 | // TODO: Checking for a test build here doesn't work when a dependent crate is being tested. 41 | #[cfg(all(windows, not(target_feature = "crt-static"), not(test)))] 42 | compile_error!("SourceMod requires the Windows CRT to be statically linked (pass `-C target-feature=+crt-static` to rustc)"); 43 | 44 | thread_local! { 45 | // TODO: This should probably be on the chopping block, it is fairly gross and not just because 46 | // it is storing a raw pointer, but I can't currently think of a better way for consumers to 47 | // be able to share the SM interfaces with natives. 48 | // 49 | // One more long-term option might be to handle this internally to the library and pass the 50 | // singleton into native callbacks as a param - if we go that route I think this and the 51 | // IPluginContext arguments need to be opt-in, possibly using attributes, so that all native 52 | // callbacks don't end up with 2 params that 90% of them don't use. 53 | // 54 | // Even then this isn't a great solution for the interfaces, maybe we should store those in 55 | // thread_local variables directly as part of the wrapping API (similar to how SM stores 56 | // the requested interfaces in globals) and offer static methods to fetch them automatically. 57 | // To do that we would only need to store IShareSys and IExtension globally, but should 58 | // probably cache all requested interfaces individually (and ideally force checking them 59 | // on load, but that is likely unrealistic.) 60 | static EXTENSION_GLOBAL: std::cell::RefCell>> = std::cell::RefCell::new(None); 61 | } 62 | 63 | #[no_mangle] 64 | pub extern "C" fn GetSMExtAPI() -> *mut sm_ext::IExtensionInterfaceAdapter<#name> { 65 | let delegate: #name = Default::default(); 66 | let extension = sm_ext::IExtensionInterfaceAdapter::new(delegate); 67 | let ptr = Box::into_raw(Box::new(extension)); 68 | EXTENSION_GLOBAL.with(|ext| { 69 | *ext.borrow_mut() = Some(ptr); 70 | ptr 71 | }) 72 | } 73 | 74 | // impl #name { 75 | // fn get() -> &'static Self { 76 | // EXTENSION_GLOBAL.with(|ext| { 77 | // unsafe { &(*ext.borrow().unwrap()).delegate } 78 | // }) 79 | // } 80 | // } 81 | 82 | impl sm_ext::ExtensionMetadata for #name { 83 | fn get_extension_name(&self) -> &'static ::std::ffi::CStr { 84 | #extension_name 85 | } 86 | fn get_extension_url(&self) -> &'static ::std::ffi::CStr { 87 | #extension_url 88 | } 89 | fn get_extension_tag(&self) -> &'static ::std::ffi::CStr { 90 | #extension_tag 91 | } 92 | fn get_extension_author(&self) -> &'static ::std::ffi::CStr { 93 | #extension_author 94 | } 95 | fn get_extension_ver_string(&self) -> &'static ::std::ffi::CStr { 96 | #extension_version 97 | } 98 | fn get_extension_description(&self) -> &'static ::std::ffi::CStr { 99 | #extension_description 100 | } 101 | fn get_extension_date_string(&self) -> &'static ::std::ffi::CStr { 102 | #extension_date 103 | } 104 | } 105 | }; 106 | 107 | expanded.into() 108 | } 109 | 110 | struct CStringToken(MetadataString); 111 | 112 | impl ToTokens for CStringToken { 113 | fn to_tokens(&self, tokens: &mut TokenStream) { 114 | let value = match &self.0 { 115 | MetadataString::String(str) => str.to_token_stream(), 116 | MetadataString::EnvVar(var) => quote! { 117 | env!(#var) 118 | }, 119 | }; 120 | 121 | // Inspired by https://crates.io/crates/c_str_macro 122 | tokens.append_all(quote! { 123 | unsafe { 124 | ::std::ffi::CStr::from_ptr(concat!(#value, "\0").as_ptr() as *const ::std::os::raw::c_char) 125 | } 126 | }); 127 | } 128 | } 129 | 130 | enum MetadataString { 131 | String(String), 132 | EnvVar(String), 133 | } 134 | 135 | struct MetadataInput { 136 | pub name: MetadataString, 137 | pub description: MetadataString, 138 | pub url: MetadataString, 139 | pub author: MetadataString, 140 | pub version: MetadataString, 141 | pub tag: MetadataString, 142 | pub date: MetadataString, 143 | } 144 | 145 | impl MetadataInput { 146 | #[allow(clippy::cognitive_complexity)] 147 | pub fn new(ast: &syn::DeriveInput) -> MetadataInput { 148 | let mut name = None; 149 | let mut description = None; 150 | let mut url = None; 151 | let mut author = None; 152 | let mut version = None; 153 | let mut tag = None; 154 | let mut date = None; 155 | 156 | let meta = ast.attrs.iter().find_map(|attr| match attr.parse_meta() { 157 | Ok(m) => { 158 | if m.path().is_ident("extension") { 159 | Some(m) 160 | } else { 161 | None 162 | } 163 | } 164 | Err(e) => panic!("unable to parse attribute: {}", e), 165 | }); 166 | 167 | if let Some(meta) = meta { 168 | let meta_list = match meta { 169 | syn::Meta::List(inner) => inner, 170 | _ => panic!("attribute 'extension' has incorrect type"), 171 | }; 172 | 173 | for item in meta_list.nested { 174 | let pair = match item { 175 | syn::NestedMeta::Meta(syn::Meta::NameValue(ref pair)) => pair, 176 | _ => panic!("unsupported attribute argument {:?}", item.to_token_stream()), 177 | }; 178 | 179 | if pair.path.is_ident("name") { 180 | if let syn::Lit::Str(ref s) = pair.lit { 181 | name = Some(s.value()); 182 | } else { 183 | panic!("name value must be string literal"); 184 | } 185 | } else if pair.path.is_ident("description") { 186 | if let syn::Lit::Str(ref s) = pair.lit { 187 | description = Some(s.value()) 188 | } else { 189 | panic!("description value must be string literal"); 190 | } 191 | } else if pair.path.is_ident("url") { 192 | if let syn::Lit::Str(ref s) = pair.lit { 193 | url = Some(s.value()) 194 | } else { 195 | panic!("url value must be string literal"); 196 | } 197 | } else if pair.path.is_ident("author") { 198 | if let syn::Lit::Str(ref s) = pair.lit { 199 | author = Some(s.value()) 200 | } else { 201 | panic!("author value must be string literal"); 202 | } 203 | } else if pair.path.is_ident("version") { 204 | if let syn::Lit::Str(ref s) = pair.lit { 205 | version = Some(s.value()) 206 | } else { 207 | panic!("version value must be string literal"); 208 | } 209 | } else if pair.path.is_ident("tag") { 210 | if let syn::Lit::Str(ref s) = pair.lit { 211 | tag = Some(s.value()) 212 | } else { 213 | panic!("tag value must be string literal"); 214 | } 215 | } else if pair.path.is_ident("date") { 216 | if let syn::Lit::Str(ref s) = pair.lit { 217 | date = Some(s.value()) 218 | } else { 219 | panic!("date value must be string literal"); 220 | } 221 | } else { 222 | panic!("unsupported attribute key '{}' found", pair.path.to_token_stream()) 223 | } 224 | } 225 | } 226 | 227 | let name = match name { 228 | Some(name) => MetadataString::String(name), 229 | None => MetadataString::EnvVar("CARGO_PKG_NAME".into()), 230 | }; 231 | 232 | let description = match description { 233 | Some(description) => MetadataString::String(description), 234 | None => MetadataString::EnvVar("CARGO_PKG_DESCRIPTION".into()), 235 | }; 236 | 237 | let url = match url { 238 | Some(url) => MetadataString::String(url), 239 | None => MetadataString::EnvVar("CARGO_PKG_HOMEPAGE".into()), 240 | }; 241 | 242 | // TODO: This probably needs a special type to post-process the author list later. 243 | let author = match author { 244 | Some(author) => MetadataString::String(author), 245 | None => MetadataString::EnvVar("CARGO_PKG_AUTHORS".into()), 246 | }; 247 | 248 | let version = match version { 249 | Some(version) => MetadataString::String(version), 250 | None => MetadataString::EnvVar("CARGO_PKG_VERSION".into()), 251 | }; 252 | 253 | // TODO: This probably should have a special type to slugify/uppercase the package name later. 254 | let tag = match tag { 255 | Some(tag) => MetadataString::String(tag), 256 | None => MetadataString::EnvVar("CARGO_PKG_NAME".into()), 257 | }; 258 | 259 | let date = match date { 260 | Some(date) => MetadataString::String(date), 261 | None => MetadataString::String("with Rust".into()), 262 | }; 263 | 264 | MetadataInput { name, description, url, author, version, tag, date } 265 | } 266 | } 267 | 268 | #[proc_macro_derive(SMInterfaceApi, attributes(interface))] 269 | pub fn derive_interface_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 270 | let input = syn::parse_macro_input!(input as syn::DeriveInput); 271 | 272 | let ident = input.ident; 273 | 274 | let attribute = input.attrs.iter().find_map(|attr| match attr.parse_meta() { 275 | Ok(m) => { 276 | if m.path().is_ident("interface") { 277 | Some(m) 278 | } else { 279 | None 280 | } 281 | } 282 | Err(e) => panic!("unable to parse attribute: {}", e), 283 | }); 284 | 285 | let mut output = TokenStream::new(); 286 | 287 | if let Some(attribute) = attribute { 288 | let nested = match attribute { 289 | syn::Meta::List(inner) => inner.nested, 290 | _ => panic!("attribute 'interface' has incorrect type"), 291 | }; 292 | 293 | if nested.len() != 2 { 294 | panic!("attribute 'interface' expected 2 params: name, version") 295 | } 296 | 297 | let interface_name = match &nested[0] { 298 | syn::NestedMeta::Lit(lit) => match lit { 299 | syn::Lit::Str(str) => str, 300 | _ => panic!("attribute 'interface' param 1 should be a string"), 301 | }, 302 | _ => panic!("attribute 'interface' param 1 should be a literal string"), 303 | }; 304 | 305 | let interface_version = match &nested[1] { 306 | syn::NestedMeta::Lit(lit) => match lit { 307 | syn::Lit::Int(int) => int, 308 | _ => panic!("attribute 'interface' param 2 should be an integer"), 309 | }, 310 | _ => panic!("attribute 'interface' param 2 should be a literal integer"), 311 | }; 312 | 313 | output.extend(quote! { 314 | impl RequestableInterface for #ident { 315 | fn get_interface_name() -> &'static str { 316 | #interface_name 317 | } 318 | 319 | fn get_interface_version() -> u32 { 320 | #interface_version 321 | } 322 | 323 | #[allow(clippy::transmute_ptr_to_ptr)] 324 | unsafe fn from_raw_interface(iface: SMInterface) -> #ident { 325 | #ident(std::mem::transmute(iface.0)) 326 | } 327 | } 328 | }); 329 | } 330 | 331 | output.extend(quote! { 332 | impl SMInterfaceApi for #ident { 333 | fn get_interface_version(&self) -> u32 { 334 | unsafe { virtual_call!(GetInterfaceVersion, self.0) } 335 | } 336 | 337 | fn get_interface_name(&self) -> &str { 338 | unsafe { 339 | let c_name = virtual_call!(GetInterfaceName, self.0); 340 | 341 | std::ffi::CStr::from_ptr(c_name).to_str().unwrap() 342 | } 343 | } 344 | 345 | fn is_version_compatible(&self, version: u32) -> bool { 346 | unsafe { virtual_call!(IsVersionCompatible, self.0, version) } 347 | } 348 | } 349 | }); 350 | 351 | output.into() 352 | } 353 | 354 | #[proc_macro_derive(ICallableApi)] 355 | pub fn derive_callable_api(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 356 | let input = syn::parse_macro_input!(input as syn::DeriveInput); 357 | 358 | let ident = &input.ident; 359 | let generics = &input.generics; 360 | let output = quote! { 361 | impl #generics ICallableApi for #ident #generics { 362 | fn push_int(&mut self, cell: i32) -> Result<(), SPError> { 363 | unsafe { 364 | let res = virtual_call!(PushCell, self.0, cell.into()); 365 | match res { 366 | SPError::None => Ok(()), 367 | _ => Err(res), 368 | } 369 | } 370 | } 371 | 372 | fn push_float(&mut self, number: f32) -> Result<(), SPError> { 373 | unsafe { 374 | let res = virtual_call!(PushFloat, self.0, number); 375 | match res { 376 | SPError::None => Ok(()), 377 | _ => Err(res), 378 | } 379 | } 380 | } 381 | 382 | fn push_string(&mut self, string: &CStr) -> Result<(), SPError> { 383 | unsafe { 384 | let res = virtual_call!(PushString, self.0, string.as_ptr()); 385 | match res { 386 | SPError::None => Ok(()), 387 | _ => Err(res), 388 | } 389 | } 390 | } 391 | } 392 | }; 393 | 394 | output.into() 395 | } 396 | 397 | /// Declares a function as a native callback and generates internal support code. 398 | /// 399 | /// A valid native callback must be a free function that is not async, not unsafe, not extern, has 400 | /// no generic parameters, the first argument takes a [`&IPluginContext`](IPluginContext), any 401 | /// remaining arguments are convertible to [`cell_t`] using [`TryIntoPlugin`] (possibly wrapped in 402 | /// an [`Option`]), and returns a type that satisfies the [`NativeResult`] trait. 403 | /// 404 | /// When the native is invoked by SourceMod the input arguments will be checked to ensure all required 405 | /// arguments have been passed and are of the correct type, and panics or error results will automatically 406 | /// be converted into a SourceMod native error using [`safe_native_invoke`]. 407 | /// 408 | /// # Example 409 | /// 410 | /// ```ignore 411 | /// use sm_ext::{native, IPluginContext}; 412 | /// 413 | /// #[native] 414 | /// fn simple_add_native(_ctx: &IPluginContext, a: i32, b: i32) -> i32 { 415 | /// a + b 416 | /// } 417 | /// ``` 418 | #[proc_macro_attribute] 419 | pub fn native(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { 420 | let mut input = syn::parse_macro_input!(item as syn::ItemFn); 421 | // println!("{:#?}", input); 422 | 423 | let mut output = TokenStream::new(); 424 | 425 | if let Some(asyncness) = &input.sig.asyncness { 426 | output.extend(error("Native callback must not be async", asyncness.span())); 427 | } 428 | 429 | if let Some(unsafety) = &input.sig.unsafety { 430 | output.extend(error("Native callback must not be unsafe", unsafety.span())); 431 | } 432 | 433 | if let Some(abi) = &input.sig.abi { 434 | output.extend(error("Native callback must use the default Rust ABI", abi.span())); 435 | } 436 | 437 | if !input.sig.generics.params.is_empty() { 438 | output.extend(error("Native callback must not have any generic parameters", input.sig.generics.span())); 439 | } 440 | 441 | let mut param_count: i32 = 0; 442 | let mut trailing_optional_count = 0; 443 | let mut param_output = TokenStream::new(); 444 | for param in &input.sig.inputs { 445 | match param { 446 | syn::FnArg::Receiver(param) => { 447 | output.extend(error("Native callback must not be a method", param.span())); 448 | } 449 | syn::FnArg::Typed(param) => { 450 | param_count += 1; 451 | if param_count == 1 { 452 | param_output.extend(quote_spanned!(param.span() => &ctx,)); 453 | continue; 454 | } 455 | 456 | let mut is_optional = false; 457 | if let syn::Type::Path(path) = &*param.ty { 458 | if path.path.segments.last().unwrap().ident == "Option" { 459 | is_optional = true; 460 | trailing_optional_count += 1; 461 | } else { 462 | trailing_optional_count = 0; 463 | } 464 | } else { 465 | trailing_optional_count = 0; 466 | } 467 | 468 | let param_idx = param_count - 1; 469 | let convert_param = quote_spanned!(param.span() => 470 | (*(args.offset(#param_idx as isize))) 471 | .try_into_plugin(&ctx) 472 | .map_err(|err| format!("Error processing argument {}: {}", #param_idx, err))? 473 | ); 474 | 475 | if is_optional { 476 | param_output.extend(quote! { 477 | if #param_idx <= count { 478 | Some(#convert_param) 479 | } else { 480 | None 481 | }, 482 | }); 483 | } else { 484 | param_output.extend(quote! { 485 | #convert_param, 486 | }); 487 | } 488 | } 489 | }; 490 | } 491 | 492 | let args_minimum = (param_count - 1) - trailing_optional_count; 493 | let wrapper_ident = &input.sig.ident; 494 | let callback_ident = format_ident!("__{}_impl", wrapper_ident); 495 | output.extend(quote! { 496 | unsafe extern "C" fn #wrapper_ident(ctx: sm_ext::IPluginContextPtr, args: *const sm_ext::cell_t) -> sm_ext::cell_t { 497 | sm_ext::safe_native_invoke(ctx, |ctx| -> Result> { 498 | use sm_ext::NativeResult; 499 | use sm_ext::TryIntoPlugin; 500 | 501 | let count: i32 = (*args).into(); 502 | if count < #args_minimum { 503 | return Err(format!("not enough arguments, got {}, expected at least {}", count, #args_minimum).into()); 504 | } 505 | 506 | let result = #callback_ident( 507 | #param_output 508 | ).into_result()?; 509 | 510 | Ok(result.try_into_plugin(&ctx) 511 | .map_err(|err| format!("error processing return value: {}", err))?) 512 | }) 513 | } 514 | }); 515 | 516 | input.sig.ident = callback_ident; 517 | output.extend(input.to_token_stream()); 518 | 519 | // println!("{}", output.to_string()); 520 | output.into() 521 | } 522 | 523 | struct ForwardInfo { 524 | ident: syn::Ident, 525 | name: Option, 526 | exec_type: syn::Path, 527 | params: Vec, 528 | ret: syn::Type, 529 | } 530 | 531 | fn parse_forward_from_field(field: &syn::Field, output: &mut TokenStream) -> Option { 532 | // TODO: It would improve diagnostics to remove the attribute if it is found. 533 | let attribute = field.attrs.iter().find_map(|attr| match attr.parse_meta() { 534 | Ok(m) => { 535 | if m.path().is_ident("global_forward") || m.path().is_ident("private_forward") { 536 | Some(m) 537 | } else { 538 | None 539 | } 540 | } 541 | Err(e) => { 542 | output.extend(e.to_compile_error()); 543 | None 544 | } 545 | })?; 546 | 547 | let (params, ret): (Vec, _) = match &field.ty { 548 | syn::Type::BareFn(ty) => ( 549 | ty.inputs.iter().cloned().collect(), 550 | match &ty.output { 551 | syn::ReturnType::Default => syn::parse_quote!(()), 552 | syn::ReturnType::Type(_, ty) => (*ty.as_ref()).clone(), 553 | }, 554 | ), 555 | _ => { 556 | output.extend(error("expected bare function", field.ty.span())); 557 | return None; 558 | } 559 | }; 560 | 561 | let nested = match &attribute { 562 | syn::Meta::List(inner) => &inner.nested, 563 | _ => { 564 | output.extend(error(&format!("attribute '{}' has incorrect type", attribute.path().get_ident().unwrap()), attribute.span())); 565 | return None; 566 | } 567 | }; 568 | 569 | if attribute.path().is_ident("global_forward") { 570 | if nested.len() != 2 { 571 | output.extend(error("Usage: #[global_forward(Forward_Name, ExecType::)]", attribute.span())); 572 | return None; 573 | } 574 | 575 | let forward_name = match &nested[0] { 576 | syn::NestedMeta::Lit(lit) => match lit { 577 | syn::Lit::Str(str) => str, 578 | _ => { 579 | output.extend(error("expected string literal", nested[0].span())); 580 | return None; 581 | } 582 | }, 583 | _ => { 584 | output.extend(error("expected string literal", nested[0].span())); 585 | return None; 586 | } 587 | }; 588 | 589 | let forward_exec_type = match &nested[1] { 590 | syn::NestedMeta::Meta(meta) => match meta { 591 | syn::Meta::Path(path) => path, 592 | _ => { 593 | output.extend(error("expected type path", nested[1].span())); 594 | return None; 595 | } 596 | }, 597 | _ => { 598 | output.extend(error("expected type path", nested[1].span())); 599 | return None; 600 | } 601 | }; 602 | 603 | Some(ForwardInfo { ident: field.ident.as_ref().unwrap().clone(), name: Some((*forward_name).clone()), exec_type: (*forward_exec_type).clone(), params, ret }) 604 | } else if attribute.path().is_ident("private_forward") { 605 | output.extend(error("#[private_forward] not implemented", attribute.span())); 606 | 607 | if nested.len() != 1 { 608 | output.extend(error("Usage: #[private_forward(ExecType::)]", attribute.span())); 609 | return None; 610 | } 611 | 612 | let forward_exec_type = match &nested[0] { 613 | syn::NestedMeta::Meta(meta) => match meta { 614 | syn::Meta::Path(path) => path, 615 | _ => { 616 | output.extend(error("expected type path", nested[0].span())); 617 | return None; 618 | } 619 | }, 620 | _ => { 621 | output.extend(error("expected type path", nested[0].span())); 622 | return None; 623 | } 624 | }; 625 | 626 | Some(ForwardInfo { ident: field.ident.as_ref().unwrap().clone(), name: None, exec_type: (*forward_exec_type).clone(), params, ret }) 627 | } else { 628 | None 629 | } 630 | } 631 | 632 | #[proc_macro_attribute] 633 | pub fn forwards(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { 634 | let mut input = syn::parse_macro_input!(item as syn::ItemStruct); 635 | // println!("{:#?}", input); 636 | 637 | let mut fields = match &mut input.fields { 638 | syn::Fields::Named(fields) => fields, 639 | _ => panic!("Expected a struct with named fields"), 640 | }; 641 | 642 | let mut output = TokenStream::new(); 643 | 644 | let mut forwards = Vec::new(); 645 | let mut filtered_fields: syn::punctuated::Punctuated = syn::punctuated::Punctuated::new(); 646 | 647 | for field in &fields.named { 648 | if let Some(forward) = parse_forward_from_field(field, &mut output) { 649 | forwards.push(forward); 650 | } else { 651 | filtered_fields.push((*field).clone()); 652 | } 653 | } 654 | 655 | fields.named = filtered_fields; 656 | 657 | output.extend(input.to_token_stream()); 658 | 659 | if forwards.is_empty() { 660 | output.extend(error("#[forwards] attribute used on struct with no forward members", input.ident.span())); 661 | return output.into(); 662 | } 663 | 664 | let mut output_thread_locals = TokenStream::new(); 665 | let mut output_trait = TokenStream::new(); 666 | let mut output_trait_impl = TokenStream::new(); 667 | let mut output_trait_impl_register = TokenStream::new(); 668 | let mut output_trait_impl_unregister = TokenStream::new(); 669 | 670 | for forward in forwards { 671 | let forward_ident = &forward.ident; 672 | let type_ident = format_ident!("__{}_forward", forward.ident); 673 | let global_ident = format_ident!("__g_{}_forward", forward.ident); 674 | 675 | let forward_name = forward.name.unwrap(); // TODO: Handle private forwards. 676 | let forward_exec_type = forward.exec_type; 677 | 678 | let mut forward_param_types = TokenStream::new(); 679 | 680 | let forward_call_return = forward.ret; 681 | let mut forward_call_args = TokenStream::new(); 682 | let mut forward_call_pushes = TokenStream::new(); 683 | 684 | for param in forward.params { 685 | let param_type = ¶m.ty; 686 | let param_name = ¶m.name.as_ref().unwrap().0; 687 | forward_param_types.extend(quote_spanned!(param_type.span() => 688 | <#param_type>::param_type(), 689 | )); 690 | forward_call_args.extend(quote_spanned!(param.span() => 691 | #param, 692 | )); 693 | forward_call_pushes.extend(quote_spanned!(param_name.span() => 694 | self.0.push(#param_name)?; 695 | )); 696 | } 697 | 698 | output.extend(quote_spanned!(forward.ident.span() => 699 | #[allow(non_camel_case_types)] 700 | struct #type_ident<'a>(&'a mut sm_ext::Forward); 701 | )); 702 | 703 | let execute_return = match &forward_call_return { 704 | syn::Type::Tuple(tuple) if tuple.elems.is_empty() => quote!(self.0.execute()?; Ok(())), 705 | _ => quote!(Ok(self.0.execute()?.into())), 706 | }; 707 | 708 | output.extend(quote_spanned!(forward.ident.span() => 709 | impl #type_ident<'_> { 710 | fn execute(&mut self, #forward_call_args) -> Result<#forward_call_return, sm_ext::SPError> { 711 | use sm_ext::Executable; 712 | #forward_call_pushes 713 | #execute_return 714 | } 715 | } 716 | )); 717 | 718 | output_thread_locals.extend(quote_spanned!(forward.ident.span() => 719 | #[allow(non_upper_case_globals)] 720 | static #global_ident: std::cell::RefCell> = std::cell::RefCell::new(None); 721 | )); 722 | 723 | output_trait.extend(quote_spanned!(forward.ident.span() => 724 | fn #forward_ident(f: F) -> R where F: FnOnce(&mut #type_ident) -> R; 725 | )); 726 | 727 | output_trait_impl_register.extend(quote_spanned!(forward.ident.span() => 728 | let #forward_ident = forward_manager.create_global_forward(#forward_name, #forward_exec_type, &[#forward_param_types])?; 729 | #global_ident.with(|fwd| { 730 | *fwd.borrow_mut() = Some(#forward_ident); 731 | }); 732 | )); 733 | 734 | output_trait_impl_unregister.extend(quote_spanned!(forward.ident.span() => 735 | #global_ident.with(|fwd| { 736 | *fwd.borrow_mut() = None; 737 | }); 738 | )); 739 | 740 | output_trait_impl.extend(quote_spanned!(forward.ident.span() => 741 | fn #forward_ident(f: F) -> R where F: FnOnce(&mut #type_ident) -> R { 742 | #global_ident.with(|fwd| { 743 | let mut fwd = fwd.borrow_mut(); 744 | let fwd = fwd.as_mut().unwrap(); 745 | let mut fwd = #type_ident(fwd); 746 | f(&mut fwd) 747 | }) 748 | } 749 | )); 750 | } 751 | 752 | output.extend(quote! { 753 | thread_local! { 754 | #output_thread_locals 755 | } 756 | }); 757 | 758 | let struct_ident = &input.ident; 759 | let trait_ident = format_ident!("__{}_forwards", input.ident); 760 | 761 | output.extend(quote! { 762 | #[allow(non_camel_case_types)] 763 | trait #trait_ident { 764 | fn register(forward_manager: &sm_ext::IForwardManager) -> Result<(), sm_ext::CreateForwardError>; 765 | fn unregister(); 766 | #output_trait 767 | } 768 | }); 769 | 770 | output.extend(quote! { 771 | impl #trait_ident for #struct_ident { 772 | fn register(forward_manager: &sm_ext::IForwardManager) -> Result<(), sm_ext::CreateForwardError> { 773 | use sm_ext::CallableParam; 774 | #output_trait_impl_register 775 | Ok(()) 776 | } 777 | 778 | fn unregister() { 779 | #output_trait_impl_unregister 780 | } 781 | 782 | #output_trait_impl 783 | } 784 | }); 785 | 786 | // println!("{}", output.to_string()); 787 | output.into() 788 | } 789 | 790 | #[proc_macro_attribute] 791 | pub fn vtable(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { 792 | let this_ptr_type = syn::parse_macro_input!(attr as syn::Path); 793 | let mut input = syn::parse_macro_input!(item as syn::ItemStruct); 794 | let mut output = TokenStream::new(); 795 | 796 | // println!("{}", input.to_token_stream().to_string()); 797 | 798 | input.attrs.push(syn::parse_quote!(#[doc(hidden)])); 799 | input.attrs.push(syn::parse_quote!(#[repr(C)])); 800 | 801 | let mut did_error = false; 802 | for field in &mut input.fields { 803 | if let syn::Type::BareFn(ty) = &mut field.ty { 804 | ty.unsafety = syn::parse_quote!(unsafe); 805 | ty.abi = syn::parse_quote!(extern "C"); 806 | 807 | // Prepend the thisptr argument 808 | ty.inputs.insert(0, syn::parse_quote!(this: #this_ptr_type)); 809 | } else { 810 | output.extend(error("All vtable struct fields must be bare functions", field.span())); 811 | did_error = true; 812 | } 813 | } 814 | 815 | if !did_error { 816 | input.attrs.push(syn::parse_quote!(#[cfg(not(all(windows, target_arch = "x86")))])); 817 | } 818 | 819 | output.extend(input.to_token_stream()); 820 | 821 | if did_error { 822 | return output.into(); 823 | } 824 | 825 | input.attrs.pop(); 826 | input.attrs.push(syn::parse_quote!(#[cfg(all(windows, target_arch = "x86", feature = "abi_thiscall"))])); 827 | 828 | for field in &mut input.fields { 829 | if let syn::Type::BareFn(ty) = &mut field.ty { 830 | if ty.variadic.is_none() { 831 | ty.abi = syn::parse_quote!(extern "thiscall"); 832 | } 833 | } 834 | } 835 | 836 | output.extend(input.to_token_stream()); 837 | 838 | input.attrs.pop(); 839 | input.attrs.push(syn::parse_quote!(#[cfg(all(windows, target_arch = "x86", not(feature = "abi_thiscall")))])); 840 | 841 | for field in &mut input.fields { 842 | if let syn::Type::BareFn(ty) = &mut field.ty { 843 | if ty.variadic.is_none() { 844 | ty.abi = syn::parse_quote!(extern "fastcall"); 845 | 846 | // Add a dummy argument to be passed in edx 847 | ty.inputs.insert(1, syn::parse_quote!(_dummy: *const usize)); 848 | } 849 | } 850 | } 851 | 852 | output.extend(input.to_token_stream()); 853 | 854 | // println!("{}", output.to_string()); 855 | 856 | output.into() 857 | } 858 | 859 | // TODO: This needs a lot of input checking and error reporting work 860 | #[proc_macro_attribute] 861 | pub fn vtable_override(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { 862 | let mut input = syn::parse_macro_input!(item as syn::ItemFn); 863 | let mut output = TokenStream::new(); 864 | 865 | // println!("{}", input.to_token_stream().to_string()); 866 | 867 | input.attrs.push(syn::parse_quote!(#[cfg(not(all(windows, target_arch = "x86")))])); 868 | 869 | input.sig.abi = syn::parse_quote!(extern "C"); 870 | 871 | output.extend(input.to_token_stream()); 872 | 873 | input.attrs.pop(); 874 | input.attrs.push(syn::parse_quote!(#[cfg(all(windows, target_arch = "x86", feature = "abi_thiscall"))])); 875 | 876 | input.sig.abi = syn::parse_quote!(extern "thiscall"); 877 | 878 | output.extend(input.to_token_stream()); 879 | 880 | input.attrs.pop(); 881 | input.attrs.push(syn::parse_quote!(#[cfg(all(windows, target_arch = "x86", not(feature = "abi_thiscall")))])); 882 | 883 | // Add a dummy argument to be passed in edx 884 | input.sig.inputs.insert(1, syn::parse_quote!(_dummy: *const usize)); 885 | 886 | input.sig.abi = syn::parse_quote!(extern "fastcall"); 887 | 888 | output.extend(input.to_token_stream()); 889 | 890 | // println!("{}", output.to_string()); 891 | 892 | output.into() 893 | } 894 | 895 | fn error(s: &str, span: Span) -> TokenStream { 896 | let mut v = Vec::new(); 897 | v.push(respan(Literal::string(&s), Span::call_site())); 898 | let group = v.into_iter().collect(); 899 | 900 | let mut r = Vec::::new(); 901 | r.push(respan(Ident::new("compile_error", span), span)); 902 | r.push(respan(Punct::new('!', Spacing::Alone), span)); 903 | r.push(respan(Group::new(Delimiter::Brace, group), span)); 904 | 905 | r.into_iter().collect() 906 | } 907 | 908 | fn respan>(t: T, span: Span) -> TokenTree { 909 | let mut t = t.into(); 910 | t.set_span(span); 911 | t 912 | } 913 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "abi_thiscall", feature(abi_thiscall))] 2 | #![allow(non_snake_case, non_camel_case_types, unused_variables)] 3 | //! This interface is extremely unstable, everything just lives in a soup at the top level for now. 4 | 5 | use std::convert::TryFrom; 6 | use std::error::Error; 7 | use std::ffi::{CStr, CString, NulError}; 8 | use std::os::raw::{c_char, c_int, c_uint, c_void}; 9 | use std::ptr::{null, null_mut}; 10 | use std::rc::Rc; 11 | use std::str::Utf8Error; 12 | 13 | pub use c_str_macro::c_str; 14 | pub use libc::size_t; 15 | 16 | pub use sm_ext_derive::{forwards, native, vtable, vtable_override, ICallableApi, SMExtension, SMInterfaceApi}; 17 | 18 | #[repr(transparent)] 19 | pub struct IdentityType(c_uint); 20 | 21 | #[repr(C)] 22 | pub enum FeatureType { 23 | Native = 0, 24 | Capability = 1, 25 | } 26 | 27 | #[repr(C)] 28 | pub enum FeatureStatus { 29 | Available = 0, 30 | Unavailable = 1, 31 | Unknown = 2, 32 | } 33 | 34 | // TODO: Investigate using a `union` for this instead. 35 | /// Wrapper type that represents a value from SourcePawn. 36 | /// 37 | /// Could be a [`i32`], [`f32`], `&i32`, `&f32`, or `&i8` (for character strings). 38 | #[repr(transparent)] 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 40 | pub struct cell_t(i32); 41 | 42 | impl std::fmt::Display for cell_t { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 44 | self.0.fmt(f) 45 | } 46 | } 47 | 48 | /// Trait to support conversions to/from [`cell_t`] that require an [`IPluginContext`] for access to plugin memory. 49 | pub trait TryFromPlugin<'ctx, T = cell_t>: Sized { 50 | type Error; 51 | 52 | fn try_from_plugin(ctx: &'ctx crate::IPluginContext, value: T) -> Result; 53 | } 54 | 55 | impl TryFromPlugin<'_, T> for U 56 | where 57 | U: TryFrom, 58 | { 59 | type Error = U::Error; 60 | 61 | fn try_from_plugin(ctx: &IPluginContext, value: T) -> Result { 62 | TryFrom::try_from(value) 63 | } 64 | } 65 | 66 | /// Trait to support conversions to/from [`cell_t`] that require an [`IPluginContext`] for access to plugin memory. 67 | /// 68 | /// As with Rust's [`TryInto`](std::convert::TryInto) and [`TryFrom`](std::convert::TryFrom), this is implemented automatically 69 | /// for types that implement [`TryFromPlugin`] which you should prefer to implement instead. 70 | pub trait TryIntoPlugin<'ctx, T = cell_t>: Sized { 71 | type Error; 72 | 73 | fn try_into_plugin(self, ctx: &'ctx IPluginContext) -> Result; 74 | } 75 | 76 | impl<'ctx, T, U> TryIntoPlugin<'ctx, U> for T 77 | where 78 | U: TryFromPlugin<'ctx, T>, 79 | { 80 | type Error = U::Error; 81 | 82 | fn try_into_plugin(self, ctx: &'ctx IPluginContext) -> Result { 83 | U::try_from_plugin(ctx, self) 84 | } 85 | } 86 | 87 | impl From for cell_t { 88 | fn from(x: i32) -> Self { 89 | cell_t(x) 90 | } 91 | } 92 | 93 | impl From for i32 { 94 | fn from(x: cell_t) -> Self { 95 | x.0 96 | } 97 | } 98 | 99 | impl From for cell_t { 100 | fn from(x: f32) -> Self { 101 | cell_t(x.to_bits() as i32) 102 | } 103 | } 104 | 105 | impl From for f32 { 106 | fn from(x: cell_t) -> Self { 107 | f32::from_bits(x.0 as u32) 108 | } 109 | } 110 | 111 | impl<'ctx> TryFromPlugin<'ctx> for &'ctx CStr { 112 | type Error = SPError; 113 | 114 | fn try_from_plugin(ctx: &'ctx IPluginContext, value: cell_t) -> Result { 115 | Ok(ctx.local_to_string(value)?) 116 | } 117 | } 118 | 119 | impl<'ctx> TryFromPlugin<'ctx> for &'ctx str { 120 | type Error = Box; 121 | 122 | fn try_from_plugin(ctx: &'ctx IPluginContext, value: cell_t) -> Result { 123 | Ok(ctx.local_to_string(value)?.to_str()?) 124 | } 125 | } 126 | 127 | // TODO: These &mut implementations seem risky, maybe a SPRef/SPString/SPArray wrapper object would be a better way to go... 128 | 129 | impl<'ctx> TryFromPlugin<'ctx> for &'ctx mut cell_t { 130 | type Error = SPError; 131 | 132 | fn try_from_plugin(ctx: &'ctx IPluginContext, value: cell_t) -> Result { 133 | Ok(ctx.local_to_phys_addr(value)?) 134 | } 135 | } 136 | 137 | impl<'ctx> TryFromPlugin<'ctx> for &'ctx mut i32 { 138 | type Error = SPError; 139 | 140 | fn try_from_plugin(ctx: &'ctx IPluginContext, value: cell_t) -> Result { 141 | let cell: &mut cell_t = value.try_into_plugin(ctx)?; 142 | unsafe { Ok(&mut *(cell as *mut cell_t as *mut i32)) } 143 | } 144 | } 145 | 146 | impl<'ctx> TryFromPlugin<'ctx> for &'ctx mut f32 { 147 | type Error = SPError; 148 | 149 | fn try_from_plugin(ctx: &'ctx IPluginContext, value: cell_t) -> Result { 150 | let cell: &mut cell_t = value.try_into_plugin(ctx)?; 151 | unsafe { Ok(&mut *(cell as *mut cell_t as *mut f32)) } 152 | } 153 | } 154 | 155 | /// Struct to contain name/fnptr pairs for native registration. 156 | /// 157 | /// SourceMod has very strict lifetime requirements for this data and you should not construct 158 | /// instances of this type yourself - use the [`register_natives!`] macro instead. 159 | #[repr(C)] 160 | pub struct NativeInfo { 161 | pub name: *const c_char, 162 | pub func: Option cell_t>, 163 | } 164 | 165 | pub struct IdentityToken { 166 | _private: [u8; 0], 167 | } 168 | 169 | pub type IdentityTokenPtr = *mut IdentityToken; 170 | 171 | pub type IExtensionInterfacePtr = *mut *mut IExtensionInterfaceVtable; 172 | 173 | #[vtable(IExtensionInterfacePtr)] 174 | pub struct IExtensionInterfaceVtable { 175 | pub GetExtensionVersion: fn() -> i32, 176 | pub OnExtensionLoad: fn(me: IExtensionPtr, sys: IShareSysPtr, error: *mut c_char, maxlength: size_t, late: bool) -> bool, 177 | pub OnExtensionUnload: fn() -> (), 178 | pub OnExtensionsAllLoaded: fn() -> (), 179 | pub OnExtensionPauseChange: fn(pause: bool) -> (), 180 | pub QueryInterfaceDrop: fn(interface: SMInterfacePtr) -> bool, 181 | pub NotifyInterfaceDrop: fn(interface: SMInterfacePtr) -> (), 182 | pub QueryRunning: fn(error: *mut c_char, maxlength: size_t) -> bool, 183 | pub IsMetamodExtension: fn() -> bool, 184 | pub GetExtensionName: fn() -> *const c_char, 185 | pub GetExtensionURL: fn() -> *const c_char, 186 | pub GetExtensionTag: fn() -> *const c_char, 187 | pub GetExtensionAuthor: fn() -> *const c_char, 188 | pub GetExtensionVerString: fn() -> *const c_char, 189 | pub GetExtensionDescription: fn() -> *const c_char, 190 | pub GetExtensionDateString: fn() -> *const c_char, 191 | pub OnCoreMapStart: fn(edict_list: *mut c_void, edict_count: c_int, client_max: c_int) -> (), 192 | pub OnDependenciesDropped: fn() -> (), 193 | pub OnCoreMapEnd: fn() -> (), 194 | } 195 | 196 | // There appears to be a bug with the MSVC linker in release mode dropping these symbols when threaded 197 | // compilation is enabled - if you run into undefined symbol errors here try setting code-units to 1. 198 | pub trait IExtensionInterface { 199 | fn on_extension_load(&mut self, me: IExtension, sys: IShareSys, late: bool) -> Result<(), Box> { 200 | Ok(()) 201 | } 202 | fn on_extension_unload(&mut self) {} 203 | fn on_extensions_all_loaded(&mut self) {} 204 | fn on_extension_pause_change(&mut self, pause: bool) {} 205 | fn on_core_map_start(&mut self, edict_list: *mut c_void, edict_count: i32, client_max: i32) {} 206 | fn on_core_map_end(&mut self) {} 207 | fn query_interface_drop(&mut self, interface: SMInterface) -> bool { 208 | false 209 | } 210 | fn notify_interface_drop(&mut self, interface: SMInterface) {} 211 | fn query_running(&mut self) -> Result<(), CString> { 212 | Ok(()) 213 | } 214 | fn on_dependencies_dropped(&mut self) {} 215 | } 216 | 217 | pub trait ExtensionMetadata { 218 | fn get_extension_name(&self) -> &'static CStr; 219 | fn get_extension_url(&self) -> &'static CStr; 220 | fn get_extension_tag(&self) -> &'static CStr; 221 | fn get_extension_author(&self) -> &'static CStr; 222 | fn get_extension_ver_string(&self) -> &'static CStr; 223 | fn get_extension_description(&self) -> &'static CStr; 224 | fn get_extension_date_string(&self) -> &'static CStr; 225 | } 226 | 227 | #[repr(C)] 228 | pub struct IExtensionInterfaceAdapter { 229 | vtable: *mut IExtensionInterfaceVtable, 230 | pub delegate: T, 231 | } 232 | 233 | impl Drop for IExtensionInterfaceAdapter { 234 | fn drop(&mut self) { 235 | unsafe { 236 | drop(Box::from_raw(self.vtable)); 237 | } 238 | } 239 | } 240 | 241 | impl IExtensionInterfaceAdapter { 242 | pub fn new(delegate: T) -> IExtensionInterfaceAdapter { 243 | let vtable = IExtensionInterfaceVtable { 244 | GetExtensionVersion: IExtensionInterfaceAdapter::::get_extension_version, 245 | OnExtensionLoad: IExtensionInterfaceAdapter::::on_extension_load, 246 | OnExtensionUnload: IExtensionInterfaceAdapter::::on_extension_unload, 247 | OnExtensionsAllLoaded: IExtensionInterfaceAdapter::::on_extensions_all_loaded, 248 | OnExtensionPauseChange: IExtensionInterfaceAdapter::::on_extension_pause_change, 249 | QueryInterfaceDrop: IExtensionInterfaceAdapter::::query_interface_drop, 250 | NotifyInterfaceDrop: IExtensionInterfaceAdapter::::notify_interface_drop, 251 | QueryRunning: IExtensionInterfaceAdapter::::query_running, 252 | IsMetamodExtension: IExtensionInterfaceAdapter::::is_metamod_extension, 253 | GetExtensionName: IExtensionInterfaceAdapter::::get_extension_name, 254 | GetExtensionURL: IExtensionInterfaceAdapter::::get_extension_url, 255 | GetExtensionTag: IExtensionInterfaceAdapter::::get_extension_tag, 256 | GetExtensionAuthor: IExtensionInterfaceAdapter::::get_extension_author, 257 | GetExtensionVerString: IExtensionInterfaceAdapter::::get_extension_ver_string, 258 | GetExtensionDescription: IExtensionInterfaceAdapter::::get_extension_description, 259 | GetExtensionDateString: IExtensionInterfaceAdapter::::get_extension_date_string, 260 | OnCoreMapStart: IExtensionInterfaceAdapter::::on_core_map_start, 261 | OnDependenciesDropped: IExtensionInterfaceAdapter::::on_dependencies_dropped, 262 | OnCoreMapEnd: IExtensionInterfaceAdapter::::on_core_map_end, 263 | }; 264 | 265 | IExtensionInterfaceAdapter { vtable: Box::into_raw(Box::new(vtable)), delegate } 266 | } 267 | 268 | #[vtable_override] 269 | unsafe fn get_extension_version(this: IExtensionInterfacePtr) -> i32 { 270 | 8 271 | } 272 | 273 | #[vtable_override] 274 | unsafe fn on_extension_load(this: IExtensionInterfacePtr, me: IExtensionPtr, sys: IShareSysPtr, error: *mut c_char, maxlength: size_t, late: bool) -> bool { 275 | let result = std::panic::catch_unwind(|| (*this.cast::()).delegate.on_extension_load(IExtension(me), IShareSys(sys), late)); 276 | 277 | match result { 278 | Ok(result) => match result { 279 | Ok(result) => true, 280 | Err(err) => { 281 | let err = CString::new(err.to_string()).unwrap_or_else(|_| c_str!("load error message contained NUL byte").into()); 282 | libc::strncpy(error, err.as_ptr(), maxlength); 283 | false 284 | } 285 | }, 286 | Err(err) => { 287 | let msg = format!( 288 | "load panicked: {}", 289 | if let Some(str_slice) = err.downcast_ref::<&'static str>() { 290 | str_slice 291 | } else if let Some(string) = err.downcast_ref::() { 292 | string 293 | } else { 294 | "unknown message" 295 | } 296 | ); 297 | 298 | let msg = CString::new(msg).unwrap_or_else(|_| c_str!("load panic message contained NUL byte").into()); 299 | libc::strncpy(error, msg.as_ptr(), maxlength); 300 | false 301 | } 302 | } 303 | } 304 | 305 | #[vtable_override] 306 | unsafe fn on_extension_unload(this: IExtensionInterfacePtr) { 307 | let _ = std::panic::catch_unwind(|| (*this.cast::()).delegate.on_extension_unload()); 308 | } 309 | 310 | #[vtable_override] 311 | unsafe fn on_extensions_all_loaded(this: IExtensionInterfacePtr) { 312 | let _ = std::panic::catch_unwind(|| (*this.cast::()).delegate.on_extensions_all_loaded()); 313 | } 314 | 315 | #[vtable_override] 316 | unsafe fn on_extension_pause_change(this: IExtensionInterfacePtr, pause: bool) { 317 | let _ = std::panic::catch_unwind(|| (*this.cast::()).delegate.on_extension_pause_change(pause)); 318 | } 319 | 320 | #[vtable_override] 321 | unsafe fn query_interface_drop(this: IExtensionInterfacePtr, interface: SMInterfacePtr) -> bool { 322 | let result = std::panic::catch_unwind(|| (*this.cast::()).delegate.query_interface_drop(SMInterface(interface))); 323 | 324 | match result { 325 | Ok(result) => result, 326 | Err(_) => false, 327 | } 328 | } 329 | 330 | #[vtable_override] 331 | unsafe fn notify_interface_drop(this: IExtensionInterfacePtr, interface: SMInterfacePtr) { 332 | let _ = std::panic::catch_unwind(|| (*this.cast::()).delegate.notify_interface_drop(SMInterface(interface))); 333 | } 334 | 335 | #[vtable_override] 336 | unsafe fn query_running(this: IExtensionInterfacePtr, error: *mut c_char, maxlength: size_t) -> bool { 337 | let result = std::panic::catch_unwind(|| match (*this.cast::()).delegate.query_running() { 338 | Ok(_) => true, 339 | Err(str) => { 340 | libc::strncpy(error, str.as_ptr(), maxlength); 341 | false 342 | } 343 | }); 344 | 345 | match result { 346 | Ok(result) => result, 347 | Err(_) => { 348 | libc::strncpy(error, c_str!("query running callback panicked").as_ptr(), maxlength); 349 | false 350 | } 351 | } 352 | } 353 | 354 | #[vtable_override] 355 | unsafe fn is_metamod_extension(this: IExtensionInterfacePtr) -> bool { 356 | false 357 | } 358 | 359 | #[vtable_override] 360 | unsafe fn get_extension_name(this: IExtensionInterfacePtr) -> *const c_char { 361 | (*this.cast::()).delegate.get_extension_name().as_ptr() 362 | } 363 | 364 | #[vtable_override] 365 | unsafe fn get_extension_url(this: IExtensionInterfacePtr) -> *const c_char { 366 | (*this.cast::()).delegate.get_extension_url().as_ptr() 367 | } 368 | 369 | #[vtable_override] 370 | unsafe fn get_extension_tag(this: IExtensionInterfacePtr) -> *const c_char { 371 | (*this.cast::()).delegate.get_extension_tag().as_ptr() 372 | } 373 | 374 | #[vtable_override] 375 | unsafe fn get_extension_author(this: IExtensionInterfacePtr) -> *const c_char { 376 | (*this.cast::()).delegate.get_extension_author().as_ptr() 377 | } 378 | 379 | #[vtable_override] 380 | unsafe fn get_extension_ver_string(this: IExtensionInterfacePtr) -> *const c_char { 381 | (*this.cast::()).delegate.get_extension_ver_string().as_ptr() 382 | } 383 | 384 | #[vtable_override] 385 | unsafe fn get_extension_description(this: IExtensionInterfacePtr) -> *const c_char { 386 | (*this.cast::()).delegate.get_extension_description().as_ptr() 387 | } 388 | 389 | #[vtable_override] 390 | unsafe fn get_extension_date_string(this: IExtensionInterfacePtr) -> *const c_char { 391 | (*this.cast::()).delegate.get_extension_date_string().as_ptr() 392 | } 393 | 394 | #[vtable_override] 395 | unsafe fn on_core_map_start(this: IExtensionInterfacePtr, edict_list: *mut c_void, edict_count: c_int, client_max: c_int) { 396 | let _ = std::panic::catch_unwind(|| (*this.cast::()).delegate.on_core_map_start(edict_list, edict_count, client_max)); 397 | } 398 | 399 | #[vtable_override] 400 | unsafe fn on_dependencies_dropped(this: IExtensionInterfacePtr) { 401 | let _ = std::panic::catch_unwind(|| (*this.cast::()).delegate.on_dependencies_dropped()); 402 | } 403 | 404 | #[vtable_override] 405 | unsafe fn on_core_map_end(this: IExtensionInterfacePtr) { 406 | let _ = std::panic::catch_unwind(|| (*this.cast::()).delegate.on_core_map_end()); 407 | } 408 | } 409 | 410 | pub type IExtensionPtr = *mut *mut IExtensionVtable; 411 | 412 | #[vtable(IExtensionPtr)] 413 | pub struct IExtensionVtable { 414 | pub IsLoaded: fn() -> bool, 415 | pub GetAPI: fn() -> IExtensionInterfacePtr, 416 | pub GetFilename: fn() -> *const c_char, 417 | pub GetIdentity: fn() -> IdentityTokenPtr, 418 | _FindFirstDependency: fn() -> *mut c_void, 419 | _FindNextDependency: fn() -> *mut c_void, 420 | _FreeDependencyIterator: fn() -> *mut c_void, 421 | pub IsRunning: fn(error: *mut c_char, maxlength: size_t) -> bool, 422 | pub IsExternal: fn() -> bool, 423 | } 424 | 425 | #[derive(Debug)] 426 | pub enum IsRunningError<'str> { 427 | WithReason(&'str str), 428 | InvalidReason(Utf8Error), 429 | } 430 | 431 | impl std::fmt::Display for IsRunningError<'_> { 432 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 433 | std::fmt::Debug::fmt(self, f) 434 | } 435 | } 436 | 437 | impl Error for IsRunningError<'_> {} 438 | 439 | #[derive(Debug)] 440 | pub struct IExtension(IExtensionPtr); 441 | 442 | impl IExtension { 443 | pub fn is_loaded(&self) -> bool { 444 | unsafe { virtual_call!(IsLoaded, self.0) } 445 | } 446 | 447 | pub fn get_api(&self) -> IExtensionInterfacePtr { 448 | unsafe { virtual_call!(GetAPI, self.0) } 449 | } 450 | 451 | pub fn get_filename(&self) -> Result<&str, Utf8Error> { 452 | unsafe { 453 | let c_name = virtual_call!(GetFilename, self.0); 454 | 455 | CStr::from_ptr(c_name).to_str() 456 | } 457 | } 458 | 459 | pub fn get_identity(&self) -> IdentityTokenPtr { 460 | unsafe { virtual_call!(GetIdentity, self.0) } 461 | } 462 | 463 | pub fn is_running(&self) -> Result<(), IsRunningError> { 464 | unsafe { 465 | let mut c_error = [0 as c_char; 256]; 466 | let result = virtual_call!(IsRunning, self.0, c_error.as_mut_ptr(), c_error.len()); 467 | 468 | if result { 469 | Ok(()) 470 | } else { 471 | match CStr::from_ptr(c_error.as_ptr()).to_str() { 472 | Ok(error) => Err(IsRunningError::WithReason(error)), 473 | Err(e) => Err(IsRunningError::InvalidReason(e)), 474 | } 475 | } 476 | } 477 | } 478 | 479 | pub fn is_external(&self) -> bool { 480 | unsafe { virtual_call!(IsExternal, self.0) } 481 | } 482 | } 483 | 484 | pub type SMInterfacePtr = *mut *mut SMInterfaceVtable; 485 | 486 | #[vtable(SMInterfacePtr)] 487 | pub struct SMInterfaceVtable { 488 | pub GetInterfaceVersion: fn() -> c_uint, 489 | pub GetInterfaceName: fn() -> *const c_char, 490 | pub IsVersionCompatible: fn(version: c_uint) -> bool, 491 | } 492 | 493 | pub trait RequestableInterface { 494 | fn get_interface_name() -> &'static str; 495 | fn get_interface_version() -> u32; 496 | 497 | /// # Safety 498 | /// 499 | /// Only for use internally by [`IShareSys::request_interface`], which always knows the correct type. 500 | unsafe fn from_raw_interface(iface: SMInterface) -> Self; 501 | } 502 | 503 | pub trait SMInterfaceApi { 504 | fn get_interface_version(&self) -> u32; 505 | fn get_interface_name(&self) -> &str; 506 | fn is_version_compatible(&self, version: u32) -> bool; 507 | } 508 | 509 | #[derive(Debug, SMInterfaceApi)] 510 | pub struct SMInterface(SMInterfacePtr); 511 | 512 | pub type IFeatureProviderPtr = *mut *mut IFeatureProviderVtable; 513 | 514 | #[vtable(IFeatureProviderPtr)] 515 | pub struct IFeatureProviderVtable {} 516 | 517 | pub type IPluginRuntimePtr = *mut *mut IPluginRuntimeVtable; 518 | 519 | #[vtable(IPluginRuntimePtr)] 520 | pub struct IPluginRuntimeVtable {} 521 | 522 | pub type IShareSysPtr = *mut *mut IShareSysVtable; 523 | 524 | #[vtable(IShareSysPtr)] 525 | pub struct IShareSysVtable { 526 | pub AddInterface: fn(myself: IExtensionPtr, iface: SMInterfacePtr) -> bool, 527 | pub RequestInterface: fn(iface_name: *const c_char, iface_vers: c_uint, myself: IExtensionPtr, iface: *mut SMInterfacePtr) -> bool, 528 | pub AddNatives: fn(myself: IExtensionPtr, natives: *const NativeInfo) -> (), 529 | pub CreateIdentType: fn(name: *const c_char) -> IdentityType, 530 | pub FindIdentType: fn(name: *const c_char) -> IdentityType, 531 | pub CreateIdentity: fn(ident_type: IdentityType, ptr: *mut c_void) -> IdentityTokenPtr, 532 | pub DestroyIdentType: fn(ident_type: IdentityType) -> (), 533 | pub DestroyIdentity: fn(identity: IdentityTokenPtr) -> (), 534 | pub AddDependency: fn(myself: IExtensionPtr, filename: *const c_char, require: bool, autoload: bool) -> (), 535 | pub RegisterLibrary: fn(myself: IExtensionPtr, name: *const c_char) -> (), 536 | _OverrideNatives: fn(myself: IExtensionPtr, natives: *const NativeInfo) -> (), 537 | pub AddCapabilityProvider: fn(myself: IExtensionPtr, provider: IFeatureProviderPtr, name: *const c_char) -> (), 538 | pub DropCapabilityProvider: fn(myself: IExtensionPtr, provider: IFeatureProviderPtr, name: *const c_char) -> (), 539 | pub TestFeature: fn(rt: IPluginRuntimePtr, feature_type: FeatureType, name: *const c_char) -> FeatureStatus, 540 | } 541 | 542 | #[derive(Debug)] 543 | pub enum RequestInterfaceError { 544 | InvalidName(NulError), 545 | InvalidInterface(String, u32), 546 | } 547 | 548 | impl std::fmt::Display for RequestInterfaceError { 549 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 550 | match self { 551 | RequestInterfaceError::InvalidName(err) => write!(f, "invalid interface name: {}", err), 552 | RequestInterfaceError::InvalidInterface(name, ver) => write!(f, "failed to get {} interface version {}", name, ver), 553 | } 554 | } 555 | } 556 | 557 | impl Error for RequestInterfaceError { 558 | fn source(&self) -> Option<&(dyn Error + 'static)> { 559 | match self { 560 | RequestInterfaceError::InvalidName(err) => Some(err), 561 | RequestInterfaceError::InvalidInterface(_, _) => None, 562 | } 563 | } 564 | } 565 | 566 | #[derive(Debug)] 567 | pub struct IShareSys(IShareSysPtr); 568 | 569 | impl IShareSys { 570 | pub fn request_interface(&self, myself: &IExtension) -> Result { 571 | let iface = self.request_raw_interface(myself, I::get_interface_name(), I::get_interface_version())?; 572 | 573 | unsafe { Ok(I::from_raw_interface(iface)) } 574 | } 575 | 576 | pub fn request_raw_interface(&self, myself: &IExtension, name: &str, version: u32) -> Result { 577 | let c_name = CString::new(name).map_err(RequestInterfaceError::InvalidName)?; 578 | 579 | unsafe { 580 | let mut iface: SMInterfacePtr = null_mut(); 581 | let res = virtual_call!(RequestInterface, self.0, c_name.as_ptr(), version, myself.0, &mut iface); 582 | 583 | if res { 584 | Ok(SMInterface(iface)) 585 | } else { 586 | Err(RequestInterfaceError::InvalidInterface(name.into(), version)) 587 | } 588 | } 589 | } 590 | 591 | /// # Safety 592 | /// 593 | /// This should be be used via the [`register_natives!`] macro only. 594 | pub unsafe fn add_natives(&self, myself: &IExtension, natives: *const NativeInfo) { 595 | virtual_call!(AddNatives, self.0, myself.0, natives) 596 | } 597 | } 598 | 599 | /// Error codes for SourcePawn routines. 600 | #[repr(C)] 601 | #[derive(Debug)] 602 | pub enum SPError { 603 | /// No error occurred 604 | None = 0, 605 | /// File format unrecognized 606 | FileFormat = 1, 607 | /// A decompressor was not found 608 | Decompressor = 2, 609 | /// Not enough space left on the heap 610 | HeapLow = 3, 611 | /// Invalid parameter or parameter type 612 | Param = 4, 613 | /// A memory address was not valid 614 | InvalidAddress = 5, 615 | /// The object in question was not found 616 | NotFound = 6, 617 | /// Invalid index parameter 618 | Index = 7, 619 | /// Not enough space left on the stack 620 | StackLow = 8, 621 | /// Debug mode was not on or debug section not found 622 | NotDebugging = 9, 623 | /// Invalid instruction was encountered 624 | InvalidInstruction = 10, 625 | /// Invalid memory access 626 | MemAccess = 11, 627 | /// Stack went beyond its minimum value 628 | StackMin = 12, 629 | /// Heap went beyond its minimum value 630 | HeapMin = 13, 631 | /// Division by zero 632 | DivideByZero = 14, 633 | /// Array index is out of bounds 634 | ArrayBounds = 15, 635 | /// Instruction had an invalid parameter 636 | InstructionParam = 16, 637 | /// A native leaked an item on the stack 638 | StackLeak = 17, 639 | /// A native leaked an item on the heap 640 | HeapLeak = 18, 641 | /// A dynamic array is too big 642 | ArrayTooBig = 19, 643 | /// Tracker stack is out of bounds 644 | TrackerBounds = 20, 645 | /// Native was pending or invalid 646 | InvalidNative = 21, 647 | /// Maximum number of parameters reached 648 | ParamsMax = 22, 649 | /// Error originates from a native 650 | Native = 23, 651 | /// Function or plugin is not runnable 652 | NotRunnable = 24, 653 | /// Function call was aborted 654 | Aborted = 25, 655 | /// Code is too old for this VM 656 | CodeTooOld = 26, 657 | /// Code is too new for this VM 658 | CodeTooNew = 27, 659 | /// Out of memory 660 | OutOfMemory = 28, 661 | /// Integer overflow (-INT_MIN / -1) 662 | IntegerOverflow = 29, 663 | /// Timeout 664 | Timeout = 30, 665 | /// Custom message 666 | User = 31, 667 | /// Custom fatal message 668 | Fatal = 32, 669 | } 670 | 671 | impl std::fmt::Display for SPError { 672 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 673 | f.pad(match self { 674 | SPError::None => "no error occurred", 675 | SPError::FileFormat => "unrecognizable file format", 676 | SPError::Decompressor => "decompressor was not found", 677 | SPError::HeapLow => "not enough space on the heap", 678 | SPError::Param => "invalid parameter or parameter type", 679 | SPError::InvalidAddress => "invalid plugin address", 680 | SPError::NotFound => "object or index not found", 681 | SPError::Index => "invalid index or index not found", 682 | SPError::StackLow => "not enough space on the stack", 683 | SPError::NotDebugging => "debug section not found or debug not enabled", 684 | SPError::InvalidInstruction => "invalid instruction", 685 | SPError::MemAccess => "invalid memory access", 686 | SPError::StackMin => "stack went below stack boundary", 687 | SPError::HeapMin => "heap went below heap boundary", 688 | SPError::DivideByZero => "divide by zero", 689 | SPError::ArrayBounds => "array index is out of bounds", 690 | SPError::InstructionParam => "instruction contained invalid parameter", 691 | SPError::StackLeak => "stack memory leaked by native", 692 | SPError::HeapLeak => "heap memory leaked by native", 693 | SPError::ArrayTooBig => "dynamic array is too big", 694 | SPError::TrackerBounds => "tracker stack is out of bounds", 695 | SPError::InvalidNative => "native is not bound", 696 | SPError::ParamsMax => "maximum number of parameters reached", 697 | SPError::Native => "native detected error", 698 | SPError::NotRunnable => "plugin not runnable", 699 | SPError::Aborted => "call was aborted", 700 | SPError::CodeTooOld => "plugin format is too old", 701 | SPError::CodeTooNew => "plugin format is too new", 702 | SPError::OutOfMemory => "out of memory", 703 | SPError::IntegerOverflow => "integer overflow", 704 | SPError::Timeout => "script execution timed out", 705 | SPError::User => "custom error", 706 | SPError::Fatal => "fatal error", 707 | }) 708 | } 709 | } 710 | 711 | impl Error for SPError {} 712 | 713 | pub type IPluginContextPtr = *mut *mut IPluginContextVtable; 714 | 715 | #[vtable(IPluginContextPtr)] 716 | pub struct IPluginContextVtable { 717 | _Destructor: fn() -> (), 718 | #[cfg(not(windows))] 719 | _Destructor2: fn() -> (), 720 | _GetVirtualMachine: fn(), 721 | _GetContext: fn(), 722 | _IsDebugging: fn(), 723 | _SetDebugBreak: fn(), 724 | _GetDebugInfo: fn(), 725 | _HeapAlloc: fn(), 726 | _HeapPop: fn(), 727 | _HeapRelease: fn(), 728 | _FindNativeByName: fn(), 729 | _GetNativeByIndex: fn(), 730 | _GetNativesNum: fn(), 731 | _FindPublicByName: fn(), 732 | _GetPublicByIndex: fn(), 733 | _GetPublicsNum: fn(), 734 | _GetPubvarByIndex: fn(), 735 | _FindPubvarByName: fn(), 736 | _GetPubvarAddrs: fn(), 737 | _GetPubVarsNum: fn(), 738 | pub LocalToPhysAddr: fn(local_addr: cell_t, phys_addr: *mut *mut cell_t) -> SPError, 739 | pub LocalToString: fn(local_addr: cell_t, addr: *mut *mut c_char) -> SPError, 740 | _StringToLocal: fn(), 741 | _StringToLocalUTF8: fn(), 742 | _PushCell: fn(), 743 | _PushCellArray: fn(), 744 | _PushString: fn(), 745 | _PushCellsFromArray: fn(), 746 | _BindNatives: fn(), 747 | _BindNative: fn(), 748 | _BindNativeToAny: fn(), 749 | _Execute: fn(), 750 | _ThrowNativeErrorEx: fn(), 751 | pub ThrowNativeError: fn(*const c_char, ...) -> cell_t, 752 | pub GetFunctionByName: fn(public_name: *const c_char) -> IPluginFunctionPtr, 753 | pub GetFunctionById: fn(func_id: u32) -> IPluginFunctionPtr, 754 | pub GetIdentity: fn() -> IdentityTokenPtr, 755 | _GetNullRef: fn(), 756 | _LocalToStringNULL: fn(), 757 | _BindNativeToIndex: fn(), 758 | _IsInExec: fn(), 759 | _GetRuntime: fn(), 760 | _Execute2: fn(), 761 | _GetLastNativeError: fn(), 762 | _GetLocalParams: fn(), 763 | _SetKey: fn(), 764 | _GetKey: fn(), 765 | _ClearLastNativeError: fn(), 766 | _APIv2: fn(), 767 | _ReportError: fn(), 768 | _ReportErrorVA: fn(), 769 | _ReportFatalError: fn(), 770 | _ReportFatalErrorVA: fn(), 771 | _ReportErrorNumber: fn(), 772 | _BlamePluginError: fn(), 773 | _CreateFrameIterator: fn(), 774 | _DestroyFrameIterator: fn(), 775 | } 776 | 777 | #[derive(Debug)] 778 | pub struct IPluginContext(IPluginContextPtr); 779 | 780 | #[derive(Debug)] 781 | pub enum GetFunctionError { 782 | UnknownFunction, 783 | } 784 | 785 | impl std::fmt::Display for GetFunctionError { 786 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 787 | std::fmt::Debug::fmt(self, f) 788 | } 789 | } 790 | 791 | impl Error for GetFunctionError {} 792 | 793 | impl IPluginContext { 794 | pub fn local_to_phys_addr(&self, local: cell_t) -> Result<&mut cell_t, SPError> { 795 | unsafe { 796 | let mut addr: *mut cell_t = null_mut(); 797 | let res = virtual_call!(LocalToPhysAddr, self.0, local, &mut addr); 798 | 799 | match res { 800 | SPError::None => Ok(&mut *addr), 801 | _ => Err(res), 802 | } 803 | } 804 | } 805 | 806 | pub fn local_to_string(&self, local: cell_t) -> Result<&CStr, SPError> { 807 | unsafe { 808 | let mut addr: *mut c_char = null_mut(); 809 | let res = virtual_call!(LocalToString, self.0, local, &mut addr); 810 | 811 | match res { 812 | SPError::None => Ok(CStr::from_ptr(addr)), 813 | _ => Err(res), 814 | } 815 | } 816 | } 817 | 818 | pub fn throw_native_error(&self, err: String) -> cell_t { 819 | let fmt = c_str!("%s"); 820 | let err = CString::new(err).unwrap_or_else(|_| c_str!("native error message contained NUL byte").into()); 821 | unsafe { virtual_call_varargs!(ThrowNativeError, self.0, fmt.as_ptr(), err.as_ptr()) } 822 | } 823 | 824 | pub fn get_function_by_id(&self, func_id: u32) -> Result { 825 | unsafe { 826 | let function = virtual_call!(GetFunctionById, self.0, func_id); 827 | if function.is_null() { 828 | Err(GetFunctionError::UnknownFunction) 829 | } else { 830 | Ok(IPluginFunction(function, self)) 831 | } 832 | } 833 | } 834 | 835 | pub fn get_identity(&self) -> IdentityTokenPtr { 836 | unsafe { virtual_call!(GetIdentity, self.0) } 837 | } 838 | } 839 | 840 | pub type IPluginFunctionPtr = *mut *mut IPluginFunctionVtable; 841 | 842 | #[vtable(IPluginFunctionPtr)] 843 | pub struct IPluginFunctionVtable { 844 | // ICallable 845 | pub PushCell: fn(cell: cell_t) -> SPError, 846 | pub PushCellByRef: fn(cell: *mut cell_t, flags: c_int) -> SPError, 847 | pub PushFloat: fn(number: f32) -> SPError, 848 | pub PushFloatByRef: fn(number: *mut f32, flags: c_int) -> SPError, 849 | pub PushArray: fn(cell: *mut cell_t, cells: c_uint, flags: c_int) -> SPError, 850 | pub PushString: fn(string: *const c_char) -> SPError, 851 | pub PushStringEx: fn(string: *const c_char, length: size_t, sz_flags: c_int, cp_flags: c_int) -> SPError, 852 | pub Cancel: fn(), 853 | 854 | // IPluginFunction 855 | pub Execute: fn(result: *mut cell_t) -> SPError, 856 | _CallFunction: fn(), 857 | _GetParentContext: fn(), 858 | pub IsRunnable: fn() -> bool, 859 | pub GetFunctionID: fn() -> u32, 860 | _Execute2: fn(), 861 | _CallFunction2: fn(), 862 | _GetParentRuntime: fn(), 863 | pub Invoke: fn(rval: *mut cell_t) -> bool, 864 | pub DebugName: fn() -> *const c_char, 865 | } 866 | 867 | #[derive(Debug, ICallableApi)] 868 | pub struct IPluginFunction<'ctx>(IPluginFunctionPtr, &'ctx IPluginContext); 869 | 870 | impl Executable for IPluginFunction<'_> { 871 | fn execute(&mut self) -> Result { 872 | unsafe { 873 | let mut result: cell_t = 0.into(); 874 | let res = virtual_call!(Execute, self.0, &mut result); 875 | match res { 876 | SPError::None => Ok(result), 877 | _ => Err(res), 878 | } 879 | } 880 | } 881 | } 882 | 883 | impl<'ctx> TryFromPlugin<'ctx> for IPluginFunction<'ctx> { 884 | type Error = GetFunctionError; 885 | 886 | fn try_from_plugin(ctx: &'ctx IPluginContext, value: cell_t) -> Result { 887 | ctx.get_function_by_id(value.0 as u32) 888 | } 889 | } 890 | 891 | /// Defines how a forward iterates through plugin functions. 892 | #[repr(C)] 893 | pub enum ExecType { 894 | /// Ignore all return values, return 0 895 | Ignore = 0, 896 | /// Only return the last exec, ignore all others 897 | Single = 1, 898 | /// Acts as an event with the ResultTypes above, no mid-Stops allowed, returns highest 899 | Event = 2, 900 | /// Acts as a hook with the ResultTypes above, mid-Stops allowed, returns highest 901 | Hook = 3, 902 | /// Same as Event except that it returns the lowest value 903 | LowEvent = 4, 904 | } 905 | 906 | /// Describes the various ways to pass parameters to plugins. 907 | #[repr(C)] 908 | pub enum ParamType { 909 | /// Any data type can be pushed 910 | Any = 0, 911 | /// Only basic cells can be pushed 912 | Cell = (1 << 1), 913 | /// Only floats can be pushed 914 | Float = (2 << 1), 915 | /// Only strings can be pushed 916 | String = (3 << 1) | 1, 917 | /// Only arrays can be pushed 918 | Array = (4 << 1) | 1, 919 | /// Same as "..." in plugins, anything can be pushed, but it will always be byref 920 | VarArgs = (5 << 1), 921 | /// Only a cell by reference can be pushed 922 | CellByRef = (1 << 1) | 1, 923 | /// Only a float by reference can be pushed 924 | FloatByRef = (2 << 1) | 1, 925 | } 926 | 927 | pub type IForwardPtr = *mut *mut IForwardVtable; 928 | 929 | #[vtable(IForwardPtr)] 930 | pub struct IForwardVtable { 931 | // ICallable 932 | pub PushCell: fn(cell: cell_t) -> SPError, 933 | pub PushCellByRef: fn(cell: *mut cell_t, flags: c_int) -> SPError, 934 | pub PushFloat: fn(number: f32) -> SPError, 935 | pub PushFloatByRef: fn(number: *mut f32, flags: c_int) -> SPError, 936 | pub PushArray: fn(cell: *mut cell_t, cells: c_uint, flags: c_int) -> SPError, 937 | pub PushString: fn(string: *const c_char) -> SPError, 938 | pub PushStringEx: fn(string: *const c_char, length: size_t, sz_flags: c_int, cp_flags: c_int) -> SPError, 939 | pub Cancel: fn(), 940 | 941 | // IForward 942 | _Destructor: fn() -> (), 943 | #[cfg(not(windows))] 944 | _Destructor2: fn() -> (), 945 | pub GetForwardName: fn() -> *const c_char, 946 | pub GetFunctionCount: fn() -> c_uint, 947 | pub GetExecType: fn() -> ExecType, 948 | pub Execute: fn(result: *mut cell_t, filter: *mut c_void) -> SPError, 949 | } 950 | 951 | pub type IChangeableForwardPtr = *mut *mut IChangeableForwardVtable; 952 | 953 | #[vtable(IChangeableForwardPtr)] 954 | pub struct IChangeableForwardVtable { 955 | // ICallable 956 | pub PushCell: fn(cell: cell_t) -> SPError, 957 | pub PushCellByRef: fn(cell: *mut cell_t, flags: c_int) -> SPError, 958 | pub PushFloat: fn(number: f32) -> SPError, 959 | pub PushFloatByRef: fn(number: *mut f32, flags: c_int) -> SPError, 960 | pub PushArray: fn(cell: *mut cell_t, cells: c_uint, flags: c_int) -> SPError, 961 | pub PushString: fn(string: *const c_char) -> SPError, 962 | pub PushStringEx: fn(string: *const c_char, length: size_t, sz_flags: c_int, cp_flags: c_int) -> SPError, 963 | pub Cancel: fn(), 964 | 965 | // IForward 966 | _Destructor: fn() -> (), 967 | #[cfg(not(windows))] 968 | _Destructor2: fn() -> (), 969 | pub GetForwardName: fn() -> *const c_char, 970 | pub GetFunctionCount: fn() -> c_uint, 971 | pub GetExecType: fn() -> ExecType, 972 | pub Execute: fn(result: *mut cell_t, filter: *mut c_void) -> SPError, 973 | 974 | // IChangeableForward 975 | #[cfg(windows)] 976 | pub RemoveFunctionById: fn(ctx: IPluginContextPtr, func: u32) -> bool, 977 | pub RemoveFunction: fn(func: IPluginFunctionPtr) -> bool, 978 | _RemoveFunctionsOfPlugin: fn(), 979 | #[cfg(windows)] 980 | pub AddFunctionById: fn(ctx: IPluginContextPtr, func: u32) -> bool, 981 | pub AddFunction: fn(func: IPluginFunctionPtr) -> bool, 982 | #[cfg(not(windows))] 983 | pub AddFunctionById: fn(ctx: IPluginContextPtr, func: u32) -> bool, 984 | #[cfg(not(windows))] 985 | pub RemoveFunctionById: fn(ctx: IPluginContextPtr, func: u32) -> bool, 986 | } 987 | 988 | pub trait CallableParam { 989 | fn push(&self, callable: &mut T) -> Result<(), SPError>; 990 | fn param_type() -> ParamType; 991 | } 992 | 993 | impl CallableParam for cell_t { 994 | fn push(&self, callable: &mut T) -> Result<(), SPError> { 995 | callable.push_int(self.0) 996 | } 997 | 998 | fn param_type() -> ParamType { 999 | ParamType::Cell 1000 | } 1001 | } 1002 | 1003 | impl CallableParam for i32 { 1004 | fn push(&self, callable: &mut T) -> Result<(), SPError> { 1005 | callable.push_int(*self) 1006 | } 1007 | 1008 | fn param_type() -> ParamType { 1009 | ParamType::Cell 1010 | } 1011 | } 1012 | 1013 | impl CallableParam for f32 { 1014 | fn push(&self, callable: &mut T) -> Result<(), SPError> { 1015 | callable.push_float(*self) 1016 | } 1017 | 1018 | fn param_type() -> ParamType { 1019 | ParamType::Float 1020 | } 1021 | } 1022 | 1023 | impl CallableParam for &CStr { 1024 | fn push(&self, callable: &mut T) -> Result<(), SPError> { 1025 | callable.push_string(self) 1026 | } 1027 | 1028 | fn param_type() -> ParamType { 1029 | ParamType::String 1030 | } 1031 | } 1032 | 1033 | // TODO: This interface is very, very rough. 1034 | pub trait ICallableApi { 1035 | fn push_int(&mut self, cell: i32) -> Result<(), SPError>; 1036 | fn push_float(&mut self, number: f32) -> Result<(), SPError>; 1037 | fn push_string(&mut self, string: &CStr) -> Result<(), SPError>; 1038 | } 1039 | 1040 | pub trait Executable: ICallableApi + Sized { 1041 | fn execute(&mut self) -> Result; 1042 | 1043 | fn push(&mut self, param: T) -> Result<(), SPError> { 1044 | param.push(self) 1045 | } 1046 | } 1047 | 1048 | #[derive(Debug, ICallableApi)] 1049 | pub struct Forward(IForwardPtr, IForwardManagerPtr); 1050 | 1051 | impl Drop for Forward { 1052 | fn drop(&mut self) { 1053 | IForwardManager(self.1).release_forward(&mut self.0); 1054 | } 1055 | } 1056 | 1057 | impl Executable for Forward { 1058 | fn execute(&mut self) -> Result { 1059 | unsafe { 1060 | let mut result: cell_t = 0.into(); 1061 | let res = virtual_call!(Execute, self.0, &mut result, null_mut()); 1062 | match res { 1063 | SPError::None => Ok(result), 1064 | _ => Err(res), 1065 | } 1066 | } 1067 | } 1068 | } 1069 | 1070 | impl Forward { 1071 | pub fn get_function_count(&self) -> u32 { 1072 | unsafe { virtual_call!(GetFunctionCount, self.0) } 1073 | } 1074 | } 1075 | 1076 | #[derive(Debug, ICallableApi)] 1077 | pub struct ChangeableForward(IChangeableForwardPtr, IForwardManagerPtr); 1078 | 1079 | impl Drop for ChangeableForward { 1080 | fn drop(&mut self) { 1081 | IForwardManager(self.1).release_forward(&mut (self.0 as IForwardPtr)); 1082 | } 1083 | } 1084 | 1085 | impl Executable for ChangeableForward { 1086 | fn execute(&mut self) -> Result { 1087 | unsafe { 1088 | let mut result: cell_t = 0.into(); 1089 | let res = virtual_call!(Execute, self.0, &mut result, null_mut()); 1090 | match res { 1091 | SPError::None => Ok(result), 1092 | _ => Err(res), 1093 | } 1094 | } 1095 | } 1096 | } 1097 | 1098 | impl ChangeableForward { 1099 | pub fn get_function_count(&self) -> u32 { 1100 | unsafe { virtual_call!(GetFunctionCount, self.0) } 1101 | } 1102 | 1103 | pub fn add_function(&mut self, func: &mut IPluginFunction) { 1104 | unsafe { 1105 | virtual_call!(AddFunction, self.0, func.0); 1106 | } 1107 | } 1108 | 1109 | pub fn remove_function(&mut self, func: &mut IPluginFunction) { 1110 | unsafe { 1111 | virtual_call!(RemoveFunction, self.0, func.0); 1112 | } 1113 | } 1114 | } 1115 | 1116 | pub type IForwardManagerPtr = *mut *mut IForwardManagerVtable; 1117 | 1118 | #[vtable(IForwardManagerPtr)] 1119 | pub struct IForwardManagerVtable { 1120 | // SMInterface 1121 | pub GetInterfaceVersion: fn() -> c_uint, 1122 | pub GetInterfaceName: fn() -> *const c_char, 1123 | pub IsVersionCompatible: fn(version: c_uint) -> bool, 1124 | 1125 | // IForwardManager 1126 | pub CreateForward: fn(name: *const c_char, et: ExecType, num_params: c_uint, types: *const ParamType, ...) -> IForwardPtr, 1127 | pub CreateForwardEx: fn(name: *const c_char, et: ExecType, num_params: c_uint, types: *const ParamType, ...) -> IChangeableForwardPtr, 1128 | pub FindForward: fn(name: *const c_char, *mut IChangeableForwardPtr) -> IForwardPtr, 1129 | pub ReleaseForward: fn(forward: IForwardPtr) -> (), 1130 | } 1131 | 1132 | #[derive(Debug)] 1133 | pub enum CreateForwardError { 1134 | InvalidName(NulError), 1135 | InvalidParams(Option), 1136 | } 1137 | 1138 | impl std::fmt::Display for CreateForwardError { 1139 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 1140 | match self { 1141 | CreateForwardError::InvalidName(err) => write!(f, "invalid forward name: {}", err), 1142 | CreateForwardError::InvalidParams(name) => match name { 1143 | Some(name) => write!(f, "failed to create forward {}: invalid params", name), 1144 | None => write!(f, "failed to create forward anonymous forward: invalid params"), 1145 | }, 1146 | } 1147 | } 1148 | } 1149 | 1150 | impl Error for CreateForwardError { 1151 | fn source(&self) -> Option<&(dyn Error + 'static)> { 1152 | match self { 1153 | CreateForwardError::InvalidName(err) => Some(err), 1154 | CreateForwardError::InvalidParams(_) => None, 1155 | } 1156 | } 1157 | } 1158 | 1159 | #[derive(Debug, SMInterfaceApi)] 1160 | #[interface("IForwardManager", 4)] 1161 | pub struct IForwardManager(IForwardManagerPtr); 1162 | 1163 | impl IForwardManager { 1164 | pub fn create_global_forward(&self, name: &str, et: ExecType, params: &[ParamType]) -> Result { 1165 | let c_name = CString::new(name).map_err(CreateForwardError::InvalidName)?; 1166 | 1167 | unsafe { 1168 | let forward = virtual_call_varargs!(CreateForward, self.0, c_name.as_ptr(), et, params.len() as u32, params.as_ptr()); 1169 | 1170 | if forward.is_null() { 1171 | Err(CreateForwardError::InvalidParams(Some(name.into()))) 1172 | } else { 1173 | Ok(Forward(forward, self.0)) 1174 | } 1175 | } 1176 | } 1177 | 1178 | pub fn create_private_forward(&self, name: Option<&str>, et: ExecType, params: &[ParamType]) -> Result { 1179 | let c_name = match name { 1180 | Some(name) => Some(CString::new(name).map_err(CreateForwardError::InvalidName)?), 1181 | None => None, 1182 | }; 1183 | 1184 | let c_name = match c_name { 1185 | Some(c_name) => c_name.as_ptr(), 1186 | None => null(), 1187 | }; 1188 | 1189 | unsafe { 1190 | let forward = virtual_call_varargs!(CreateForwardEx, self.0, c_name, et, params.len() as u32, params.as_ptr()); 1191 | 1192 | if forward.is_null() { 1193 | Err(CreateForwardError::InvalidParams(name.map(|name| name.into()))) 1194 | } else { 1195 | Ok(ChangeableForward(forward, self.0)) 1196 | } 1197 | } 1198 | } 1199 | 1200 | fn release_forward(&self, forward: &mut IForwardPtr) { 1201 | if forward.is_null() { 1202 | panic!("release_forward called on null forward ptr") 1203 | } 1204 | 1205 | unsafe { 1206 | virtual_call!(ReleaseForward, self.0, *forward); 1207 | *forward = null_mut(); 1208 | } 1209 | } 1210 | } 1211 | 1212 | #[repr(transparent)] 1213 | #[derive(Debug, Copy, Clone, PartialEq)] 1214 | pub struct HandleTypeId(c_uint); 1215 | 1216 | impl HandleTypeId { 1217 | pub fn is_valid(self) -> bool { 1218 | self.0 != 0 1219 | } 1220 | 1221 | pub fn invalid() -> Self { 1222 | Self(0) 1223 | } 1224 | } 1225 | 1226 | #[repr(transparent)] 1227 | #[derive(Debug, Copy, Clone, PartialEq)] 1228 | pub struct HandleId(c_uint); 1229 | 1230 | impl HandleId { 1231 | pub fn is_valid(self) -> bool { 1232 | self.0 != 0 1233 | } 1234 | 1235 | pub fn invalid() -> Self { 1236 | Self(0) 1237 | } 1238 | } 1239 | 1240 | impl From for HandleId { 1241 | fn from(x: cell_t) -> Self { 1242 | Self(x.0 as u32) 1243 | } 1244 | } 1245 | 1246 | impl From for cell_t { 1247 | fn from(x: HandleId) -> Self { 1248 | Self(x.0 as i32) 1249 | } 1250 | } 1251 | 1252 | impl CallableParam for HandleId { 1253 | fn push(&self, callable: &mut T) -> Result<(), SPError> { 1254 | callable.push_int(self.0 as i32) 1255 | } 1256 | 1257 | fn param_type() -> ParamType { 1258 | ParamType::Cell 1259 | } 1260 | } 1261 | 1262 | /// Lists the possible handle error codes. 1263 | #[repr(C)] 1264 | #[derive(Debug)] 1265 | pub enum HandleError { 1266 | /// No error 1267 | None = 0, 1268 | /// The handle has been freed and reassigned 1269 | Changed = 1, 1270 | /// The handle has a different type registered 1271 | Type = 2, 1272 | /// The handle has been freed 1273 | Freed = 3, 1274 | /// Generic internal indexing error 1275 | Index = 4, 1276 | /// No access permitted to free this handle 1277 | Access = 5, 1278 | /// The limited number of handles has been reached 1279 | Limit = 6, 1280 | /// The identity token was not usable 1281 | Identity = 7, 1282 | /// Owners do not match for this operation 1283 | Owner = 8, 1284 | /// Unrecognized security structure version 1285 | Version = 9, 1286 | /// An invalid parameter was passed 1287 | Parameter = 10, 1288 | /// This type cannot be inherited 1289 | NoInherit = 11, 1290 | } 1291 | 1292 | impl std::fmt::Display for HandleError { 1293 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 1294 | f.pad(match self { 1295 | HandleError::None => "no error", 1296 | HandleError::Changed => "the handle has been freed and reassigned", 1297 | HandleError::Type => "the handle has a different type registered", 1298 | HandleError::Freed => "the handle has been freed", 1299 | HandleError::Index => "generic internal indexing error", 1300 | HandleError::Access => "no access permitted to free this handle", 1301 | HandleError::Limit => "the limited number of handles has been reached", 1302 | HandleError::Identity => "the identity token was not usable", 1303 | HandleError::Owner => "owners do not match for this operation", 1304 | HandleError::Version => "unrecognized security structure version", 1305 | HandleError::Parameter => "an invalid parameter was passed", 1306 | HandleError::NoInherit => "this type cannot be inherited", 1307 | }) 1308 | } 1309 | } 1310 | 1311 | impl Error for HandleError {} 1312 | 1313 | pub type IHandleTypeDispatchPtr = *mut *mut IHandleTypeDispatchVtable; 1314 | 1315 | #[vtable(IHandleTypeDispatchPtr)] 1316 | pub struct IHandleTypeDispatchVtable { 1317 | pub GetDispatchVersion: fn() -> c_uint, 1318 | pub OnHandleDestroy: fn(ty: HandleTypeId, object: *mut c_void) -> (), 1319 | pub GetHandleApproxSize: fn(ty: HandleTypeId, object: *mut c_void, size: *mut c_uint) -> bool, 1320 | } 1321 | 1322 | #[repr(C)] 1323 | pub struct IHandleTypeDispatchAdapter { 1324 | vtable: *mut IHandleTypeDispatchVtable, 1325 | phantom: std::marker::PhantomData, 1326 | } 1327 | 1328 | impl Drop for IHandleTypeDispatchAdapter { 1329 | fn drop(&mut self) { 1330 | unsafe { 1331 | drop(Box::from_raw(self.vtable)); 1332 | } 1333 | } 1334 | } 1335 | 1336 | impl Default for IHandleTypeDispatchAdapter { 1337 | fn default() -> Self { 1338 | Self::new() 1339 | } 1340 | } 1341 | 1342 | impl IHandleTypeDispatchAdapter { 1343 | pub fn new() -> IHandleTypeDispatchAdapter { 1344 | let vtable = IHandleTypeDispatchVtable { 1345 | GetDispatchVersion: IHandleTypeDispatchAdapter::::get_dispatch_version, 1346 | OnHandleDestroy: IHandleTypeDispatchAdapter::::on_handle_destroy, 1347 | GetHandleApproxSize: IHandleTypeDispatchAdapter::::get_handle_approx_size, 1348 | }; 1349 | 1350 | IHandleTypeDispatchAdapter { vtable: Box::into_raw(Box::new(vtable)), phantom: std::marker::PhantomData } 1351 | } 1352 | 1353 | #[vtable_override] 1354 | unsafe fn get_dispatch_version(this: IHandleTypeDispatchPtr) -> u32 { 1355 | ::get_interface_version() 1356 | } 1357 | 1358 | #[vtable_override] 1359 | unsafe fn on_handle_destroy(this: IHandleTypeDispatchPtr, ty: HandleTypeId, object: *mut c_void) { 1360 | drop(Rc::from_raw(object as *mut T)); 1361 | } 1362 | 1363 | #[vtable_override] 1364 | unsafe fn get_handle_approx_size(this: IHandleTypeDispatchPtr, ty: HandleTypeId, object: *mut c_void, size: *mut c_uint) -> bool { 1365 | // This isn't ideal as it doesn't account for dynamic sizes, probably need to add a trait at some point 1366 | // for people to implement this properly. See also: https://github.com/rust-lang/rust/issues/63073 1367 | // This also isn't accounting for the Rc overhead as we're dealing with the internal ptr only. 1368 | let object = object as *mut T; 1369 | *size = std::mem::size_of_val(&*object) as u32; 1370 | 1371 | *size != 0 1372 | } 1373 | } 1374 | 1375 | /// This pair of tokens is used for identification. 1376 | #[repr(C)] 1377 | #[derive(Debug)] 1378 | pub struct HandleSecurity { 1379 | /// Owner of the Handle 1380 | pub owner: IdentityTokenPtr, 1381 | /// Owner of the Type 1382 | pub identity: IdentityTokenPtr, 1383 | } 1384 | 1385 | impl HandleSecurity { 1386 | pub fn new(owner: IdentityTokenPtr, identity: IdentityTokenPtr) -> Self { 1387 | Self { owner, identity } 1388 | } 1389 | } 1390 | 1391 | pub type IHandleSysPtr = *mut *mut IHandleSysVtable; 1392 | 1393 | #[vtable(IHandleSysPtr)] 1394 | pub struct IHandleSysVtable { 1395 | // SMInterface 1396 | pub GetInterfaceVersion: fn() -> c_uint, 1397 | pub GetInterfaceName: fn() -> *const c_char, 1398 | pub IsVersionCompatible: fn(version: c_uint) -> bool, 1399 | 1400 | // IHandleSys 1401 | pub CreateType: fn(name: *const c_char, dispatch: IHandleTypeDispatchPtr, parent: HandleTypeId, typeAccess: *const c_void, handleAccess: Option<&HandleAccess>, ident: IdentityTokenPtr, err: *mut HandleError) -> HandleTypeId, 1402 | pub RemoveType: fn(ty: HandleTypeId, ident: IdentityTokenPtr) -> bool, 1403 | pub FindHandleType: fn(name: *const c_char, ty: *mut HandleTypeId) -> bool, 1404 | pub CreateHandle: fn(ty: HandleTypeId, object: *mut c_void, owner: IdentityTokenPtr, ident: IdentityTokenPtr, err: *mut HandleError) -> HandleId, 1405 | pub FreeHandle: fn(handle: HandleId, security: *const HandleSecurity) -> HandleError, 1406 | pub CloneHandle: fn(handle: HandleId, newHandle: *mut HandleId, newOwner: IdentityTokenPtr, security: *const HandleSecurity) -> HandleError, 1407 | pub ReadHandle: fn(handle: HandleId, ty: HandleTypeId, security: *const HandleSecurity, object: *mut *mut c_void) -> HandleError, 1408 | pub InitAccessDefaults: fn(typeAccess: *mut c_void, handleAccess: *mut c_void) -> bool, 1409 | pub CreateHandleEx: fn(ty: HandleTypeId, object: *mut c_void, security: *const HandleSecurity, access: Option<&HandleAccess>, err: *mut HandleError) -> HandleId, 1410 | pub FastCloneHandle: fn(handle: HandleId) -> HandleId, 1411 | pub TypeCheck: fn(given: HandleTypeId, actual: HandleTypeId) -> bool, 1412 | } 1413 | 1414 | #[derive(Debug)] 1415 | pub struct HandleType { 1416 | iface: IHandleSysPtr, 1417 | id: HandleTypeId, 1418 | dispatch: *mut IHandleTypeDispatchAdapter, 1419 | ident: IdentityTokenPtr, 1420 | } 1421 | 1422 | impl Drop for HandleType { 1423 | fn drop(&mut self) { 1424 | IHandleSys(self.iface).remove_type(self).unwrap(); 1425 | 1426 | unsafe { 1427 | drop(Box::from_raw(self.dispatch)); 1428 | } 1429 | } 1430 | } 1431 | 1432 | impl HandleType { 1433 | pub fn create_handle(&self, object: Rc, owner: IdentityTokenPtr, access: Option<&HandleAccess>) -> Result { 1434 | IHandleSys(self.iface).create_handle(self, object, owner, access) 1435 | } 1436 | 1437 | pub fn clone_handle(&self, handle: HandleId, owner: IdentityTokenPtr, new_owner: IdentityTokenPtr) -> Result { 1438 | IHandleSys(self.iface).clone_handle(self, handle, owner, new_owner) 1439 | } 1440 | 1441 | pub fn free_handle(&self, handle: HandleId, owner: IdentityTokenPtr) -> Result<(), HandleError> { 1442 | IHandleSys(self.iface).free_handle(self, handle, owner) 1443 | } 1444 | 1445 | pub fn read_handle(&self, handle: HandleId, owner: IdentityTokenPtr) -> Result, HandleError> { 1446 | IHandleSys(self.iface).read_handle(self, handle, owner) 1447 | } 1448 | } 1449 | 1450 | #[derive(Debug)] 1451 | pub enum CreateHandleTypeError { 1452 | InvalidName(NulError), 1453 | HandleError(String, HandleError), 1454 | } 1455 | 1456 | impl std::fmt::Display for CreateHandleTypeError { 1457 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 1458 | match self { 1459 | CreateHandleTypeError::InvalidName(err) => write!(f, "invalid handle type name: {}", err), 1460 | CreateHandleTypeError::HandleError(name, err) => write!(f, "failed to create handle type {}: {}", name, err), 1461 | } 1462 | } 1463 | } 1464 | 1465 | impl Error for CreateHandleTypeError { 1466 | fn source(&self) -> Option<&(dyn Error + 'static)> { 1467 | match self { 1468 | CreateHandleTypeError::InvalidName(err) => Some(err), 1469 | CreateHandleTypeError::HandleError(_, err) => Some(err), 1470 | } 1471 | } 1472 | } 1473 | 1474 | #[repr(C)] 1475 | pub enum HandleAccessRestriction { 1476 | Any = 0, 1477 | IdentityOnly = 1, 1478 | OwnerOnly = 2, 1479 | OwnerAndIdentity = 3, 1480 | } 1481 | 1482 | #[repr(C)] 1483 | pub struct HandleAccess { 1484 | version: u32, 1485 | pub read_access: HandleAccessRestriction, 1486 | pub delete_access: HandleAccessRestriction, 1487 | pub clone_access: HandleAccessRestriction, 1488 | } 1489 | 1490 | impl HandleAccess { 1491 | pub fn new() -> Self { 1492 | HandleAccess { 1493 | version: ::get_interface_version(), 1494 | read_access: HandleAccessRestriction::IdentityOnly, 1495 | delete_access: HandleAccessRestriction::OwnerOnly, 1496 | clone_access: HandleAccessRestriction::Any, 1497 | } 1498 | } 1499 | } 1500 | 1501 | impl Default for HandleAccess { 1502 | fn default() -> Self { 1503 | Self::new() 1504 | } 1505 | } 1506 | 1507 | #[derive(Debug, SMInterfaceApi)] 1508 | #[interface("IHandleSys", 5)] 1509 | pub struct IHandleSys(IHandleSysPtr); 1510 | 1511 | impl IHandleSys { 1512 | pub fn create_type(&self, name: &str, handle_access: Option<&HandleAccess>, ident: IdentityTokenPtr) -> Result, CreateHandleTypeError> { 1513 | unsafe { 1514 | let c_name = CString::new(name).map_err(CreateHandleTypeError::InvalidName)?; 1515 | let dispatch = Box::into_raw(Box::new(IHandleTypeDispatchAdapter::::new())); 1516 | 1517 | let mut err: HandleError = HandleError::None; 1518 | let id = virtual_call!(CreateType, self.0, c_name.as_ptr(), dispatch as IHandleTypeDispatchPtr, HandleTypeId::invalid(), null(), handle_access, ident, &mut err); 1519 | 1520 | if id.is_valid() { 1521 | Ok(HandleType { iface: self.0, id, dispatch, ident }) 1522 | } else { 1523 | Err(CreateHandleTypeError::HandleError(name.into(), err)) 1524 | } 1525 | } 1526 | } 1527 | 1528 | fn remove_type(&self, ty: &mut HandleType) -> Result<(), bool> { 1529 | unsafe { 1530 | if virtual_call!(RemoveType, self.0, ty.id, ty.ident) { 1531 | Ok(()) 1532 | } else { 1533 | Err(false) 1534 | } 1535 | } 1536 | } 1537 | 1538 | fn create_handle(&self, ty: &HandleType, object: Rc, owner: IdentityTokenPtr, access: Option<&HandleAccess>) -> Result { 1539 | unsafe { 1540 | let object = Rc::into_raw(object) as *mut c_void; 1541 | let security = HandleSecurity::new(owner, ty.ident); 1542 | let mut err: HandleError = HandleError::None; 1543 | let id = virtual_call!(CreateHandleEx, self.0, ty.id, object, &security, access, &mut err); 1544 | if id.is_valid() { 1545 | Ok(id) 1546 | } else { 1547 | Err(err) 1548 | } 1549 | } 1550 | } 1551 | 1552 | fn free_handle(&self, ty: &HandleType, handle: HandleId, owner: IdentityTokenPtr) -> Result<(), HandleError> { 1553 | unsafe { 1554 | let security = HandleSecurity::new(owner, ty.ident); 1555 | let err = virtual_call!(FreeHandle, self.0, handle, &security); 1556 | match err { 1557 | HandleError::None => Ok(()), 1558 | _ => Err(err), 1559 | } 1560 | } 1561 | } 1562 | 1563 | fn clone_handle(&self, ty: &HandleType, handle: HandleId, owner: IdentityTokenPtr, new_owner: IdentityTokenPtr) -> Result { 1564 | unsafe { 1565 | let security = HandleSecurity::new(owner, ty.ident); 1566 | let mut new_handle = HandleId::invalid(); 1567 | let err = virtual_call!(CloneHandle, self.0, handle, &mut new_handle, new_owner, &security); 1568 | match err { 1569 | HandleError::None => Ok(new_handle), 1570 | _ => Err(err), 1571 | } 1572 | } 1573 | } 1574 | 1575 | fn read_handle(&self, ty: &HandleType, handle: HandleId, owner: IdentityTokenPtr) -> Result, HandleError> { 1576 | unsafe { 1577 | let security = HandleSecurity::new(owner, ty.ident); 1578 | let mut object: *mut c_void = null_mut(); 1579 | let err = virtual_call!(ReadHandle, self.0, handle, ty.id, &security, &mut object); 1580 | match err { 1581 | HandleError::None => Ok({ 1582 | // https://github.com/rust-lang/rust/issues/48108 1583 | let object = Rc::from_raw(object as *mut T); 1584 | std::mem::forget(object.clone()); 1585 | object 1586 | }), 1587 | _ => Err(err), 1588 | } 1589 | } 1590 | } 1591 | } 1592 | 1593 | /// Describes various ways of formatting a base path. 1594 | #[repr(C)] 1595 | #[derive(Debug)] 1596 | pub enum PathType { 1597 | /// No base path 1598 | Path_None = 0, 1599 | /// Base path is absolute mod folder 1600 | Path_Game = 1, 1601 | /// Base path is absolute to SourceMod 1602 | Path_SM = 2, 1603 | /// Base path is relative to SourceMod 1604 | Path_SM_Rel = 3, 1605 | } 1606 | 1607 | pub type GameFrameHookFunc = unsafe extern "C" fn(simulating: bool); 1608 | 1609 | pub type ISourceModPtr = *mut *mut ISourceModVtable; 1610 | 1611 | #[vtable(ISourceModPtr)] 1612 | pub struct ISourceModVtable { 1613 | // SMInterface 1614 | pub GetInterfaceVersion: fn() -> c_uint, 1615 | pub GetInterfaceName: fn() -> *const c_char, 1616 | pub IsVersionCompatible: fn(version: c_uint) -> bool, 1617 | 1618 | // ISourceMod 1619 | pub GetGamePath: fn() -> *const c_char, 1620 | pub GetSourceModPath: fn() -> *const c_char, 1621 | pub BuildPath: fn(ty: PathType, buffer: *mut c_char, maxlength: size_t, format: *const c_char, ...) -> size_t, 1622 | pub LogMessage: fn(ext: IExtensionPtr, format: *const c_char, ...) -> (), 1623 | pub LogError: fn(ext: IExtensionPtr, format: *const c_char, ...) -> (), 1624 | pub FormatString: fn(buffer: *mut c_char, maxlength: size_t, context: IPluginContextPtr, params: *const cell_t, param: c_uint) -> size_t, 1625 | _CreateDataPack: fn(), 1626 | _FreeDataPack: fn(), 1627 | _GetDataPackHandleType: fn(), 1628 | _ReadKeyValuesHandle: fn(), 1629 | pub GetGameFolderName: fn() -> *const c_char, 1630 | pub GetScriptingEngine: fn() -> *mut c_void, 1631 | pub GetScriptingVM: fn() -> *mut c_void, 1632 | _GetAdjustedTime: fn(), 1633 | pub SetGlobalTarget: fn(index: c_uint) -> c_uint, 1634 | pub GetGlobalTarget: fn() -> c_uint, 1635 | pub AddGameFrameHook: fn(hook: GameFrameHookFunc) -> (), 1636 | pub RemoveGameFrameHook: fn(hook: GameFrameHookFunc) -> (), 1637 | pub Format: fn(buffer: *mut c_char, maxlength: size_t, format: *const c_char, ...) -> size_t, 1638 | _FormatArgs: fn(), 1639 | pub AddFrameAction: fn(func: unsafe extern "C" fn(*mut c_void), data: *mut c_void) -> (), 1640 | pub GetCoreConfigValue: fn(key: *const c_char) -> *const c_char, 1641 | pub GetPluginId: fn() -> c_int, 1642 | pub GetShApiVersion: fn() -> c_int, 1643 | pub IsMapRunning: fn() -> bool, 1644 | pub FromPseudoAddress: fn(pseudo: u32) -> *mut c_void, 1645 | pub ToPseudoAddress: fn(addr: *mut c_void) -> u32, 1646 | } 1647 | 1648 | #[derive(Debug, SMInterfaceApi)] 1649 | #[interface("ISourceMod", 14)] 1650 | pub struct ISourceMod(ISourceModPtr); 1651 | 1652 | pub struct GameFrameHookId(GameFrameHookFunc, ISourceModPtr); 1653 | 1654 | impl Drop for GameFrameHookId { 1655 | fn drop(&mut self) { 1656 | ISourceMod(self.1).remove_game_frame_hook(self.0); 1657 | } 1658 | } 1659 | 1660 | unsafe extern "C" fn frame_action_trampoline(func: *mut c_void) { 1661 | let mut func: Box = Box::from_raw(func as *mut _); 1662 | (*func)() 1663 | } 1664 | 1665 | impl ISourceMod { 1666 | pub fn log_message(&self, myself: &IExtension, msg: String) { 1667 | let fmt = c_str!("%s"); 1668 | let msg = CString::new(msg).expect("log message contained NUL byte"); 1669 | unsafe { virtual_call_varargs!(LogMessage, self.0, myself.0, fmt.as_ptr(), msg.as_ptr()) } 1670 | } 1671 | 1672 | pub fn log_error(&self, myself: &IExtension, msg: String) { 1673 | let fmt = c_str!("%s"); 1674 | let msg = CString::new(msg).expect("log message contained NUL byte"); 1675 | unsafe { virtual_call_varargs!(LogError, self.0, myself.0, fmt.as_ptr(), msg.as_ptr()) } 1676 | } 1677 | 1678 | /// Add a function that will be called every game frame until the [`GameFrameHookId`] return value 1679 | /// is dropped. This is a fairly low-level building block as the callback must be `extern "C"`. 1680 | pub fn add_game_frame_hook(&self, hook: GameFrameHookFunc) -> GameFrameHookId { 1681 | unsafe { 1682 | virtual_call!(AddGameFrameHook, self.0, hook); 1683 | } 1684 | 1685 | GameFrameHookId(hook, self.0) 1686 | } 1687 | 1688 | fn remove_game_frame_hook(&self, hook: GameFrameHookFunc) { 1689 | unsafe { 1690 | virtual_call!(RemoveGameFrameHook, self.0, hook); 1691 | } 1692 | } 1693 | 1694 | // TODO: If we implement a [`Send`] subset of [`ISourceMod`] this function should be included but the closure must also be [`Send`]. 1695 | /// Add a function that will be called on the next game frame. This has a runtime cost as this API 1696 | /// is thread-safe on the SM side, but it supports a Rust closure so is more flexible than [`ISourceMod::add_game_frame_hook`]. 1697 | pub fn add_frame_action(&self, func: F) 1698 | where 1699 | F: FnMut() + 'static, 1700 | { 1701 | unsafe { 1702 | let func = Box::into_raw(Box::new(func)); 1703 | virtual_call!(AddFrameAction, self.0, frame_action_trampoline::, func as *mut c_void); 1704 | } 1705 | } 1706 | } 1707 | 1708 | /// Helper for virtual function invocation that works with the `#[vtable]` attribute to support 1709 | /// virtual calls on Windows without compiler support for the `thiscall` calling convention. 1710 | #[macro_export] 1711 | macro_rules! virtual_call { 1712 | ($name:ident, $this:expr, $($param:expr),* $(,)?) => { 1713 | ((**$this).$name)( 1714 | $this, 1715 | #[cfg(all(windows, target_arch = "x86", not(feature = "abi_thiscall")))] 1716 | std::ptr::null_mut(), 1717 | $( 1718 | $param, 1719 | )* 1720 | ) 1721 | }; 1722 | ($name:ident, $this:expr) => { 1723 | virtual_call!($name, $this, ) 1724 | }; 1725 | } 1726 | 1727 | // TODO: Figure out a way to make this type-safe (and hopefully avoid the need for it completely.) 1728 | /// Helper for varargs-using virtual function invocation that works with the `#[vtable]` attribute to 1729 | /// support virtual calls on Windows without compiler support for the `thiscall` calling convention. 1730 | #[macro_export] 1731 | macro_rules! virtual_call_varargs { 1732 | ($name:ident, $this:expr, $($param:expr),* $(,)?) => { 1733 | ((**$this).$name)( 1734 | $this, 1735 | $( 1736 | $param, 1737 | )* 1738 | ) 1739 | }; 1740 | ($name:ident, $this:expr) => { 1741 | virtual_call!($name, $this, ) 1742 | }; 1743 | } 1744 | 1745 | #[macro_export] 1746 | macro_rules! register_natives { 1747 | ($sys:expr, $myself:expr, [$(($name:expr, $func:expr)),* $(,)?]) => { 1748 | unsafe { 1749 | let mut vec = Vec::new(); 1750 | $( 1751 | let name = concat!($name, "\0").as_ptr() as *const ::std::os::raw::c_char; 1752 | vec.push($crate::NativeInfo { 1753 | name: name, 1754 | func: Some($func), 1755 | }); 1756 | )* 1757 | vec.push($crate::NativeInfo { 1758 | name: ::std::ptr::null(), 1759 | func: None, 1760 | }); 1761 | 1762 | // This leaks vec so that it remains valid. 1763 | // TODO: Look into making it static somewhere, it only has to live as long as the extension is loaded. 1764 | // Would probably need some of the nightly macro features, which tbh would help the native callbacks anyway. 1765 | let boxed = vec.into_boxed_slice(); 1766 | $sys.add_natives($myself, Box::leak(boxed).as_ptr()); 1767 | } 1768 | }; 1769 | } 1770 | 1771 | /// The return type for native callbacks. 1772 | pub trait NativeResult { 1773 | type Ok; 1774 | type Err; 1775 | 1776 | fn into_result(self) -> Result; 1777 | } 1778 | 1779 | /// Dummy error used for [`NativeResult`] implementations that can never fail. 1780 | #[derive(Debug)] 1781 | pub struct DummyNativeError; 1782 | 1783 | impl std::fmt::Display for DummyNativeError { 1784 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 1785 | std::fmt::Debug::fmt(self, f) 1786 | } 1787 | } 1788 | 1789 | impl Error for DummyNativeError {} 1790 | 1791 | impl NativeResult for () { 1792 | type Ok = i32; 1793 | type Err = DummyNativeError; 1794 | 1795 | fn into_result(self) -> Result { 1796 | Ok(0) 1797 | } 1798 | } 1799 | 1800 | impl<'ctx, T> NativeResult for T 1801 | where 1802 | T: TryIntoPlugin<'ctx, cell_t>, 1803 | { 1804 | type Ok = T; 1805 | type Err = DummyNativeError; 1806 | 1807 | fn into_result(self) -> Result { 1808 | Ok(self) 1809 | } 1810 | } 1811 | 1812 | impl NativeResult for Result<(), E> { 1813 | type Ok = i32; 1814 | type Err = E; 1815 | 1816 | #[allow(clippy::type_complexity)] 1817 | fn into_result(self) -> Result< as NativeResult>::Ok, as NativeResult>::Err> { 1818 | self.map(|_| 0) 1819 | } 1820 | } 1821 | 1822 | impl<'ctx, T, E> NativeResult for Result 1823 | where 1824 | T: TryIntoPlugin<'ctx, cell_t>, 1825 | { 1826 | type Ok = T; 1827 | type Err = E; 1828 | 1829 | #[allow(clippy::type_complexity)] 1830 | fn into_result(self) -> Result< as NativeResult>::Ok, as NativeResult>::Err> { 1831 | self 1832 | } 1833 | } 1834 | 1835 | /// Wrapper to invoke a native callback and translate a [`panic!`] or [`Err`](std::result::Result::Err) 1836 | /// return into a SourceMod error using [`IPluginContext::throw_native_error`]. 1837 | /// 1838 | /// This is used internally by the `#[native]` attribute. 1839 | pub fn safe_native_invoke(ctx: IPluginContextPtr, f: F) -> cell_t 1840 | where 1841 | F: FnOnce(&IPluginContext) -> Result> + std::panic::UnwindSafe, 1842 | { 1843 | let ctx = IPluginContext(ctx); 1844 | let result = std::panic::catch_unwind(|| f(&ctx)); 1845 | 1846 | match result { 1847 | Ok(result) => match result { 1848 | Ok(result) => result, 1849 | Err(err) => ctx.throw_native_error(err.to_string()), 1850 | }, 1851 | Err(err) => { 1852 | let msg = format!( 1853 | "native panicked: {}", 1854 | if let Some(str_slice) = err.downcast_ref::<&'static str>() { 1855 | str_slice 1856 | } else if let Some(string) = err.downcast_ref::() { 1857 | string 1858 | } else { 1859 | "unknown message" 1860 | } 1861 | ); 1862 | 1863 | ctx.throw_native_error(msg) 1864 | } 1865 | } 1866 | } 1867 | --------------------------------------------------------------------------------