├── .gitignore ├── gmod ├── README.md ├── src │ ├── net.rs │ ├── lua │ │ ├── raw_bind.rs │ │ ├── returns.rs │ │ ├── push.rs │ │ ├── mod.rs │ │ ├── import.rs │ │ └── lua_state.rs │ ├── hax.rs │ ├── msgc.rs │ ├── userdata.rs │ ├── gmcl.rs │ └── lib.rs └── Cargo.toml ├── tests ├── .gitignore └── userdata │ ├── rust-toolchain.toml │ ├── Cargo.toml │ ├── src │ └── lib.rs │ └── Cargo.lock ├── gmod-macros ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── rust-toolchain.toml ├── examples ├── printing-to-console │ ├── rust-toolchain.toml │ ├── README.md │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── my-first-binary-module │ ├── rust-toolchain.toml │ ├── Cargo.toml │ ├── src │ └── lib.rs │ └── README.md ├── .editorconfig ├── Cargo.toml ├── README.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /gmod/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | */target/ -------------------------------------------------------------------------------- /gmod-macros/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /tests/userdata/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /examples/printing-to-console/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /examples/my-first-binary-module/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "gmod", 4 | "gmod-macros" 5 | ] 6 | exclude = [ 7 | "examples/my-first-binary-module", 8 | "examples/printing-to-console" 9 | ] -------------------------------------------------------------------------------- /examples/my-first-binary-module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "my-first-binary-module" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | gmod = "*" -------------------------------------------------------------------------------- /examples/printing-to-console/README.md: -------------------------------------------------------------------------------- 1 | # Printing to Console Example 2 | 3 | This is an example of using a module to print to console. 4 | Compiling should follow the instructions in [my-first-binary-module](../my-first-binary-module/README.md). 5 | -------------------------------------------------------------------------------- /tests/userdata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "userdata" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | gmod = { path = "../../gmod" } 12 | 13 | [workspace] -------------------------------------------------------------------------------- /examples/printing-to-console/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "printing-to-console" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | gmod = {version = "*", features = ["gmcl"], default-features = false} -------------------------------------------------------------------------------- /examples/my-first-binary-module/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate gmod; 2 | 3 | #[gmod13_open] 4 | fn gmod13_open(lua: gmod::lua::State) -> i32 { 5 | println!("Hello from binary module!"); 6 | 0 7 | } 8 | 9 | #[gmod13_close] 10 | fn gmod13_close(lua: gmod::lua::State) -> i32 { 11 | println!("Goodbye from binary module!"); 12 | 0 13 | } -------------------------------------------------------------------------------- /gmod-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gmod-macros" 3 | version = "2.0.1" 4 | authors = ["William Venner "] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "Proc macros for gmod-rs" 8 | repository = "https://github.com/WilliamVenner/gmod-rs" 9 | 10 | [features] 11 | gmcl = [] 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1" 18 | syn = { version = "1", features = ["full"] } 19 | quote = "1" 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/gmod.svg)](https://crates.io/crates/gmod) 2 | 3 | [![docs.rs](https://docs.rs/gmod/badge.svg)](https://docs.rs/gmod) 4 | 5 | # ⚙ gmod-rs 6 | 7 | A swiss army knife for creating binary modules for Garry's Mod in Rust. 8 | 9 | # Examples 10 | 11 | [Click here](https://github.com/WilliamVenner/gmod-rs/tree/master/examples/) to see examples. 12 | 13 | # Nightly requirement 14 | 15 | Currently, this crate requires the Rust Nightly compiler to be used. -------------------------------------------------------------------------------- /examples/printing-to-console/src/lib.rs: -------------------------------------------------------------------------------- 1 | use gmod::gmcl::override_stdout; 2 | use gmod::lua::State; 3 | 4 | #[macro_use] extern crate gmod; 5 | 6 | #[gmod13_open] 7 | fn gmod13_open(lua: State) -> i32 { 8 | // Here, if this module is running on the client. 9 | if lua.is_client() { 10 | // We overwrite println! so it prints to the console. 11 | override_stdout() 12 | } 13 | 14 | if lua.is_server() { 15 | println!("Hello Server, this is a binary module!") 16 | } else { 17 | println!("Hello Client, this is a binary module!") 18 | } 19 | 20 | 0 21 | } 22 | 23 | #[gmod13_close] 24 | fn gmod13_close(lua: State) -> i32 { 25 | println!("Goodbye from binary module!"); 26 | 0 27 | } 28 | -------------------------------------------------------------------------------- /gmod/src/net.rs: -------------------------------------------------------------------------------- 1 | use crate::{lua::{self, LuaFunction}, lua_string}; 2 | 3 | #[inline(always)] 4 | pub unsafe fn add_network_strings>(lua: lua::State, network_strings: &[S]) { 5 | match network_strings.len() { 6 | 0 => {}, 7 | 1 => { 8 | lua.get_global(lua_string!("util")); 9 | lua.get_field(-1, lua_string!("AddNetworkString")); 10 | lua.push_string(network_strings[0].as_ref()); 11 | lua.call(1, 0); 12 | lua.pop(); 13 | }, 14 | _ => { 15 | lua.get_global(lua_string!("util")); 16 | lua.get_field(-1, lua_string!("AddNetworkString")); 17 | for network_string in network_strings { 18 | lua.push_value(-1); 19 | lua.push_string(network_string.as_ref()); 20 | lua.call(1, 0); 21 | } 22 | lua.pop_n(2); 23 | } 24 | } 25 | } 26 | 27 | #[inline(always)] 28 | pub unsafe fn receive>(lua: lua::State, network_string: S, func: LuaFunction) { 29 | lua.get_global(lua_string!("net")); 30 | lua.get_field(-1, lua_string!("Receive")); 31 | lua.push_string(network_string.as_ref()); 32 | lua.push_function(func); 33 | lua.call(2, 0); 34 | lua.pop(); 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 William Venner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /gmod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gmod" 3 | version = "17.0.0" 4 | authors = ["William Venner "] 5 | edition = "2021" 6 | license = "MIT" 7 | description = "A swiss army knife for creating binary modules for Garry's Mod in Rust" 8 | repository = "https://github.com/WilliamVenner/gmod-rs" 9 | keywords = ["gmod", "garrysmod", "module", "glua"] 10 | categories = ["api-bindings", "external-ffi-bindings", "game-development", "development-tools"] 11 | 12 | [features] 13 | default = ["hax"] 14 | hax = ["ctor", "skidscan", "retour", "fn_type_alias", "fn_abi", "cfg_table", "null_fn", "fn_has_this"] 15 | gmcl = ["gmod-macros/gmcl"] 16 | 17 | [dependencies] 18 | gmod-macros = { version = "2.0.1", path = "../gmod-macros" } 19 | 20 | libloading = "0" 21 | cstr = "0" 22 | lazy_static = "1" 23 | 24 | retour = { version = "0.4.0-alpha.4", features = ["thiscall-abi"], optional = true } 25 | ctor = { version = "0", optional = true } 26 | skidscan = { version = "2", optional = true } 27 | 28 | fn_type_alias = { version = "0", optional = true } 29 | fn_abi = { version = "2", optional = true } 30 | cfg_table = { version = "1", optional = true } 31 | null_fn = { version = "0", optional = true } 32 | fn_has_this = { version = "0", optional = true } 33 | -------------------------------------------------------------------------------- /gmod/src/lua/raw_bind.rs: -------------------------------------------------------------------------------- 1 | use crate::lua::*; 2 | 3 | pub trait CLuaFunction: Copy {} 4 | 5 | macro_rules! impl_c_lua_function { 6 | ($($($arg:ident) *;)*) => { 7 | $( 8 | impl<$($arg, )* R> CLuaFunction for extern "C-unwind" fn($($arg),*) -> R {} 9 | impl<$($arg, )* R> CLuaFunction for unsafe extern "C-unwind" fn($($arg),*) -> R {} 10 | impl<$($arg, )* R> CLuaFunction for extern "C" fn($($arg),*) -> R {} 11 | impl<$($arg, )* R> CLuaFunction for unsafe extern "C" fn($($arg),*) -> R {} 12 | )* 13 | }; 14 | } 15 | impl_c_lua_function!( 16 | ; 17 | T1; 18 | T1 T2; 19 | T1 T2 T3; 20 | T1 T2 T3 T4; 21 | T1 T2 T3 T4 T5; 22 | T1 T2 T3 T4 T5 T6; 23 | T1 T2 T3 T4 T5 T6 T7; 24 | T1 T2 T3 T4 T5 T6 T7 T8; 25 | T1 T2 T3 T4 T5 T6 T7 T8 T9; 26 | T1 T2 T3 T4 T5 T6 T7 T8 T9 T10; 27 | T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11; 28 | T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12; 29 | T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13; 30 | T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14; 31 | T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15; 32 | T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16; 33 | ); 34 | 35 | impl State { 36 | #[inline(always)] 37 | /// Binds to a raw Lua C function. 38 | /// 39 | /// If anything is missing from this library, you can use this function to bind it yourself. 40 | /// 41 | /// Note, this may be a somewhat expensive operation, so storing its result in some way is recommended. 42 | pub unsafe fn raw_bind(&self, symbol: &[u8]) -> Result { 43 | LUA_SHARED.library.get::(symbol).map(|f| *f) 44 | } 45 | } -------------------------------------------------------------------------------- /gmod/src/hax.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | /// Common pattern for detouring. 3 | macro_rules! find_gmod_signature { 4 | (($library:ident, $library_path:ident), @EXPORT = $export:literal) => { 5 | $library.get(concat!($export, '\0').as_bytes()).ok().map(|func: $crate::libloading::Symbol<'_, _>| *func) 6 | }; 7 | 8 | (($library:ident, $library_path:ident), @SIG = $sig:literal) => { 9 | $crate::sigscan::signature!($sig).scan_module($library_path).ok().map(|x| std::mem::transmute(x)) 10 | }; 11 | 12 | (($library:ident, $library_path:ident) -> { 13 | win64_x86_64: [$($win64_x86_64:tt)+], 14 | win32_x86_64: [$($win32_x86_64:tt)+], 15 | 16 | linux64_x86_64: [$($linux64_x86_64:tt)+], 17 | linux32_x86_64: [$($linux32_x86_64:tt)+], 18 | 19 | win32: [$($win32:tt)+], 20 | linux32: [$($linux32:tt)+], 21 | }) => {{ 22 | let x86_64 = $crate::is_x86_64(); 23 | if x86_64 { 24 | #[cfg(all(target_os = "windows", target_pointer_width = "64"))] { 25 | $crate::find_gmod_signature!(($library, $library_path), $($win64_x86_64)+) 26 | } 27 | #[cfg(all(target_os = "windows", target_pointer_width = "32"))] { 28 | $crate::find_gmod_signature!(($library, $library_path), $($win32_x86_64)+) 29 | } 30 | #[cfg(all(target_os = "linux", target_pointer_width = "64"))] { 31 | $crate::find_gmod_signature!(($library, $library_path), $($linux64_x86_64)+) 32 | } 33 | #[cfg(all(target_os = "linux", target_pointer_width = "32"))] { 34 | $crate::find_gmod_signature!(($library, $library_path), $($linux32_x86_64)+) 35 | } 36 | } else { 37 | #[cfg(target_os = "windows")] { 38 | $crate::find_gmod_signature!(($library, $library_path), $($win32)+) 39 | } 40 | #[cfg(target_os = "linux")] { 41 | $crate::find_gmod_signature!(($library, $library_path), $($linux32)+) 42 | } 43 | } 44 | }} 45 | } -------------------------------------------------------------------------------- /gmod/src/lua/returns.rs: -------------------------------------------------------------------------------- 1 | use std::{num::NonZeroI32, borrow::Cow}; 2 | 3 | #[repr(transparent)] 4 | pub struct ValuesReturned(pub i32); 5 | 6 | impl From for i32 { 7 | #[inline(always)] 8 | fn from(v: ValuesReturned) -> Self { 9 | v.0 10 | } 11 | } 12 | 13 | impl From for ValuesReturned { 14 | #[inline(always)] 15 | fn from(n: i32) -> Self { 16 | ValuesReturned(n) 17 | } 18 | } 19 | 20 | impl From for ValuesReturned { 21 | #[inline(always)] 22 | fn from(n: NonZeroI32) -> ValuesReturned { 23 | ValuesReturned(i32::from(n)) 24 | } 25 | } 26 | 27 | impl From<()> for ValuesReturned { 28 | #[inline(always)] 29 | fn from(_: ()) -> ValuesReturned { 30 | ValuesReturned(0) 31 | } 32 | } 33 | 34 | impl From> for ValuesReturned { 35 | #[inline(always)] 36 | fn from(opt: Option) -> ValuesReturned { 37 | ValuesReturned(match opt { 38 | Some(vals) => i32::from(vals), 39 | None => { 40 | unsafe { super::state().push_nil() }; 41 | 1 42 | }, 43 | }) 44 | } 45 | } 46 | 47 | pub trait DisplayLuaError { 48 | fn display_lua_error(&self) -> Cow<'_, str>; 49 | } 50 | impl DisplayLuaError for E { 51 | #[inline(always)] 52 | fn display_lua_error(&self) -> Cow<'_, str> { 53 | Cow::Owned(format!("{:?}", self)) 54 | } 55 | } 56 | impl From> for ValuesReturned { 57 | #[inline(always)] 58 | fn from(res: Result) -> ValuesReturned { 59 | match res { 60 | Ok(vals) => ValuesReturned(vals), 61 | Err(err) => unsafe { super::state().error(err.display_lua_error().as_ref()) } 62 | } 63 | } 64 | } 65 | impl From> for ValuesReturned { 66 | #[inline(always)] 67 | fn from(res: Result<(), E>) -> ValuesReturned { 68 | match res { 69 | Ok(_) => ValuesReturned(0), 70 | Err(err) => unsafe { super::state().error(err.display_lua_error().as_ref()) } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /tests/userdata/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate gmod; 3 | 4 | static mut DROP_OK: bool = false; 5 | 6 | #[derive(PartialEq, Eq, Debug)] 7 | pub struct DropMe { 8 | pub x: i32, 9 | pub y: i32, 10 | pub z: i32, 11 | pub hello: String 12 | } 13 | impl Drop for DropMe { 14 | fn drop(&mut self) { 15 | unsafe { 16 | if DROP_OK { 17 | DROP_OK = false; 18 | println!("USERDATA DROP TEST PASSED"); 19 | println!("USERDATA DROP TEST PASSED"); 20 | println!("USERDATA DROP TEST PASSED"); 21 | println!("USERDATA DROP TEST PASSED"); 22 | println!("USERDATA DROP TEST PASSED"); 23 | println!("USERDATA DROP TEST PASSED"); 24 | println!("USERDATA DROP TEST PASSED"); 25 | println!("USERDATA DROP TEST PASSED"); 26 | println!("USERDATA DROP TEST PASSED"); 27 | println!("USERDATA DROP TEST PASSED"); 28 | println!("USERDATA DROP TEST PASSED"); 29 | println!("USERDATA DROP TEST PASSED"); 30 | println!("USERDATA DROP TEST PASSED"); 31 | } else { 32 | panic!("Dropped too early or too late"); 33 | } 34 | } 35 | } 36 | } 37 | 38 | macro_rules! drop_me { 39 | () => { 40 | DropMe { 41 | x: 69, 42 | y: 420, 43 | z: 123, 44 | hello: "Hello".to_string() 45 | } 46 | }; 47 | } 48 | 49 | #[gmod13_open] 50 | unsafe fn gmod13_open(lua: gmod::lua::State) -> i32 { 51 | let ud = lua.new_userdata(drop_me!(), None); 52 | assert_eq!(&*ud, Box::leak(Box::new(drop_me!()))); 53 | 54 | lua.set_global(lua_string!("GMOD_RUST_DROP_TEST")); 55 | 56 | lua.push_nil(); 57 | lua.set_global(lua_string!("GMOD_RUST_DROP_TEST")); 58 | DROP_OK = true; 59 | 60 | lua.get_global(lua_string!("collectgarbage")); 61 | lua.push_value(-1); 62 | lua.call(0, 0); 63 | lua.call(0, 0); 64 | 65 | let ud = lua.new_userdata(420_i32, None); 66 | assert_eq!(*ud, 420_i32); 67 | 68 | lua.get_global(lua_string!("collectgarbage")); 69 | lua.push_value(-1); 70 | lua.call(0, 0); 71 | lua.call(0, 0); 72 | 73 | 0 74 | } -------------------------------------------------------------------------------- /gmod/src/msgc.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | 3 | use std::os::raw::c_char; 4 | 5 | #[inline(always)] 6 | pub fn printf_escape>(str: S) -> String { 7 | str.as_ref().replace('\\', "\\\\").replace('%', "%%") 8 | } 9 | 10 | #[repr(C)] 11 | pub struct Color { 12 | r: u8, 13 | g: u8, 14 | b: u8, 15 | a: u8 16 | } 17 | impl Color { 18 | #[inline(always)] 19 | pub const fn new(r: u8, g: u8, b: u8) -> Color { 20 | Color { r, g, b, a: 255 } 21 | } 22 | } 23 | 24 | lazy_static::lazy_static! { 25 | pub static ref ConColorMsg: libloading::Symbol<'static, unsafe extern "C" fn(&Color, *const c_char, ...)> = unsafe { 26 | #[cfg(all(target_os = "windows", target_pointer_width = "64"))] 27 | let lib = libloading::Library::new("bin/win64/tier0.dll").expect("Failed to open tier0.dll"); 28 | 29 | #[cfg(all(target_os = "windows", target_pointer_width = "32"))] 30 | let lib = libloading::Library::new("bin/tier0.dll").or_else(|_| libloading::Library::new("bin/win32/tier0.dll")).expect("Failed to open tier0.dll"); 31 | 32 | #[cfg(all(target_os = "linux", target_pointer_width = "64"))] 33 | let lib = libloading::Library::new("bin/linux64/libtier0.so").expect("Failed to open libtier0.so"); 34 | 35 | #[cfg(all(target_os = "linux", target_pointer_width = "32"))] 36 | let lib = libloading::Library::new("bin/libtier0_srv.so").or_else(|_| libloading::Library::new("bin/linux32/libtier0.so")).expect("Failed to open libtier0.so"); 37 | 38 | #[cfg(target_os = "macos")] 39 | let lib = libloading::Library::new("bin/libtier0.dylib").or_else(|_| libloading::Library::new("GarrysMod_Signed.app/Contents/MacOS/libtier0.dylib")).expect("Failed to open libtier0.dylib"); 40 | 41 | let lib = Box::leak(Box::new(lib)); 42 | { 43 | #[cfg(all(target_os = "windows", target_pointer_width = "64"))] { 44 | lib.get(b"?ConColorMsg@@YAXAEBVColor@@PEBDZZ\0") 45 | } 46 | #[cfg(all(target_os = "windows", target_pointer_width = "32"))] { 47 | match lib.get(b"?ConColorMsg@@YAXABVColor@@PBDZZ\0") { 48 | Ok(symbol) => Ok(symbol), 49 | Err(_) => lib.get(b"?ConColorMsg@@YAXABVColor@@PBDZZ\0") 50 | } 51 | } 52 | #[cfg(any(target_os = "linux", target_os = "macos"))] { 53 | lib.get(b"_Z11ConColorMsgRK5ColorPKcz\0") 54 | } 55 | } 56 | .expect("Failed to get ConColorMsg") 57 | }; 58 | } 59 | #[macro_export] 60 | macro_rules! colormsg { 61 | ($($arg:tt),+) => { 62 | $($crate::colormsg!(@print $arg));+ 63 | }; 64 | 65 | (@print [$r:literal, $g:literal, $b:literal] $fmt:literal % ($($arg:tt),+)) => { 66 | $crate::msgc::ConColorMsg( 67 | &$crate::msgc::Color::new($r, $g, $b), 68 | $crate::msgc::printf_escape(format!(concat!($fmt, '\0'), $($arg),+)).as_ptr() as *const _, 69 | ) 70 | }; 71 | 72 | (@print [$r:literal, $g:literal, $b:literal] $str:literal) => { 73 | $crate::msgc::ConColorMsg( 74 | &$crate::msgc::Color::new($r, $g, $b), 75 | $crate::msgc::printf_escape(concat!($str, '\0')).as_ptr() as *const _, 76 | ) 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /gmod/src/userdata.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] 3 | pub enum UserData { 4 | None = 255, 5 | 6 | Nil = 0, 7 | Bool, 8 | LightUserData, 9 | Number, 10 | String, 11 | Table, 12 | Function, 13 | UserData, 14 | Thread, 15 | 16 | // GMod Types 17 | Entity, 18 | Vector, 19 | Angle, 20 | PhysObj, 21 | Save, 22 | Restore, 23 | DamageInfo, 24 | EffectData, 25 | MoveData, 26 | RecipientFilter, 27 | UserCmd, 28 | ScriptedVehicle, 29 | Material, 30 | Panel, 31 | Particle, 32 | ParticleEmitter, 33 | Texture, 34 | UserMsg, 35 | ConVar, 36 | IMesh, 37 | Matrix, 38 | Sound, 39 | PixelVisHandle, 40 | DLight, 41 | Video, 42 | File, 43 | Locomotion, 44 | Path, 45 | NavArea, 46 | SoundHandle, 47 | NavLadder, 48 | ParticleSystem, 49 | ProjectedTexture, 50 | PhysCollide, 51 | SurfaceInfo, 52 | 53 | MAX 54 | } 55 | 56 | #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] 57 | #[repr(C)] 58 | pub struct Vector { 59 | pub x: f32, 60 | pub y: f32, 61 | pub z: f32 62 | } 63 | 64 | #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] 65 | #[repr(C)] 66 | pub struct Angle { 67 | pub p: f32, 68 | pub y: f32, 69 | pub r: f32 70 | } 71 | 72 | #[repr(C)] 73 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 74 | pub struct TaggedUserData 75 | { 76 | pub data: *mut core::ffi::c_void, 77 | pub r#type: UserData 78 | } 79 | 80 | pub trait CoercibleUserData {} 81 | 82 | macro_rules! userdata { 83 | ($(UserData::$enum:ident => $struct:ident),+) => { 84 | $(impl CoercibleUserData for $struct {})+ 85 | 86 | impl TaggedUserData { 87 | /// Coerce this tagged UserData into its corresponding Rust struct, if possible. 88 | /// 89 | /// This will perform a type check to ensure that the tagged userdata matches the user data you are coercing to. 90 | pub fn coerce<'a, T: CoercibleUserData>(&self) -> Result<&mut T, UserData> { 91 | match self.r#type { 92 | $(UserData::$enum => Ok(unsafe { &mut *(self.data as *mut T) }),)+ 93 | _ => Err(self.r#type) 94 | } 95 | } 96 | 97 | /// Coerce this tagged UserData into its corresponding Rust struct, if possible. 98 | /// 99 | /// # Safety 100 | /// This will NOT perform a type check to ensure that the tagged userdata matches the user data you are coercing to. 101 | /// 102 | /// Coercing to the wrong type is undefined behaviour and is likely to crash your program. 103 | pub unsafe fn coerce_unchecked<'a, 'b, T: CoercibleUserData>(&'a self) -> &'b mut T { 104 | &mut *(self.data as *mut T) 105 | } 106 | } 107 | }; 108 | } 109 | userdata! { 110 | UserData::Vector => Vector, 111 | UserData::Angle => Angle 112 | } 113 | 114 | pub(crate) unsafe extern "C-unwind" fn __gc(lua: crate::lua::State) -> i32 { 115 | let userdata = lua.to_userdata(1) as *mut T; 116 | std::ptr::read(userdata); 117 | 0 118 | } -------------------------------------------------------------------------------- /examples/my-first-binary-module/README.md: -------------------------------------------------------------------------------- 1 | # Installing Rust 2 | 3 | Installing Rust is as easy as downloading [rustup](https://rustup.rs/) and running it! 4 | 5 | # Building the example 6 | 7 | To build the example in debug mode, you'll need to specify the target architecture for your build. 8 | 9 | | Platform | Command | Description | 10 | |:---:|:---:|:---:| 11 | | `win32` | `cargo build --target i686-pc-windows-msvc` | Windows 32-bit
Use this if your server is running Windows and is on the `main` branch of Garry's Mod (this is the default branch.) | 12 | | `win64` | `cargo build --target x86_64-pc-windows-msvc` | Windows 64-bit
Use this if your server is running Windows and is on the `x86-64` branch of Garry's Mod. | 13 | | `linux` | `cargo build --target i686-unknown-linux-gnu` | Linux 32-bit
Use this if your server is running Linux and is on the `main` branch of Garry's Mod (this is the default branch.) | 14 | | `linux64` | `cargo build --target x86_64-unknown-linux-gnu` |Linux 64-bit
Use this if your server is running Linux and is on the `x86-64` branch of Garry's Mod. | 15 | 16 | You can find the compiled binary in `target//debug/my_first_binary_module.dll` on Windows or `target//debug/libmy_first_binary_module.so` on Linux. 17 | 18 | If Rust complains it can't find the target/toolchain, you'll need to install it. By default Rust only installs your system's native toolchain, which is most likely Windows 64-bit (`x86_64-pc-windows-msvc`) 19 | 20 | I don't recommend cross-compiling Linux binaries on Windows. If you want to compile Linux binaries on Windows, do it in WSL. 21 | 22 | # Using the example in Garry's Mod 23 | 24 | First, rename the compiled binary to `gmsv_my_first_binary_module_PLATFORM.dll` where `PLATFORM` is one of the following: 25 | 26 | | Platform | Description | 27 | |:---:|:---:| 28 | | `win32` | Windows 32-bit
Use this if your server is running Windows and is on the `main` branch of Garry's Mod (this is the default branch.) | 29 | | `win64` | Windows 64-bit
Use this if your server is running Windows and is on the `x86-64` branch of Garry's Mod. | 30 | | `linux` | Linux 32-bit
Use this if your server is running Linux and is on the `main` branch of Garry's Mod (this is the default branch.) | 31 | | `linux64` | Linux 64-bit
Use this if your server is running Linux and is on the `x86-64` branch of Garry's Mod. | 32 | 33 | Then, move the compiled binary to `garrysmod/lua/bin/` on your server. If the `bin` folder doesn't exist, create it. 34 | 35 | Finally, you can load the module from Lua! 36 | 37 | ```lua 38 | require("my_first_binary_module") 39 | ``` 40 | 41 | # Preparing your module for release 42 | 43 | If you've written a useful module and want to release it to the world, or just on your server, build with the `--release` flag: 44 | 45 | `cargo build --target --release` 46 | 47 | This enables performance optimization of the compiled binary and removes debug symbols which make the binary huge, whilst taking longer to compile. 48 | 49 | On Linux, you'll want to run the `strip` command on the compiled binary to remove debug symbols. 50 | -------------------------------------------------------------------------------- /gmod/src/gmcl.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::{Arc, Mutex, TryLockError, atomic::AtomicBool}, time::Duration, os::raw::c_char, thread::JoinHandle}; 2 | 3 | lazy_static::lazy_static! { 4 | static ref STDOUT_OVERRIDE_THREAD: Mutex>> = Mutex::new(None); 5 | } 6 | 7 | static SHUTDOWN_FLAG: AtomicBool = AtomicBool::new(false); 8 | 9 | /// This function will **permanently** redirect stdout to the client console. 10 | /// 11 | /// This allows for `println!()` and friends to print to the client console. 12 | /// 13 | /// # IMPORTANT 14 | /// 15 | /// You must undo this action when your module is unloaded or the game will crash. 16 | /// 17 | /// This will be done automatically for you if you use the `#[gmod13_close]` attribute macro, otherwise, please call `gmod::gmcl::restore_stdout()` in your custom `gmod13_close` function. 18 | pub fn override_stdout() { 19 | let mut join_handle = STDOUT_OVERRIDE_THREAD.lock().unwrap(); 20 | 21 | if join_handle.is_some() { 22 | // We don't need to override twice 23 | return; 24 | } 25 | 26 | unsafe { 27 | let (_lib, _path) = crate::open_library!("tier0").expect("Failed to open tier0.dll"); 28 | 29 | #[allow(non_snake_case)] 30 | let ConMsg: extern "C" fn(*const c_char, ...) = *{ 31 | #[cfg(target_os = "windows")] { 32 | _lib.get({ 33 | #[cfg(all(target_os = "windows", target_pointer_width = "64"))] { 34 | b"?ConMsg@@YAXPEBDZZ\0" 35 | } 36 | #[cfg(all(target_os = "windows", target_pointer_width = "32"))] { 37 | b"?ConMsg@@YAXPBDZZ\0" 38 | } 39 | }) 40 | } 41 | #[cfg(any(target_os = "linux", target_os = "macos"))] { 42 | _lib.get(b"ConMsg\0").or_else(|_| _lib.get(b"_Z6ConMsgPKcz\0")) 43 | } 44 | }.expect("Failed to find ConMsg"); 45 | 46 | let output_buf = Arc::new(Mutex::new(Vec::new())); 47 | let output_buf_ref = output_buf.clone(); 48 | 49 | // This is actually a really dumb implementation, but appears to be the only way, unfortunately. 50 | join_handle.replace(std::thread::spawn(move || loop { 51 | match output_buf.try_lock() { 52 | Ok(mut data) => if !data.is_empty() { 53 | data.push(0); // cheeky 54 | ConMsg(data.as_ptr() as *const i8); 55 | 56 | data.truncate(0); 57 | }, 58 | Err(TryLockError::Poisoned(err)) => panic!("{}", err), 59 | Err(TryLockError::WouldBlock) => { 60 | std::hint::spin_loop(); 61 | std::thread::yield_now(); 62 | continue 63 | } 64 | } 65 | if SHUTDOWN_FLAG.load(std::sync::atomic::Ordering::Relaxed) { 66 | break; 67 | } 68 | std::thread::sleep(Duration::from_millis(250)); 69 | })); 70 | 71 | std::io::set_output_capture(Some(output_buf_ref)); 72 | }; 73 | } 74 | 75 | /// Undoes `gmod::gmcl::override_stdout`. You must call this function in a custom `gmod13_close` function (you are not using the crate's provided `#[gmod13_close]` attribute macro) if you override stdout. 76 | pub fn restore_stdout() { 77 | SHUTDOWN_FLAG.store(true, std::sync::atomic::Ordering::Release); 78 | 79 | if let Some(join_handle) = STDOUT_OVERRIDE_THREAD.lock().unwrap().take() { 80 | let _ = join_handle.join(); 81 | } 82 | 83 | std::io::set_output_capture(None); // TODO fix side effect 84 | } -------------------------------------------------------------------------------- /gmod-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate syn; 3 | 4 | #[macro_use] 5 | extern crate quote; 6 | 7 | use proc_macro::TokenStream; 8 | use quote::ToTokens; 9 | use syn::ItemFn; 10 | 11 | macro_rules! wrap_compile_error { 12 | ($input:ident, $code:expr) => {{ 13 | let orig_tokens = $input.clone(); 14 | match (|| -> Result { $code })() { 15 | Ok(tokens) => tokens, 16 | Err(_) => return orig_tokens 17 | } 18 | }}; 19 | } 20 | 21 | fn check_lua_function(input: &mut ItemFn) { 22 | assert!(input.sig.asyncness.is_none(), "Cannot be async"); 23 | assert!(input.sig.constness.is_none(), "Cannot be const"); 24 | assert!(input.sig.inputs.len() == 1, "There can only be one argument, and it should be a pointer to the Lua state (gmod::lua::State)"); 25 | assert!(input.sig.abi.is_none() || input.sig.abi.as_ref().and_then(|abi| abi.name.as_ref()).map(|abi| abi.value() == "C-unwind").unwrap_or(true), "Do not specify an ABI"); 26 | input.sig.abi = Some(syn::parse_quote!(extern "C-unwind")); 27 | } 28 | 29 | fn genericify_return(item_fn: &mut ItemFn) { 30 | let stmts = std::mem::take(&mut item_fn.block.stmts); 31 | let output = std::mem::replace(&mut item_fn.sig.output, parse_quote!(-> i32)); 32 | item_fn.block.stmts = vec![syn::parse2(quote!({::gmod::lua::ValuesReturned::from((|| #output {#(#stmts);*})()).into()})).unwrap()]; 33 | } 34 | 35 | #[proc_macro_attribute] 36 | pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream { 37 | wrap_compile_error!(tokens, { 38 | let mut input = syn::parse::(tokens)?; 39 | 40 | let lua_ident = format_ident!("{}", match &input.sig.inputs[0] { 41 | syn::FnArg::Typed(arg) => arg.pat.to_token_stream().to_string(), 42 | _ => unreachable!(), 43 | }); 44 | 45 | // Capture the Lua state 46 | input.block.stmts.insert(0, syn::parse2(quote!(::gmod::lua::__set_state__internal(#lua_ident);)).unwrap()); 47 | 48 | // Load lua_shared 49 | input.block.stmts.insert(0, syn::parse2(quote!(#[allow(unused_unsafe)] unsafe { ::gmod::lua::load() })).unwrap()); 50 | 51 | // Make sure it's valid 52 | check_lua_function(&mut input); 53 | 54 | // No mangling 55 | input.attrs.push(parse_quote!(#[no_mangle])); 56 | 57 | // Make the return type nice and dynamic 58 | genericify_return(&mut input); 59 | 60 | Ok(input.into_token_stream().into()) 61 | }) 62 | } 63 | 64 | #[proc_macro_attribute] 65 | pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream { 66 | wrap_compile_error!(tokens, { 67 | let mut input = syn::parse::(tokens)?; 68 | 69 | // Make sure it's valid 70 | check_lua_function(&mut input); 71 | 72 | // No mangling 73 | input.attrs.push(parse_quote!(#[no_mangle])); 74 | 75 | // Shutdown gmcl thread if it's running 76 | #[cfg(feature = "gmcl")] { 77 | let stmts = std::mem::take(&mut input.block.stmts); 78 | input.block.stmts = vec![syn::parse2(quote!({ 79 | let ret = (|| {#(#stmts);*})(); 80 | ::gmod::gmcl::restore_stdout(); 81 | ret 82 | })).unwrap()]; 83 | } 84 | 85 | // Make the return type nice and dynamic 86 | genericify_return(&mut input); 87 | 88 | Ok(input.into_token_stream().into()) 89 | }) 90 | } 91 | 92 | #[proc_macro_attribute] 93 | pub fn lua_function(_attr: TokenStream, tokens: TokenStream) -> TokenStream { 94 | wrap_compile_error!(tokens, { 95 | let mut input = syn::parse::(tokens)?; 96 | 97 | // Make sure it's valid 98 | check_lua_function(&mut input); 99 | 100 | // Make the return type nice and dynamic 101 | genericify_return(&mut input); 102 | 103 | Ok(input.into_token_stream().into()) 104 | }) 105 | } -------------------------------------------------------------------------------- /gmod/src/lua/push.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime}; 2 | 3 | pub trait PushToLua: Sized { 4 | /// Pushes this value to the Lua stack. 5 | unsafe fn push_to_lua(self, lua: crate::lua::State); 6 | } 7 | pub trait TryPushToLua: Sized { 8 | /// Checked `push_to_lua` for types that may not fit in an `i32` 9 | unsafe fn try_push_to_lua(self, lua: crate::lua::State) -> Result<(), Self>; 10 | } 11 | pub trait ForcePushToLua: Sized { 12 | /// `push_to_lua` but may result in loss of data 13 | unsafe fn force_push_to_lua(self, lua: crate::lua::State); 14 | } 15 | pub trait PushCollectionToLua: Sized { 16 | /// Pushes this collection to a table at the top of the Lua stack. 17 | /// 18 | /// **You must create the table yourself** 19 | unsafe fn push_to_lua_table(self, lua: crate::lua::State); 20 | } 21 | 22 | impl TryPushToLua for P { 23 | #[inline] 24 | unsafe fn try_push_to_lua(self, lua: crate::lua::State) -> Result<(), Self> { 25 | self.push_to_lua(lua); 26 | Ok(()) 27 | } 28 | } 29 | implForcePushToLua for P { 30 | #[inline] 31 | unsafe fn force_push_to_lua(self, lua: crate::lua::State) { 32 | self.push_to_lua(lua); 33 | } 34 | } 35 | 36 | macro_rules! push_primitives { 37 | {$($ty:ty => $fn:ident),*} => {$( 38 | impl PushToLua for $ty { 39 | #[inline] 40 | unsafe fn push_to_lua(self, lua: crate::lua::State) { 41 | lua.$fn(self as _); 42 | } 43 | } 44 | )*}; 45 | } 46 | macro_rules! try_push_primitives { 47 | {$($ty:ty => $fn:ident / $forcefn:ident),*} => {$( 48 | impl TryPushToLua for $ty { 49 | #[inline] 50 | unsafe fn try_push_to_lua(self, lua: crate::lua::State) -> Result<(), Self> { 51 | lua.$fn(match self.try_into() { 52 | Ok(v) => v, 53 | Err(e) => return Err(self) 54 | }); 55 | Ok(()) 56 | } 57 | } 58 | impl ForcePushToLua for $ty { 59 | #[inline] 60 | unsafe fn force_push_to_lua(self, lua: crate::lua::State) { 61 | lua.$forcefn(self as _); 62 | } 63 | } 64 | )*}; 65 | } 66 | 67 | push_primitives! { 68 | &str => push_string, 69 | bool => push_boolean, 70 | f64 => push_number, 71 | f32 => push_number, 72 | u8 => push_integer, 73 | i8 => push_integer, 74 | u16 => push_integer, 75 | i16 => push_integer, 76 | i32 => push_integer 77 | } 78 | try_push_primitives! { 79 | u32 => push_integer / push_number, 80 | i64 => push_integer / push_number, 81 | u64 => push_integer / push_number, 82 | u128 => push_integer / push_number, 83 | i128 => push_integer / push_number 84 | } 85 | 86 | impl PushToLua for String { 87 | #[inline] 88 | unsafe fn push_to_lua(self, lua: crate::lua::State) { 89 | lua.push_string(&self); 90 | } 91 | } 92 | impl PushToLua for Vec { 93 | #[inline] 94 | unsafe fn push_to_lua(self, lua: crate::lua::State) { 95 | lua.push_binary_string(&self); 96 | } 97 | } 98 | impl PushToLua for &[u8] { 99 | #[inline] 100 | unsafe fn push_to_lua(self, lua: crate::lua::State) { 101 | lua.push_binary_string(self); 102 | } 103 | } 104 | impl PushToLua for Duration { 105 | #[inline] 106 | unsafe fn push_to_lua(self, lua: crate::lua::State) { 107 | lua.push_number(self.as_secs_f64()); 108 | } 109 | } 110 | impl PushToLua for Option { 111 | #[inline] 112 | unsafe fn push_to_lua(self, lua: crate::lua::State) { 113 | match self { 114 | Some(val) => val.push_to_lua(lua), 115 | None => lua.push_nil() 116 | } 117 | } 118 | } 119 | impl PushCollectionToLua for std::collections::BTreeMap { 120 | #[inline] 121 | unsafe fn push_to_lua_table(self, lua: crate::lua::State) { 122 | for (k, v) in self { 123 | k.push_to_lua(lua); 124 | v.push_to_lua(lua); 125 | lua.set_table(-3); 126 | } 127 | } 128 | } 129 | impl PushCollectionToLua for Vec { 130 | #[inline] 131 | unsafe fn push_to_lua_table(self, lua: crate::lua::State) { 132 | iterator(lua, &mut self.into_iter()) 133 | } 134 | } 135 | 136 | impl TryPushToLua for SystemTime { 137 | #[inline] 138 | unsafe fn try_push_to_lua(self, lua: crate::lua::State) -> Result<(), Self> { 139 | lua.push_number(self.duration_since(SystemTime::UNIX_EPOCH).map_err(|_| self)?.as_secs_f64()); 140 | Ok(()) 141 | } 142 | } 143 | 144 | /// Pushes all elements in an iterator to a Lua table at the top of the stack. 145 | /// 146 | /// **You must create the table yourself** 147 | #[inline] 148 | pub unsafe fn iterator>(lua: crate::lua::State, iter: &mut I) { 149 | for (i, val) in iter.enumerate() { 150 | lua.push_integer((i + 1) as _); 151 | val.push_to_lua(lua); 152 | lua.set_table(-3); 153 | } 154 | } -------------------------------------------------------------------------------- /gmod/src/lua/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | mod import; 4 | use std::cell::Cell; 5 | 6 | pub use import::*; 7 | 8 | mod lua_state; 9 | pub use lua_state::LuaState as State; 10 | 11 | mod push; 12 | pub use push::*; 13 | 14 | mod returns; 15 | pub use returns::ValuesReturned; 16 | 17 | mod raw_bind; 18 | 19 | #[derive(Debug, Clone)] 20 | pub enum LuaError { 21 | /// Out of memory 22 | /// 23 | /// `LUA_ERRMEM` 24 | MemoryAllocationError, 25 | 26 | /// A syntax error occurred in the passed Lua source code. 27 | /// 28 | /// `LUA_ERRSYNTAX` 29 | SyntaxError(Option), 30 | 31 | /// Lua failed to load the given file. 32 | /// 33 | /// `LUA_ERRFILE` 34 | FileError(Option), 35 | 36 | /// A runtime error occurred. 37 | /// 38 | /// `LUA_ERRRUN` 39 | RuntimeError(Option), 40 | 41 | /// An error occurred while running the error handler function. 42 | /// 43 | /// `LUA_ERRERR` 44 | ErrorHandlerError, 45 | 46 | /// Unknown Lua error code 47 | Unknown(i32), 48 | } 49 | 50 | /// Converts a string literal to a Lua-compatible NUL terminated string at compile time. 51 | #[macro_export] 52 | macro_rules! lua_string { 53 | ( $str:literal ) => { 54 | $crate::cstr::cstr!($str).as_ptr() 55 | }; 56 | } 57 | 58 | /// Enforces a debug assertion that the Lua stack is unchanged after this block of code is executed. 59 | /// 60 | /// Useful for ensuring stack hygiene. 61 | /// 62 | /// `lua` is the Lua state to check. 63 | /// 64 | /// # Example 65 | /// 66 | /// ```rust,norun 67 | /// lua_stack_guard!(lua => { 68 | /// lua.get_global(lua_string!("hook")); 69 | /// lua.get_field(-1, lua_string!("Add")); 70 | /// lua.push_string("PlayerInitialSpawn"); 71 | /// lua.push_string("RustHook"); 72 | /// lua.push_function(player_initial_spawn); 73 | /// lua.call(3, 0); 74 | /// // lua.pop(); 75 | /// }); 76 | /// // PANIC: stack is dirty! We forgot to pop the hook library off the stack. 77 | /// ``` 78 | #[macro_export] 79 | macro_rules! lua_stack_guard { 80 | ( $lua:ident => $code:block ) => {{ 81 | #[cfg(debug_assertions)] { 82 | let top = $lua.get_top(); 83 | let ret = $code; 84 | if top != $lua.get_top() { 85 | $lua.dump_stack(); 86 | panic!("Stack is dirty! Expected the stack to have {} elements, but it has {}!", top, $lua.get_top()); 87 | } 88 | ret 89 | } 90 | 91 | #[cfg(not(debug_assertions))] 92 | $code 93 | }}; 94 | 95 | ( $lua:ident => $elem:literal => $code:block ) => {{ 96 | #[cfg(debug_assertions)] { 97 | let ret = (|| $code)(); 98 | if $lua.get_top() != $elem { 99 | $lua.dump_stack(); 100 | panic!("Stack is dirty! Expected the stack to have ", $elem, " (fixed size) elements, but it has {}!", $lua.get_top()); 101 | } 102 | ret 103 | } 104 | 105 | #[cfg(not(debug_assertions))] 106 | $code 107 | }}; 108 | } 109 | 110 | #[derive(Clone, Copy)] 111 | #[repr(C)] 112 | pub struct LuaDebug { 113 | pub event: i32, 114 | pub name: LuaString, 115 | pub namewhat: LuaString, 116 | pub what: LuaString, 117 | pub source: LuaString, 118 | pub currentline: i32, 119 | pub nups: i32, 120 | pub linedefined: i32, 121 | pub lastlinedefined: i32, 122 | pub short_src: [std::os::raw::c_char; LUA_IDSIZE], 123 | pub i_ci: i32 124 | } 125 | 126 | #[inline(always)] 127 | /// Loads lua_shared and imports all functions. This is already done for you if you add `#[gmod::gmod13_open]` to your `gmod13_open` function. 128 | pub unsafe fn load() { 129 | import::LUA_SHARED.load() 130 | } 131 | 132 | thread_local! { 133 | #[cfg(debug_assertions)] 134 | static LUA: Cell> = Cell::new(None); 135 | 136 | #[cfg(not(debug_assertions))] 137 | static LUA: Cell = Cell::new(State(std::ptr::null_mut())); 138 | } 139 | /// Acquires a pointer to the Lua state for the current thread. 140 | /// 141 | /// This will panic if called from anywhere but the main thread. This will panic if you are not using the `#[gmod13_open]` macro to open the Lua state. 142 | /// 143 | /// This will NOT panic in release mode under these conditions and will instead cause undefined behaviour. 144 | pub unsafe fn state() -> State { 145 | LUA.with(|cell| { 146 | #[cfg(debug_assertions)] { 147 | cell.get().expect("The Lua state cannot be found in this thread. Perhaps you are calling this function from a thread other than the main thread? Perhaps you forgot to use the `#[gmod13_open]` macro?") 148 | } 149 | #[cfg(not(debug_assertions))] { 150 | cell.get() 151 | } 152 | }) 153 | } 154 | 155 | #[doc(hidden)] 156 | #[allow(non_snake_case)] 157 | pub fn __set_state__internal(state: State) { 158 | LUA.with(|cell| { 159 | #[cfg(debug_assertions)] 160 | cell.set(Some(state)); 161 | 162 | #[cfg(not(debug_assertions))] 163 | cell.set(state); 164 | }) 165 | } 166 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "cc" 13 | version = "1.0.70" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "cfg_table" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "78518bef4e5cc70a69a0bd1905594f8033b2f7492c9d7c0f173208c0945c30a2" 28 | 29 | [[package]] 30 | name = "cstr" 31 | version = "0.2.9" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "f2846d3636dcaff720d311ea8983f5fa7a8288632b2f95145dd4b5819c397fd8" 34 | dependencies = [ 35 | "proc-macro2", 36 | "quote", 37 | ] 38 | 39 | [[package]] 40 | name = "ctor" 41 | version = "0.1.21" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" 44 | dependencies = [ 45 | "quote", 46 | "syn", 47 | ] 48 | 49 | [[package]] 50 | name = "fn_abi" 51 | version = "2.0.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "451b828bd3c5f19949222834f58b38c3670604dcaa5f2b7aec50c0cd58f34900" 54 | dependencies = [ 55 | "proc-macro2", 56 | "quote", 57 | "syn", 58 | "syn_squash", 59 | ] 60 | 61 | [[package]] 62 | name = "fn_has_this" 63 | version = "0.1.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "eb93938067213676967092012a53515e926de7c813afccd9b075ba0ec78dbba9" 66 | dependencies = [ 67 | "proc-macro2", 68 | "quote", 69 | "syn", 70 | "syn_squash", 71 | ] 72 | 73 | [[package]] 74 | name = "fn_type_alias" 75 | version = "0.1.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "809e807f01217f1f89959fbeebd10c6471b5ef3971fce1b02a70fb7212cc6c1f" 78 | dependencies = [ 79 | "proc-macro2", 80 | "quote", 81 | "syn", 82 | ] 83 | 84 | [[package]] 85 | name = "generic-array" 86 | version = "0.14.4" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 89 | dependencies = [ 90 | "typenum", 91 | "version_check", 92 | ] 93 | 94 | [[package]] 95 | name = "gmod" 96 | version = "17.0.0" 97 | dependencies = [ 98 | "cfg_table", 99 | "cstr", 100 | "ctor", 101 | "fn_abi", 102 | "fn_has_this", 103 | "fn_type_alias", 104 | "gmod-macros", 105 | "lazy_static", 106 | "libloading", 107 | "null_fn", 108 | "retour", 109 | "skidscan", 110 | ] 111 | 112 | [[package]] 113 | name = "gmod-macros" 114 | version = "2.0.1" 115 | dependencies = [ 116 | "proc-macro2", 117 | "quote", 118 | "syn", 119 | ] 120 | 121 | [[package]] 122 | name = "lazy_static" 123 | version = "1.4.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 126 | 127 | [[package]] 128 | name = "libc" 129 | version = "0.2.102" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" 132 | 133 | [[package]] 134 | name = "libloading" 135 | version = "0.7.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" 138 | dependencies = [ 139 | "cfg-if", 140 | "winapi", 141 | ] 142 | 143 | [[package]] 144 | name = "libudis86-sys" 145 | version = "0.2.1" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" 148 | dependencies = [ 149 | "cc", 150 | "libc", 151 | ] 152 | 153 | [[package]] 154 | name = "mach" 155 | version = "0.3.2" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 158 | dependencies = [ 159 | "libc", 160 | ] 161 | 162 | [[package]] 163 | name = "mmap-fixed-fixed" 164 | version = "0.1.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc" 167 | dependencies = [ 168 | "libc", 169 | "winapi", 170 | ] 171 | 172 | [[package]] 173 | name = "null_fn" 174 | version = "0.1.1" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "345919cfb52d0d8da7efc21aea56046ca96d9e3a8adfdbdb27ef1172829976c1" 177 | dependencies = [ 178 | "proc-macro2", 179 | "quote", 180 | "syn", 181 | ] 182 | 183 | [[package]] 184 | name = "proc-macro-crate" 185 | version = "1.1.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" 188 | dependencies = [ 189 | "thiserror", 190 | "toml", 191 | ] 192 | 193 | [[package]] 194 | name = "proc-macro2" 195 | version = "1.0.29" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 198 | dependencies = [ 199 | "unicode-xid", 200 | ] 201 | 202 | [[package]] 203 | name = "quote" 204 | version = "1.0.9" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 207 | dependencies = [ 208 | "proc-macro2", 209 | ] 210 | 211 | [[package]] 212 | name = "region" 213 | version = "3.0.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" 216 | dependencies = [ 217 | "bitflags", 218 | "libc", 219 | "mach", 220 | "winapi", 221 | ] 222 | 223 | [[package]] 224 | name = "retour" 225 | version = "0.1.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "a83f9f8fa2d02a11cb18d157ca898f1d8a197c19b084ef8d0a8da256a5c58935" 228 | dependencies = [ 229 | "cfg-if", 230 | "generic-array", 231 | "lazy_static", 232 | "libc", 233 | "libudis86-sys", 234 | "mmap-fixed-fixed", 235 | "region", 236 | "slice-pool", 237 | ] 238 | 239 | [[package]] 240 | name = "serde" 241 | version = "1.0.130" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 244 | 245 | [[package]] 246 | name = "skidscan" 247 | version = "2.0.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "60c2b7c0d9358c69c16b05037d80ffc810655ea7a2625beeb8af8a8b37126768" 250 | dependencies = [ 251 | "libc", 252 | "skidscan-macros", 253 | "winapi", 254 | ] 255 | 256 | [[package]] 257 | name = "skidscan-macros" 258 | version = "0.1.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "881ef2150f0e4e2a855bb661a3ecd621ab1b6aa07b34aef941d1bd37b4267e8d" 261 | dependencies = [ 262 | "proc-macro-crate", 263 | "syn", 264 | ] 265 | 266 | [[package]] 267 | name = "slice-pool" 268 | version = "0.4.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "733fc6e5f1bd3a8136f842c9bdea4e5f17c910c2fcc98c90c3aa7604ef5e2e7a" 271 | 272 | [[package]] 273 | name = "syn" 274 | version = "1.0.77" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" 277 | dependencies = [ 278 | "proc-macro2", 279 | "quote", 280 | "unicode-xid", 281 | ] 282 | 283 | [[package]] 284 | name = "syn_squash" 285 | version = "0.1.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "134b985708d02b2569ab2b9e27c8758ca4baaaf725341a572d59bc2d174b9bb5" 288 | 289 | [[package]] 290 | name = "thiserror" 291 | version = "1.0.29" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" 294 | dependencies = [ 295 | "thiserror-impl", 296 | ] 297 | 298 | [[package]] 299 | name = "thiserror-impl" 300 | version = "1.0.29" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" 303 | dependencies = [ 304 | "proc-macro2", 305 | "quote", 306 | "syn", 307 | ] 308 | 309 | [[package]] 310 | name = "toml" 311 | version = "0.5.8" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 314 | dependencies = [ 315 | "serde", 316 | ] 317 | 318 | [[package]] 319 | name = "typenum" 320 | version = "1.14.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 323 | 324 | [[package]] 325 | name = "unicode-xid" 326 | version = "0.2.2" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 329 | 330 | [[package]] 331 | name = "version_check" 332 | version = "0.9.3" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 335 | 336 | [[package]] 337 | name = "winapi" 338 | version = "0.3.9" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 341 | dependencies = [ 342 | "winapi-i686-pc-windows-gnu", 343 | "winapi-x86_64-pc-windows-gnu", 344 | ] 345 | 346 | [[package]] 347 | name = "winapi-i686-pc-windows-gnu" 348 | version = "0.4.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 351 | 352 | [[package]] 353 | name = "winapi-x86_64-pc-windows-gnu" 354 | version = "0.4.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 357 | -------------------------------------------------------------------------------- /tests/userdata/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "cc" 13 | version = "1.0.71" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "cfg_table" 25 | version = "0.1.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "49f3690291e34881a89ceb8e80802f1582f29b1361fd476eeefb4e89dd6a121e" 28 | 29 | [[package]] 30 | name = "cstr" 31 | version = "0.2.9" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "f2846d3636dcaff720d311ea8983f5fa7a8288632b2f95145dd4b5819c397fd8" 34 | dependencies = [ 35 | "proc-macro2", 36 | "quote", 37 | ] 38 | 39 | [[package]] 40 | name = "ctor" 41 | version = "0.1.21" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" 44 | dependencies = [ 45 | "quote", 46 | "syn", 47 | ] 48 | 49 | [[package]] 50 | name = "detour" 51 | version = "0.8.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "f3c83fabcc3bc336e19320c13576ea708a15deec201d6b879b7ad1b92734d7b9" 54 | dependencies = [ 55 | "cfg-if", 56 | "generic-array", 57 | "lazy_static", 58 | "libc", 59 | "libudis86-sys", 60 | "mmap-fixed", 61 | "region", 62 | "slice-pool", 63 | ] 64 | 65 | [[package]] 66 | name = "fn_abi" 67 | version = "2.0.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "451b828bd3c5f19949222834f58b38c3670604dcaa5f2b7aec50c0cd58f34900" 70 | dependencies = [ 71 | "proc-macro2", 72 | "quote", 73 | "syn", 74 | "syn_squash", 75 | ] 76 | 77 | [[package]] 78 | name = "fn_has_this" 79 | version = "0.1.1" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "eb93938067213676967092012a53515e926de7c813afccd9b075ba0ec78dbba9" 82 | dependencies = [ 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | "syn_squash", 87 | ] 88 | 89 | [[package]] 90 | name = "fn_type_alias" 91 | version = "0.1.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "809e807f01217f1f89959fbeebd10c6471b5ef3971fce1b02a70fb7212cc6c1f" 94 | dependencies = [ 95 | "proc-macro2", 96 | "quote", 97 | "syn", 98 | ] 99 | 100 | [[package]] 101 | name = "generic-array" 102 | version = "0.14.4" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 105 | dependencies = [ 106 | "typenum", 107 | "version_check", 108 | ] 109 | 110 | [[package]] 111 | name = "gmod" 112 | version = "6.0.1" 113 | dependencies = [ 114 | "cfg_table", 115 | "cstr", 116 | "ctor", 117 | "detour", 118 | "fn_abi", 119 | "fn_has_this", 120 | "fn_type_alias", 121 | "gmod-macros", 122 | "lazy_static", 123 | "libloading", 124 | "null_fn", 125 | "skidscan", 126 | ] 127 | 128 | [[package]] 129 | name = "gmod-macros" 130 | version = "1.0.0" 131 | dependencies = [ 132 | "proc-macro2", 133 | "quote", 134 | "syn", 135 | ] 136 | 137 | [[package]] 138 | name = "kernel32-sys" 139 | version = "0.2.2" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 142 | dependencies = [ 143 | "winapi 0.2.8", 144 | "winapi-build", 145 | ] 146 | 147 | [[package]] 148 | name = "lazy_static" 149 | version = "1.4.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 152 | 153 | [[package]] 154 | name = "libc" 155 | version = "0.2.105" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" 158 | 159 | [[package]] 160 | name = "libloading" 161 | version = "0.7.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" 164 | dependencies = [ 165 | "cfg-if", 166 | "winapi 0.3.9", 167 | ] 168 | 169 | [[package]] 170 | name = "libudis86-sys" 171 | version = "0.2.1" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" 174 | dependencies = [ 175 | "cc", 176 | "libc", 177 | ] 178 | 179 | [[package]] 180 | name = "mach" 181 | version = "0.3.2" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 184 | dependencies = [ 185 | "libc", 186 | ] 187 | 188 | [[package]] 189 | name = "mmap-fixed" 190 | version = "0.1.5" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "27c1ae264d6343d3b4079549f6bc9e6d074dc4106cb1324c7753c6ce11d07b21" 193 | dependencies = [ 194 | "kernel32-sys", 195 | "libc", 196 | "winapi 0.2.8", 197 | ] 198 | 199 | [[package]] 200 | name = "null_fn" 201 | version = "0.1.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "345919cfb52d0d8da7efc21aea56046ca96d9e3a8adfdbdb27ef1172829976c1" 204 | dependencies = [ 205 | "proc-macro2", 206 | "quote", 207 | "syn", 208 | ] 209 | 210 | [[package]] 211 | name = "proc-macro-crate" 212 | version = "1.1.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" 215 | dependencies = [ 216 | "thiserror", 217 | "toml", 218 | ] 219 | 220 | [[package]] 221 | name = "proc-macro2" 222 | version = "1.0.30" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" 225 | dependencies = [ 226 | "unicode-xid", 227 | ] 228 | 229 | [[package]] 230 | name = "quote" 231 | version = "1.0.10" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 234 | dependencies = [ 235 | "proc-macro2", 236 | ] 237 | 238 | [[package]] 239 | name = "region" 240 | version = "2.2.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" 243 | dependencies = [ 244 | "bitflags", 245 | "libc", 246 | "mach", 247 | "winapi 0.3.9", 248 | ] 249 | 250 | [[package]] 251 | name = "serde" 252 | version = "1.0.130" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 255 | 256 | [[package]] 257 | name = "skidscan" 258 | version = "0.1.3" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "e599f7639f0115549da310b45dc29319858981e20dd5e15ee66f69fc8219092e" 261 | dependencies = [ 262 | "libc", 263 | "skidscan-macros", 264 | "winapi 0.3.9", 265 | ] 266 | 267 | [[package]] 268 | name = "skidscan-macros" 269 | version = "0.1.2" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "881ef2150f0e4e2a855bb661a3ecd621ab1b6aa07b34aef941d1bd37b4267e8d" 272 | dependencies = [ 273 | "proc-macro-crate", 274 | "syn", 275 | ] 276 | 277 | [[package]] 278 | name = "slice-pool" 279 | version = "0.4.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "733fc6e5f1bd3a8136f842c9bdea4e5f17c910c2fcc98c90c3aa7604ef5e2e7a" 282 | 283 | [[package]] 284 | name = "syn" 285 | version = "1.0.80" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 288 | dependencies = [ 289 | "proc-macro2", 290 | "quote", 291 | "unicode-xid", 292 | ] 293 | 294 | [[package]] 295 | name = "syn_squash" 296 | version = "0.1.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "134b985708d02b2569ab2b9e27c8758ca4baaaf725341a572d59bc2d174b9bb5" 299 | 300 | [[package]] 301 | name = "thiserror" 302 | version = "1.0.30" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 305 | dependencies = [ 306 | "thiserror-impl", 307 | ] 308 | 309 | [[package]] 310 | name = "thiserror-impl" 311 | version = "1.0.30" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 314 | dependencies = [ 315 | "proc-macro2", 316 | "quote", 317 | "syn", 318 | ] 319 | 320 | [[package]] 321 | name = "toml" 322 | version = "0.5.8" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 325 | dependencies = [ 326 | "serde", 327 | ] 328 | 329 | [[package]] 330 | name = "typenum" 331 | version = "1.14.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 334 | 335 | [[package]] 336 | name = "unicode-xid" 337 | version = "0.2.2" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 340 | 341 | [[package]] 342 | name = "userdata" 343 | version = "0.1.0" 344 | dependencies = [ 345 | "gmod", 346 | ] 347 | 348 | [[package]] 349 | name = "version_check" 350 | version = "0.9.3" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 353 | 354 | [[package]] 355 | name = "winapi" 356 | version = "0.2.8" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 359 | 360 | [[package]] 361 | name = "winapi" 362 | version = "0.3.9" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 365 | dependencies = [ 366 | "winapi-i686-pc-windows-gnu", 367 | "winapi-x86_64-pc-windows-gnu", 368 | ] 369 | 370 | [[package]] 371 | name = "winapi-build" 372 | version = "0.1.1" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 375 | 376 | [[package]] 377 | name = "winapi-i686-pc-windows-gnu" 378 | version = "0.4.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 381 | 382 | [[package]] 383 | name = "winapi-x86_64-pc-windows-gnu" 384 | version = "0.4.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 387 | -------------------------------------------------------------------------------- /gmod/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [Available Lua Functions](https://docs.rs/gmod/latest/gmod/lua/struct.State.html) 2 | 3 | #![allow(clippy::missing_safety_doc)] 4 | #![allow(clippy::result_unit_err)] 5 | 6 | #![feature(thread_id_value)] 7 | 8 | #![cfg_attr(feature = "gmcl", feature(internal_output_capture))] 9 | 10 | #[cfg(not(all(any(target_os = "windows", target_os = "linux", target_os = "macos"), any(target_pointer_width = "32", target_pointer_width = "64"))))] 11 | compile_error!("Unsupported platform"); 12 | 13 | pub use cstr; 14 | pub use libloading; 15 | pub use gmod_macros::*; 16 | 17 | #[cfg(feature = "hax")] 18 | mod haxports { 19 | #[cfg(not(target_os = "macos"))] 20 | pub use skidscan as sigscan; 21 | 22 | #[cfg(target_os = "macos")] 23 | compile_error!("Sigscanning is currently not supported on MacOS, please disable the `hax` feature on gmod-rs using `default-features = false` to make a normal module"); 24 | 25 | pub use retour as detour; 26 | pub use ctor::{ctor as dllopen, dtor as dllclose}; 27 | 28 | pub use fn_type_alias::*; 29 | pub use fn_abi::*; 30 | pub use cfg_table::*; 31 | pub use null_fn::*; 32 | pub use fn_has_this::*; 33 | } 34 | #[cfg(feature = "hax")] 35 | pub use haxports::*; 36 | 37 | /// Lua interface 38 | pub mod lua; 39 | 40 | /// Colorful printing 41 | pub mod msgc; 42 | 43 | /// Advanced dark magic utilities 44 | pub mod hax; 45 | 46 | /// Userdata types 47 | pub mod userdata; 48 | 49 | /// Net library helpers 50 | pub mod net; 51 | 52 | /// Clientside module helpers 53 | #[cfg(feature = "gmcl")] 54 | pub mod gmcl; 55 | 56 | /// Returns whether this client is running the x86-64 branch 57 | pub fn is_x86_64() -> bool { 58 | #[cfg(target_pointer_width = "64")] { 59 | // 64-bit can only be x86-64 60 | true 61 | } 62 | #[cfg(target_pointer_width = "32")] { 63 | lazy_static::lazy_static! { 64 | static ref IS_X86_64: bool = { 65 | use std::path::PathBuf; 66 | 67 | #[cfg(target_os = "macos")] { 68 | PathBuf::from("garrysmod/bin/lua_shared.dylib").is_file() 69 | } 70 | #[cfg(target_os = "windows")] { 71 | PathBuf::from("srcds_win64.exe").is_file() 72 | } 73 | #[cfg(target_os = "linux")] { 74 | // Check executable name 75 | match std::env::current_exe().expect("Failed to get executable path").file_name().expect("Failed to get executable file name").to_string_lossy().as_ref() { 76 | #[cfg(target_os = "windows")] 77 | "srcds.exe" => false, 78 | 79 | #[cfg(target_os = "linux")] 80 | "srcds_linux" => false, 81 | 82 | #[cfg(target_os = "linux")] 83 | "srcds" => true, 84 | 85 | _ => { 86 | // Check bin folder 87 | #[cfg(target_os = "linux")] { 88 | PathBuf::from("bin/linux64").is_dir() 89 | } 90 | #[cfg(target_os = "windows")] { 91 | PathBuf::from("bin/win64").is_dir() 92 | } 93 | } 94 | } 95 | } 96 | }; 97 | } 98 | *IS_X86_64 99 | } 100 | } 101 | 102 | /// Opens & returns a shared library loaded by Garry's Mod using the raw path to the module. 103 | /// 104 | /// # Example 105 | /// ```no_run 106 | /// // This would only work on Windows x86-64 branch in 64-bit mode 107 | /// let (engine, engine_path): (gmod::libloading::Library, &'static str) = open_library_srv!("bin/win64/engine.dll").expect("Failed to open engine.dll!"); 108 | /// println!("Opened engine.dll from: {}", engine_path); 109 | /// ``` 110 | #[macro_export] 111 | macro_rules! open_library_raw { 112 | ($($path:literal),+) => { 113 | match $crate::libloading::Library::new(concat!($($path),+)) { 114 | Ok(lib) => Ok((lib, concat!($($path),+))), 115 | Err(err) => Err((err, concat!($($path),+))) 116 | } 117 | } 118 | } 119 | 120 | /// Opens & returns a shared library loaded by Garry's Mod, in "server mode" (will prioritize _srv.so on Linux main branch) 121 | /// 122 | /// Respects 32-bit/64-bit main/x86-64 branches and finds the correct library. 123 | /// 124 | /// # Example 125 | /// ```no_run 126 | /// let (engine, engine_path): (gmod::libloading::Library, &'static str) = open_library_srv!("engine").expect("Failed to open engine.dll!"); 127 | /// println!("Opened engine.dll from: {}", engine_path); 128 | /// ``` 129 | #[macro_export] 130 | macro_rules! open_library_srv { 131 | ($name:literal) => {{ 132 | #[cfg(all(target_os = "windows", target_pointer_width = "64"))] { 133 | $crate::__private__gmod_rs__try_chained_open! { 134 | $crate::open_library_raw!("bin/win64/", $name, ".dll"), 135 | $crate::open_library_raw!($name) 136 | } 137 | } 138 | #[cfg(all(target_os = "windows", target_pointer_width = "32"))] { 139 | $crate::__private__gmod_rs__try_chained_open! { 140 | $crate::open_library_raw!("bin/", $name, ".dll"), 141 | $crate::open_library_raw!("garrysmod/bin/", $name, ".dll"), 142 | $crate::open_library_raw!($name) 143 | } 144 | } 145 | 146 | #[cfg(all(target_os = "linux", target_pointer_width = "64"))] { 147 | $crate::__private__gmod_rs__try_chained_open! { 148 | $crate::open_library_raw!("bin/linux64/", $name, ".so"), 149 | $crate::open_library_raw!("bin/linux64/lib", $name, ".so"), 150 | $crate::open_library_raw!($name) 151 | } 152 | } 153 | #[cfg(all(target_os = "linux", target_pointer_width = "32"))] { 154 | $crate::__private__gmod_rs__try_chained_open! { 155 | $crate::open_library_raw!("bin/linux32/", $name, ".so"), 156 | $crate::open_library_raw!("bin/linux32/lib", $name, ".so"), 157 | $crate::open_library_raw!("bin/", $name, "_srv.so"), 158 | $crate::open_library_raw!("bin/lib", $name, "_srv.so"), 159 | $crate::open_library_raw!("garrysmod/bin/", $name, "_srv.so"), 160 | $crate::open_library_raw!("garrysmod/bin/lib", $name, "_srv.so"), 161 | $crate::open_library_raw!("bin/", $name, ".so"), 162 | $crate::open_library_raw!("bin/lib", $name, ".so"), 163 | $crate::open_library_raw!("garrysmod/bin/", $name, ".so"), 164 | $crate::open_library_raw!("garrysmod/bin/lib", $name, ".so"), 165 | $crate::open_library_raw!($name) 166 | } 167 | } 168 | 169 | #[cfg(target_os = "macos")] { 170 | $crate::__private__gmod_rs__try_chained_open! { 171 | $crate::open_library_raw!("GarrysMod_Signed.app/Contents/MacOS/", $name, ".dylib"), 172 | $crate::open_library_raw!("GarrysMod_Signed.app/Contents/MacOS/lib", $name, ".dylib"), 173 | $crate::open_library_raw!("bin/", $name, "_srv.dylib"), 174 | $crate::open_library_raw!("bin/lib", $name, "_srv.dylib"), 175 | $crate::open_library_raw!("garrysmod/bin/", $name, "_srv.dylib"), 176 | $crate::open_library_raw!("garrysmod/bin/lib", $name, "_srv.dylib"), 177 | $crate::open_library_raw!("bin/", $name, ".dylib"), 178 | $crate::open_library_raw!("bin/lib", $name, ".dylib"), 179 | $crate::open_library_raw!("garrysmod/bin/", $name, ".dylib"), 180 | $crate::open_library_raw!("garrysmod/bin/lib", $name, ".dylib"), 181 | $crate::open_library_raw!($name) 182 | } 183 | } 184 | }}; 185 | } 186 | 187 | /// Opens & returns a shared library loaded by Garry's Mod. You are most likely looking for `open_library_srv!`, as this will prioritize non-_srv.so libraries on Linux main branch. 188 | /// 189 | /// Respects 32-bit/64-bit main/x86-64 branches and finds the correct library. 190 | /// 191 | /// # Example 192 | /// ```no_run 193 | /// let (engine, engine_path): (gmod::libloading::Library, &'static str) = open_library!("engine").expect("Failed to open engine.dll!"); 194 | /// println!("Opened engine.dll from: {}", engine_path); 195 | /// ``` 196 | #[macro_export] 197 | macro_rules! open_library { 198 | ($name:literal) => {{ 199 | #[cfg(all(target_os = "windows", target_pointer_width = "64"))] { 200 | $crate::__private__gmod_rs__try_chained_open! { 201 | $crate::open_library_raw!("bin/win64/", $name, ".dll"), 202 | $crate::open_library_raw!($name) 203 | } 204 | } 205 | #[cfg(all(target_os = "windows", target_pointer_width = "32"))] { 206 | $crate::__private__gmod_rs__try_chained_open! { 207 | $crate::open_library_raw!("bin/", $name, ".dll"), 208 | $crate::open_library_raw!("garrysmod/bin/", $name, ".dll"), 209 | $crate::open_library_raw!($name) 210 | } 211 | } 212 | 213 | #[cfg(all(target_os = "linux", target_pointer_width = "64"))] { 214 | $crate::__private__gmod_rs__try_chained_open! { 215 | $crate::open_library_raw!("bin/linux64/", $name, ".so"), 216 | $crate::open_library_raw!("bin/linux64/lib", $name, ".so"), 217 | $crate::open_library_raw!($name) 218 | } 219 | } 220 | #[cfg(all(target_os = "linux", target_pointer_width = "32"))] { 221 | $crate::__private__gmod_rs__try_chained_open! { 222 | $crate::open_library_raw!("bin/linux32/", $name, ".so"), 223 | $crate::open_library_raw!("bin/linux32/lib", $name, ".so"), 224 | $crate::open_library_raw!("bin/", $name, ".so"), 225 | $crate::open_library_raw!("bin/lib", $name, ".so"), 226 | $crate::open_library_raw!("garrysmod/bin/", $name, ".so"), 227 | $crate::open_library_raw!("garrysmod/bin/lib", $name, ".so"), 228 | $crate::open_library_raw!("bin/", $name, "_srv.so"), 229 | $crate::open_library_raw!("bin/lib", $name, "_srv.so"), 230 | $crate::open_library_raw!("garrysmod/bin/", $name, "_srv.so"), 231 | $crate::open_library_raw!("garrysmod/bin/lib", $name, "_srv.so"), 232 | $crate::open_library_raw!($name) 233 | } 234 | } 235 | 236 | #[cfg(target_os = "macos")] { 237 | $crate::__private__gmod_rs__try_chained_open! { 238 | $crate::open_library_raw!("GarrysMod_Signed.app/Contents/MacOS/", $name, ".dylib"), 239 | $crate::open_library_raw!("GarrysMod_Signed.app/Contents/MacOS/lib", $name, ".dylib"), 240 | $crate::open_library_raw!("bin/", $name, ".dylib"), 241 | $crate::open_library_raw!("bin/lib", $name, ".dylib"), 242 | $crate::open_library_raw!("garrysmod/bin/", $name, ".dylib"), 243 | $crate::open_library_raw!("garrysmod/bin/lib", $name, ".dylib"), 244 | $crate::open_library_raw!("bin/", $name, "_srv.dylib"), 245 | $crate::open_library_raw!("bin/lib", $name, "_srv.dylib"), 246 | $crate::open_library_raw!("garrysmod/bin/", $name, "_srv.dylib"), 247 | $crate::open_library_raw!("garrysmod/bin/lib", $name, "_srv.dylib"), 248 | $crate::open_library_raw!($name) 249 | } 250 | } 251 | }}; 252 | } 253 | 254 | #[derive(Default)] 255 | #[doc(hidden)] 256 | pub struct OpenGmodLibraryErrs(pub std::collections::HashMap<&'static str, libloading::Error>); 257 | impl std::error::Error for OpenGmodLibraryErrs {} 258 | impl std::fmt::Display for OpenGmodLibraryErrs { 259 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 260 | writeln!(f)?; 261 | for (path, err) in &self.0 { 262 | writeln!(f, "{} = {}", path, err)?; 263 | } 264 | writeln!(f)?; 265 | Ok(()) 266 | } 267 | } 268 | impl std::fmt::Debug for OpenGmodLibraryErrs { 269 | #[inline] 270 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 271 | std::fmt::Display::fmt(self, f) 272 | } 273 | } 274 | 275 | #[doc(hidden)] 276 | #[macro_export] 277 | macro_rules! __private__gmod_rs__try_chained_open { 278 | {$($expr:expr),+} => { 279 | loop { 280 | let mut errors = $crate::OpenGmodLibraryErrs::default(); 281 | $( 282 | match $expr { 283 | Ok(val) => break Ok(val), 284 | Err((err, path)) => { errors.0.insert(path, err); } 285 | } 286 | )+ 287 | break Err(errors); 288 | } 289 | }; 290 | } 291 | 292 | /// You don't need to use this if you are using the `#[gmod13_open]` macro. 293 | pub unsafe fn set_lua_state(state: *mut std::ffi::c_void) { 294 | lua::__set_state__internal(lua::State(state)); 295 | lua::load(); 296 | } -------------------------------------------------------------------------------- /gmod/src/lua/import.rs: -------------------------------------------------------------------------------- 1 | #[cfg(debug_assertions)] 2 | use std::sync::atomic::AtomicI64; 3 | 4 | use std::{cell::UnsafeCell, ffi::c_void}; 5 | 6 | use libloading::{Library, Symbol}; 7 | 8 | use super::{LuaError, State as LuaState, LuaDebug, returns::ValuesReturned}; 9 | 10 | pub type LuaInt = isize; 11 | pub type LuaSize = usize; 12 | pub type LuaString = *const std::os::raw::c_char; 13 | pub type LuaFunction = unsafe extern "C-unwind" fn(state: LuaState) -> i32; 14 | pub type LuaNumber = f64; 15 | pub type LuaReference = i32; 16 | 17 | pub const LUA_REGISTRYINDEX: i32 = -10000; 18 | pub const LUA_ENVIRONINDEX: i32 = -10001; 19 | pub const LUA_GLOBALSINDEX: i32 = -10002; 20 | 21 | pub const LUA_MULTRET: i32 = -1; 22 | pub const LUA_NOREF: LuaReference = -2; 23 | pub const LUA_REFNIL: LuaReference = -1; 24 | 25 | pub const LUA_TNONE: i32 = -1; 26 | pub const LUA_TNIL: i32 = 0; 27 | pub const LUA_TBOOLEAN: i32 = 1; 28 | pub const LUA_TLIGHTUSERDATA: i32 = 2; 29 | pub const LUA_TNUMBER: i32 = 3; 30 | pub const LUA_TSTRING: i32 = 4; 31 | pub const LUA_TTABLE: i32 = 5; 32 | pub const LUA_TFUNCTION: i32 = 6; 33 | pub const LUA_TUSERDATA: i32 = 7; 34 | pub const LUA_TTHREAD: i32 = 8; 35 | 36 | pub const LUA_OK: i32 = 0; 37 | pub const LUA_YIELD: i32 = 1; 38 | pub const LUA_ERRRUN: i32 = 2; 39 | pub const LUA_ERRSYNTAX: i32 = 3; 40 | pub const LUA_ERRMEM: i32 = 4; 41 | pub const LUA_ERRERR: i32 = 5; 42 | pub const LUA_ERRFILE: i32 = LUA_ERRERR + 1; 43 | 44 | pub const LUA_IDSIZE: usize = 60; 45 | 46 | impl LuaError { 47 | fn get_error_message(lua_state: LuaState) -> Option { 48 | unsafe { lua_state.get_string(-1).map(|str| str.into_owned()) } 49 | } 50 | 51 | pub(crate) fn from_lua_state(lua_state: LuaState, lua_int_error_code: i32) -> Self { 52 | use super::LuaError::*; 53 | match lua_int_error_code { 54 | LUA_ERRMEM => MemoryAllocationError, 55 | LUA_ERRERR => ErrorHandlerError, 56 | LUA_ERRSYNTAX | LUA_ERRRUN | LUA_ERRFILE => { 57 | let msg = LuaError::get_error_message(lua_state); 58 | match lua_int_error_code { 59 | LUA_ERRSYNTAX => SyntaxError(msg), 60 | LUA_ERRRUN => RuntimeError(msg), 61 | LUA_ERRFILE => FileError(msg), 62 | _ => unreachable!(), 63 | } 64 | } 65 | _ => Unknown(lua_int_error_code), 66 | } 67 | } 68 | } 69 | 70 | #[cfg_attr(not(debug_assertions), repr(transparent))] 71 | pub struct LuaSharedInterface(pub(crate) UnsafeCell<*mut LuaShared>, #[cfg(debug_assertions)] AtomicI64); 72 | impl LuaSharedInterface { 73 | #[cfg(debug_assertions)] 74 | pub(crate) fn debug_assertions(&self) { 75 | assert!(!unsafe { *self.0.get() }.is_null(), "The Lua state has not been initialized yet. Add `#[gmod::gmod13_open]` to your module's gmod13_open function to fix this. You can also manually set the Lua state with `gmod::set_lua_state(*mut c_void)`"); 76 | 77 | let thread_id = u64::from(std::thread::current().id().as_u64()) as i64; 78 | match self.1.compare_exchange(-1, thread_id, std::sync::atomic::Ordering::SeqCst, std::sync::atomic::Ordering::SeqCst) { 79 | Ok(-1) => {}, // This is the first thread to use this Lua state. 80 | Ok(_) => unreachable!(), 81 | Err(remembered_thread_id) => assert_eq!(thread_id, remembered_thread_id, "Tried to access the Lua state from another thread! The Lua state is NOT thread-safe, and should only be accessed from the main thread.") 82 | } 83 | } 84 | 85 | pub(super) unsafe fn load(&self) { 86 | *self.0.get() = Box::leak(Box::new(LuaShared::import())); 87 | } 88 | 89 | pub(super) unsafe fn set(&self, ptr: *mut c_void) { 90 | *self.0.get() = ptr as *mut LuaShared; 91 | } 92 | } 93 | impl std::ops::Deref for LuaSharedInterface { 94 | type Target = LuaShared; 95 | 96 | #[inline] 97 | fn deref(&self) -> &Self::Target { 98 | #[cfg(debug_assertions)] 99 | self.debug_assertions(); 100 | 101 | unsafe { &**self.0.get() } 102 | } 103 | } 104 | impl std::ops::DerefMut for LuaSharedInterface { 105 | #[inline] 106 | fn deref_mut(&mut self) -> &mut Self::Target { 107 | #[cfg(debug_assertions)] 108 | self.debug_assertions(); 109 | 110 | unsafe { &mut **self.0.get_mut() } 111 | } 112 | } 113 | 114 | pub static mut LUA_SHARED: LuaSharedInterface = LuaSharedInterface(UnsafeCell::new(std::ptr::null_mut()), #[cfg(debug_assertions)] AtomicI64::new(-1)); 115 | 116 | pub struct LuaShared { 117 | pub(crate) library: &'static libloading::Library, 118 | pub lual_newstate: Symbol<'static, unsafe extern "C-unwind" fn() -> LuaState>, 119 | pub lual_openlibs: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState)>, 120 | pub lual_loadfile: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, path: LuaString) -> i32>, 121 | pub lual_loadstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, path: LuaString) -> i32>, 122 | pub lual_loadbuffer: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, buff: LuaString, sz: LuaSize, name: LuaString) -> i32>, 123 | pub lua_getfield: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, k: LuaString)>, 124 | pub lua_pushvalue: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, 125 | pub lua_pushlightuserdata: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, data: *mut c_void)>, 126 | pub lua_pushboolean: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, bool: i32)>, 127 | pub lua_tolstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, out_size: *mut LuaSize) -> LuaString>, 128 | pub lua_pcall: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, nargs: i32, nresults: i32, errfunc: i32) -> i32>, 129 | pub lua_remove: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, 130 | pub lua_gettop: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState) -> i32>, 131 | pub lua_type: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, 132 | pub lua_typename: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, lua_type_id: i32) -> LuaString>, 133 | pub lua_setfield: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, k: LuaString)>, 134 | pub lua_call: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, nargs: i32, nresults: i32)>, 135 | pub lua_createtable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, narr: i32, nrec: i32)>, 136 | pub lua_settop: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, count: i32)>, 137 | pub lua_replace: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, 138 | pub lua_pushlstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, data: LuaString, length: LuaSize)>, 139 | pub lua_pushcclosure: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, func: LuaFunction, upvalues: i32)>, 140 | pub lua_settable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, 141 | pub lua_gettable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, 142 | pub lua_error: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState) -> i32>, 143 | pub lua_insert: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, 144 | pub lual_checkinteger: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32) -> LuaInt>, 145 | pub lual_checklstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32, out_size: *mut LuaSize) -> LuaString>, 146 | pub lua_toboolean: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, 147 | pub lual_checktype: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, r#type: i32)>, 148 | pub lua_setmetatable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, 149 | pub lua_pushinteger: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, int: LuaInt)>, 150 | pub lua_pushnumber: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, int: LuaNumber)>, 151 | pub lua_pushnil: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState)>, 152 | pub lual_checknumber: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32) -> LuaNumber>, 153 | pub lua_tointeger: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> LuaInt>, 154 | pub lua_tonumber: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> LuaNumber>, 155 | pub lual_checkudata: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32, name: LuaString) -> *mut std::ffi::c_void>, 156 | pub lual_ref: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, 157 | pub lual_unref: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, r#ref: i32)>, 158 | pub lua_objlen: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, 159 | pub lua_rawgeti: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, t: i32, index: i32)>, 160 | pub lua_rawseti: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, t: i32, index: i32)>, 161 | pub lua_getmetatable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, 162 | pub lua_rawequal: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, a: i32, b: i32) -> i32>, 163 | pub lua_touserdata: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> *mut std::ffi::c_void>, 164 | pub lua_getinfo: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, what: LuaString, ar: *mut LuaDebug) -> i32>, 165 | pub lua_getstack: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, level: i32, ar: *mut LuaDebug) -> i32>, 166 | pub lua_next: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, 167 | pub lua_topointer: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> *const c_void>, 168 | pub lua_newuserdata: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, size: usize) -> *mut c_void>, 169 | pub lual_newmetatable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, name: LuaString) -> i32>, 170 | pub lua_resume: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, narg: i32) -> i32>, 171 | pub lua_newthread: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState) -> LuaState>, 172 | pub lua_yield: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, nresults: i32) -> i32>, 173 | pub lua_pushthread: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState) -> i32>, 174 | pub lua_tothread: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> LuaState>, 175 | pub lua_status: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState) -> i32>, 176 | pub lua_xmove: Symbol<'static, unsafe extern "C-unwind" fn(thread1: LuaState, thread2: LuaState, n: i32)>, 177 | pub lua_equal: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index1: i32, index2: i32) -> i32>, 178 | } 179 | unsafe impl Sync for LuaShared {} 180 | impl LuaShared { 181 | fn import() -> Self { 182 | unsafe { 183 | let (library, path) = Self::find_lua_shared(); 184 | let library = Box::leak(Box::new(library)); 185 | 186 | macro_rules! find_symbol { 187 | ( $symbol:literal ) => { 188 | Self::find_symbol(library, concat!($symbol, "\0").as_bytes()) 189 | }; 190 | } 191 | 192 | Self { 193 | lual_newstate: find_symbol!("luaL_newstate"), 194 | lual_openlibs: find_symbol!("luaL_openlibs"), 195 | lua_pushlightuserdata: find_symbol!("lua_pushlightuserdata"), 196 | lual_checktype: find_symbol!("luaL_checktype"), 197 | lual_loadfile: find_symbol!("luaL_loadfile"), 198 | lual_loadstring: find_symbol!("luaL_loadstring"), 199 | lual_loadbuffer: find_symbol!("luaL_loadbuffer"), 200 | lua_getfield: find_symbol!("lua_getfield"), 201 | lua_pushvalue: find_symbol!("lua_pushvalue"), 202 | lua_pushboolean: find_symbol!("lua_pushboolean"), 203 | lua_tolstring: find_symbol!("lua_tolstring"), 204 | lua_pcall: find_symbol!("lua_pcall"), 205 | lua_remove: find_symbol!("lua_remove"), 206 | lua_gettop: find_symbol!("lua_gettop"), 207 | lua_type: find_symbol!("lua_type"), 208 | lua_typename: find_symbol!("lua_typename"), 209 | lua_setfield: find_symbol!("lua_setfield"), 210 | lua_call: find_symbol!("lua_call"), 211 | lua_createtable: find_symbol!("lua_createtable"), 212 | lua_settop: find_symbol!("lua_settop"), 213 | lua_replace: find_symbol!("lua_replace"), 214 | lua_pushlstring: find_symbol!("lua_pushlstring"), 215 | lua_pushcclosure: find_symbol!("lua_pushcclosure"), 216 | lua_settable: find_symbol!("lua_settable"), 217 | lua_gettable: find_symbol!("lua_gettable"), 218 | lua_error: find_symbol!("lua_error"), 219 | lua_insert: find_symbol!("lua_insert"), 220 | lual_checkinteger: find_symbol!("luaL_checkinteger"), 221 | lual_checklstring: find_symbol!("luaL_checklstring"), 222 | lua_toboolean: find_symbol!("lua_toboolean"), 223 | lua_pushnumber: find_symbol!("lua_pushnumber"), 224 | lua_pushinteger: find_symbol!("lua_pushinteger"), 225 | lua_pushnil: find_symbol!("lua_pushnil"), 226 | lual_checknumber: find_symbol!("luaL_checknumber"), 227 | lua_tointeger: find_symbol!("lua_tointeger"), 228 | lua_tonumber: find_symbol!("lua_tonumber"), 229 | lual_checkudata: find_symbol!("luaL_checkudata"), 230 | lual_ref: find_symbol!("luaL_ref"), 231 | lual_unref: find_symbol!("luaL_unref"), 232 | lua_setmetatable: find_symbol!("lua_setmetatable"), 233 | lua_objlen: find_symbol!("lua_objlen"), 234 | lua_rawgeti: find_symbol!("lua_rawgeti"), 235 | lua_rawseti: find_symbol!("lua_rawseti"), 236 | lua_getmetatable: find_symbol!("lua_getmetatable"), 237 | lua_rawequal: find_symbol!("lua_rawequal"), 238 | lua_touserdata: find_symbol!("lua_touserdata"), 239 | lua_getinfo: find_symbol!("lua_getinfo"), 240 | lua_getstack: find_symbol!("lua_getstack"), 241 | lua_next: find_symbol!("lua_next"), 242 | lua_topointer: find_symbol!("lua_topointer"), 243 | lua_newuserdata: find_symbol!("lua_newuserdata"), 244 | lual_newmetatable: find_symbol!("luaL_newmetatable"), 245 | lua_resume: find_symbol!("lua_resume_real"), 246 | lua_newthread: find_symbol!("lua_newthread"), 247 | lua_yield: find_symbol!("lua_yield"), 248 | lua_pushthread: find_symbol!("lua_pushthread"), 249 | lua_tothread: find_symbol!("lua_tothread"), 250 | lua_status: find_symbol!("lua_status"), 251 | lua_xmove: find_symbol!("lua_xmove"), 252 | lua_equal: find_symbol!("lua_equal"), 253 | library, 254 | } 255 | } 256 | } 257 | 258 | unsafe fn find_symbol(library: &'static Library, name: &[u8]) -> Symbol<'static, T> { 259 | match library.get(name) { 260 | Ok(symbol) => symbol, 261 | Err(err) => panic!("Failed to find symbol \"{}\"\n{:#?}", String::from_utf8_lossy(name), err), 262 | } 263 | } 264 | 265 | #[cfg(all(target_os = "windows", target_pointer_width = "64"))] 266 | pub unsafe fn find_lua_shared() -> (Library, &'static str) { 267 | crate::open_library_raw!("bin/win64/lua_shared.dll") 268 | .expect("Failed to load lua_shared.dll") 269 | } 270 | 271 | #[cfg(all(target_os = "windows", target_pointer_width = "32"))] 272 | pub unsafe fn find_lua_shared() -> (Library, &'static str) { 273 | crate::__private__gmod_rs__try_chained_open! { 274 | crate::open_library_raw!("garrysmod/bin/lua_shared.dll"), 275 | crate::open_library_raw!("bin/lua_shared.dll") 276 | } 277 | .expect("Failed to load lua_shared.dll") 278 | } 279 | 280 | #[cfg(all(target_os = "linux", target_pointer_width = "32"))] 281 | pub unsafe fn find_lua_shared() -> (Library, &'static str) { 282 | crate::__private__gmod_rs__try_chained_open! { 283 | crate::open_library_raw!("garrysmod/bin/lua_shared_srv.so"), 284 | crate::open_library_raw!("bin/linux32/lua_shared.so"), 285 | crate::open_library_raw!("garrysmod/bin/lua_shared.so") 286 | } 287 | .expect("Failed to find lua_shared.so or lua_shared_srv.so") 288 | } 289 | 290 | #[cfg(all(target_os = "linux", target_pointer_width = "64"))] 291 | pub unsafe fn find_lua_shared() -> (Library, &'static str) { 292 | crate::open_library_raw!("bin/linux64/lua_shared.so") 293 | .expect("Failed to find lua_shared.so") 294 | } 295 | 296 | #[cfg(all(target_os = "macos", target_pointer_width = "32"))] 297 | pub unsafe fn find_lua_shared() -> (Library, &'static str) { 298 | crate::open_library_raw!("garrysmod/bin/lua_shared.dylib") 299 | .expect("Failed to find lua_shared.dylib") 300 | } 301 | 302 | #[cfg(all(target_os = "macos", target_pointer_width = "64"))] 303 | pub unsafe fn find_lua_shared() -> (Library, &'static str) { 304 | crate::open_library_raw!("GarrysMod_Signed.app/Contents/MacOS/lua_shared.dylib") 305 | .expect("Failed to find lua_shared.dylib") 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /gmod/src/lua/lua_state.rs: -------------------------------------------------------------------------------- 1 | use std::{mem::MaybeUninit, borrow::Cow, ffi::c_void}; 2 | use crate::{userdata::TaggedUserData, lua::*}; 3 | 4 | unsafe fn handle_pcall_ignore(lua: State) { 5 | crate::lua_stack_guard!(lua => { 6 | lua.get_global(crate::lua_string!("ErrorNoHaltWithStack")); 7 | if lua.is_nil(-1) { 8 | eprintln!("[ERROR] {:?}", lua.get_string(-2)); 9 | lua.pop(); 10 | } else { 11 | #[cfg(debug_assertions)] { 12 | lua.push_string(&format!("[pcall_ignore] {}", lua.get_string(-2).expect("Expected a string here"))); 13 | } 14 | #[cfg(not(debug_assertions))] { 15 | lua.push_value(-2); 16 | } 17 | 18 | lua.call(1, 0); 19 | } 20 | }); 21 | lua.pop(); 22 | } 23 | 24 | #[repr(transparent)] 25 | #[derive(Clone, Copy, Debug)] 26 | pub struct LuaState(pub *mut std::ffi::c_void); 27 | 28 | impl LuaState { 29 | pub unsafe fn new() -> Result { 30 | let lua = (LUA_SHARED.lual_newstate)(); 31 | (LUA_SHARED.lual_openlibs)(lua); 32 | if lua.is_null() { 33 | Err(LuaError::MemoryAllocationError) 34 | } else { 35 | Ok(lua) 36 | } 37 | } 38 | 39 | /// Returns whether this is the clientside Lua state or not. 40 | pub unsafe fn is_client(&self) -> bool { 41 | self.get_global(crate::lua_string!("CLIENT")); 42 | let client = self.get_boolean(-1); 43 | self.pop(); 44 | client 45 | } 46 | 47 | /// Returns whether this is the serverside Lua state or not. 48 | pub unsafe fn is_server(&self) -> bool { 49 | self.get_global(crate::lua_string!("SERVER")); 50 | let server = self.get_boolean(-1); 51 | self.pop(); 52 | server 53 | } 54 | 55 | /// Returns whether this is the menu Lua state or not. 56 | pub unsafe fn is_menu(&self) -> bool { 57 | self.get_global(crate::lua_string!("MENU_DLL")); 58 | let menu = self.get_boolean(-1); 59 | self.pop(); 60 | menu 61 | } 62 | 63 | /// Returns the Lua string as a slice of bytes. 64 | /// 65 | /// **WARNING:** This will CHANGE the type of the value at the given index to a string. 66 | /// 67 | /// Returns None if the value at the given index is not convertible to a string. 68 | pub unsafe fn get_binary_string(&self, index: i32) -> Option<&[u8]> { 69 | let mut len: usize = 0; 70 | let ptr = (LUA_SHARED.lua_tolstring)(*self, index, &mut len); 71 | 72 | if ptr.is_null() { 73 | return None; 74 | } 75 | 76 | Some(std::slice::from_raw_parts(ptr as *const u8, len)) 77 | } 78 | 79 | /// Returns the Lua string as a Rust UTF-8 String. 80 | /// 81 | /// **WARNING:** This will CHANGE the type of the value at the given index to a string. 82 | /// 83 | /// Returns None if the value at the given index is not convertible to a string. 84 | /// 85 | /// This is a lossy operation, and will replace any invalid UTF-8 sequences with the Unicode replacement character. See the documentation for `String::from_utf8_lossy` for more information. 86 | /// 87 | /// If you need raw data, use `get_binary_string`. 88 | pub unsafe fn get_string(&self, index: i32) -> Option> { 89 | let mut len: usize = 0; 90 | let ptr = (LUA_SHARED.lua_tolstring)(*self, index, &mut len); 91 | 92 | if ptr.is_null() { 93 | return None; 94 | } 95 | 96 | let bytes = std::slice::from_raw_parts(ptr as *const u8, len); 97 | 98 | Some(String::from_utf8_lossy(bytes)) 99 | } 100 | 101 | /// Returns the name of the type of the value at the given index. 102 | pub unsafe fn get_type(&self, index: i32) -> &str { 103 | let lua_type = (LUA_SHARED.lua_type)(*self, index); 104 | let lua_type_str_ptr = (LUA_SHARED.lua_typename)(*self, lua_type); 105 | let lua_type_str = std::ffi::CStr::from_ptr(lua_type_str_ptr); 106 | unsafe { std::str::from_utf8_unchecked(lua_type_str.to_bytes()) } 107 | } 108 | 109 | #[inline(always)] 110 | pub unsafe fn get_top(&self) -> i32 { 111 | (LUA_SHARED.lua_gettop)(*self) 112 | } 113 | 114 | #[inline(always)] 115 | /// Pops the stack, inserts the value into the registry table, and returns the registry index of the value. 116 | /// 117 | /// Use `from_reference` with the reference index to push the value back onto the stack. 118 | /// 119 | /// Use `dereference` to free the reference from the registry table. 120 | pub unsafe fn reference(&self) -> LuaReference { 121 | (LUA_SHARED.lual_ref)(*self, LUA_REGISTRYINDEX) 122 | } 123 | 124 | #[inline(always)] 125 | pub unsafe fn dereference(&self, r#ref: LuaReference) { 126 | (LUA_SHARED.lual_unref)(*self, LUA_REGISTRYINDEX, r#ref) 127 | } 128 | 129 | #[inline(always)] 130 | pub unsafe fn from_reference(&self, r#ref: LuaReference) { 131 | self.raw_geti(LUA_REGISTRYINDEX, r#ref) 132 | } 133 | 134 | #[inline(always)] 135 | /// You may be looking for `is_none_or_nil` 136 | pub unsafe fn is_nil(&self, index: i32) -> bool { 137 | (LUA_SHARED.lua_type)(*self, index) == LUA_TNIL 138 | } 139 | 140 | #[inline(always)] 141 | pub unsafe fn is_none(&self, index: i32) -> bool { 142 | (LUA_SHARED.lua_type)(*self, index) == LUA_TNONE 143 | } 144 | 145 | #[inline(always)] 146 | pub unsafe fn is_none_or_nil(&self, index: i32) -> bool { 147 | self.is_nil(index) || self.is_none(index) 148 | } 149 | 150 | #[inline(always)] 151 | pub unsafe fn is_function(&self, index: i32) -> bool { 152 | (LUA_SHARED.lua_type)(*self, index) == LUA_TFUNCTION 153 | } 154 | 155 | #[inline(always)] 156 | pub unsafe fn is_table(&self, index: i32) -> bool { 157 | (LUA_SHARED.lua_type)(*self, index) == LUA_TTABLE 158 | } 159 | 160 | #[inline(always)] 161 | pub unsafe fn is_boolean(&self, index: i32) -> bool { 162 | (LUA_SHARED.lua_type)(*self, index) == LUA_TBOOLEAN 163 | } 164 | 165 | #[inline(always)] 166 | pub unsafe fn remove(&self, index: i32) { 167 | (LUA_SHARED.lua_remove)(*self, index) 168 | } 169 | 170 | #[inline(always)] 171 | pub unsafe fn push_value(&self, index: i32) { 172 | (LUA_SHARED.lua_pushvalue)(*self, index) 173 | } 174 | 175 | #[inline(always)] 176 | pub unsafe fn push_lightuserdata(&self, data: *mut c_void) { 177 | (LUA_SHARED.lua_pushlightuserdata)(*self, data) 178 | } 179 | 180 | #[inline(always)] 181 | pub unsafe fn get_field(&self, index: i32, k: LuaString) { 182 | (LUA_SHARED.lua_getfield)(*self, index, k) 183 | } 184 | 185 | #[inline(always)] 186 | pub unsafe fn push_boolean(&self, boolean: bool) { 187 | (LUA_SHARED.lua_pushboolean)(*self, if boolean { 1 } else { 0 }) 188 | } 189 | 190 | #[inline(always)] 191 | pub unsafe fn push_integer(&self, int: LuaInt) { 192 | (LUA_SHARED.lua_pushinteger)(*self, int) 193 | } 194 | 195 | #[inline(always)] 196 | pub unsafe fn push_number(&self, num: LuaNumber) { 197 | (LUA_SHARED.lua_pushnumber)(*self, num) 198 | } 199 | 200 | #[inline(always)] 201 | pub unsafe fn push_nil(&self) { 202 | (LUA_SHARED.lua_pushnil)(*self) 203 | } 204 | 205 | #[inline(always)] 206 | pub unsafe fn push_thread(&self) -> i32 { 207 | (LUA_SHARED.lua_pushthread)(*self) 208 | } 209 | 210 | #[inline(always)] 211 | pub unsafe fn to_thread(&self, index: i32) -> State { 212 | (LUA_SHARED.lua_tothread)(*self, index) 213 | } 214 | 215 | #[inline(always)] 216 | pub unsafe fn pcall(&self, nargs: i32, nresults: i32, errfunc: i32) -> i32 { 217 | (LUA_SHARED.lua_pcall)(*self, nargs, nresults, errfunc) 218 | } 219 | 220 | /// Same as pcall, but ignores any runtime error and calls `ErrorNoHaltWithStack` instead with the error message. 221 | /// 222 | /// Returns whether the execution was successful. 223 | pub unsafe fn pcall_ignore(&self, nargs: i32, nresults: i32) -> bool { 224 | match self.pcall(nargs, nresults, 0) { 225 | LUA_OK => true, 226 | LUA_ERRRUN => { 227 | handle_pcall_ignore(*self); 228 | false 229 | } 230 | err => { 231 | #[cfg(debug_assertions)] 232 | eprintln!("[gmod-rs] pcall_ignore unknown error: {}", err); 233 | false 234 | } 235 | } 236 | } 237 | 238 | pub unsafe fn load_string(&self, src: LuaString) -> Result<(), LuaError> { 239 | let lua_error_code = (LUA_SHARED.lual_loadstring)(*self, src); 240 | if lua_error_code == 0 { 241 | Ok(()) 242 | } else { 243 | Err(LuaError::from_lua_state(*self, lua_error_code)) 244 | } 245 | } 246 | 247 | pub unsafe fn load_buffer(&self, buff: &[u8], name: LuaString) -> Result<(), LuaError> { 248 | let lua_error_code = (LUA_SHARED.lual_loadbuffer)(*self, buff.as_ptr() as LuaString, buff.len(), name); 249 | if lua_error_code == 0 { 250 | Ok(()) 251 | } else { 252 | Err(LuaError::from_lua_state(*self, lua_error_code)) 253 | } 254 | } 255 | 256 | pub unsafe fn load_file(&self, path: LuaString) -> Result<(), LuaError> { 257 | let lua_error_code = (LUA_SHARED.lual_loadfile)(*self, path); 258 | if lua_error_code == 0 { 259 | Ok(()) 260 | } else { 261 | Err(LuaError::from_lua_state(*self, lua_error_code)) 262 | } 263 | } 264 | 265 | #[inline(always)] 266 | pub unsafe fn pop(&self) { 267 | self.pop_n(1); 268 | } 269 | 270 | #[inline(always)] 271 | pub unsafe fn pop_n(&self, count: i32) { 272 | self.set_top(-count - 1); 273 | } 274 | 275 | #[inline(always)] 276 | pub unsafe fn set_top(&self, index: i32) { 277 | (LUA_SHARED.lua_settop)(*self, index) 278 | } 279 | 280 | #[inline(always)] 281 | pub unsafe fn lua_type(&self, index: i32) -> i32 { 282 | (LUA_SHARED.lua_type)(*self, index) 283 | } 284 | 285 | pub unsafe fn lua_type_name(&self, lua_type_id: i32) -> Cow<'_, str> { 286 | let hackfix = self.get_top(); // https://github.com/Facepunch/garrysmod-issues/issues/5134 287 | 288 | let type_str_ptr = (LUA_SHARED.lua_typename)(*self, lua_type_id); 289 | 290 | self.pop_n((self.get_top() - hackfix).max(0)); 291 | 292 | let type_str = std::ffi::CStr::from_ptr(type_str_ptr); 293 | type_str.to_string_lossy() 294 | } 295 | 296 | #[inline(always)] 297 | pub unsafe fn replace(&self, index: i32) { 298 | (LUA_SHARED.lua_replace)(*self, index) 299 | } 300 | 301 | #[inline(always)] 302 | pub unsafe fn push_globals(&self) { 303 | (LUA_SHARED.lua_pushvalue)(*self, LUA_GLOBALSINDEX) 304 | } 305 | 306 | #[inline(always)] 307 | pub unsafe fn push_registry(&self) { 308 | (LUA_SHARED.lua_pushvalue)(*self, LUA_REGISTRYINDEX) 309 | } 310 | 311 | #[inline(always)] 312 | pub unsafe fn push_string(&self, data: &str) { 313 | (LUA_SHARED.lua_pushlstring)(*self, data.as_ptr() as LuaString, data.len()) 314 | } 315 | 316 | #[inline(always)] 317 | pub unsafe fn push_binary_string(&self, data: &[u8]) { 318 | (LUA_SHARED.lua_pushlstring)(*self, data.as_ptr() as LuaString, data.len()) 319 | } 320 | 321 | #[inline(always)] 322 | pub unsafe fn push_function(&self, func: LuaFunction) { 323 | (LUA_SHARED.lua_pushcclosure)(*self, func, 0) 324 | } 325 | 326 | #[inline(always)] 327 | /// Creates a closure, which can be used as a function with stored data (upvalues) 328 | /// 329 | /// ## Example 330 | /// 331 | /// ```ignore 332 | /// #[lua_function] 333 | /// unsafe fn foo(lua: gmod::lua::State) { 334 | /// lua.get_closure_arg(1); 335 | /// let hello = lua.get_string(-1); 336 | /// println!("{}", hello); 337 | /// } 338 | /// 339 | /// lua.push_string("Hello, world!"); 340 | /// lua.push_closure(foo, 1); 341 | /// ``` 342 | pub unsafe fn push_closure(&self, func: LuaFunction, n: i32) { 343 | debug_assert!(n <= 255, "Can't push more than 255 arguments into a closure"); 344 | (LUA_SHARED.lua_pushcclosure)(*self, func, n) 345 | } 346 | 347 | #[inline(always)] 348 | /// Pushes the `n`th closure argument onto the stack 349 | /// 350 | /// ## Example 351 | /// 352 | /// ```ignore 353 | /// #[lua_function] 354 | /// unsafe fn foo(lua: gmod::lua::State) { 355 | /// lua.push_closure_arg(1); 356 | /// let hello = lua.get_string(-1); 357 | /// println!("{}", hello); 358 | /// } 359 | /// 360 | /// lua.push_string("Hello, world!"); 361 | /// lua.push_closure(foo, 1); 362 | /// ``` 363 | pub unsafe fn push_closure_arg(&self, n: i32) { 364 | self.push_value(self.upvalue_index(n)); 365 | } 366 | 367 | #[inline(always)] 368 | /// Equivalent to C `lua_upvalueindex` macro 369 | pub const fn upvalue_index(&self, idx: i32) -> i32 { 370 | LUA_GLOBALSINDEX - idx 371 | } 372 | 373 | #[inline(always)] 374 | pub unsafe fn set_table(&self, index: i32) { 375 | (LUA_SHARED.lua_settable)(*self, index) 376 | } 377 | 378 | #[inline(always)] 379 | pub unsafe fn set_field(&self, index: i32, k: LuaString) { 380 | (LUA_SHARED.lua_setfield)(*self, index, k) 381 | } 382 | 383 | #[inline(always)] 384 | pub unsafe fn get_global(&self, name: LuaString) { 385 | (LUA_SHARED.lua_getfield)(*self, LUA_GLOBALSINDEX, name) 386 | } 387 | 388 | #[inline(always)] 389 | pub unsafe fn set_global(&self, name: LuaString) { 390 | (LUA_SHARED.lua_setfield)(*self, LUA_GLOBALSINDEX, name) 391 | } 392 | 393 | #[inline(always)] 394 | /// WARNING: Any Lua errors caused by calling the function will longjmp and prevent any further execution of your code. 395 | /// 396 | /// To workaround this, use `pcall_ignore`, which will call `ErrorNoHaltWithStack` instead and allow your code to continue executing. 397 | pub unsafe fn call(&self, nargs: i32, nresults: i32) { 398 | (LUA_SHARED.lua_call)(*self, nargs, nresults) 399 | } 400 | 401 | #[inline(always)] 402 | pub unsafe fn insert(&self, index: i32) { 403 | (LUA_SHARED.lua_insert)(*self, index) 404 | } 405 | 406 | /// Creates a new table and pushes it to the stack. 407 | /// seq_n is a hint as to how many sequential elements the table may have. 408 | /// hash_n is a hint as to how many non-sequential/hashed elements the table may have. 409 | /// Lua may use these hints to preallocate memory. 410 | #[inline(always)] 411 | pub unsafe fn create_table(&self, seq_n: i32, hash_n: i32) { 412 | (LUA_SHARED.lua_createtable)(*self, seq_n, hash_n) 413 | } 414 | 415 | /// Creates a new table and pushes it to the stack without memory preallocation hints. 416 | /// Equivalent to `create_table(0, 0)` 417 | #[inline(always)] 418 | pub unsafe fn new_table(&self) { 419 | (LUA_SHARED.lua_createtable)(*self, 0, 0) 420 | } 421 | 422 | #[inline(always)] 423 | pub unsafe fn get_table(&self, index: i32) { 424 | (LUA_SHARED.lua_gettable)(*self, index) 425 | } 426 | 427 | pub unsafe fn check_binary_string(&self, arg: i32) -> &[u8] { 428 | let mut len: usize = 0; 429 | let ptr = (LUA_SHARED.lual_checklstring)(*self, arg, &mut len); 430 | std::slice::from_raw_parts(ptr as *const u8, len) 431 | } 432 | 433 | pub unsafe fn check_string(&self, arg: i32) -> Cow<'_, str> { 434 | let mut len: usize = 0; 435 | let ptr = (LUA_SHARED.lual_checklstring)(*self, arg, &mut len); 436 | String::from_utf8_lossy(std::slice::from_raw_parts(ptr as *const u8, len)) 437 | } 438 | 439 | #[inline(always)] 440 | pub unsafe fn check_userdata(&self, arg: i32, name: LuaString) -> *mut TaggedUserData { 441 | (LUA_SHARED.lual_checkudata)(*self, arg, name) as *mut _ 442 | } 443 | 444 | pub unsafe fn test_userdata(&self, index: i32, name: LuaString) -> bool { 445 | if !(LUA_SHARED.lua_touserdata)(*self, index).is_null() && self.get_metatable(index) != 0 { 446 | self.get_field(LUA_REGISTRYINDEX, name); 447 | let result = self.raw_equal(-1, -2); 448 | self.pop_n(2); 449 | if result { 450 | return true; 451 | } 452 | } 453 | false 454 | } 455 | 456 | #[inline(always)] 457 | pub unsafe fn raw_equal(&self, a: i32, b: i32) -> bool { 458 | (LUA_SHARED.lua_rawequal)(*self, a, b) == 1 459 | } 460 | 461 | #[inline(always)] 462 | pub unsafe fn get_metatable(&self, index: i32) -> i32 { 463 | (LUA_SHARED.lua_getmetatable)(*self, index) 464 | } 465 | 466 | #[inline(always)] 467 | pub unsafe fn check_table(&self, arg: i32) { 468 | (LUA_SHARED.lual_checktype)(*self, arg, LUA_TTABLE) 469 | } 470 | 471 | #[inline(always)] 472 | pub unsafe fn check_function(&self, arg: i32) { 473 | (LUA_SHARED.lual_checktype)(*self, arg, LUA_TFUNCTION) 474 | } 475 | 476 | #[inline(always)] 477 | pub unsafe fn check_integer(&self, arg: i32) -> LuaInt { 478 | (LUA_SHARED.lual_checkinteger)(*self, arg) 479 | } 480 | 481 | #[inline(always)] 482 | pub unsafe fn check_number(&self, arg: i32) -> f64 { 483 | (LUA_SHARED.lual_checknumber)(*self, arg) 484 | } 485 | 486 | #[inline(always)] 487 | pub unsafe fn check_boolean(&self, arg: i32) -> bool { 488 | (LUA_SHARED.lual_checktype)(*self, arg, LUA_TBOOLEAN); 489 | (LUA_SHARED.lua_toboolean)(*self, arg) == 1 490 | } 491 | 492 | #[inline(always)] 493 | pub unsafe fn to_integer(&self, index: i32) -> LuaInt { 494 | (LUA_SHARED.lua_tointeger)(*self, index) 495 | } 496 | 497 | #[inline(always)] 498 | pub unsafe fn to_number(&self, index: i32) -> f64 { 499 | (LUA_SHARED.lua_tonumber)(*self, index) 500 | } 501 | 502 | #[inline(always)] 503 | pub unsafe fn get_boolean(&self, index: i32) -> bool { 504 | (LUA_SHARED.lua_toboolean)(*self, index) == 1 505 | } 506 | 507 | #[inline(always)] 508 | pub unsafe fn set_metatable(&self, index: i32) -> i32 { 509 | (LUA_SHARED.lua_setmetatable)(*self, index) 510 | } 511 | 512 | #[inline(always)] 513 | #[allow(clippy::len_without_is_empty)] 514 | pub unsafe fn len(&self, index: i32) -> i32 { 515 | (LUA_SHARED.lua_objlen)(*self, index) 516 | } 517 | 518 | #[inline(always)] 519 | pub unsafe fn raw_geti(&self, t: i32, index: i32) { 520 | (LUA_SHARED.lua_rawgeti)(*self, t, index) 521 | } 522 | 523 | #[inline(always)] 524 | pub unsafe fn raw_seti(&self, t: i32, index: i32) { 525 | (LUA_SHARED.lua_rawseti)(*self, t, index) 526 | } 527 | 528 | #[inline(always)] 529 | pub unsafe fn next(&self, index: i32) -> i32 { 530 | (LUA_SHARED.lua_next)(*self, index) 531 | } 532 | 533 | #[inline(always)] 534 | pub unsafe fn to_pointer(&self, index: i32) -> *const c_void { 535 | (LUA_SHARED.lua_topointer)(*self, index) 536 | } 537 | 538 | #[inline(always)] 539 | pub unsafe fn to_userdata(&self, index: i32) -> *mut c_void { 540 | (LUA_SHARED.lua_touserdata)(*self, index) 541 | } 542 | 543 | #[inline(always)] 544 | pub unsafe fn coroutine_new(&self) -> State { 545 | (LUA_SHARED.lua_newthread)(*self) 546 | } 547 | 548 | #[inline(always)] 549 | #[must_use] 550 | pub unsafe fn coroutine_yield(&self, nresults: i32) -> i32 { 551 | (LUA_SHARED.lua_yield)(*self, nresults) 552 | } 553 | 554 | #[inline(always)] 555 | #[must_use] 556 | pub unsafe fn coroutine_resume(&self, narg: i32) -> i32 { 557 | (LUA_SHARED.lua_resume)(*self, narg) 558 | } 559 | 560 | #[inline(always)] 561 | /// Exchange values between different threads of the same global state. 562 | /// 563 | /// This function pops `n` values from the stack `self`, and pushes them onto the stack `target_thread`. 564 | pub unsafe fn coroutine_exchange(&self, target_thread: State, n: i32) { 565 | (LUA_SHARED.lua_xmove)(*self, target_thread, n) 566 | } 567 | 568 | #[inline(always)] 569 | pub unsafe fn equal(&self, index1: i32, index2: i32) -> bool { 570 | (LUA_SHARED.lua_equal)(*self, index1, index2) == 1 571 | } 572 | 573 | #[inline(always)] 574 | /// See `call` 575 | pub unsafe fn coroutine_resume_call(&self, narg: i32) { 576 | match (LUA_SHARED.lua_resume)(*self, narg) { 577 | LUA_OK => {}, 578 | LUA_ERRRUN => self.error(self.get_string(-2).unwrap_or(Cow::Borrowed("Unknown error")).as_ref()), 579 | LUA_ERRMEM => self.error("Out of memory"), 580 | _ => self.error("Unknown internal Lua error") 581 | } 582 | } 583 | 584 | #[inline(always)] 585 | /// See `pcall_ignore` 586 | pub unsafe fn coroutine_resume_pcall_ignore(&self, narg: i32) -> Result { 587 | match (LUA_SHARED.lua_resume)(*self, narg) { 588 | status @ (LUA_OK | LUA_YIELD) => Ok(status), 589 | LUA_ERRRUN => { 590 | handle_pcall_ignore(*self); 591 | Err(()) 592 | }, 593 | err => { 594 | #[cfg(debug_assertions)] 595 | eprintln!("[gmod-rs] coroutine_resume_pcall_ignore unknown error: {}", err); 596 | Err(()) 597 | } 598 | } 599 | } 600 | 601 | #[inline(always)] 602 | pub unsafe fn coroutine_status(&self) -> i32 { 603 | (LUA_SHARED.lua_status)(*self) 604 | } 605 | 606 | /// Creates a new table in the registry with the given `name` as the key if it doesn't already exist, and pushes it onto the stack. 607 | /// 608 | /// Returns if the metatable was already present in the registry. 609 | #[inline(always)] 610 | pub unsafe fn new_metatable(&self, name: LuaString) -> bool { 611 | (LUA_SHARED.lual_newmetatable)(*self, name) == 0 612 | } 613 | 614 | pub unsafe fn new_userdata(&self, data: T, metatable: Option) -> *mut T { 615 | let has_metatable = if std::mem::needs_drop::() { 616 | if let Some(metatable) = metatable { 617 | self.push_value(metatable); 618 | } else { 619 | self.new_table(); 620 | } 621 | self.push_function(crate::userdata::__gc::); 622 | self.set_field(-2, crate::lua_string!("__gc")); 623 | true 624 | } else if let Some(metatable) = metatable { 625 | self.push_value(metatable); 626 | true 627 | } else { 628 | false 629 | }; 630 | 631 | let ptr = (LUA_SHARED.lua_newuserdata)(*self, std::mem::size_of::()) as *mut T; 632 | 633 | debug_assert_eq!(ptr as usize % std::mem::align_of::(), 0, "Lua userdata is unaligned!"); 634 | 635 | if has_metatable { 636 | self.push_value(-2); 637 | self.set_metatable(-2); 638 | self.remove(self.get_top() - 1); 639 | self.remove(self.get_top() - 1); 640 | } 641 | 642 | ptr.write(data); 643 | ptr 644 | } 645 | 646 | #[cold] 647 | pub unsafe fn error>(&self, msg: S) -> ! { 648 | self.push_string(msg.as_ref()); 649 | (LUA_SHARED.lua_error)(*self); 650 | unreachable!() 651 | } 652 | 653 | pub unsafe fn debug_getinfo_from_ar(&self, ar: &mut LuaDebug, what: LuaString) -> Result<(), ()> { 654 | if (LUA_SHARED.lua_getinfo)(*self, what, ar as *mut LuaDebug) != 0 { 655 | Ok(()) 656 | } else { 657 | Err(()) 658 | } 659 | } 660 | 661 | /// `what` should start with `>` and pop a function off the stack 662 | pub unsafe fn debug_getinfo_from_stack(&self, what: LuaString) -> Option { 663 | let mut ar = MaybeUninit::uninit(); 664 | if (LUA_SHARED.lua_getinfo)(*self, what, ar.as_mut_ptr()) != 0 { 665 | Some(ar.assume_init()) 666 | } else { 667 | None 668 | } 669 | } 670 | 671 | pub unsafe fn get_stack_at(&self, level: i32) -> Option { 672 | let mut ar = MaybeUninit::uninit(); 673 | if (LUA_SHARED.lua_getstack)(*self, level, ar.as_mut_ptr()) != 0 { 674 | Some(ar.assume_init()) 675 | } else { 676 | None 677 | } 678 | } 679 | 680 | pub unsafe fn debug_getinfo_at(&self, level: i32, what: LuaString) -> Option { 681 | let mut ar = MaybeUninit::uninit(); 682 | if (LUA_SHARED.lua_getstack)(*self, level, ar.as_mut_ptr()) != 0 && (LUA_SHARED.lua_getinfo)(*self, what, ar.as_mut_ptr()) != 0 { 683 | return Some(ar.assume_init()); 684 | } 685 | None 686 | } 687 | 688 | pub unsafe fn dump_stack(&self) { 689 | let top = self.get_top(); 690 | println!("\n=== STACK DUMP ==="); 691 | println!("Stack size: {}", top); 692 | for i in 1..=top { 693 | let lua_type = self.lua_type(i); 694 | let lua_type_name = self.lua_type_name(lua_type); 695 | match lua_type_name.as_ref() { 696 | "string" => println!("{}. {}: {:?}", i, lua_type_name, { 697 | self.push_value(i); 698 | let str = self.get_string(-1); 699 | self.pop(); 700 | str 701 | }), 702 | "boolean" => println!("{}. {}: {:?}", i, lua_type_name, { 703 | self.push_value(i); 704 | let bool = self.get_boolean(-1); 705 | self.pop(); 706 | bool 707 | }), 708 | "number" => println!("{}. {}: {:?}", i, lua_type_name, { 709 | self.push_value(i); 710 | let n = self.to_number(-1); 711 | self.pop(); 712 | n 713 | }), 714 | _ => println!("{}. {}", i, lua_type_name), 715 | } 716 | } 717 | println!(); 718 | } 719 | 720 | pub unsafe fn dump_val(&self, index: i32) -> String { 721 | let lua_type_name = self.lua_type_name(self.lua_type(index)); 722 | match lua_type_name.as_ref() { 723 | "string" => { 724 | self.push_value(index); 725 | let str = self.get_string(-1); 726 | self.pop(); 727 | format!("{:?}", str.unwrap().into_owned()) 728 | }, 729 | "boolean" => { 730 | self.push_value(index); 731 | let boolean = self.get_boolean(-1); 732 | self.pop(); 733 | format!("{}", boolean) 734 | }, 735 | "number" => { 736 | self.push_value(index); 737 | let n = self.to_number(-1); 738 | self.pop(); 739 | format!("{}", n) 740 | }, 741 | _ => lua_type_name.into_owned(), 742 | } 743 | } 744 | } 745 | impl std::ops::Deref for LuaState { 746 | type Target = *mut std::ffi::c_void; 747 | 748 | #[inline(always)] 749 | fn deref(&self) -> &Self::Target { 750 | &self.0 751 | } 752 | } 753 | --------------------------------------------------------------------------------