Add method of overriding stdout to client console for gmcl modules

This commit is contained in:
William Venner 2021-12-09 19:32:56 +00:00
parent a1f4f9cbac
commit b2861e3e4d
7 changed files with 77 additions and 7 deletions

4
Cargo.lock generated
View File

@ -144,7 +144,7 @@ dependencies = [
"fn_abi", "fn_abi",
"fn_has_this", "fn_has_this",
"fn_type_alias", "fn_type_alias",
"gmod-macros 1.0.1", "gmod-macros 1.0.2",
"gmserverplugin", "gmserverplugin",
"lazy_static", "lazy_static",
"libloading", "libloading",
@ -165,7 +165,7 @@ dependencies = [
[[package]] [[package]]
name = "gmod-macros" name = "gmod-macros"
version = "1.0.1" version = "1.0.2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "gmod-macros" name = "gmod-macros"
version = "1.0.1" version = "1.0.2"
authors = ["William Venner <william@venner.io>"] authors = ["William Venner <william@venner.io>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"

View File

@ -6,7 +6,7 @@ extern crate quote;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use syn::{ReturnType, ItemFn}; use syn::{ReturnType, ItemFn, Attribute};
fn check_lua_function(input: &mut ItemFn) { fn check_lua_function(input: &mut ItemFn) {
assert!(input.sig.asyncness.is_none(), "Cannot be async"); 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")); 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] #[proc_macro_attribute]
pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream { pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(tokens as ItemFn); let mut input = parse_macro_input!(tokens as ItemFn);
check_lua_function(&mut input); check_lua_function(&mut input);
input.block.stmts.insert(0, syn::parse2(quote!(#[allow(unused_unsafe)] unsafe { ::gmod::lua::load() })).unwrap()); 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() 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 { pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(tokens as ItemFn); let mut input = parse_macro_input!(tokens as ItemFn);
check_lua_function(&mut input); check_lua_function(&mut input);
no_mangle(&mut input);
input.into_token_stream().into() input.into_token_stream().into()
} }

View File

@ -13,9 +13,10 @@ categories = ["api-bindings", "external-ffi-bindings", "game-development", "deve
default = ["hax"] default = ["hax"]
hax = ["ctor", "skidscan", "detour", "fn_type_alias", "fn_abi", "cfg_table", "null_fn", "fn_has_this"] hax = ["ctor", "skidscan", "detour", "fn_type_alias", "fn_abi", "cfg_table", "null_fn", "fn_has_this"]
server-plugin = ["gmserverplugin"] server-plugin = ["gmserverplugin"]
gmcl = []
[dependencies] [dependencies]
gmod-macros = { version = "1.0.1", path = "../gmod-macros" } gmod-macros = { version = "1.0.2", path = "../gmod-macros" }
gmserverplugin = { version = "1", optional = true } gmserverplugin = { version = "1", optional = true }
libloading = "0" libloading = "0"

51
gmod/src/gmcl.rs Normal file
View File

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

View File

@ -1,6 +1,8 @@
#![feature(c_unwind)] #![feature(c_unwind)]
#![feature(thread_id_value)] #![feature(thread_id_value)]
#![cfg_attr(feature = "gmcl", feature(internal_output_capture))]
pub use cstr; pub use cstr;
pub use libloading; pub use libloading;
pub use gmod_macros::*; pub use gmod_macros::*;
@ -38,6 +40,10 @@ pub mod userdata;
/// Net library helpers /// Net library helpers
pub mod net; pub mod net;
/// Clientside module helpers
#[cfg(feature = "gmcl")]
pub mod gmcl;
/// Returns whether this client is running the x86-64 branch /// Returns whether this client is running the x86-64 branch
pub fn is_x86_64() -> bool { pub fn is_x86_64() -> bool {
#[cfg(target_pointer_width = "64")] { #[cfg(target_pointer_width = "64")] {

View File

@ -74,10 +74,10 @@ impl LuaError {
} }
#[cfg_attr(not(debug_assertions), repr(transparent))] #[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 { impl LuaSharedInterface {
#[cfg(debug_assertions)] #[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)`"); 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; let thread_id = u64::from(std::thread::current().id().as_u64()) as i64;