diff --git a/Cargo.lock b/Cargo.lock index a44b1df..9da013c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ dependencies = [ "fn_abi", "fn_has_this", "fn_type_alias", - "gmod-macros 1.0.1", + "gmod-macros 1.0.2", "gmserverplugin", "lazy_static", "libloading", @@ -165,7 +165,7 @@ dependencies = [ [[package]] name = "gmod-macros" -version = "1.0.1" +version = "1.0.2" dependencies = [ "proc-macro2", "quote", diff --git a/gmod-macros/Cargo.toml b/gmod-macros/Cargo.toml index 62d26a3..555b8c9 100644 --- a/gmod-macros/Cargo.toml +++ b/gmod-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gmod-macros" -version = "1.0.1" +version = "1.0.2" authors = ["William Venner "] edition = "2018" license = "MIT" diff --git a/gmod-macros/src/lib.rs b/gmod-macros/src/lib.rs index 3b2fb59..d1a96fc 100644 --- a/gmod-macros/src/lib.rs +++ b/gmod-macros/src/lib.rs @@ -6,7 +6,7 @@ extern crate quote; use proc_macro::TokenStream; use quote::ToTokens; -use syn::{ReturnType, ItemFn}; +use syn::{ReturnType, ItemFn, Attribute}; fn check_lua_function(input: &mut ItemFn) { assert!(input.sig.asyncness.is_none(), "Cannot be async"); @@ -17,11 +17,22 @@ fn check_lua_function(input: &mut ItemFn) { input.sig.abi = Some(syn::parse_quote!(extern "C-unwind")); } +fn no_mangle(item_fn: &mut ItemFn) { + item_fn.attrs.push(Attribute { + path: parse_quote!(no_mangle), + tokens: proc_macro2::TokenStream::new(), + style: syn::AttrStyle::Outer, + pound_token: Default::default(), + bracket_token: Default::default(), + }); +} + #[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); input.block.stmts.insert(0, syn::parse2(quote!(#[allow(unused_unsafe)] unsafe { ::gmod::lua::load() })).unwrap()); + no_mangle(&mut input); input.into_token_stream().into() } @@ -29,6 +40,7 @@ pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream { pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream { let mut input = parse_macro_input!(tokens as ItemFn); check_lua_function(&mut input); + no_mangle(&mut input); input.into_token_stream().into() } diff --git a/gmod/Cargo.toml b/gmod/Cargo.toml index 10f7315..ec67a06 100644 --- a/gmod/Cargo.toml +++ b/gmod/Cargo.toml @@ -13,9 +13,10 @@ categories = ["api-bindings", "external-ffi-bindings", "game-development", "deve default = ["hax"] hax = ["ctor", "skidscan", "detour", "fn_type_alias", "fn_abi", "cfg_table", "null_fn", "fn_has_this"] server-plugin = ["gmserverplugin"] +gmcl = [] [dependencies] -gmod-macros = { version = "1.0.1", path = "../gmod-macros" } +gmod-macros = { version = "1.0.2", path = "../gmod-macros" } gmserverplugin = { version = "1", optional = true } libloading = "0" diff --git a/gmod/src/gmcl.rs b/gmod/src/gmcl.rs new file mode 100644 index 0000000..daab00f --- /dev/null +++ b/gmod/src/gmcl.rs @@ -0,0 +1,51 @@ +use std::{sync::{Arc, Mutex, TryLockError}, time::Duration, os::raw::c_char}; + +/// This function will **permanently** redirect stdout to the client console. +/// +/// This allows for `println!()` and friends to print to the client console. +pub fn override_stdout() { + unsafe { + let (_lib, _path) = crate::open_library!("tier0").expect("Failed to open tier0.dll"); + + #[allow(non_snake_case)] + let ConMsg: extern "C" fn(*const c_char, ...) = *{ + #[cfg(target_os = "windows")] { + _lib.get({ + #[cfg(all(target_os = "windows", target_pointer_width = "64"))] { + b"?ConMsg@@YAXPEBDZZ\0" + } + #[cfg(all(target_os = "windows", target_pointer_width = "32"))] { + b"?ConMsg@@YAXPBDZZ\0" + } + }) + } + #[cfg(target_os = "linux")] { + _lib.get(b"ConMsg\0").or_else(|_| _lib.get(b"_Z6ConMsgPKcz\0")) + } + }.expect("Failed to find ConMsg"); + + let output_buf = Arc::new(Mutex::new(Vec::new())); + let output_buf_ref = output_buf.clone(); + + // This is actually a really dumb implementation, but appears to be the only way, unfortunately. + std::thread::spawn(move || loop { + match output_buf.try_lock() { + Ok(mut data) => if !data.is_empty() { + data.push(0); // cheeky + ConMsg(data.as_ptr() as *const i8); + + data.truncate(0); + }, + Err(TryLockError::Poisoned(err)) => panic!("{}", err), + Err(TryLockError::WouldBlock) => { + std::hint::spin_loop(); + std::thread::yield_now(); + continue + } + } + std::thread::sleep(Duration::from_millis(250)); + }); + + std::io::set_output_capture(Some(output_buf_ref)); + }; +} \ No newline at end of file diff --git a/gmod/src/lib.rs b/gmod/src/lib.rs index 42ac275..d72182b 100644 --- a/gmod/src/lib.rs +++ b/gmod/src/lib.rs @@ -1,6 +1,8 @@ #![feature(c_unwind)] #![feature(thread_id_value)] +#![cfg_attr(feature = "gmcl", feature(internal_output_capture))] + pub use cstr; pub use libloading; pub use gmod_macros::*; @@ -38,6 +40,10 @@ pub mod userdata; /// Net library helpers pub mod net; +/// Clientside module helpers +#[cfg(feature = "gmcl")] +pub mod gmcl; + /// Returns whether this client is running the x86-64 branch pub fn is_x86_64() -> bool { #[cfg(target_pointer_width = "64")] { diff --git a/gmod/src/lua/import.rs b/gmod/src/lua/import.rs index 40b09c3..315a756 100644 --- a/gmod/src/lua/import.rs +++ b/gmod/src/lua/import.rs @@ -74,10 +74,10 @@ impl LuaError { } #[cfg_attr(not(debug_assertions), repr(transparent))] -pub struct LuaSharedInterface(UnsafeCell<*mut LuaShared>, #[cfg(debug_assertions)] AtomicI64); +pub struct LuaSharedInterface(pub(crate) UnsafeCell<*mut LuaShared>, #[cfg(debug_assertions)] AtomicI64); impl LuaSharedInterface { #[cfg(debug_assertions)] - fn debug_assertions(&self) { + pub(crate) fn debug_assertions(&self) { 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 load the Lua state with `gmod::load_lua_state()` or `gmod::set_lua_state(*mut c_void)`"); let thread_id = u64::from(std::thread::current().id().as_u64()) as i64;