From 2b5b6f190d1c75c894ece853e43fcd48fb7025dc Mon Sep 17 00:00:00 2001 From: restitux Date: Fri, 8 Aug 2025 00:17:56 -0600 Subject: [PATCH] backend: handle app already running logic + refactor response --- Cargo.lock | 1 + gamestream-webtransport-proxy/Cargo.toml | 1 + gamestream-webtransport-proxy/src/apps.rs | 37 +++++++++- gamestream-webtransport-proxy/src/main.rs | 1 + .../src/responses.rs | 23 ++++++ gamestream-webtransport-proxy/src/stream.rs | 74 +++++++++++-------- 6 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 gamestream-webtransport-proxy/src/responses.rs diff --git a/Cargo.lock b/Cargo.lock index cdd1719..5680d85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -671,6 +671,7 @@ dependencies = [ "getrandom 0.3.3", "hex", "hmac-sha256", + "http", "libc", "moonlight-common-c-sys", "openssl", diff --git a/gamestream-webtransport-proxy/Cargo.toml b/gamestream-webtransport-proxy/Cargo.toml index c7ffab8..08c7252 100644 --- a/gamestream-webtransport-proxy/Cargo.toml +++ b/gamestream-webtransport-proxy/Cargo.toml @@ -9,6 +9,7 @@ directories = "6.0.0" getrandom = { version = "0.3.3", features = ["std"] } hex = "0.4.3" hmac-sha256 = "1.1.12" +http = "1.3.1" libc = "0.2.174" moonlight-common-c-sys = { path = "../moonlight-common-c-sys" } openssl = "0.10.73" diff --git a/gamestream-webtransport-proxy/src/apps.rs b/gamestream-webtransport-proxy/src/apps.rs index f2fa328..ed7500f 100644 --- a/gamestream-webtransport-proxy/src/apps.rs +++ b/gamestream-webtransport-proxy/src/apps.rs @@ -4,7 +4,12 @@ use salvo::prelude::*; use serde::{Deserialize, Serialize}; use tracing::{debug, error}; -use crate::{common::AppResult, state::StateReader}; +use crate::{ + common, + common::{AppError, AppResult}, + responses, + state::StateReader, +}; #[derive(Deserialize)] struct AppListRespApp { @@ -29,6 +34,7 @@ struct App { title: String, id: u64, hdr_supported: bool, + active: bool, } #[derive(Debug, Serialize, ToSchema)] @@ -40,7 +46,7 @@ struct GetAppsResponse { impl crate::backend::Backend { #[craft(endpoint(status_codes(StatusCode::OK, StatusCode::INTERNAL_SERVER_ERROR)))] pub async fn get_apps(self: ::std::sync::Arc) -> AppResult> { - let standard_error = Err(crate::common::AppError { + let standard_error = Err(AppError { status_code: StatusCode::INTERNAL_SERVER_ERROR, description: "failed to get available apps".to_string(), }); @@ -67,7 +73,29 @@ impl crate::backend::Backend { }; for (_, server) in servers.into_iter() { - let mut base_url = crate::common::base_url( + let mut server_info_base_url = common::base_url( + "https", + &server.host, + server.https_port(), + &unique_id, + "serverinfo", + None, + ); + + let server_info = common::get_url(&mut server_info_base_url, true) + .await + .unwrap(); + debug!("server_info: {server_info}"); + let server_info: responses::ServerInfoResponse = + match serde_xml_rs::from_str(&server_info) { + Ok(s) => s, + Err(e) => { + error!("Could not parse serverinfo response: {e}"); + return standard_error; + } + }; + + let mut base_url = common::base_url( "https", &server.host, server.https_port(), @@ -76,7 +104,7 @@ impl crate::backend::Backend { None, ); - let resp = match crate::common::get_url(&mut base_url, true).await { + let resp = match common::get_url(&mut base_url, true).await { Ok(r) => r, Err(e) => { error!("could not get applist from server {}: {}", server.name, e); @@ -103,6 +131,7 @@ impl crate::backend::Backend { title: a.app_title, hdr_supported: a.is_hdr_supported, id: a.id, + active: a.id == server_info.currentgame, }) .rev() .collect(); diff --git a/gamestream-webtransport-proxy/src/main.rs b/gamestream-webtransport-proxy/src/main.rs index c19504c..98552f9 100644 --- a/gamestream-webtransport-proxy/src/main.rs +++ b/gamestream-webtransport-proxy/src/main.rs @@ -9,6 +9,7 @@ mod common; mod gamestream; mod pair; mod proxy; +mod responses; mod state; mod stream; diff --git a/gamestream-webtransport-proxy/src/responses.rs b/gamestream-webtransport-proxy/src/responses.rs new file mode 100644 index 0000000..6ae5ebe --- /dev/null +++ b/gamestream-webtransport-proxy/src/responses.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct ServerInfoResponse { + pub hostname: String, + pub appversion: String, + pub GfeVersion: String, + pub uniqueid: uuid::Uuid, + pub HttpsPort: u16, + pub ExternalPort: u16, + pub MaxLumaPixelsHEVC: u64, + pub mac: String, + //pub ServerCommand: String, + //pub Permission: u64, + //pub VirtualDisplayCapable: bool, + //pub VirtualDisplayDriverReady: bool, + pub LocalIP: String, + pub ServerCodecModeSupport: i32, + pub PairStatus: u64, + pub currentgame: u64, + pub state: String, +} + diff --git a/gamestream-webtransport-proxy/src/stream.rs b/gamestream-webtransport-proxy/src/stream.rs index 9227e4d..d79ad1e 100644 --- a/gamestream-webtransport-proxy/src/stream.rs +++ b/gamestream-webtransport-proxy/src/stream.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anyhow::{Context, Result}; use moonlight_common_c_sys::SCM_H264; use salvo::prelude::*; @@ -6,7 +8,7 @@ use tracing::{debug, error, info}; use crate::{ common::{AppError, AppResult, get_url}, - proxy, + proxy, responses, state::{GamestreamServer, StateReadAccess, StateReader}, }; @@ -25,33 +27,16 @@ struct PostStreamStartResponse { //cert_hash: String, } -#[derive(Deserialize)] -struct ServerInfoResponse { - hostname: String, - appversion: String, - GfeVersion: String, - uniqueid: uuid::Uuid, - HttpsPort: u16, - ExternalPort: u16, - MaxLumaPixelsHEVC: u64, - mac: String, - //ServerCommand: String, - //Permission: u64, - //VirtualDisplayCapable: bool, - //VirtualDisplayDriverReady: bool, - LocalIP: String, - ServerCodecModeSupport: i32, - PairStatus: u64, - currentgame: u64, - state: String, -} - #[derive(Deserialize)] struct LaunchResponse { + #[serde(rename = "@status_code")] + status_code: String, + #[serde(rename = "@status_message")] + status_message: Option, #[serde(rename = "sessionUrl0")] - session_url_0: url::Url, + session_url_0: Option, #[serde(rename = "gamesession")] - game_session: u64, + game_session: Option, } fn get_server(reader: &StateReadAccess, server: &String) -> Result> { @@ -108,7 +93,7 @@ impl crate::backend::Backend { Ok(s) => match s { Some(s) => s, None => { - info!("No server with name: {}", body.server); + error!("No server with name: {}", body.server); return Err(AppError { status_code: StatusCode::BAD_REQUEST, description: "No server configured with that name.".to_string(), @@ -139,8 +124,9 @@ impl crate::backend::Backend { ); let server_info = crate::common::get_url(&mut base_url, true).await.unwrap(); - debug!("app_info: {server_info}"); - let server_info: ServerInfoResponse = match serde_xml_rs::from_str(&server_info) { + debug!("server_info: {server_info}"); + let server_info: responses::ServerInfoResponse = match serde_xml_rs::from_str(&server_info) + { Ok(s) => s, Err(e) => { error!("Could not parse serverinfo response: {e}"); @@ -227,6 +213,36 @@ impl crate::backend::Backend { } }; + let Ok(launch_resp_status) = http::StatusCode::from_str(&launch_response.status_code) + else { + error!( + "Server returned invalid status code: {}", + launch_response.status_code + ); + return standard_error; + }; + if !launch_resp_status.is_success() { + let status_message = launch_response + .status_message + .unwrap_or("Server did not provide a status message".to_string()); + + info!("Could not launch game: {}", status_message,); + return Err(AppError { + status_code: StatusCode::INTERNAL_SERVER_ERROR, + description: format!("Could not launch game: {}", status_message), + }); + } + + let Some(url) = launch_response.session_url_0 else { + error!("Server response was missing session_url_0"); + return standard_error; + }; + + let Some(game_session) = launch_response.game_session else { + error!("Server response was missing game_session"); + return standard_error; + }; + let stream_id = uuid::Uuid::new_v4(); let server_codec_mode_support = if server_info.ServerCodecModeSupport == 0 { @@ -237,8 +253,8 @@ impl crate::backend::Backend { let stream = crate::backend::Stream { id: stream_id, - url: launch_response.session_url_0, - game_session: launch_response.game_session, + url, + game_session, server_address: server.host.clone(), stream_config: body.stream_config.clone(), app_version: server_info.appversion,