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_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",

View File

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

View File

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

View File

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

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(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")] {

View File

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