Initial commit

This commit is contained in:
William Venner 2021-09-09 21:50:58 +01:00
commit 6a3f374c96
15 changed files with 1197 additions and 0 deletions

7
.editorconfig Normal file
View File

@ -0,0 +1,7 @@
root = true
[*]
indent_style = tab
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

270
Cargo.lock generated Normal file
View File

@ -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"

5
Cargo.toml Normal file
View File

@ -0,0 +1,5 @@
[workspace]
members = [
"gmod",
"gmod-macros"
]

21
LICENSE Normal file
View File

@ -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.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# ⚙ gmod-rs
A swiss army knife for creating binary modules for Garry's Mod in Rust.

16
gmod-macros/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "gmod-macros"
version = "0.1.0"
authors = ["William Venner <william@venner.io>"]
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"

39
gmod-macros/src/lib.rs Normal file
View File

@ -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))
}

14
gmod/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "gmod"
version = "0.1.0"
authors = ["William Venner <william@venner.io>"]
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" }

11
gmod/src/lib.rs Normal file
View File

@ -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;

215
gmod/src/lua/import.rs Normal file
View File

@ -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<String> {
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<T>(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")
}
}

466
gmod/src/lua/lua_state.rs Normal file
View File

@ -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<std::borrow::Cow<'_, str>> {
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<S: AsRef<str>>(&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<LuaDebug> {
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<LuaDebug> {
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()
}
}
}

46
gmod/src/lua/mod.rs Normal file
View File

@ -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<String>),
/// Lua failed to load the given file.
///
/// `LUA_ERRFILE`
FileError(Option<String>),
/// A runtime error occurred.
///
/// `LUA_ERRRUN`
RuntimeError(Option<String>),
/// 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()
};
}

81
gmod/src/msgc.rs Normal file
View File

@ -0,0 +1,81 @@
#![allow(non_upper_case_globals)]
use std::os::raw::c_char;
#[inline]
pub fn printf_escape<S: AsRef<str>>(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 _,
)
};
}

2
rust-toolchain.toml Normal file
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"