From 6a4bef69dd61e3e345d6f3b7ac8a0a15581c13a7 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Fri, 10 Feb 2023 01:21:58 -0800 Subject: [PATCH] remote api: implement rcon --- rust/src/adapters/actual/game.rs | 24 ++++++++++++++++++++---- rust/src/adapters/mock/game.rs | 2 +- rust/src/lib.rs | 23 ++++++++++++++++++++++- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/rust/src/adapters/actual/game.rs b/rust/src/adapters/actual/game.rs index 774e37adb..85474a7c5 100644 --- a/rust/src/adapters/actual/game.rs +++ b/rust/src/adapters/actual/game.rs @@ -1,6 +1,7 @@ -use super::raw_bindings::{cmd_source_t_src_command, svs, Cmd_ExecuteString}; +use super::raw_bindings::{cmd_source_t_src_command, svs, Cmd_ExecuteString, Con_Redirect}; use crate::tracing_init; use once_cell::sync::Lazy; +use std::cell::RefCell; use std::ffi::CString; use tokio::sync::mpsc; use tokio::sync::mpsc::error::{SendError, TryRecvError}; @@ -12,6 +13,10 @@ static mut FRAME_QUEUE: Lazy<(mpsc::Sender, mpsc::Receiver> = Lazy::new(|| unsafe { &FRAME_QUEUE.0 }); +thread_local! { + static REDIRECTED_CONSOLE_OUTPUT: RefCell> = RefCell::default(); +} + #[no_mangle] pub unsafe extern "C" fn Rust_Frame() { let mut game = Game { _field: () }; @@ -120,13 +125,24 @@ impl Game { } } - #[allow(dead_code)] - pub fn rcon(&mut self, cmd: &str) { - // TODO return output string + // TODO create rcon_tab_autocomplete function that returns a list of possible completions + pub fn rcon(&mut self, cmd: &str) -> String { + unsafe extern "C" fn rcon_redirect(s: *const std::ffi::c_char) { + let s = unsafe { std::ffi::CStr::from_ptr(s) }; + REDIRECTED_CONSOLE_OUTPUT.with(|r| { + r.borrow_mut().push(s.to_string_lossy().into_owned()); + }); + } + + assert!(REDIRECTED_CONSOLE_OUTPUT.with(|r| r.borrow().is_empty())); + let cmd = CString::new(cmd).unwrap(); unsafe { + Con_Redirect(Some(rcon_redirect)); Cmd_ExecuteString(cmd.as_ptr(), cmd_source_t_src_command); + Con_Redirect(None); } + REDIRECTED_CONSOLE_OUTPUT.with(|r| r.take()).concat() } } diff --git a/rust/src/adapters/mock/game.rs b/rust/src/adapters/mock/game.rs index 5abb26486..7fe60ae64 100644 --- a/rust/src/adapters/mock/game.rs +++ b/rust/src/adapters/mock/game.rs @@ -32,7 +32,7 @@ impl Game { pub fn set_player_health(&mut self, health: f32) {} - pub fn rcon(&mut self, cmd: &str) { + pub fn rcon(&mut self, cmd: &str) -> String { unimplemented!(); } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b0d822269..e429abfe3 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -11,7 +11,7 @@ use adapters::{ }; use axum::{ extract, - routing::{get, patch}, + routing::{get, patch, post}, Json, Router, }; use axum_helpers::QuakeAPIResponseError; @@ -108,6 +108,7 @@ fn create_app(is_unix_socket: bool) -> Router { let mut app = Router::new() .route("/", get(root)) .route("/entities", get(entities)) + .route("/rcon", post(rcon)) .route("/player", get(player)) .route("/player", patch(patch_player)) .layer( @@ -141,6 +142,16 @@ struct PatchPlayerEntity { health: Option, } +#[derive(Deserialize, Debug, Clone, PartialEq)] +struct PostRconRequestBody { + command: String, +} + +#[derive(Serialize, Debug, Clone, PartialEq)] +struct PostRconResponse { + output: String, +} + async fn player() -> Result>, QuakeAPIResponseError> { let player_health = adapters::game::Game::run_in_game_thread_with_result(|game| game.player_health()).await?; @@ -164,3 +175,13 @@ async fn patch_player( async fn entities() -> &'static str { "TODO all entities in JSON form" } + +async fn rcon( + extract::Json(body): extract::Json, +) -> Result, QuakeAPIResponseError> { + let output = adapters::game::Game::run_in_game_thread_mut_with_result(move |game| { + game.rcon(&body.command) + }) + .await?; + Ok(Json(PostRconResponse { output })) +}