From 6a3f374c96928887167275a060c502e205e0b1ab Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 9 Sep 2021 21:50:58 +0100 Subject: [PATCH] Initial commit --- .editorconfig | 7 + .gitignore | 1 + Cargo.lock | 270 ++++++++++++++++++++++ Cargo.toml | 5 + LICENSE | 21 ++ README.md | 3 + gmod-macros/Cargo.toml | 16 ++ gmod-macros/src/lib.rs | 39 ++++ gmod/Cargo.toml | 14 ++ gmod/src/lib.rs | 11 + gmod/src/lua/import.rs | 215 ++++++++++++++++++ gmod/src/lua/lua_state.rs | 466 ++++++++++++++++++++++++++++++++++++++ gmod/src/lua/mod.rs | 46 ++++ gmod/src/msgc.rs | 81 +++++++ rust-toolchain.toml | 2 + 15 files changed, 1197 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 gmod-macros/Cargo.toml create mode 100644 gmod-macros/src/lib.rs create mode 100644 gmod/Cargo.toml create mode 100644 gmod/src/lib.rs create mode 100644 gmod/src/lua/import.rs create mode 100644 gmod/src/lua/lua_state.rs create mode 100644 gmod/src/lua/mod.rs create mode 100644 gmod/src/msgc.rs create mode 100644 rust-toolchain.toml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5d34721 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..83f2d51 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,270 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cstr" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11a39d776a3b35896711da8a04dc1835169dcd36f710878187637314e47941b" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "detour" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c83fabcc3bc336e19320c13576ea708a15deec201d6b879b7ad1b92734d7b9" +dependencies = [ + "cfg-if", + "generic-array", + "lazy_static", + "libc", + "libudis86-sys", + "mmap-fixed", + "region", + "slice-pool", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gmod" +version = "0.1.0" +dependencies = [ + "cstr", + "ctor", + "detour", + "gmod-macros", + "lazy_static", + "libloading", + "skidscan", +] + +[[package]] +name = "gmod-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" + +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if", + "winapi 0.3.9", +] + +[[package]] +name = "libudis86-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "mmap-fixed" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c1ae264d6343d3b4079549f6bc9e6d074dc4106cb1324c7753c6ce11d07b21" +dependencies = [ + "kernel32-sys", + "libc", + "winapi 0.2.8", +] + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi 0.3.9", +] + +[[package]] +name = "skidscan" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c52ddc5d6dde2bbb702e353d78c059cbbe07f15189ead0b7ac7da49b44bacc" +dependencies = [ + "libc", + "skidscan-macros", + "winapi 0.3.9", +] + +[[package]] +name = "skidscan-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c98a622ae309bfbfe69e99bbf9e8ce23efc15817eda4bda1369bbb3eea668f83" +dependencies = [ + "syn", +] + +[[package]] +name = "slice-pool" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733fc6e5f1bd3a8136f842c9bdea4e5f17c910c2fcc98c90c3aa7604ef5e2e7a" + +[[package]] +name = "syn" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7206d44 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "gmod", + "gmod-macros" +] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..187018d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 William Venner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7cff27b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ⚙ gmod-rs + +A swiss army knife for creating binary modules for Garry's Mod in Rust. diff --git a/gmod-macros/Cargo.toml b/gmod-macros/Cargo.toml new file mode 100644 index 0000000..f5de247 --- /dev/null +++ b/gmod-macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "gmod-macros" +version = "0.1.0" +authors = ["William Venner "] +edition = "2018" +license = "MIT" +description = "Proc macros for gmod-rs" +repository = "https://github.com/WilliamVenner/gmod-rs" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +syn = { version = "1", features = ["full"] } +quote = "1" diff --git a/gmod-macros/src/lib.rs b/gmod-macros/src/lib.rs new file mode 100644 index 0000000..d34f8e0 --- /dev/null +++ b/gmod-macros/src/lib.rs @@ -0,0 +1,39 @@ +#[macro_use] +extern crate syn; + +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; +use quote::ToTokens; +use syn::{ReturnType, ItemFn}; + +fn check_lua_function(input: &mut ItemFn) { + assert!(input.sig.asyncness.is_none(), "Cannot be async"); + assert!(input.sig.constness.is_none(), "Cannot be const"); + 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)"); + assert!(matches!(&input.sig.output, ReturnType::Type(_, r#type) if r#type.to_token_stream().to_string() == "i32"), "The output must be an i32, representing the number of return values of the function"); + 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"); + input.sig.abi = Some(syn::parse_quote!(extern "C-unwind")); +} + +#[proc_macro_attribute] +pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(tokens as ItemFn); + check_lua_function(&mut input); + TokenStream::from(quote!(#[no_mangle] #input)) +} + +#[proc_macro_attribute] +pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(tokens as ItemFn); + check_lua_function(&mut input); + TokenStream::from(quote!(#input)) +} + +#[proc_macro_attribute] +pub fn lua_function(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(tokens as ItemFn); + check_lua_function(&mut input); + TokenStream::from(quote!(#input)) +} \ No newline at end of file diff --git a/gmod/Cargo.toml b/gmod/Cargo.toml new file mode 100644 index 0000000..7fbd1f5 --- /dev/null +++ b/gmod/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "gmod" +version = "0.1.0" +authors = ["William Venner "] +edition = "2018" + +[dependencies] +libloading = "0" +detour = "0" +skidscan = "0" +cstr = "0" +lazy_static = "1" +ctor = "0" +gmod-macros = { version = "0.1.0", path = "../gmod-macros" } diff --git a/gmod/src/lib.rs b/gmod/src/lib.rs new file mode 100644 index 0000000..50b4ba4 --- /dev/null +++ b/gmod/src/lib.rs @@ -0,0 +1,11 @@ +#![feature(c_unwind)] + +pub use libloading; +pub use detour; +pub use skidscan as sigscan; +pub use cstr; +pub use ctor::{ctor as dllopen, dtor as dllclose}; +pub use gmod_macros::*; + +pub mod lua; +pub mod msgc; \ No newline at end of file diff --git a/gmod/src/lua/import.rs b/gmod/src/lua/import.rs new file mode 100644 index 0000000..bece999 --- /dev/null +++ b/gmod/src/lua/import.rs @@ -0,0 +1,215 @@ +use libloading::{Library, Symbol}; + +macro_rules! find_library { + ($path:literal) => { + Library::new($path).map(|lib| (lib, $path)) + }; +} + +use super::{LuaError, State as LuaState, lua_state::LuaDebug}; + +pub type LuaInt = isize; +pub type LuaSize = usize; +pub type LuaString = *const std::os::raw::c_char; +pub type LuaFunction = unsafe extern "C-unwind" fn(state: LuaState) -> i32; +pub type LuaNumber = f64; +pub type LuaReference = i32; + +pub const LUA_REGISTRYINDEX: i32 = -10000; +pub const LUA_ENVIRONINDEX: i32 = -10001; +pub const LUA_GLOBALSINDEX: i32 = -10002; + +pub const LUA_MULTRET: i32 = -1; +pub const LUA_NOREF: LuaReference = -2; +pub const LUA_REFNIL: LuaReference = -1; + +pub const LUA_TNONE: i32 = -1; +pub const LUA_TNIL: i32 = 0; +pub const LUA_TBOOLEAN: i32 = 1; +pub const LUA_TLIGHTUSERDATA: i32 = 2; +pub const LUA_TNUMBER: i32 = 3; +pub const LUA_TSTRING: i32 = 4; +pub const LUA_TTABLE: i32 = 5; +pub const LUA_TFUNCTION: i32 = 6; +pub const LUA_TUSERDATA: i32 = 7; +pub const LUA_TTHREAD: i32 = 8; + +pub const LUA_OK: i32 = 0; +pub const LUA_YIELD: i32 = 1; +pub const LUA_ERRRUN: i32 = 2; +pub const LUA_ERRSYNTAX: i32 = 3; +pub const LUA_ERRMEM: i32 = 4; +pub const LUA_ERRERR: i32 = 5; +pub const LUA_ERRFILE: i32 = LUA_ERRERR + 1; + +pub const LUA_IDSIZE: usize = 60; + +impl LuaError { + fn get_error_message(lua_state: LuaState) -> Option { + unsafe { lua_state.get_string(-1).map(|str| str.into_owned()) } + } + + pub(crate) fn from_lua_state(lua_state: LuaState, lua_int_error_code: i32) -> Self { + use super::LuaError::*; + match lua_int_error_code { + LUA_ERRMEM => MemoryAllocationError, + LUA_ERRERR => ErrorHandlerError, + LUA_ERRSYNTAX | LUA_ERRRUN | LUA_ERRFILE => { + let msg = LuaError::get_error_message(lua_state); + match lua_int_error_code { + LUA_ERRSYNTAX => SyntaxError(msg), + LUA_ERRRUN => RuntimeError(msg), + LUA_ERRFILE => FileError(msg), + _ => unreachable!(), + } + } + _ => Unknown(lua_int_error_code), + } + } +} + +lazy_static::lazy_static! { + pub(super) static ref LUA_SHARED: LuaShared = LuaShared::import(); +} + +pub(super) struct LuaShared { + pub lual_loadfile: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, path: LuaString) -> i32>, + pub lual_loadstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, path: LuaString) -> i32>, + pub lua_getfield: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, k: LuaString)>, + pub lua_pushvalue: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, + pub lua_pushboolean: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, bool: i32)>, + pub lua_tolstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, out_size: *mut LuaSize) -> LuaString>, + pub lua_pcall: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, nargs: i32, nresults: i32, errfunc: i32) -> i32>, + pub lua_remove: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, + pub lua_gettop: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState) -> i32>, + pub lua_type: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, + pub lua_typename: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, lua_type_id: i32) -> LuaString>, + pub lua_setfield: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, k: LuaString)>, + pub lua_call: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, nargs: i32, nresults: i32)>, + pub lua_createtable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, narr: i32, nrec: i32)>, + pub lua_settop: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, count: i32)>, + pub lua_replace: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, + pub lua_pushlstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, data: LuaString, length: LuaSize)>, + pub lua_pushcclosure: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, func: LuaFunction, upvalues: i32)>, + pub lua_settable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, + pub lua_gettable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, + pub lua_error: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState) -> i32>, + pub lua_insert: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32)>, + pub lual_checkinteger: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32) -> LuaInt>, + pub lual_checklstring: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32, out_size: *mut LuaSize) -> LuaString>, + pub lua_toboolean: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, + pub lua_setmetatable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, + pub lua_pushinteger: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, int: LuaInt)>, + pub lua_pushnumber: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, int: LuaNumber)>, + pub lua_pushnil: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState)>, + pub lual_checknumber: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32) -> LuaNumber>, + pub lua_tointeger: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> LuaInt>, + pub lua_tonumber: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> LuaNumber>, + pub lual_checkudata: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, arg: i32, name: LuaString) -> *mut std::ffi::c_void>, + pub lual_ref: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, + pub lual_unref: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32, r#ref: i32)>, + pub lua_objlen: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, + pub lua_rawgeti: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, t: i32, index: i32)>, + pub lua_rawseti: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, t: i32, index: i32)>, + pub lua_getmetatable: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, + pub lua_rawequal: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, a: i32, b: i32) -> i32>, + pub lua_touserdata: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> *mut std::ffi::c_void>, + pub lua_getinfo: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, what: LuaString, ar: *mut LuaDebug) -> i32>, + pub lua_getstack: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, level: i32, ar: *mut LuaDebug) -> i32>, + pub lua_next: Symbol<'static, unsafe extern "C-unwind" fn(state: LuaState, index: i32) -> i32>, +} +unsafe impl Sync for LuaShared {} +impl LuaShared { + fn import() -> Self { + unsafe { + let (library, path) = Self::find_lua_shared(); + let library = Box::leak(Box::new(library)); // Keep this library referenced forever + + macro_rules! find_symbol { + ( $symbol:literal ) => { + Self::find_symbol(library, concat!($symbol, "\0").as_bytes()) + }; + } + + Self { + lual_loadfile: find_symbol!("luaL_loadfile"), + lual_loadstring: find_symbol!("luaL_loadstring"), + lua_getfield: find_symbol!("lua_getfield"), + lua_pushvalue: find_symbol!("lua_pushvalue"), + lua_pushboolean: find_symbol!("lua_pushboolean"), + lua_tolstring: find_symbol!("lua_tolstring"), + lua_pcall: find_symbol!("lua_pcall"), + lua_remove: find_symbol!("lua_remove"), + lua_gettop: find_symbol!("lua_gettop"), + lua_type: find_symbol!("lua_type"), + lua_typename: find_symbol!("lua_typename"), + lua_setfield: find_symbol!("lua_setfield"), + lua_call: find_symbol!("lua_call"), + lua_createtable: find_symbol!("lua_createtable"), + lua_settop: find_symbol!("lua_settop"), + lua_replace: find_symbol!("lua_replace"), + lua_pushlstring: find_symbol!("lua_pushlstring"), + lua_pushcclosure: find_symbol!("lua_pushcclosure"), + lua_settable: find_symbol!("lua_settable"), + lua_gettable: find_symbol!("lua_gettable"), + lua_error: find_symbol!("lua_error"), + lua_insert: find_symbol!("lua_insert"), + lual_checkinteger: find_symbol!("luaL_checkinteger"), + lual_checklstring: find_symbol!("luaL_checklstring"), + lua_toboolean: find_symbol!("lua_toboolean"), + lua_pushnumber: find_symbol!("lua_pushnumber"), + lua_pushinteger: find_symbol!("lua_pushinteger"), + lua_pushnil: find_symbol!("lua_pushnil"), + lual_checknumber: find_symbol!("luaL_checknumber"), + lua_tointeger: find_symbol!("lua_tointeger"), + lua_tonumber: find_symbol!("lua_tonumber"), + lual_checkudata: find_symbol!("luaL_checkudata"), + lual_ref: find_symbol!("luaL_ref"), + lual_unref: find_symbol!("luaL_unref"), + lua_setmetatable: find_symbol!("lua_setmetatable"), + lua_objlen: find_symbol!("lua_objlen"), + lua_rawgeti: find_symbol!("lua_rawgeti"), + lua_rawseti: find_symbol!("lua_rawseti"), + lua_getmetatable: find_symbol!("lua_getmetatable"), + lua_rawequal: find_symbol!("lua_rawequal"), + lua_touserdata: find_symbol!("lua_touserdata"), + lua_getinfo: find_symbol!("lua_getinfo"), + lua_getstack: find_symbol!("lua_getstack"), + lua_next: find_symbol!("lua_next"), + } + } + } + + unsafe fn find_symbol(library: &'static Library, name: &[u8]) -> Symbol<'static, T> { + match library.get(name) { + Ok(symbol) => symbol, + Err(err) => panic!("Failed to find symbol \"{}\"\n{:#?}", String::from_utf8_lossy(name), err), + } + } + + #[cfg(all(target_os = "windows", target_pointer_width = "64"))] + unsafe fn find_lua_shared() -> (Library, &'static str) { + find_library!("bin/win64/lua_shared.dll") + .expect("Failed to load lua_shared.dll") + } + + #[cfg(all(target_os = "windows", target_pointer_width = "32"))] + unsafe fn find_lua_shared() -> (Library, &'static str) { + find_library!("garrysmod/bin/lua_shared.dll") + .or_else(|_| find_library!("bin/lua_shared.dll")) + .expect("Failed to load lua_shared.dll") + } + + #[cfg(all(target_os = "linux", target_pointer_width = "32"))] + unsafe fn find_lua_shared() -> (Library, &'static str) { + find_library!("garrysmod/bin/lua_shared_srv.so") + .or_else(|_| find_library!("bin/linux32/lua_shared.so")) + .expect("Failed to find lua_shared.so or lua_shared_srv.so") + } + + #[cfg(all(target_os = "linux", target_pointer_width = "64"))] + unsafe fn find_lua_shared() -> (Library, &'static str) { + find_library!("bin/linux64/lua_shared.so") + .expect("Failed to find lua_shared.so") + } +} diff --git a/gmod/src/lua/lua_state.rs b/gmod/src/lua/lua_state.rs new file mode 100644 index 0000000..3343498 --- /dev/null +++ b/gmod/src/lua/lua_state.rs @@ -0,0 +1,466 @@ +use std::{mem::MaybeUninit, borrow::Cow, ffi::c_void}; + +use crate::lua::*; + +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct LuaState(pub *mut std::ffi::c_void); +unsafe impl Send for LuaState {} +impl LuaState { + + /// Returns the Lua string as a slice of bytes. + /// + /// **WARNING:** This will CHANGE the type of the value at the given index to a string. + /// + /// Returns None if the value at the given index is not convertible to a string. + pub unsafe fn get_binary_string(&self, index: i32) -> Option<&[u8]> { + let mut len: usize = 0; + let ptr = (LUA_SHARED.lua_tolstring)(*self, index, &mut len); + + if ptr.is_null() { + return None; + } + + Some(std::slice::from_raw_parts(ptr as *const u8, len)) + } + + /// Returns the Lua string as a Rust UTF-8 String. + /// + /// **WARNING:** This will CHANGE the type of the value at the given index to a string. + /// + /// Returns None if the value at the given index is not convertible to a string. + /// + /// 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. + /// + /// If you need raw data, use `get_binary_string`. + pub unsafe fn get_string(&self, index: i32) -> Option> { + let mut len: usize = 0; + let ptr = (LUA_SHARED.lua_tolstring)(*self, index, &mut len); + + if ptr.is_null() { + return None; + } + + let bytes = std::slice::from_raw_parts(ptr as *const u8, len); + + Some(String::from_utf8_lossy(bytes)) + } + + /// Returns the name of the type of the value at the given index. + pub unsafe fn get_type(&self, index: i32) -> &str { + let lua_type = (LUA_SHARED.lua_type)(*self, index); + let lua_type_str_ptr = (LUA_SHARED.lua_typename)(*self, lua_type); + let lua_type_str = std::ffi::CStr::from_ptr(lua_type_str_ptr); + unsafe { std::str::from_utf8_unchecked(lua_type_str.to_bytes()) } + } + + #[inline] + pub unsafe fn get_top(&self) -> i32 { + (LUA_SHARED.lua_gettop)(*self) + } + + #[inline] + /// Pops the stack, inserts the value into the registry table, and returns the registry index of the value. + /// + /// Use `from_reference` with the reference index to push the value back onto the stack. + /// + /// Use `dereference` to free the reference from the registry table. + pub unsafe fn reference(&self) -> LuaReference { + (LUA_SHARED.lual_ref)(*self, LUA_REGISTRYINDEX) + } + + #[inline] + pub unsafe fn dereference(&self, r#ref: LuaReference) { + (LUA_SHARED.lual_unref)(*self, LUA_REGISTRYINDEX, r#ref) + } + + #[inline] + pub unsafe fn from_reference(&self, r#ref: LuaReference) { + self.raw_geti(LUA_REGISTRYINDEX, r#ref) + } + + #[inline] + pub unsafe fn is_nil(&self, index: i32) -> bool { + (LUA_SHARED.lua_type)(*self, index) == LUA_TNIL + } + + #[inline] + pub unsafe fn is_function(&self, index: i32) -> bool { + (LUA_SHARED.lua_type)(*self, index) == LUA_TFUNCTION + } + + #[inline] + pub unsafe fn is_table(&self, index: i32) -> bool { + (LUA_SHARED.lua_type)(*self, index) == LUA_TTABLE + } + + #[inline] + pub unsafe fn is_boolean(&self, index: i32) -> bool { + (LUA_SHARED.lua_type)(*self, index) == LUA_TBOOLEAN + } + + #[inline] + pub unsafe fn remove(&self, index: i32) { + (LUA_SHARED.lua_remove)(*self, index) + } + + #[inline] + pub unsafe fn push_value(&self, index: i32) { + (LUA_SHARED.lua_pushvalue)(*self, index) + } + + #[inline] + pub unsafe fn get_field(&self, index: i32, k: LuaString) { + (LUA_SHARED.lua_getfield)(*self, index, k) + } + + #[inline] + pub unsafe fn push_boolean(&self, boolean: bool) { + (LUA_SHARED.lua_pushboolean)(*self, if boolean { 1 } else { 0 }) + } + + #[inline] + pub unsafe fn push_integer(&self, int: LuaInt) { + (LUA_SHARED.lua_pushinteger)(*self, int) + } + + #[inline] + pub unsafe fn push_number(&self, num: LuaNumber) { + (LUA_SHARED.lua_pushnumber)(*self, num) + } + + #[inline] + pub unsafe fn push_nil(&self) { + (LUA_SHARED.lua_pushnil)(*self) + } + + #[inline] + pub unsafe fn pcall(&self, nargs: i32, nresults: i32, errfunc: i32) -> i32 { + (LUA_SHARED.lua_pcall)(*self, nargs, nresults, errfunc) + } + + pub unsafe fn load_string(&self, src: LuaString) -> Result<(), LuaError> { + let lua_error_code = (LUA_SHARED.lual_loadstring)(*self, src); + if lua_error_code == 0 { + Ok(()) + } else { + Err(LuaError::from_lua_state(*self, lua_error_code)) + } + } + + pub unsafe fn load_file(&self, path: LuaString) -> Result<(), LuaError> { + let lua_error_code = (LUA_SHARED.lual_loadfile)(*self, path); + if lua_error_code == 0 { + Ok(()) + } else { + Err(LuaError::from_lua_state(*self, lua_error_code)) + } + } + + #[inline] + pub unsafe fn pop(&self) { + self.pop_n(1); + } + + #[inline] + pub unsafe fn pop_n(&self, count: i32) { + self.set_top(-count - 1); + } + + #[inline] + pub unsafe fn set_top(&self, index: i32) { + (LUA_SHARED.lua_settop)(*self, index) + } + + #[inline] + pub unsafe fn lua_type(&self, index: i32) -> i32 { + (LUA_SHARED.lua_type)(*self, index) + } + + pub unsafe fn lua_type_name(&self, lua_type_id: i32) -> Cow<'_, str> { + let type_str_ptr = (LUA_SHARED.lua_typename)(*self, lua_type_id); + let type_str = std::ffi::CStr::from_ptr(type_str_ptr); + type_str.to_string_lossy() + } + + #[inline] + pub unsafe fn replace(&self, index: i32) { + (LUA_SHARED.lua_replace)(*self, index) + } + + #[inline] + pub unsafe fn push_globals(&self) { + (LUA_SHARED.lua_pushvalue)(*self, LUA_GLOBALSINDEX) + } + + #[inline] + pub unsafe fn push_string(&self, data: &str) { + (LUA_SHARED.lua_pushlstring)(*self, data.as_ptr() as LuaString, data.len()) + } + + #[inline] + pub unsafe fn push_binary_string(&self, data: &[u8]) { + (LUA_SHARED.lua_pushlstring)(*self, data.as_ptr() as LuaString, data.len()) + } + + #[inline] + pub unsafe fn push_function(&self, func: LuaFunction) { + (LUA_SHARED.lua_pushcclosure)(*self, func, 0) + } + + #[inline] + pub unsafe fn set_table(&self, index: i32) { + (LUA_SHARED.lua_settable)(*self, index) + } + + #[inline] + pub unsafe fn set_field(&self, index: i32, k: LuaString) { + (LUA_SHARED.lua_setfield)(*self, index, k) + } + + #[inline] + pub unsafe fn get_global(&self, name: LuaString) { + (LUA_SHARED.lua_getfield)(*self, LUA_GLOBALSINDEX, name) + } + + #[inline] + pub unsafe fn set_global(&self, name: LuaString) { + (LUA_SHARED.lua_setfield)(*self, LUA_GLOBALSINDEX, name) + } + + #[inline] + pub unsafe fn call(&self, nargs: i32, nresults: i32) { + (LUA_SHARED.lua_call)(*self, nargs, nresults) + } + + #[inline] + pub unsafe fn insert(&self, index: i32) { + (LUA_SHARED.lua_insert)(*self, index) + } + + /// Creates a new table and pushes it to the stack. + /// seq_n is a hint as to how many sequential elements the table may have. + /// hash_n is a hint as to how many non-sequential/hashed elements the table may have. + /// Lua may use these hints to preallocate memory. + #[inline] + pub unsafe fn create_table(&self, seq_n: i32, hash_n: i32) { + (LUA_SHARED.lua_createtable)(*self, seq_n, hash_n) + } + + /// Creates a new table and pushes it to the stack without memory preallocation hints. + /// Equivalent to `create_table(0, 0)` + #[inline] + pub unsafe fn new_table(&self) { + (LUA_SHARED.lua_createtable)(*self, 0, 0) + } + + #[inline] + pub unsafe fn get_table(&self, index: i32) { + (LUA_SHARED.lua_gettable)(*self, index) + } + + pub unsafe fn check_binary_string(&self, arg: i32) -> &[u8] { + let mut len: usize = 0; + let ptr = (LUA_SHARED.lual_checklstring)(*self, arg, &mut len); + std::slice::from_raw_parts(ptr as *const u8, len) + } + + pub unsafe fn check_string(&self, arg: i32) -> Cow<'_, str> { + let mut len: usize = 0; + let ptr = (LUA_SHARED.lual_checklstring)(*self, arg, &mut len); + String::from_utf8_lossy(std::slice::from_raw_parts(ptr as *const u8, len)) + } + + #[inline] + pub unsafe fn check_userdata(&self, arg: i32, name: LuaString) -> *const c_void { + (LUA_SHARED.lual_checkudata)(*self, arg, name) + } + + pub unsafe fn test_userdata(&self, index: i32, name: LuaString) -> bool { + if !(LUA_SHARED.lua_touserdata)(*self, index).is_null() { + if self.get_metatable(index) != 0 { + self.get_field(LUA_REGISTRYINDEX, name); + let result = self.raw_equal(-1, -2); + self.pop_n(2); + if result { + return true; + } + } + } + false + } + + #[inline] + pub unsafe fn raw_equal(&self, a: i32, b: i32) -> bool { + (LUA_SHARED.lua_rawequal)(*self, a, b) == 1 + } + + #[inline] + pub unsafe fn get_metatable(&self, index: i32) -> i32 { + (LUA_SHARED.lua_getmetatable)(*self, index) + } + + #[inline] + pub unsafe fn check_integer(&self, arg: i32) -> LuaInt { + (LUA_SHARED.lual_checkinteger)(*self, arg) + } + + #[inline] + pub unsafe fn check_number(&self, arg: i32) -> f64 { + (LUA_SHARED.lual_checknumber)(*self, arg) + } + + #[inline] + pub unsafe fn to_integer(&self, index: i32) -> LuaInt { + (LUA_SHARED.lua_tointeger)(*self, index) + } + + #[inline] + pub unsafe fn to_number(&self, index: i32) -> f64 { + (LUA_SHARED.lua_tonumber)(*self, index) + } + + #[inline] + pub unsafe fn get_boolean(&self, index: i32) -> bool { + (LUA_SHARED.lua_toboolean)(*self, index) == 1 + } + + #[inline] + pub unsafe fn set_metatable(&self, index: i32) -> i32 { + (LUA_SHARED.lua_setmetatable)(*self, index) + } + + #[inline] + pub unsafe fn len(&self, index: i32) -> i32 { + (LUA_SHARED.lua_objlen)(*self, index) + } + + #[inline] + pub unsafe fn raw_geti(&self, t: i32, index: i32) { + (LUA_SHARED.lua_rawgeti)(*self, t, index) + } + + #[inline] + pub unsafe fn raw_seti(&self, t: i32, index: i32) { + (LUA_SHARED.lua_rawseti)(*self, t, index) + } + + #[inline] + pub unsafe fn next(&self, index: i32) -> i32 { + (LUA_SHARED.lua_next)(*self, index) + } + + pub unsafe fn error>(&self, msg: S) -> ! { + self.push_string(msg.as_ref()); + (LUA_SHARED.lua_error)(*self); + unreachable!() + } + + pub unsafe fn debug_get_info(&self, what: LuaString) -> Option { + let mut ar = MaybeUninit::uninit(); + if (LUA_SHARED.lua_getinfo)(*self, what, ar.as_mut_ptr()) != 0 { + Some(ar.assume_init()) + } else { + None + } + } + + pub unsafe fn debug_get_invocation_info(&self, level: i32, what: LuaString) -> Option { + let mut ar = MaybeUninit::uninit(); + if (LUA_SHARED.lua_getstack)(*self, level, ar.as_mut_ptr()) != 0 { + if (LUA_SHARED.lua_getinfo)(*self, what, ar.as_mut_ptr()) != 0 { + return Some(ar.assume_init()); + } + } + None + } + + #[cfg(debug_assertions)] + pub unsafe fn dump_stack(&self) { + let top = self.get_top(); + println!("\n=== STACK DUMP ==="); + println!("Stack size: {}", top); + for i in 1..=top { + let lua_type = self.lua_type(i); + let lua_type_name = self.lua_type_name(lua_type); + match lua_type_name.as_ref() { + "string" => println!("{}. {}: {:?}", i, lua_type_name, { + self.push_value(i); + let str = self.get_string(-1); + self.pop(); + str + }), + _ => println!("{}. {}", i, lua_type_name), + } + } + println!(); + } + + #[cfg(debug_assertions)] + pub unsafe fn dump_val(&self, index: i32) -> String { + let lua_type_name = self.lua_type_name(self.lua_type(index)); + match lua_type_name.as_ref() { + "string" => { + self.push_value(index); + let str = self.get_string(-1); + self.pop(); + format!("{:?}", str.unwrap().into_owned()) + }, + "boolean" => { + self.push_value(index); + let boolean = self.get_boolean(index); + self.pop(); + format!("{}", boolean) + }, + "number" => { + self.push_value(index); + let n = self.to_number(index); + self.pop(); + format!("{}", n) + }, + _ => lua_type_name.into_owned(), + } + } +} +impl std::ops::Deref for LuaState { + type Target = *mut std::ffi::c_void; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct LuaDebug { + pub event: i32, + pub name: LuaString, + pub namewhat: LuaString, + pub what: LuaString, + pub source: LuaString, + pub currentline: i32, + pub nups: i32, + pub linedefined: i32, + pub lastlinedefined: i32, + pub short_src: [std::os::raw::c_char; LUA_IDSIZE], + i_ci: i32 +} +impl std::fmt::Debug for LuaDebug { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + unsafe { + f.debug_struct("LuaDebug") + .field("event", &self.event) + .field("name", &std::ffi::CStr::from_ptr(self.name)) + .field("namewhat", &std::ffi::CStr::from_ptr(self.namewhat)) + .field("what", &std::ffi::CStr::from_ptr(self.what)) + .field("source", &std::ffi::CStr::from_ptr(self.source)) + .field("currentline", &self.currentline) + .field("nups", &self.nups) + .field("linedefined", &self.linedefined) + .field("lastlinedefined", &self.lastlinedefined) + .field("short_src", &std::ffi::CStr::from_ptr(self.short_src.as_ptr())) + .field("i_ci", &self.i_ci) + .finish() + } + } +} diff --git a/gmod/src/lua/mod.rs b/gmod/src/lua/mod.rs new file mode 100644 index 0000000..66c898e --- /dev/null +++ b/gmod/src/lua/mod.rs @@ -0,0 +1,46 @@ +#![allow(unused)] + +mod import; +pub use import::*; + +mod lua_state; +pub use lua_state::LuaState as State; + +#[derive(Debug, Clone)] +pub enum LuaError { + /// Out of memory + /// + /// `LUA_ERRMEM` + MemoryAllocationError, + + /// A syntax error occurred in the passed Lua source code. + /// + /// `LUA_ERRSYNTAX` + SyntaxError(Option), + + /// Lua failed to load the given file. + /// + /// `LUA_ERRFILE` + FileError(Option), + + /// A runtime error occurred. + /// + /// `LUA_ERRRUN` + RuntimeError(Option), + + /// An error occurred while running the error handler function. + /// + /// `LUA_ERRERR` + ErrorHandlerError, + + /// Unknown Lua error code + Unknown(i32), +} + +/// Converts a string literal to a Lua-compatible NUL terminated string at compile time. +#[macro_export] +macro_rules! lua_string { + ( $str:literal ) => { + ::gmod::cstr::cstr!($str).as_ptr() + }; +} diff --git a/gmod/src/msgc.rs b/gmod/src/msgc.rs new file mode 100644 index 0000000..2099536 --- /dev/null +++ b/gmod/src/msgc.rs @@ -0,0 +1,81 @@ +#![allow(non_upper_case_globals)] + +use std::os::raw::c_char; + +#[inline] +pub fn printf_escape>(str: S) -> String { + str.as_ref().replace('\\', "\\\\").replace('%', "%%") +} + +#[repr(C)] +pub struct Color { + r: u8, + g: u8, + b: u8, + a: u8 +} +impl Color { + #[inline] + pub const fn new(r: u8, g: u8, b: u8) -> Color { + Color { r, g, b, a: 255 } + } +} + +lazy_static::lazy_static! { + pub static ref ConColorMsg: libloading::Symbol<'static, unsafe extern "C" fn(&Color, *const c_char, ...)> = unsafe { + #[cfg(all(target_os = "windows", target_pointer_width = "64"))] + let lib = libloading::Library::new("bin/win64/tier0.dll").expect("Failed to open tier0.dll"); + + #[cfg(all(target_os = "windows", target_pointer_width = "32"))] + let lib = libloading::Library::new("bin/tier0.dll").or_else(|_| libloading::Library::new("bin/win32/tier0.dll")).expect("Failed to open tier0.dll"); + + #[cfg(all(target_os = "linux", target_pointer_width = "64"))] + let lib = libloading::Library::new("bin/linux64/libtier0.so").expect("Failed to open libtier0.so"); + + #[cfg(all(target_os = "linux", target_pointer_width = "32"))] + let lib = libloading::Library::new("bin/libtier0_srv.so").or_else(|_| libloading::Library::new("bin/linux32/libtier0.so")).expect("Failed to open libtier0.so"); + + let lib = Box::leak(Box::new(lib)); + { + #[cfg(all(target_os = "windows", target_pointer_width = "64"))] { + lib.get(b"?ConColorMsg@@YAXAEBVColor@@PEBDZZ\0") + } + #[cfg(all(target_os = "windows", target_pointer_width = "32"))] { + match lib.get(b"?ConColorMsg@@YAXABVColor@@PBDZZ\0") { + Ok(symbol) => Ok(symbol), + Err(_) => lib.get(b"?ConColorMsg@@YAXABVColor@@PBDZZ\0") + } + } + #[cfg(all(target_os = "linux", target_pointer_width = "64"))] { + lib.get(b"_Z11ConColorMsgRK5ColorPKcz\0") + } + #[cfg(all(target_os = "linux", target_pointer_width = "32"))] { + match lib.get(b"_Z11ConColorMsgRK5ColorPKcz\0") { + Ok(symbol) => Ok(symbol), + Err(_) => lib.get(b"_Z11ConColorMsgRK5ColorPKcz\0") + } + } + } + .expect("Failed to get ConColorMsg") + }; +} +#[macro_export] +macro_rules! colormsg { + ($($($arg:expr)+),+) => { + $(::gmod::msgc::colormsg!(@print $($arg)+));+ + }; + + (@print [$r:literal, $g:literal, $b:literal] $fmt:literal % ($($arg:tt),+)) => { + ::gmod::msgc::ConColorMsg( + &::gmod::msgc::Color::new($r, $g, $b), + ::gmod::msgc::printf_escape(format!(concat!($fmt, '\0'), $($arg),+)).as_ptr() as *const _, + ) + }; + + (@print [$r:literal, $g:literal, $b:literal] $str:literal) => { + ::gmod::msgc::ConColorMsg( + &::gmod::msgc::Color::new($r, $g, $b), + ::gmod::msgc::printf_escape(concat!($str, '\0')).as_ptr() as *const _, + ) + }; +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file