diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..a626e5c --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +/crates/ @BattleDash +/crates/maxima_ui @headassbtw diff --git a/Cargo.toml b/Cargo.toml index 3fee2fe..7c7d183 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,7 @@ [workspace] resolver = "2" -members = [ - "maxima-bootstrap", - "maxima-cli", - "maxima-lib", - "maxima-native", - "maxima-service", - "maxima-tui", - "maxima-ui", - "maxima-resources", -] +exclude = [ ".gitignore", ".github/*" ] +members = [ "crates/*" ] [profile.release] strip = true diff --git a/README.md b/README.md index f8c8080..1746970 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -# Maxima +# MAXIMA ## A free and open-source replacement for the EA Desktop Launcher ![Logo](images/1500x500.jpg) > [!WARNING] -> Maxima is in beta. Parts of it may not be fully stable, and we're actively fixing bugs and adding features. Please create an [issue](https://github.com/ArmchairDevelopers/Maxima/issues) if you notice strange behavior. +> Maxima is in a very alpha state. Parts of it may not be fully stable, and we are actively fixing bugs and adding features. As such, we recommend backing up save files before using Maxima, just to be safe. Please create an [issue](https://github.com/ArmchairDevelopers/Maxima/issues) if you notice strange behavior. Maxima is an open-source replacement for the EA Desktop/Origin game launcher, running natively on Linux and Windows, with MacOS support in progress. -Maxima itself is a library (`maxima-lib`), with povided CLI (`maxima-cli`), TUI (`maxima-tui`), and GUI (`maxima-ui`) frontends. Other launchers compatible with Maxima's license may implement it as a backend. It's used by our sister project, [KYBER](https://uplink.kyber.gg/news/features-overview). +Maxima itself is a server (`maxima_server`) accessed through a client (`maxima_lib`). This provides support for multiple simultaneous frontends, meaning you can, for example, run the CLI and GUI at the same time, and their states will be synced. + +We, by default, provide CLI (`maxima_cli`), TUI (`maxima_tui`), and GUI (`maxima_ui`) frontends. Other launchers compatible with Maxima's license may implement it as a backend. It's used by our sister project, [KYBER](https://uplink.kyber.gg/news/features-overview). ![UI](images/UI.png) @@ -22,8 +24,7 @@ Maxima itself is a library (`maxima-lib`), with povided CLI (`maxima-cli`), TUI - Playing games installed with EA Desktop on Maxima + vice versa - Displaying your in-game status to your friends, and viewing your friends' status' - Locating games - - Running games under [wine-ge](https://github.com/GloriousEggroll/wine-ge-custom) on Linux/SteamDeck - - `wine-ge` is automatically installed, but base `wine` must already be installed on the system. We're looking into implementing [umu-launcher](https://github.com/Open-Wine-Components/umu-launcher) to fix this. + - Running games under wine ([umu-launcher](https://github.com/Open-Wine-Components/umu-launcher)) on Linux/SteamDeck **In-Dev:** - MacOS support @@ -32,18 +33,18 @@ Maxima itself is a library (`maxima-lib`), with povided CLI (`maxima-cli`), TUI **Planned:** - Library documentation/examples - Support for installing DLCs - - Full EA Desktop interopability. Games installed with EA Desktop already appear on Maxima, but to take it a step further we'd like the ability to, for example, start a download on EA Desktop and continue it on Maxima. + - Full EA Desktop interopability. Games installed with EA Desktop already appear on Maxima, but to take it a step further we'd like the ability to, for example, start a download on EA Desktop and continue it on Maxima - Cleaner/Stabler downloader implementation - Progressive/Selective installs - - Some games are able to start without being fully installed, and some games contain language-specific files. + - Some games are able to start without being fully installed, and some games contain language-specific files - Support for the store (buying games) - Friend Adding/Removing/Inviting - Status setting; locked to "online" at the moment **Unsupported:** - - Battlefield 3/4 are currently unsupported due to how battlelog does game launching. This is on our radar, but isn't a huge priority at the moment. + - Battlefield 3/4 are currently unsupported due to how battlelog does game launching. This is on our radar, but isn't a huge priority at the moment - Please file an issue if you find more games that don't work - - Old games like Dead Space 2 and BFBC2 are unsupported due to being pre-"Download-In-Place" era games. They have a different manifest format which we need to make a parser for. + - Old games like Dead Space 2 and BFBC2 are unsupported due to being pre-"Download-In-Place" era games. They have a different manifest format which we need to make a parser for # CLI Usage `maxima-cli` standalone will launch an interactive CLI mode to install and launch games. @@ -54,5 +55,5 @@ Maxima itself is a library (`maxima-lib`), with povided CLI (`maxima-cli`), TUI It's the farthest you can get from the Origin. ## Maintainers: - - [Sean Kahler](https://github.com/BattleDash) (Lib, Bootstrap, Service) + - [Sean Kahler](https://github.com/BattleDash) (Server, Lib, Proto, Bootstrap) - [Nick Whelan](https://github.com/headassbtw) (UI) diff --git a/maxima-bootstrap/Cargo.toml b/crates/maxima_bootstrap/Cargo.toml similarity index 90% rename from maxima-bootstrap/Cargo.toml rename to crates/maxima_bootstrap/Cargo.toml index b1e426b..d423082 100644 --- a/maxima-bootstrap/Cargo.toml +++ b/crates/maxima_bootstrap/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "maxima-bootstrap" +name = "maxima_bootstrap" description = "Maxima handler for custom EA protocols" version = "0.1.0" authors = ["Sean Kahler "] edition = "2021" [dependencies] -maxima-lib = { path = "../maxima-lib" } +maxima = { path = "../maxima_lib" } tokio = { version = "1.28.2", features = [ "full", "rt", @@ -38,7 +38,7 @@ windows-service = "0.6.0" cacao = "0.3.2" [build-dependencies] -maxima-resources = { path = "../maxima-resources" } +maxima_resources = { path = "../maxima_resources" } [[bin]] name = "maxima-bootstrap" diff --git a/maxima-bootstrap/build.rs b/crates/maxima_bootstrap/build.rs similarity index 100% rename from maxima-bootstrap/build.rs rename to crates/maxima_bootstrap/build.rs diff --git a/maxima-bootstrap/src/macos.rs b/crates/maxima_bootstrap/src/macos.rs similarity index 100% rename from maxima-bootstrap/src/macos.rs rename to crates/maxima_bootstrap/src/macos.rs diff --git a/maxima-bootstrap/src/main.rs b/crates/maxima_bootstrap/src/main.rs similarity index 100% rename from maxima-bootstrap/src/main.rs rename to crates/maxima_bootstrap/src/main.rs diff --git a/maxima-cli/Cargo.toml b/crates/maxima_cli/Cargo.toml similarity index 80% rename from maxima-cli/Cargo.toml rename to crates/maxima_cli/Cargo.toml index 7bbedf6..127fe77 100644 --- a/maxima-cli/Cargo.toml +++ b/crates/maxima_cli/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "maxima-cli" +name = "maxima_cli" version = "0.1.0" authors = ["Sean Kahler "] edition = "2021" [dependencies] -maxima-lib = { path = "../maxima-lib" } +maxima = { path = "../maxima_lib" } clap = { version = "4.3.5", features = [ "derive" ] } tokio = { version = "1.28.2", features = [ "full", "rt", "rt-multi-thread", "time", "net" ] } serde = { version = "1.0.164", features = [ "derive" ] } @@ -24,8 +24,4 @@ winreg = "0.50.0" is_elevated = "0.1.2" [build-dependencies] -maxima-resources = { path = "../maxima-resources" } - -[[bin]] -name = "maxima-cli" -path = "src/main.rs" +maxima_resources = { path = "../maxima_resources" } diff --git a/maxima-cli/build.rs b/crates/maxima_cli/build.rs similarity index 100% rename from maxima-cli/build.rs rename to crates/maxima_cli/build.rs diff --git a/maxima-cli/src/main.rs b/crates/maxima_cli/src/main.rs similarity index 100% rename from maxima-cli/src/main.rs rename to crates/maxima_cli/src/main.rs diff --git a/maxima-lib/Cargo.toml b/crates/maxima_lib/Cargo.toml similarity index 97% rename from maxima-lib/Cargo.toml rename to crates/maxima_lib/Cargo.toml index 2ac1069..bd03b9a 100644 --- a/maxima-lib/Cargo.toml +++ b/crates/maxima_lib/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "maxima-lib" +name = "maxima" version = "1.0.0" edition = "2021" authors = ["Sean Kahler "] @@ -20,6 +20,7 @@ tokio = { version = "1.28.2", features = [ "time", "net", ] } +maxima_proto = { path = "../maxima_proto" } moka = { version = "0.12.1", features = ["sync"] } serde = { version = "1.0.164", features = ["derive"] } serde_json = "1.0.97" diff --git a/crates/maxima_lib/examples/dev.rs b/crates/maxima_lib/examples/dev.rs new file mode 100644 index 0000000..8a55e80 --- /dev/null +++ b/crates/maxima_lib/examples/dev.rs @@ -0,0 +1,3 @@ +fn main() { + +} \ No newline at end of file diff --git a/crates/maxima_lib/src/lib.rs b/crates/maxima_lib/src/lib.rs new file mode 100644 index 0000000..820ef95 --- /dev/null +++ b/crates/maxima_lib/src/lib.rs @@ -0,0 +1 @@ +pub use maxima_proto::models; diff --git a/maxima-native/Cargo.toml b/crates/maxima_native/Cargo.toml similarity index 88% rename from maxima-native/Cargo.toml rename to crates/maxima_native/Cargo.toml index 9415d7b..952e527 100644 --- a/maxima-native/Cargo.toml +++ b/crates/maxima_native/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "maxima-native" +name = "maxima_native" version = "0.1.0" authors = ["Sean Kahler "] edition = "2021" [dependencies] -maxima-lib = { path = "../maxima-lib" } +maxima = { path = "../maxima_lib" } tokio = { version = "1.28.2", features = [ "full", "rt", @@ -43,7 +43,7 @@ widestring = "1.0.2" dll-syringe = "0.15.2" [build-dependencies] -maxima-resources = { path = "../maxima-resources" } +maxima_resources = { path = "../maxima_resources" } cbindgen = "0.25.0" [lib] diff --git a/crates/maxima_native/bindings.h b/crates/maxima_native/bindings.h new file mode 100644 index 0000000..c7106ad --- /dev/null +++ b/crates/maxima_native/bindings.h @@ -0,0 +1,149 @@ +#define ERR_SUCCESS 0 + +#define ERR_UNKNOWN 1 + +#define ERR_CHECK_LE 2 + +#define ERR_LOGIN_FAILED 3 + +#define ERR_INVALID_ARGUMENT 4 + +#define ERR_NOT_LOGGED_IN 5 + +/** + * Get the last error. + */ +const char *maxima_get_last_error(void); + +/** + * Set up Maxima's logging. + */ +uintptr_t maxima_init_logger(void); + +/** + * Create an asynchronous runtime. + */ +uintptr_t maxima_create_runtime(void **runtime_out); + +/** + * Check if the Maxima Background Service is installed and valid. + */ +uintptr_t maxima_is_service_valid(bool *out); + +/** + * Check if the Maxima Background Service is running. + */ +uintptr_t maxima_is_service_running(bool *out); + +/** + * Register the Maxima Background Service. Runs maxima-bootstrap for admin access. + */ +uintptr_t maxima_register_service(void); + +/** + * Start the Maxima Background Service. + */ +uintptr_t maxima_start_service(void **runtime); + +/** + * Stop the Maxima Background Service. + */ +uintptr_t maxima_stop_service(void **runtime); + +/** + * Check if the Windows Registry is properly set up for Maxima. + */ +bool maxima_check_registry_validity(void); + +/** + * Request the Maxima Background Service to set up the Windows Registry. + */ +uintptr_t maxima_request_registry_setup(void **runtime); + +/** + * Log into an EA account and retrieve an access token. Opens the EA website for authentication. + */ +uintptr_t maxima_login(void **runtime, char **token_out); + +/** + * Log into an EA account with a persona (email/username) and password. + */ +uintptr_t maxima_login_manual(void **runtime, void **mx, const char *persona, const char *password); + +/** + * Retrieve the access token for the currently selected account. Can return [ERR_NOT_LOGGED_IN] + */ +uintptr_t maxima_access_token(void **runtime, void **mx, const char **token_out); + +/** + * Retrieve a nucleus auth code with the specified client id. Can return [ERR_NOT_LOGGED_IN] + */ +uintptr_t maxima_auth_exchange(void **runtime, + void **mx, + const char *client_id, + const char **code_out); + +/** + * Create a Maxima object. + */ +const void *maxima_mx_create(void **runtime); + +/** + * Set the stored token retrieved from [maxima_login]. + */ +uintptr_t maxima_mx_set_access_token(void **runtime, const void **mx, const char *token); + +/** + * Set the port to be used for the LSX server. This will be automatically passed to games. + * Note that not every game supports a custom LSX port, the default is 3216. + */ +void maxima_mx_set_lsx_port(void **runtime, const void **mx, unsigned short port); + +/** + * Start the LSX server used for game communication. + */ +uintptr_t maxima_mx_start_lsx(void **runtime, const void **mx); + +/** + * Consume pending LSX events. + */ +uintptr_t maxima_mx_consume_lsx_events(void **runtime, + const void **mx, + const char ***events_out, + unsigned int **event_pids_out, + unsigned int *event_count_out); + +/** + * Free LSX events retrieved from [maxima_mx_consume_lsx_events]. + */ +void maxima_mx_free_lsx_events(char **events, unsigned int event_count); + +/** + * Launch a game with Maxima, providing an EA Offer ID. + */ +uintptr_t maxima_launch_game(void **runtime, const void **mx, const char *c_offer_id); + +/** + * Find an owned game's offer ID by its slug. + */ +uintptr_t maxima_find_owned_offer(void **runtime, + const void **mx, + const char *c_game_slug, + const char **offer_id_out); + +/** + * Get the local user's display name. + */ +uintptr_t maxima_get_local_display_name(void **runtime, + const void **mx, + const char **display_name_out); + +/** + * Pull the application's window into the foreground. + */ +uintptr_t maxima_take_foreground_focus(void); + +/** + * Read the path for an EA game. + */ +uintptr_t maxima_read_game_path(const char *c_name, const char **c_out_path); diff --git a/maxima-native/build.rs b/crates/maxima_native/build.rs similarity index 100% rename from maxima-native/build.rs rename to crates/maxima_native/build.rs diff --git a/maxima-native/src/lib.rs b/crates/maxima_native/src/lib.rs similarity index 100% rename from maxima-native/src/lib.rs rename to crates/maxima_native/src/lib.rs diff --git a/maxima-native/test/example.c b/crates/maxima_native/test/example.c similarity index 100% rename from maxima-native/test/example.c rename to crates/maxima_native/test/example.c diff --git a/crates/maxima_proto/Cargo.toml b/crates/maxima_proto/Cargo.toml new file mode 100644 index 0000000..690a3fd --- /dev/null +++ b/crates/maxima_proto/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "maxima_proto" +version = "1.0.0" +edition = "2021" +authors = ["Sean Kahler "] +categories = ["games"] +description = "A free and open-source replacement for the EA Desktop Launcher" +keywords = ["electronic-arts", "linux-gaming"] +license = "GPL-3.0" +readme = "README.md" +repository = "https://github.com/ArmchairDevelopers/Maxima" +documentation = "https://docs.rs/maxima" +rust-version = "1.81.0" + +[dependencies] +tokio = { version = "1.28.2", features = [ + "full", + "rt", + "rt-multi-thread", + "time", + "net", +] } +serde = { version = "1.0.164", features = ["derive"] } +serde_json = "1.0.97" +paste = "1.0.12" +anyhow = "1.0.71" +derive-getters = "0.3.0" +derive_builder = "0.12.0" +rand = "0.8.5" +futures = "0.3.30" +bytes = "1.5.0" +async-trait = "0.1.81" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } +thiserror = "2.0.3" +const-fnv1a-hash = "1.1.0" +typed-builder = "0.20.0" diff --git a/crates/maxima_proto/examples/auth_check.rs b/crates/maxima_proto/examples/auth_check.rs new file mode 100644 index 0000000..1565aa7 --- /dev/null +++ b/crates/maxima_proto/examples/auth_check.rs @@ -0,0 +1,20 @@ +use maxima_proto::comp::{auth::CheckAuthRequest, util::IdentificationRequest}; +use tracing::info; + +mod entry; + +#[tokio::main] +async fn main() { + let (_, component_man) = entry::client_setup::create_conn_man(); + + let req = IdentificationRequest::builder() + .client_id("Test".to_owned()) + .version("Test".to_owned()) + .build(); + + let _ = component_man.util().identify(req).await.expect("Failed to identify"); + + let req = CheckAuthRequest::builder().allow_cached(false).build(); + let res = component_man.auth().check(req).await; + info!("Logged in?: {:#?}", res.unwrap()); +} diff --git a/crates/maxima_proto/examples/client.rs b/crates/maxima_proto/examples/client.rs new file mode 100644 index 0000000..737883b --- /dev/null +++ b/crates/maxima_proto/examples/client.rs @@ -0,0 +1,17 @@ +use maxima_proto::comp::util::IdentificationRequest; +use tracing::info; + +mod entry; + +#[tokio::main] +async fn main() { + let (_, component_man) = entry::client_setup::create_conn_man(); + + let req = IdentificationRequest::builder() + .client_id("Test".to_owned()) + .version("Test".to_owned()) + .build(); + + let response = component_man.util().identify(req).await; + info!("Response: {:#?}", response.unwrap()); +} diff --git a/crates/maxima_proto/examples/entry/client_setup.rs b/crates/maxima_proto/examples/entry/client_setup.rs new file mode 100644 index 0000000..494e361 --- /dev/null +++ b/crates/maxima_proto/examples/entry/client_setup.rs @@ -0,0 +1,17 @@ +use std::time::Duration; + +use maxima_proto::{comm::client::ProtoConnectionManager, comp::ClientComponentManager}; +use tracing::Level; +use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; + +pub fn create_conn_man() -> (ProtoConnectionManager, ClientComponentManager) { + tracing_subscriber::registry() + .with(EnvFilter::from_default_env().add_directive(Level::INFO.into())) + .with(fmt::Layer::default()) + .init(); + + let conn_man = ProtoConnectionManager::new(Duration::from_secs(5)); + let comp_man = ClientComponentManager::new(conn_man.clone()); + + (conn_man, comp_man) +} diff --git a/crates/maxima_proto/examples/entry/mod.rs b/crates/maxima_proto/examples/entry/mod.rs new file mode 100644 index 0000000..03d3833 --- /dev/null +++ b/crates/maxima_proto/examples/entry/mod.rs @@ -0,0 +1 @@ +pub mod client_setup; diff --git a/crates/maxima_proto/examples/local_user.rs b/crates/maxima_proto/examples/local_user.rs new file mode 100644 index 0000000..fbff966 --- /dev/null +++ b/crates/maxima_proto/examples/local_user.rs @@ -0,0 +1,20 @@ +use maxima_proto::comp::util::IdentificationRequest; +use tracing::info; + +mod entry; + +#[tokio::main] +async fn main() { + let (_, comp_man) = entry::client_setup::create_conn_man(); + + let req = IdentificationRequest::builder() + .client_id("Test".to_owned()) + .version("Test".to_owned()) + .build(); + + let _ = comp_man.util().identify(req).await.expect("Failed to identify"); + + let req = (); + let res = comp_man.users().local_user(req).await; + info!("User: {:#?}", res.unwrap()); +} diff --git a/crates/maxima_proto/src/comm/client.rs b/crates/maxima_proto/src/comm/client.rs new file mode 100644 index 0000000..1ea8a20 --- /dev/null +++ b/crates/maxima_proto/src/comm/client.rs @@ -0,0 +1,348 @@ +use std::{ + collections::HashMap, + error::Error, + io::{self, ErrorKind}, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, + time::Duration, +}; + +use anyhow::Result; +use bytes::{Buf, BufMut, BytesMut}; +use serde::{Deserialize, Serialize}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, + sync::{mpsc, oneshot, Mutex}, + time, +}; +use tracing::{debug, error, info, warn}; + +use crate::comm::proto::{ProtoHeader, ProtoPacketType}; + +use super::proto::ProtoError; + +const TCP_HOST: &str = "127.0.0.1:3727"; + +pub struct ClientProtoRequest { + packet_type: ProtoPacketType, + id: u32, + component: u32, + command: u32, + payload: Vec, + response_tx: Option, String>>>, +} + +pub trait ProtoNotificationHandler { + fn handle(&self, payload: Vec); +} + +type ProtoNotificationMap = + Arc) + Send + Sync>)>>>>; + +#[derive(Clone)] +pub struct ProtoConnectionManager { + request_tx: mpsc::Sender, + request_index: Arc, + notification_handlers: ProtoNotificationMap, + handler_id_counter: Arc>, +} + +impl ProtoConnectionManager { + pub fn new(reconnect_delay: Duration) -> Self { + let (request_tx, request_rx) = mpsc::channel(32); + + let notification_map = Arc::new(Mutex::new(HashMap::new())); + + let cloned_notif_map = notification_map.clone(); + tokio::spawn(async move { + ProtoConnectionManager::run(reconnect_delay, request_rx, cloned_notif_map).await; + }); + + let man = Self { + request_tx: request_tx.clone(), + request_index: Arc::new(AtomicU32::new(0)), + notification_handlers: notification_map, + handler_id_counter: Arc::new(Mutex::new(0)), + }; + + let cloned_man = man.clone(); + tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_secs(30)).await; + cloned_man.send_ping().await.unwrap(); + } + }); + + man + } + + async fn run( + reconnect_delay: Duration, + mut request_rx: mpsc::Receiver, + notification_map: ProtoNotificationMap, + ) { + loop { + let tcp_host = + std::env::var("MAXIMA_SERVER_HOST").unwrap_or_else(|_| TCP_HOST.to_string()); + match TcpStream::connect(tcp_host).await { + Ok(stream) => { + if let Err(e) = ProtoConnectionManager::handle_stream( + stream, + &mut request_rx, + ¬ification_map, + ) + .await + { + error!("Stream error: {}", e); + // Reconnection will be attempted after the delay + } + } + Err(e) => { + error!("Failed to connect: {}", e); + } + } + + time::sleep(reconnect_delay).await; + } + } + + async fn handle_stream( + mut stream: TcpStream, + request_rx: &mut mpsc::Receiver, + notification_map: &ProtoNotificationMap, + ) -> Result<(), Box> { + let mut pending_responses: HashMap, String>>> = + HashMap::new(); + + let mut expected_size: i32 = -1; + let mut bytes = BytesMut::with_capacity(1024 * 12); + + info!("Connected"); + + loop { + tokio::select! { + size = stream.read_buf(&mut bytes) => { + match size { + Ok(0) => { + warn!("Proto connection closed"); + break; + }, + Ok(_) => { + loop { + debug!("Reading message {} bytes", bytes.len()); + + if bytes.len() < ProtoHeader::SIZE as usize { + break; + } + + if expected_size == -1 { + let mut cloned = bytes.clone().freeze(); + let header = ProtoHeader::from(&mut cloned); + + expected_size = (header.data_size as u32) as i32; + } + + let full_expected_size = (expected_size + ProtoHeader::SIZE as i32) as usize; + if bytes.len() < full_expected_size { + break; + } + + debug!("Got full message, {} bytes", full_expected_size); + + // We have the full message now, processing time + let mut buf = bytes.clone().freeze().slice(0usize..full_expected_size); + + bytes.advance(full_expected_size); + expected_size = -1; + + let header = ProtoHeader::from(&mut buf); + + assert!(buf.remaining() == header.data_size as usize, "Payload size mismatch: {} != {} / {}", buf.remaining(), header.data_size as usize, full_expected_size); + + debug!("Message: {:?}", header); + + match header.packet_type { + ProtoPacketType::Message => { + unimplemented!("Client should not receive messages") + } + ProtoPacketType::Reply => { + if let Some(tx) = + pending_responses.remove(&header.packet_id) + { + tx.send(Ok(buf.to_vec())).ok(); + } else { + println!("Received message with no pending response: {}", header.packet_id); + } + } + ProtoPacketType::Error => { + if let Some(tx) = + pending_responses.remove(&header.packet_id) + { + tx.send(Err(String::from_utf8(buf.to_vec()).expect("Failed to convert server error data to string"))).ok(); + } else { + println!("Received message with no pending response: {}", header.packet_id); + } + } + ProtoPacketType::Notification => { + let handlers = notification_map.lock().await; + if let Some(handlers_vec) = handlers.get(&(header.component.clone(), header.command.clone())) { + for (_, handler) in handlers_vec { + handler(buf.to_vec()); + } + } else { + debug!("Received notification with no handler [Component: {}, Command: {}]", header.component, header.command); + } + } + ProtoPacketType::PingReply => {} + _ => { + info!("Received non-reply type: {:?} [Component: {}, Command: {}]", header.packet_type, header.component, header.command); + } + }; + } + }, + Err(e) => { + println!("Failed to read from proto socket: {} ({} existing bytes)", e, bytes.len()); + break; + }, + } + }, + request = request_rx.recv(), if expected_size == -1 => { + if let Some(request) = request { + debug!("Sending message {}, {}", request.id, request.payload.len() as u32); + + let mut buf = BytesMut::new(); + let header = ProtoHeader { + data_size: request.payload.len() as u32, + packet_id: request.id, + packet_type: request.packet_type, + component: request.component, + command: request.command, + }; + + header.serialize(&mut buf); + buf.put(request.payload.as_slice()); + + let mut frozen = buf.freeze(); + stream.write_buf(&mut frozen).await.unwrap(); + stream.flush().await.unwrap(); + + if let Some(response_tx) = request.response_tx { + pending_responses.insert(request.id, response_tx); + } + } + }, + } + } + + info!("Disconnected"); + Ok(()) + } + + pub async fn register_notification_handler( + &self, + component: u32, + command: u32, + handler: Arc) + Send + Sync>, + ) -> usize { + let mut id_counter = self.handler_id_counter.lock().await; + let id = *id_counter; + *id_counter += 1; + self.notification_handlers + .lock() + .await + .entry((component, command)) + .or_insert_with(Vec::new) + .push((id, handler)); + id + } + + pub async fn unregister_notification_handler( + &self, + component: u32, + command: u32, + handler_id: usize, + ) { + let mut handlers = self.notification_handlers.lock().await; + if let Some(handlers_vec) = handlers.get_mut(&(component, command)) { + handlers_vec.retain(|(id, _)| *id != handler_id); + } + } + + pub async fn send_request_raw( + &self, + component: u32, + command: u32, + message: Vec, + ) -> Result, ProtoError> { + let (response_tx, response_rx) = oneshot::channel(); + + let index = self.request_index.fetch_add(1, Ordering::SeqCst); + + self.request_tx + .send(ClientProtoRequest { + packet_type: ProtoPacketType::Message, + id: index, + component, + command, + payload: message, + response_tx: Some(response_tx), + }) + .await + .map_err(|x| ProtoError::SendError(x))?; + + match response_rx.await { + Ok(response) => response.map_err(|x| serde_json::from_str(&x).expect("Failed to deserialize error")), + Err(err) => Err(ProtoError::IoError(io::Error::new( + ErrorKind::Other, + format!("Failed to receive response: {:?}", err), + ))), + } + } + + pub async fn send_request( + &self, + component: u32, + command: u32, + message: M, + ) -> Result + where + M: Serialize, + R: for<'a> Deserialize<'a>, + { + match self + .send_request_raw( + component, + command, + serde_json::to_vec(&message).expect("Failed to serialize request"), + ) + .await + { + Ok(x) => { + if x.is_empty() { + return Err(ProtoError::NoData); + } + + Ok(serde_json::from_slice(&x).expect("Failed to deserialize response")) + } + Err(err) => Err(err), + } + } + + pub async fn send_ping(&self) -> Result<(), mpsc::error::SendError> { + let index = self.request_index.fetch_add(1, Ordering::SeqCst); + + self.request_tx + .send(ClientProtoRequest { + packet_type: ProtoPacketType::Ping, + id: index, + component: 0, + command: 0, + payload: Vec::new(), + response_tx: None, + }) + .await + } +} diff --git a/crates/maxima_proto/src/comm/mod.rs b/crates/maxima_proto/src/comm/mod.rs new file mode 100644 index 0000000..b346540 --- /dev/null +++ b/crates/maxima_proto/src/comm/mod.rs @@ -0,0 +1,4 @@ +pub mod client; +pub mod proto; +pub mod router; +pub mod server; diff --git a/crates/maxima_proto/src/comm/proto.rs b/crates/maxima_proto/src/comm/proto.rs new file mode 100644 index 0000000..389b04e --- /dev/null +++ b/crates/maxima_proto/src/comm/proto.rs @@ -0,0 +1,318 @@ +use std::io; + +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; + +use super::{client::ClientProtoRequest, router::ProtoResult}; + +#[derive(thiserror::Error, Debug, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ProtoError { + /// This is messy because ProtoError is inside the component error + /// enums, which makes this sort of cyclic. This is needed for the + /// upstreaming of errors from the component to router layer + #[error("Component error: {0}")] + ComponentError(String), + /// Internal is the client representation of the forwarded + /// server error types. An error log will occur on the server + /// whenever an Internal error is sent to the client + #[error("Internal error: {0}")] + Internal(String), + #[error("Anyhow error: {0}")] + #[serde(skip)] + AnyhowError(#[from] anyhow::Error), + #[error("IO error: {0}")] + #[serde(skip)] + IoError(#[from] io::Error), + #[error("Send error: {0}")] + #[serde(skip)] + SendError(#[from] mpsc::error::SendError), + #[error("Server did not send any data")] + NoData, + #[error("Unknown component: {0}")] + UnknownComponent(u32), + #[error("Unknown command {1} in component {0}")] + UnknownCommand(u32, u32), + #[error("The requested command requires authentication")] + Unauthenticated, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum ProtoPacketType { + Message, + Reply, + Error, + Notification, + Ping, + PingReply, +} + +#[derive(Debug)] +#[repr(C)] +pub struct ProtoHeader { + pub data_size: u32, + pub packet_id: u32, + pub packet_type: ProtoPacketType, + pub component: u32, + pub command: u32, +} + +impl ProtoHeader { + pub const SIZE: usize = 4 + 4 + 1 + 4 + 4; + + pub fn from(buf: &mut Bytes) -> Self { + Self { + data_size: buf.get_u32(), + packet_id: buf.get_u32(), + packet_type: match buf.get_u8() { + 0 => ProtoPacketType::Message, + 1 => ProtoPacketType::Reply, + 2 => ProtoPacketType::Error, + 3 => ProtoPacketType::Notification, + 4 => ProtoPacketType::Ping, + 5 => ProtoPacketType::PingReply, + val => panic!("Invalid message type {val}"), + }, + component: buf.get_u32(), + command: buf.get_u32(), + } + } + + pub fn serialize(&self, buf: &mut BytesMut) { + buf.put_u32(self.data_size); + buf.put_u32(self.packet_id); + buf.put_u8(self.packet_type as u8); + buf.put_u32(self.component); + buf.put_u32(self.command); + } +} + +pub trait ProtoComponent: Send + Sync { + const ID: u32; + const NAME: &'static str; + + fn command_name(&self, id: u32) -> Option<&'static str>; + fn call(&self, id: u32, client_id: u32, data: &[u8]) -> ProtoResult; +} + +pub struct ProtoRequest { + inner: T, + client_id: u32, +} + +impl ProtoRequest { + pub fn new(inner: T, client_id: u32) -> Self { + Self { inner, client_id } + } + + pub fn into_inner(self) -> T { + self.inner + } + + pub fn inner(&self) -> &T { + &self.inner + } + + pub fn client_id(&self) -> u32 { + self.client_id + } +} + +#[macro_export] +macro_rules! proto_component { + ( + $component_name:ident; + $(errors { + $( + $(#[$error_field_attr:meta])* + $error_name:ident + ),* $(,)? + })? + $(rpc { + $( + $(#[$command_field_attr:meta])* + $command_name:ident($command_var:ty): $command_return_type:ty + ),* $(,)? + })? + ) => { + paste::paste! { + const []: u32 = + const_fnv1a_hash::fnv1a_hash_32(stringify!($component_name).as_bytes(), None); + + #[derive(thiserror::Error, Debug, serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + pub enum [<$component_name Error>] { + #[error("Proto error: {0}")] + Proto(#[from] crate::comm::proto::ProtoError), + $( + $( + $(#[$error_field_attr])* + $error_name + ),* + )? + } + + impl From for [<$component_name Error>] { + fn from(value: std::io::Error) -> Self { + Self::Proto(crate::comm::proto::ProtoError::IoError(value)) + } + } + + impl From for [<$component_name Error>] { + fn from(value: anyhow::Error) -> Self { + Self::Proto(crate::comm::proto::ProtoError::AnyhowError(value)) + } + } + + $( + $( + const []: u32 = + const_fnv1a_hash::fnv1a_hash_32(stringify!($command_name).as_bytes(), None); + )* + + #[async_trait::async_trait] + pub trait []: Clone + Send + Sync + 'static { + const ID: u32 = []; + const NAME: &'static str = stringify!($component_name); + + $( + const []: u32 = []; + + $(#[$command_field_attr])* + async fn $command_name(&self, request: crate::comm::proto::ProtoRequest<$command_var>) -> Result<$command_return_type, [<$component_name Error>]>; + )* + } + + #[derive(Debug, Clone)] + pub struct [<$component_name Server>]]> { + inner: std::sync::Arc, + } + + impl]> [<$component_name Server>] { + pub fn new(inner: T) -> Self { + Self { inner: std::sync::Arc::new(inner) } + } + } + + impl]> crate::comm::proto::ProtoComponent for [<$component_name Server>] { + const ID: u32 = T::ID; + const NAME: &'static str = T::NAME; + + fn command_name(&self, id: u32) -> Option<&'static str> { + // We unfortunately can't use a match here because of weird pattern requirements for const variables. + $( + if id == T::[] { return Some(stringify!($command_name)); } + )* + None + } + + fn call(&self, id: u32, client_id: u32, data: &[u8]) -> crate::comm::router::ProtoResult { + let inner = self.inner.clone(); + + $( + // See above note. + if id == T::[] { + let request_data = serde_json::from_slice(data).expect("Failed to deserialize request"); + let request = crate::comm::proto::ProtoRequest::new(request_data, client_id); + return Box::pin(async move { + let result = ]>::$command_name(&inner, request).await; + result.map(|x| serde_json::to_vec(&x).expect("Failed to serialize response")).map_err(|x| { + #[allow(irrefutable_let_patterns)] + if let [<$component_name Error>]::Proto(proto) = x { + return proto; + } + + crate::comm::proto::ProtoError::ComponentError(serde_json::to_string(&x).expect("Failed to serialize error")) + }) + }); + } + )* + + Box::pin(async move { + Err(crate::comm::proto::ProtoError::UnknownCommand(Self::ID, id)) + }) + } + } + + #[derive(Clone)] + pub struct [] { + pub conn_man: crate::comm::client::ProtoConnectionManager, + } + + impl [] { + pub fn new(conn_man: crate::comm::client::ProtoConnectionManager) -> Self { + Self { conn_man } + } + + $( + $(#[$command_field_attr])* + pub async fn $command_name(&self, request: $command_var) -> Result<$command_return_type, [<$component_name Error>]> { + match self.conn_man.send_request( + [], + [], + request + ).await { + Ok(res) => Ok(res), + Err(err) => { + if let crate::comm::proto::ProtoError::ComponentError(data) = err { + return Err(serde_json::from_str(&data).expect("Failed to deserialize error")); + } + + Err([<$component_name Error>]::Proto(err)) + } + } + } + )* + } + )? + } + }; +} + +#[macro_export] +macro_rules! proto_client_component_manager { + ( + $($component_name:ident $snake_name:ident),* $(,)? + ) => { + paste::paste! { + #[derive(Clone)] + pub struct ClientComponentManager { + $( + $snake_name: [], + )* + } + + impl ClientComponentManager { + pub fn new(conn_man: crate::comm::client::ProtoConnectionManager) -> Self { + Self { + $( + $snake_name: []::new(conn_man.clone()), + )* + } + } + + $( + pub fn $snake_name(&self) -> & [] { + &self.$snake_name + } + )* + } + } + }; +} + +#[macro_export] +macro_rules! proto_struct { + ($name:ident, { $($(#[$meta:meta])* $field:ident : $ty:ty),* $(,)? }) => { + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, typed_builder::TypedBuilder, derive_getters::Getters)] + pub struct $name { + $( + $(#[$meta])* + $field: $ty, + )* + } + }; +} + diff --git a/crates/maxima_proto/src/comm/router.rs b/crates/maxima_proto/src/comm/router.rs new file mode 100644 index 0000000..e07907d --- /dev/null +++ b/crates/maxima_proto/src/comm/router.rs @@ -0,0 +1,97 @@ +use std::{collections::HashMap, sync::Arc}; + +use futures::future::BoxFuture; +use tracing::{debug, info}; + +use super::proto::{ProtoComponent, ProtoError}; + +pub type ProtoResult = BoxFuture<'static, Result, ProtoError>>; + +pub struct RoutingData<'a> { + pub id: u32, + pub client_id: u32, + pub data: &'a [u8], +} + +type ProtoComponentCallFn = Arc ProtoResult + Send + Sync>; +type ProtoComponentCommandNameFn = Arc Option<&'static str> + Send + Sync>; + +struct RouterComponent { + name: &'static str, + call_fn: ProtoComponentCallFn, + command_name_fn: ProtoComponentCommandNameFn, +} + +impl RouterComponent { + pub fn new(component: C) -> Self { + let component = Arc::new(component); + let component2 = component.clone(); // Is there a better way to do this? + + Self { + name: C::NAME, + call_fn: Arc::new(move |data| component.call(data.id, data.client_id, data.data)), + command_name_fn: Arc::new(move |id| component2.command_name(id)), + } + } +} + +pub struct ProtoRouter { + components: HashMap, +} + +impl ProtoRouter { + pub fn builder() -> Self { + Self { + components: HashMap::new(), + } + } + + pub fn add_component(mut self, component: C) -> Self { + info!("Registering component {}", C::NAME); + + self.components + .insert(C::ID, RouterComponent::new(component)); + self + } + + pub(crate) async fn call( + &self, + component_id: u32, + data: RoutingData<'_>, + ) -> Result, ProtoError> { + let component = match self.components.get(&component_id) { + Some(component) => component, + None => return Err(ProtoError::UnknownComponent(component_id)), + }; + + debug!( + "[{}:{}] Client '{}' is calling RPC", + component.name, + (component.command_name_fn)(data.id).unwrap_or(&data.id.to_string()), + data.client_id, + ); + + (component.call_fn)(data).await + } + + pub fn _command_name(&self, component_id: u32, id: u32) -> Result<&'static str, ProtoError> { + let component = match self.components.get(&component_id) { + Some(component) => component, + None => return Err(ProtoError::UnknownComponent(component_id)), + }; + + (component.command_name_fn)(id).ok_or(ProtoError::UnknownCommand(component_id, id)) + } + + pub fn rpc_name(&self, component_id: u32, id: u32) -> String { + let component = match self.components.get(&component_id) { + Some(component) => component, + None => return format!("{component_id}:{id}"), + }; + + let command_name = (component.command_name_fn)(id) + .map(|x| x.to_owned()) + .unwrap_or(id.to_string()); + format!("{}:{}", component.name, command_name) + } +} diff --git a/crates/maxima_proto/src/comm/server.rs b/crates/maxima_proto/src/comm/server.rs new file mode 100644 index 0000000..e061aa0 --- /dev/null +++ b/crates/maxima_proto/src/comm/server.rs @@ -0,0 +1,188 @@ +use std::{io, sync::Arc}; + +use bytes::{Buf, BufMut, BytesMut}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream}, + sync::mpsc, +}; +use tracing::{debug, error, info, warn}; + +use crate::comm::{ + proto::{ProtoError, ProtoHeader, ProtoPacketType}, + router::{ProtoRouter, RoutingData}, +}; + +#[derive(thiserror::Error, Debug)] +pub enum ServerError { + #[error("IO error: {0}")] + IoError(#[from] io::Error), +} + +pub struct ProtoServerPacket { + pub packet_id: u32, + pub packet_type: ProtoPacketType, + pub component: u32, + pub command: u32, + pub data: Vec, +} + +pub struct ProtoServer { + router: Arc, +} + +impl ProtoServer { + pub fn new(router: ProtoRouter) -> Self { + Self { + router: Arc::new(router), + } + } + + pub async fn serve(&self) -> Result<(), ServerError> { + let addr = "127.0.0.1:3727".to_string(); + + let listener = TcpListener::bind(&addr).await?; + info!("Listening on: {}", addr); + + loop { + let (stream, _) = listener.accept().await?; + + let router = self.router.clone(); + tokio::spawn(async move { + Self::handle_stream(stream, router).await; + }); + } + } + + async fn handle_stream(mut stream: TcpStream, router: Arc) { + let client_id = rand::random::(); + + let mut expected_size: i32 = -1; + let mut bytes = BytesMut::with_capacity(1024 * 12); + + info!("New client connected: '{client_id}'"); + + let (request_tx, mut request_rx) = mpsc::channel(32); + + loop { + tokio::select! { + size = stream.read_buf(&mut bytes) => { + match size { + Ok(0) => { + warn!("Client connection '{client_id}' closed"); + break; + }, + Ok(_) => { + loop { + debug!("Reading message {} bytes", bytes.len()); + + if bytes.len() < ProtoHeader::SIZE { + break; + } + + if expected_size == -1 { + let mut cloned = bytes.clone().freeze(); + let header = ProtoHeader::from(&mut cloned); + + expected_size = header.data_size as i32; + } + + let full_expected_size = (expected_size + ProtoHeader::SIZE as i32) as usize; + if bytes.len() < full_expected_size { + break; + } + + debug!("Got full message, {} bytes", full_expected_size); + + // We have the full message now, processing time + let mut buf = bytes.clone().freeze().slice(0usize..full_expected_size); + + bytes.advance(full_expected_size); + expected_size = -1; + + let header = ProtoHeader::from(&mut buf); + if header.packet_type == ProtoPacketType::Ping { + request_tx.send(ProtoServerPacket { + packet_id: header.packet_id, + packet_type: ProtoPacketType::PingReply, + component: header.component, + command: header.command, + data: vec![], + }).await.unwrap(); + continue; + } + + assert!(buf.remaining() == header.data_size as usize, "Payload size mismatch: {} != {} / {}", buf.remaining(), header.data_size as usize, full_expected_size); + + debug!("Message: {:?}", header); + + let router = router.clone(); + let request_tx = request_tx.clone(); + + tokio::spawn(async move { + match router.call(header.component, RoutingData { + id: header.command, + client_id, + data: &buf + }).await { + Ok(data) => { + request_tx.send(ProtoServerPacket { + packet_id: header.packet_id, + packet_type: ProtoPacketType::Reply, + component: header.component, + command: header.command, + data + }).await.unwrap(); + }, + Err(err) => { + error!("[{}] RPC from client '{}' Failed: {}", router.rpc_name(header.component, header.command), client_id, err); + + let err = match err { + ProtoError::AnyhowError(err) => ProtoError::Internal(err.to_string()), + ProtoError::IoError(err) => ProtoError::Internal(err.to_string()), + _ => err + }; + + request_tx.send(ProtoServerPacket { + packet_id: header.packet_id, + packet_type: ProtoPacketType::Error, + component: header.component, + command: header.command, + data: serde_json::to_vec(&err).expect("Failed to serialize error") + }).await.unwrap(); + }, + } + }); + } + } + Err(e) => { + error!("Failed to read from socket: {} ({} existing bytes)", e, bytes.len()); + break; + } + } + } + request = request_rx.recv(), if expected_size == -1 => { + if let Some(request) = request { + debug!("Sending message {}, {}", request.packet_id, request.data.len() as u32); + + let mut buf = BytesMut::new(); + let header = ProtoHeader { + data_size: request.data.len() as u32, + packet_id: request.packet_id, + packet_type: request.packet_type, + component: request.component, + command: request.command, + }; + + header.serialize(&mut buf); + buf.put(request.data.as_slice()); + + let mut frozen = buf.freeze(); + stream.write_buf(&mut frozen).await.unwrap(); + stream.flush().await.unwrap(); + } + }, + } + } + } +} diff --git a/crates/maxima_proto/src/comp/auth.rs b/crates/maxima_proto/src/comp/auth.rs new file mode 100644 index 0000000..da7f048 --- /dev/null +++ b/crates/maxima_proto/src/comp/auth.rs @@ -0,0 +1,21 @@ +use crate::{proto_component, proto_struct}; + +proto_struct!(LoginRequest, { +}); + +proto_struct!(CheckAuthRequest, { + /// If false, the current login will be checked + /// against EA servers for validity. If true, + /// only the current login token's expiry will + /// be checked + allow_cached: bool, +}); + +proto_component!( + Authentication; + + rpc { + check(CheckAuthRequest): bool, + login(LoginRequest): (), + } +); diff --git a/crates/maxima_proto/src/comp/library.rs b/crates/maxima_proto/src/comp/library.rs new file mode 100644 index 0000000..fb2e73d --- /dev/null +++ b/crates/maxima_proto/src/comp/library.rs @@ -0,0 +1,20 @@ +use crate::{models::library::Game, proto_component}; + +proto_component!( + Library; + + errors { + #[error("Offer not found")] + OfferNotFound, + #[error("Cannot launch game as it is not installed")] + LaunchGameNotInstalled, + #[error("Game path must be specified when launching in OnlineOffline mode")] + LaunchGamePathRequired, + #[error("Content ID was specified as an offer ID when launching in OnlineOffline mode")] + LaunchContentIdRequired, + } + + rpc { + games(()): Vec, + } +); diff --git a/crates/maxima_proto/src/comp/mod.rs b/crates/maxima_proto/src/comp/mod.rs new file mode 100644 index 0000000..7d4022c --- /dev/null +++ b/crates/maxima_proto/src/comp/mod.rs @@ -0,0 +1,16 @@ +pub mod auth; +pub mod library; +pub mod users; +pub mod util; + +use auth::ClientAuthenticationComponent; +use users::ClientUsersComponent; +use util::ClientUtilitiesComponent; + +use crate::proto_client_component_manager; + +proto_client_component_manager!( + Authentication auth, + Users users, + Utilities util, +); diff --git a/crates/maxima_proto/src/comp/users.rs b/crates/maxima_proto/src/comp/users.rs new file mode 100644 index 0000000..254bfef --- /dev/null +++ b/crates/maxima_proto/src/comp/users.rs @@ -0,0 +1,15 @@ +use crate::{models::user::User, proto_component}; + +proto_component!( + Users; + + errors { + #[error("User Not Found")] + UserNotFound, + } + + rpc { + /// Will throw AuthenticationRequired when not logged in + local_user(()): User, + } +); diff --git a/crates/maxima_proto/src/comp/util.rs b/crates/maxima_proto/src/comp/util.rs new file mode 100644 index 0000000..5ce43ce --- /dev/null +++ b/crates/maxima_proto/src/comp/util.rs @@ -0,0 +1,25 @@ +use crate::{proto_component, proto_struct}; + +proto_struct!(IdentificationRequest, { + /// A string identifying the connecting client. + client_id: String, + version: String, +}); + +proto_struct!(IdentificationResponse, { + client_id: u32, + server_version: String, +}); + +proto_component!( + Utilities; + + errors { + #[error("Incompatible Server Version")] + IncompatibleVersion, + } + + rpc { + identify(IdentificationRequest): IdentificationResponse, + } +); diff --git a/crates/maxima_proto/src/lib.rs b/crates/maxima_proto/src/lib.rs new file mode 100644 index 0000000..e56ff9b --- /dev/null +++ b/crates/maxima_proto/src/lib.rs @@ -0,0 +1,5 @@ +pub mod comm; +pub mod comp; +pub mod models; + +pub use async_trait::async_trait; diff --git a/crates/maxima_proto/src/models/library.rs b/crates/maxima_proto/src/models/library.rs new file mode 100644 index 0000000..a97b099 --- /dev/null +++ b/crates/maxima_proto/src/models/library.rs @@ -0,0 +1,6 @@ +use crate::proto_struct; + +proto_struct!(Game, { + offer_id: String, + name: String, +}); diff --git a/crates/maxima_proto/src/models/mod.rs b/crates/maxima_proto/src/models/mod.rs new file mode 100644 index 0000000..1f060ce --- /dev/null +++ b/crates/maxima_proto/src/models/mod.rs @@ -0,0 +1,2 @@ +pub mod library; +pub mod user; diff --git a/crates/maxima_proto/src/models/user.rs b/crates/maxima_proto/src/models/user.rs new file mode 100644 index 0000000..fcdf54c --- /dev/null +++ b/crates/maxima_proto/src/models/user.rs @@ -0,0 +1,9 @@ +use crate::proto_struct; + +proto_struct!(User, { + account_id: String, + persona_id: String, + display_name: String, + unique_name: String, + nickname: String, +}); diff --git a/maxima-resources/Cargo.toml b/crates/maxima_resources/Cargo.toml similarity index 77% rename from maxima-resources/Cargo.toml rename to crates/maxima_resources/Cargo.toml index 5ef40c6..06d6465 100644 --- a/maxima-resources/Cargo.toml +++ b/crates/maxima_resources/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "maxima-resources" +name = "maxima_resources" version = "0.1.0" authors = ["Sean Kahler "] edition = "2021" @@ -9,4 +9,4 @@ winres = "0.1.12" [lib] name = "maxima_resources" -path = "src/lib.rs" \ No newline at end of file +path = "src/lib.rs" diff --git a/maxima-resources/assets/logo.ico b/crates/maxima_resources/assets/logo.ico similarity index 100% rename from maxima-resources/assets/logo.ico rename to crates/maxima_resources/assets/logo.ico diff --git a/maxima-resources/assets/logo.png b/crates/maxima_resources/assets/logo.png similarity index 100% rename from maxima-resources/assets/logo.png rename to crates/maxima_resources/assets/logo.png diff --git a/maxima-resources/src/lib.rs b/crates/maxima_resources/src/lib.rs similarity index 53% rename from maxima-resources/src/lib.rs rename to crates/maxima_resources/src/lib.rs index c21bca1..a80c112 100644 --- a/maxima-resources/src/lib.rs +++ b/crates/maxima_resources/src/lib.rs @@ -1,6 +1,14 @@ -use std::{io, path::PathBuf}; +use std::{io, path::PathBuf, process::Command}; pub fn maxima_windows_rc(internal_name: &str, display_name: &str) -> io::Result<()> { + let output = Command::new("git") + .args(&["rev-parse", "--short", "HEAD"]) + .output() + .unwrap(); + let git_hash = String::from_utf8(output.stdout).unwrap(); + + println!("cargo:rustc-env=GIT_HASH={}", git_hash); + if !cfg!(target_os = "windows") { return Ok(()); } @@ -9,16 +17,22 @@ pub fn maxima_windows_rc(internal_name: &str, display_name: &str) -> io::Result< let license = env!("CARGO_PKG_LICENSE"); let repository = env!("CARGO_PKG_REPOSITORY"); + let version = env!("CARGO_PKG_VERSION"); let mut res = winres::WindowsResource::new(); res.set_icon(assets_path.join("logo.ico").to_str().unwrap()) - .set("Comments", &format!("Maxima Game Launcher - {}", repository)) + .set( + "Comments", + &format!("Maxima Game Launcher - {}", repository), + ) .set("CompanyName", "Armchair Developers") .set("FileDescription", display_name) + .set("FileVersion", &git_hash) .set("InternalName", internal_name) .set("LegalTrademarks", license) - .set("ProductName", display_name); + .set("ProductName", display_name) + .set("ProductVersion", version); res.compile()?; Ok(()) -} \ No newline at end of file +} diff --git a/crates/maxima_server/Cargo.toml b/crates/maxima_server/Cargo.toml new file mode 100644 index 0000000..a3a9367 --- /dev/null +++ b/crates/maxima_server/Cargo.toml @@ -0,0 +1,109 @@ +[package] +name = "maxima_server" +version = "1.0.0" +edition = "2021" +authors = ["Sean Kahler "] +categories = ["games"] +description = "A free and open-source replacement for the EA Desktop Launcher" +keywords = ["electronic-arts", "linux-gaming"] +license = "GPL-3.0" +readme = "README.md" +repository = "https://github.com/ArmchairDevelopers/Maxima" +documentation = "https://docs.rs/maxima" +rust-version = "1.81.0" + +[dependencies] +tokio = { version = "1.28.2", features = [ + "full", + "rt", + "rt-multi-thread", + "time", + "net", +] } +maxima_proto = { path = "../maxima_proto" } +moka = { version = "0.12.1", features = ["sync"] } +serde = { version = "1.0.164", features = ["derive"] } +serde_json = "1.0.97" +quick-xml = { version = "0.30.0", features = ["serialize"] } +paste = "1.0.12" +anyhow = "1.0.71" +hex = "0.4.3" +lazy_static = "1.4.0" +reqwest = { version = "0.11.18", features = ["stream", "rustls-tls", "gzip", "json"], default-features = false } +base64 = "0.21.2" +enable-ansi-support = "0.2.1" +chrono = "0.4.26" +regex = "1.8.4" +directories = "5.0.1" +open = "5.0.0" +querystring = "1.1.0" +ureq = { version = "2.9.1", features = ["tls"] } +sysinfo = "0.29.7" +strum_macros = "0.25.2" +sha2-const = "0.1.2" +tar = "0.4" +flate2 = { version = "1.0.28", default-features = false, features = ["zlib-default"] } +xz2 = "0.1.7" +zstd = "0.13.0" +toml = "0.8.8" +bytebuffer = "2.2.0" +derive-getters = "0.3.0" +derive_builder = "0.12.0" +mac_address = "1.1.5" +filetime = "0.2.23" +rand = "0.8.5" +ring = "0.17.7" +futures = "0.3.30" +prost = "0.12.3" +encoding = "0.2.33" +async-compression = { version = "0.4.5", features = ["tokio", "deflate"] } +tokio-util = { version = "0.7.10", features = ["io", "compat"] } +uuid = "1.7.0" +tokio-rustls = "0.23.1" +rustls = "0.20.0" +webpki-roots = "0.22.0" +aes = "0.8.4" +ecb = { version = "0.1.2", features = ["std"] } +cbc = { version = "0.1.2", features = ["std"] } +glob = "0.3.1" +md5 = "0.7.0" +bytes = "1.5.0" +async-trait = "0.1.81" +crc32fast = "1.4.2" +async-recursion = "1.1.1" +pelite = "0.10.0" +gethostname = "0.5.0" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } +thiserror = "2.0.3" +const-fnv1a-hash = "1.1.0" +tracing-appender = "0.2.3" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.9", features = [ + "memoryapi", + "handleapi", + "synchapi", + "wincon", + "consoleapi", + "psapi", + "wininet", + "winerror", + "iphlpapi", + "tcpmib", + "winsvc", + "impl-default" +] } +winreg = "0.51.0" +windows-service = "0.6.0" +is_elevated = "0.1.2" +widestring = "1.0.2" +dll-syringe = "0.15.2" +wmi = "0.13.1" + +[target.'cfg(target_os = "macos")'.dependencies] +smbios-lib = "0.9.1" + +[build-dependencies] +maxima_resources = { path = "../maxima_resources" } +prost-build = "0.12.3" diff --git a/crates/maxima_server/build.rs b/crates/maxima_server/build.rs new file mode 100644 index 0000000..866afe4 --- /dev/null +++ b/crates/maxima_server/build.rs @@ -0,0 +1,6 @@ +use maxima_resources::maxima_windows_rc; + +fn main() -> std::io::Result<()> { + prost_build::compile_protos(&["src/rtm/proto/rtm.proto"], &["src/rtm/proto/"])?; + maxima_windows_rc("maximaserver", "Maxima Server") +} diff --git a/crates/maxima_server/src/comp/auth.rs b/crates/maxima_server/src/comp/auth.rs new file mode 100644 index 0000000..18c6c82 --- /dev/null +++ b/crates/maxima_server/src/comp/auth.rs @@ -0,0 +1,28 @@ +use maxima_proto::{ + comm::proto::ProtoRequest, + comp::auth::{ServerAuthenticationComponent, AuthenticationError, CheckAuthRequest, LoginRequest}, +}; + +use crate::core::auth::storage::LockedAuthStorage; + +#[derive(Clone)] +pub struct AuthComponent { + pub auth_storage: LockedAuthStorage, +} + +#[maxima_proto::async_trait] +impl ServerAuthenticationComponent for AuthComponent { + async fn check(&self, request: ProtoRequest) -> Result { + let req = request.into_inner(); + let _allow_cached = req.allow_cached(); + + let mut auth_storage = self.auth_storage.lock().await; + let logged_in = auth_storage.logged_in().await; + + Ok(logged_in) + } + + async fn login(&self, _request: ProtoRequest) -> Result<(), AuthenticationError> { + Ok(()) + } +} diff --git a/crates/maxima_server/src/comp/library.rs b/crates/maxima_server/src/comp/library.rs new file mode 100644 index 0000000..1082b05 --- /dev/null +++ b/crates/maxima_server/src/comp/library.rs @@ -0,0 +1,22 @@ +use maxima_proto::{ + comm::proto::{ProtoError, ProtoRequest}, + comp::library::{LibraryError, ServerLibraryComponent}, + models::library::Game, +}; + +use crate::core::{auth::storage::LockedAuthStorage, user_man::LockedUserManager}; + +#[derive(Clone)] +pub struct LibraryComponent { + pub auth_storage: LockedAuthStorage, + pub user_man: LockedUserManager, +} + +#[maxima_proto::async_trait] +impl ServerLibraryComponent for LibraryComponent { + async fn games(&self, _request: ProtoRequest<()>) -> Result, LibraryError> { + let user_man = self.user_man.lock().await; + + Err(LibraryError::Proto(ProtoError::NoData)) + } +} diff --git a/crates/maxima_server/src/comp/mod.rs b/crates/maxima_server/src/comp/mod.rs new file mode 100644 index 0000000..57c653c --- /dev/null +++ b/crates/maxima_server/src/comp/mod.rs @@ -0,0 +1,4 @@ +pub mod auth; +pub mod library; +pub mod users; +pub mod util; diff --git a/crates/maxima_server/src/comp/users.rs b/crates/maxima_server/src/comp/users.rs new file mode 100644 index 0000000..f0c7e6d --- /dev/null +++ b/crates/maxima_server/src/comp/users.rs @@ -0,0 +1,34 @@ +use maxima_proto::{ + comm::proto::{ProtoError, ProtoRequest}, + comp::users::{ServerUsersComponent, UsersError}, + models::user::User, +}; + +use crate::core::{auth::storage::LockedAuthStorage, user_man::LockedUserManager}; + +#[derive(Clone)] +pub struct UsersComponent { + pub auth_storage: LockedAuthStorage, + pub user_man: LockedUserManager, +} + +#[maxima_proto::async_trait] +impl ServerUsersComponent for UsersComponent { + async fn local_user(&self, _request: ProtoRequest<()>) -> Result { + let user_man = self.user_man.lock().await; + + let res = match user_man.local_user().await { + Ok(user) => Ok(user), + Err(err) => { + // Blah. + if err.to_string().contains("UNAUTHENTICATED") { + Err(UsersError::Proto(ProtoError::Unauthenticated)) + } else { + Err(err)? + } + } + }; + + res + } +} diff --git a/crates/maxima_server/src/comp/util.rs b/crates/maxima_server/src/comp/util.rs new file mode 100644 index 0000000..934d5f9 --- /dev/null +++ b/crates/maxima_server/src/comp/util.rs @@ -0,0 +1,31 @@ +use tracing::info; + +use maxima_proto::{ + comm::proto::ProtoRequest, + comp::util::{IdentificationRequest, IdentificationResponse, ServerUtilitiesComponent, UtilitiesError}, +}; + +use crate::core::auth::storage::LockedAuthStorage; + +#[derive(Clone)] +pub struct UtilComponent { + pub auth_storage: LockedAuthStorage, +} + +#[maxima_proto::async_trait] +impl ServerUtilitiesComponent for UtilComponent { + async fn identify( + &self, + request: ProtoRequest, + ) -> Result { + let client_id = request.client_id(); + + let req = request.into_inner(); + info!("Client '{}' identified: {}", client_id, req.client_id()); + + Ok(IdentificationResponse::builder() + .client_id(client_id) + .server_version(env!("CARGO_PKG_VERSION").to_owned()) + .build()) + } +} diff --git a/maxima-lib/src/content/downloader.rs b/crates/maxima_server/src/content/downloader.rs similarity index 78% rename from maxima-lib/src/content/downloader.rs rename to crates/maxima_server/src/content/downloader.rs index 3d01ab7..4eb38d6 100644 --- a/maxima-lib/src/content/downloader.rs +++ b/crates/maxima_server/src/content/downloader.rs @@ -1,30 +1,35 @@ -use std::{cmp, io::{self, Seek, SeekFrom, Write}, path::{Path, PathBuf}, pin::Pin, prelude, sync::{Arc, Mutex}, task}; +use std::{ + io, + path::{Path, PathBuf}, + pin::Pin, + prelude, + sync::{Arc, Mutex}, + task, +}; +use crate::{ + content::{ + zip::CompressionType, + zlib::restore_zlib_state, + }, + util::native::maxima_dir, +}; use anyhow::{bail, Context, Result}; use async_compression::tokio::write::DeflateDecoder; use async_trait::async_trait; -use bytes::{Buf, BufMut, Bytes, BytesMut}; +use bytes::{Buf, Bytes}; use derive_getters::Getters; +use flate2::bufread::DeflateDecoder as BufreadDeflateDecoder; use futures::{Stream, StreamExt, TryStreamExt}; -use log::{debug, error, info, warn}; use reqwest::Client; -use strum_macros::Display; -use flate2::bufread::DeflateDecoder as BufreadDeflateDecoder; use std::io::{Cursor, Read}; +use strum_macros::Display; use tokio::{ fs::{create_dir, create_dir_all, File, OpenOptions}, - io::{AsyncSeekExt, AsyncWrite, BufReader, BufWriter}, - runtime::Handle, + io::{AsyncWrite, BufReader, BufWriter}, }; -use tokio::io::AsyncReadExt; use tokio_util::compat::FuturesAsyncReadCompatExt; -use crate::{ - content::{ - zip::CompressionType, - zlib::{restore_zlib_state, write_zlib_state}, - }, - util::{hash::hash_file_crc32, native::maxima_dir}, -}; +use tracing::{debug, error, warn}; use super::zip::{ZipFile, ZipFileEntry}; @@ -41,14 +46,7 @@ fn zstate_path(id: &str, path: &str) -> PathBuf { #[async_trait] trait DownloadDecoder: Send { - fn save_state(&mut self, buf: &mut BytesMut); fn restore_state(&mut self, buf: &mut Bytes); - - fn seek(&mut self, pos: SeekFrom); - - fn write_in_pos(&self) -> u64; - fn write_out_pos(&self) -> u64; - fn get_mut<'b>(&mut self) -> Arc>; } @@ -66,12 +64,6 @@ impl ZLibDeflateDecoder { #[async_trait] impl DownloadDecoder for ZLibDeflateDecoder { - fn save_state(&mut self, buf: &mut BytesMut) { - let mut decoder = self.decoder.lock().unwrap(); - let zstream = decoder.inner_mut().decoder_mut().inner.decompress.get_raw(); - write_zlib_state(buf, zstream); - } - fn restore_state(&mut self, buf: &mut Bytes) { let mut decoder = self.decoder.lock().unwrap(); let decompress = &mut decoder.inner_mut().decoder_mut().inner.decompress; @@ -80,29 +72,6 @@ impl DownloadDecoder for ZLibDeflateDecoder { restore_zlib_state(buf, zstream); } - fn seek(&mut self, pos: SeekFrom) { - let mut decoder = self.decoder.lock().unwrap(); - let file = decoder.get_mut(); - - let handle = Handle::current(); - let _ = handle.enter(); - futures::executor::block_on(file.seek(pos)).unwrap(); - } - - fn write_in_pos(&self) -> u64 { - let mut decoder = self.decoder.lock().unwrap(); - let decompress = &mut decoder.inner_mut().decoder_mut().inner.decompress; - let zstream = decompress.get_raw(); - zstream.total_in as u64 - } - - fn write_out_pos(&self) -> u64 { - let mut decoder = self.decoder.lock().unwrap(); - let decompress = &mut decoder.inner_mut().decoder_mut().inner.decompress; - let zstream = decompress.get_raw(); - zstream.total_out as u64 - } - fn get_mut(&mut self) -> Arc> { self.decoder.clone() } @@ -124,31 +93,10 @@ impl NoopDecoder { #[async_trait] impl DownloadDecoder for NoopDecoder { - fn save_state(&mut self, buf: &mut BytesMut) { - self.pos = self.writer.lock().unwrap().buffer().len() as u64; - buf.put_u64(self.pos); - } - fn restore_state(&mut self, buf: &mut Bytes) { self.pos = buf.get_u64(); } - fn seek(&mut self, pos: SeekFrom) { - let mut file = self.writer.lock().unwrap(); - - let handle = Handle::current(); - let _ = handle.enter(); - futures::executor::block_on(file.seek(pos)).unwrap(); - } - - fn write_in_pos(&self) -> u64 { - self.pos - } - - fn write_out_pos(&self) -> u64 { - self.pos - } - fn get_mut<'b>(&mut self) -> Arc> { self.writer.clone() } @@ -157,34 +105,20 @@ impl DownloadDecoder for NoopDecoder { trait AsyncWriteWrapper: AsyncWrite + Unpin + Send {} impl AsyncWriteWrapper for T {} -struct AsyncWriterWrapper<'a> { - id: String, - path: String, - zlib_state_file: std::fs::File, - decoder: &'a mut Box, +struct AsyncWriterWrapper { inner: Arc>, } -impl<'a> AsyncWriterWrapper<'a> { - async fn new(id: String, path: String, decoder: &'a mut Box) -> Self { +impl AsyncWriterWrapper { + async fn new(decoder: &mut Box) -> Self { let inner = decoder.get_mut(); - AsyncWriterWrapper { - id: id.to_owned(), - path: path.to_owned(), - zlib_state_file: std::fs::OpenOptions::new() - .write(true) - .create(true) - .open(zstate_path(&id, &path)) - .unwrap(), - decoder, - inner, - } + AsyncWriterWrapper { inner } } } -impl<'a> AsyncWrite for AsyncWriterWrapper<'a> { +impl AsyncWrite for AsyncWriterWrapper { fn poll_write( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8], ) -> task::Poll> { @@ -236,14 +170,12 @@ enum EntryDownloadState { } struct DownloadContext { - id: String, path: PathBuf, } type BytesDownloadedCallback = Box; struct EntryDownloadRequest<'a> { - context: &'a DownloadContext, url: &'a str, entry: &'a ZipFileEntry, client: Client, @@ -253,7 +185,6 @@ struct EntryDownloadRequest<'a> { impl<'a> EntryDownloadRequest<'a> { pub fn new( - context: &'a DownloadContext, url: &'a str, entry: &'a ZipFileEntry, client: Client, @@ -261,7 +192,6 @@ impl<'a> EntryDownloadRequest<'a> { callback: Option, ) -> Self { Self { - context, url, entry, client, @@ -371,12 +301,7 @@ impl<'a> EntryDownloadRequest<'a> { // let out_pos = self.decoder.write_out_pos(); // self.decoder.seek(SeekFrom::Start(out_pos)); - let mut wrapper = AsyncWriterWrapper::new( - self.context.id.to_owned(), - self.entry.name().to_owned(), - &mut self.decoder, - ) - .await; + let mut wrapper = AsyncWriterWrapper::new(&mut self.decoder).await; let result = tokio::io::copy(&mut stream_reader, &mut wrapper) .await @@ -419,19 +344,11 @@ impl ZipDownloader { }) } - pub async fn read_zip_entry_bytes( - &self, - entry: &ZipFileEntry, - length: u64, - ) -> Result { + pub async fn read_zip_entry_bytes(&self, entry: &ZipFileEntry, length: u64) -> Result { let offset = entry.data_offset(); let compressed_size = *entry.compressed_size(); - let range_header = format!( - "bytes={}-{}", - offset, - offset + compressed_size - 1 - ); + let range_header = format!("bytes={}-{}", offset, offset + compressed_size - 1); let response = self .client @@ -459,14 +376,13 @@ impl ZipDownloader { Bytes::copy_from_slice(&compressed_data[..available_length as usize]) } CompressionType::Deflate => { - let mut decoder = BufreadDeflateDecoder::new(Cursor::new(&compressed_data)); + let decoder = BufreadDeflateDecoder::new(Cursor::new(&compressed_data)); let mut limited_reader = decoder.take(length); let mut decompressed_data = Vec::with_capacity(length as usize); limited_reader.read_to_end(&mut decompressed_data)?; Bytes::from(decompressed_data) } - _ => bail!("Unsupported compression type"), }; Ok(decompressed_data) @@ -509,7 +425,6 @@ impl ZipDownloader { .await?; let context = DownloadContext { - id: self.id.to_owned(), path: self.path.clone(), }; @@ -544,7 +459,6 @@ impl ZipDownloader { } let mut request = EntryDownloadRequest::new( - &context, &self.url, entry, self.client.clone(), @@ -565,7 +479,7 @@ struct ByteCountingStream<'a, S> { impl<'a, S> ByteCountingStream<'a, S> where - S: Stream>, + S: Stream>, { fn new(inner: S, callback: Option<&'a BytesDownloadedCallback>) -> Self { ByteCountingStream { @@ -574,15 +488,11 @@ where callback, } } - - fn byte_count(&self) -> usize { - self.byte_count - } } impl<'a, S> Stream for ByteCountingStream<'a, S> where - S: Stream> + Unpin, + S: Stream> + Unpin, { type Item = Result; diff --git a/maxima-lib/src/content/manager.rs b/crates/maxima_server/src/content/manager.rs similarity index 99% rename from maxima-lib/src/content/manager.rs rename to crates/maxima_server/src/content/manager.rs index 3b6d28e..a33d746 100644 --- a/maxima-lib/src/content/manager.rs +++ b/crates/maxima_server/src/content/manager.rs @@ -10,7 +10,7 @@ use anyhow::{bail, Result}; use derive_builder::Builder; use derive_getters::Getters; use futures::StreamExt; -use log::{debug, error, info}; +use tracing::{debug, error, info}; use serde::{Deserialize, Serialize}; use tokio::{fs, sync::Notify}; use tokio_util::sync::CancellationToken; diff --git a/maxima-lib/src/content/mod.rs b/crates/maxima_server/src/content/mod.rs similarity index 100% rename from maxima-lib/src/content/mod.rs rename to crates/maxima_server/src/content/mod.rs diff --git a/maxima-lib/src/content/zip.rs b/crates/maxima_server/src/content/zip.rs similarity index 99% rename from maxima-lib/src/content/zip.rs rename to crates/maxima_server/src/content/zip.rs index aa28424..d1e9324 100644 --- a/maxima-lib/src/content/zip.rs +++ b/crates/maxima_server/src/content/zip.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result}; use bytebuffer::{ByteBuffer, Endian}; use derive_getters::Getters; use encoding::{all::WINDOWS_1252, DecoderTrap, Encoding}; -use log::{debug, warn}; +use tracing::{debug, warn}; use reqwest::Client; use std::cmp; diff --git a/maxima-lib/src/content/zlib.rs b/crates/maxima_server/src/content/zlib.rs similarity index 98% rename from maxima-lib/src/content/zlib.rs rename to crates/maxima_server/src/content/zlib.rs index 97c3c49..01123c6 100644 --- a/maxima-lib/src/content/zlib.rs +++ b/crates/maxima_server/src/content/zlib.rs @@ -5,7 +5,7 @@ use std::{ use bytes::{Buf, BufMut, Bytes, BytesMut}; use flate2::raw::{gz_headerp, mz_stream, z_streamp}; -use log::error; +use tracing::error; pub const Z_ENOUGH_LENS: usize = 852; pub const Z_ENOUGH_DISTS: usize = 592; @@ -90,7 +90,7 @@ macro_rules! if_win { type ZSize = if_win!(u32, c_ulong); type ZChecksum = if_win!(u32, c_ulong); -pub(crate) fn write_zlib_state(buf: &mut BytesMut, stream: &mut mz_stream) { +pub(crate) fn _write_zlib_state(buf: &mut BytesMut, stream: &mut mz_stream) { buf.put_u32(Z_MAGIC); buf.put_u64(stream.total_in as u64); diff --git a/maxima-lib/src/core/auth/context.rs b/crates/maxima_server/src/core/auth/context.rs similarity index 100% rename from maxima-lib/src/core/auth/context.rs rename to crates/maxima_server/src/core/auth/context.rs diff --git a/maxima-lib/src/core/auth/hardware.rs b/crates/maxima_server/src/core/auth/hardware.rs similarity index 99% rename from maxima-lib/src/core/auth/hardware.rs rename to crates/maxima_server/src/core/auth/hardware.rs index 94d55e9..f02626a 100644 --- a/maxima-lib/src/core/auth/hardware.rs +++ b/crates/maxima_server/src/core/auth/hardware.rs @@ -3,6 +3,7 @@ use gethostname::gethostname; use hex::ToHex; use regex::Regex; use ring::digest::SHA1_FOR_LEGACY_USE_ONLY; +use tracing::debug; use std::arch::x86_64::CpuidResult; #[derive(Debug)] @@ -49,7 +50,7 @@ impl HardwareInfo { pub fn new(version: u32) -> anyhow::Result { use std::collections::HashMap; - use log::warn; + use tracing::warn; use wmi::{COMLibrary, FilterValue, WMIConnection}; use crate::util::wmi_utils; @@ -443,11 +444,14 @@ impl HardwareInfo { let mut final_data = buffer.join(";").to_string(); final_data.push(';'); + if self.version >= 2 { final_data.push_str(&self.cpu_details.brand_name); final_data.push(';'); } - log::debug!("Hardware hash string \"{}\"", final_data); + + debug!("Hardware hash string \"{}\"", final_data); + let digest = ring::digest::digest(&SHA1_FOR_LEGACY_USE_ONLY, final_data.as_bytes()); if self.version < 4 { // they fucked up the format and used :x instead of :02x diff --git a/maxima-lib/src/core/auth/login.rs b/crates/maxima_server/src/core/auth/login.rs similarity index 100% rename from maxima-lib/src/core/auth/login.rs rename to crates/maxima_server/src/core/auth/login.rs diff --git a/maxima-lib/src/core/auth/mod.rs b/crates/maxima_server/src/core/auth/mod.rs similarity index 98% rename from maxima-lib/src/core/auth/mod.rs rename to crates/maxima_server/src/core/auth/mod.rs index 144a1f2..dd87f4c 100644 --- a/maxima-lib/src/core/auth/mod.rs +++ b/crates/maxima_server/src/core/auth/mod.rs @@ -128,7 +128,7 @@ pub async fn nucleus_connect_token_refresh(refresh_token: &str) -> Result Result { + async fn validate(&mut self) -> bool { let access_token = self.access_token().await; - if access_token.is_err() { - return Ok(false); + if let Err(err) = access_token { + warn!("Failed to retrieve access token: {err}"); + return false; } let access_token = access_token.unwrap().to_owned(); let token_info = NucleusTokenInfo::fetch(&self.client, &access_token).await; if token_info.is_err() { - return Ok(false); + return false; } if self.user_id != *token_info.unwrap().user_id() { - return Ok(false); + return false; } - Ok(true) + true } async fn refresh(&mut self) -> Result<()> { @@ -197,11 +198,14 @@ impl AuthStorage { Ok(()) } - pub async fn logged_in(&mut self) -> Result { - Ok(match self.current() { - Some(account) => account.validate().await?, + pub async fn logged_in(&mut self) -> bool { + let result = match self.current() { + Some(account) => account.validate().await, None => false, - }) + }; + + self.save_if_dirty().ok(); + result } pub fn current(&mut self) -> Option<&mut AuthAccount> { @@ -235,6 +239,10 @@ impl AuthStorage { Ok(Some(access_token)) } + pub async fn access_token_or_err(&mut self) -> Result { + self.access_token().await?.ok_or(CoreError::Unauthenticated) + } + /// Add an account from a token response and set it as the currently selected one pub async fn add_account(&mut self, response: &TokenResponse) -> Result<()> { let mut account = AuthAccount::from_token_response(response).await?; diff --git a/maxima-lib/src/core/auth/token_info.rs b/crates/maxima_server/src/core/auth/token_info.rs similarity index 100% rename from maxima-lib/src/core/auth/token_info.rs rename to crates/maxima_server/src/core/auth/token_info.rs diff --git a/maxima-lib/src/core/background_service_nix.rs b/crates/maxima_server/src/core/background_service_nix.rs similarity index 99% rename from maxima-lib/src/core/background_service_nix.rs rename to crates/maxima_server/src/core/background_service_nix.rs index 8f3d6e9..98dbe77 100644 --- a/maxima-lib/src/core/background_service_nix.rs +++ b/crates/maxima_server/src/core/background_service_nix.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Result}; use base64::{engine::general_purpose, Engine}; use lazy_static::lazy_static; -use log::debug; +use tracing::debug; use regex::Regex; use serde::Serialize; diff --git a/maxima-lib/src/core/background_service_win.rs b/crates/maxima_server/src/core/background_service_win.rs similarity index 98% rename from maxima-lib/src/core/background_service_win.rs rename to crates/maxima_server/src/core/background_service_win.rs index 26b57f1..033352e 100644 --- a/maxima-lib/src/core/background_service_win.rs +++ b/crates/maxima_server/src/core/background_service_win.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Result}; use dll_syringe::{process::OwnedProcess, Syringe}; -use log::debug; +use tracing::debug; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; diff --git a/maxima-lib/src/core/cache.rs b/crates/maxima_server/src/core/cache.rs similarity index 100% rename from maxima-lib/src/core/cache.rs rename to crates/maxima_server/src/core/cache.rs diff --git a/maxima-lib/src/core/clients.rs b/crates/maxima_server/src/core/clients.rs similarity index 100% rename from maxima-lib/src/core/clients.rs rename to crates/maxima_server/src/core/clients.rs diff --git a/maxima-lib/src/core/cloudsync.rs b/crates/maxima_server/src/core/cloudsync.rs similarity index 97% rename from maxima-lib/src/core/cloudsync.rs rename to crates/maxima_server/src/core/cloudsync.rs index cd188fc..0bf158f 100644 --- a/maxima-lib/src/core/cloudsync.rs +++ b/crates/maxima_server/src/core/cloudsync.rs @@ -1,19 +1,18 @@ use std::{ collections::HashMap, - env, path::{Path, PathBuf}, }; use anyhow::{bail, Context, Result}; use derive_getters::Getters; use futures::StreamExt; -use log::debug; use reqwest::{Client, ClientBuilder}; use serde::{Deserialize, Serialize}; use tokio::{ fs::{File, OpenOptions}, io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, }; +use tracing::debug; use super::{auth::storage::LockedAuthStorage, endpoints::API_CLOUDSYNC, library::OwnedOffer}; @@ -418,6 +417,7 @@ impl<'a> CloudSyncLock<'a> { } } +#[derive(Clone)] pub struct CloudSyncClient { auth: LockedAuthStorage, client: Client, @@ -430,8 +430,7 @@ impl CloudSyncClient { client: ClientBuilder::default() .gzip(true) .build() - .context("Failed to build CloudSync HTTP client") - .unwrap(), + .expect("Failed to build CloudSync HTTP client"), } } @@ -522,11 +521,13 @@ mod tests { #[tokio::test] async fn read_files() -> Result<()> { let auth = AuthStorage::load()?; - if !auth.lock().await.logged_in().await? { + if !auth.lock().await.logged_in().await { bail!("Test cannot run when logged out"); } - let mut library = GameLibrary::new(auth.clone()).await; + let library = GameLibrary::new(auth.clone()).await; + let mut library = library.lock().await; + let offer = library .game_by_base_slug("star-wars-battlefront-2") .await @@ -545,11 +546,13 @@ mod tests { #[tokio::test] async fn write_files() -> Result<()> { let auth = AuthStorage::load()?; - if !auth.lock().await.logged_in().await? { + if !auth.lock().await.logged_in().await { bail!("Test cannot run when logged out"); } - let mut library = GameLibrary::new(auth.clone()).await; + let library = GameLibrary::new(auth.clone()).await; + let mut library = library.lock().await; + let offer = library .game_by_base_slug("star-wars-battlefront-2") .await diff --git a/maxima-lib/src/core/concurrency.rs b/crates/maxima_server/src/core/concurrency.rs similarity index 100% rename from maxima-lib/src/core/concurrency.rs rename to crates/maxima_server/src/core/concurrency.rs diff --git a/maxima-lib/src/core/dip.rs b/crates/maxima_server/src/core/dip.rs similarity index 100% rename from maxima-lib/src/core/dip.rs rename to crates/maxima_server/src/core/dip.rs diff --git a/maxima-lib/src/core/ecommerce.rs b/crates/maxima_server/src/core/ecommerce.rs similarity index 99% rename from maxima-lib/src/core/ecommerce.rs rename to crates/maxima_server/src/core/ecommerce.rs index 511187f..b7675ee 100644 --- a/maxima-lib/src/core/ecommerce.rs +++ b/crates/maxima_server/src/core/ecommerce.rs @@ -69,7 +69,6 @@ macro_rules! ecommerce_type { } ) => { paste::paste! { - // Main struct definition $(#[$message_attr])* #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] diff --git a/maxima-lib/src/core/endpoints.rs b/crates/maxima_server/src/core/endpoints.rs similarity index 100% rename from maxima-lib/src/core/endpoints.rs rename to crates/maxima_server/src/core/endpoints.rs diff --git a/crates/maxima_server/src/core/error.rs b/crates/maxima_server/src/core/error.rs new file mode 100644 index 0000000..b92b300 --- /dev/null +++ b/crates/maxima_server/src/core/error.rs @@ -0,0 +1,23 @@ +use std::io; + +#[derive(thiserror::Error, Debug)] +pub enum CoreError { + #[error("IO error: {0}")] + IoError(#[from] io::Error), + #[error("Anyhow error: {0}")] + AnyhowError(#[from] anyhow::Error), + #[error("Unauthenticated")] + Unauthenticated, + #[error("Offer not found")] + OfferNotFound, + #[error("Cannot launch game '{0}' as it is not installed")] + LaunchGameNotInstalled(String), + #[error("Game path must be specified when launching in OnlineOffline mode")] + LaunchGamePathRequired, + #[error("The path to the game was not able to be automatically found")] + LaunchGamePathNotFound, + #[error("Content ID was specified as an offer ID when launching in OnlineOffline mode: {0}")] + LaunchContentIdRequired(String), + #[error("Offline mode is not yet supported")] + LaunchOfflineUnsupported +} diff --git a/maxima-lib/src/core/graphql/AcceptFriendInvitation.gql b/crates/maxima_server/src/core/graphql/AcceptFriendInvitation.gql similarity index 100% rename from maxima-lib/src/core/graphql/AcceptFriendInvitation.gql rename to crates/maxima_server/src/core/graphql/AcceptFriendInvitation.gql diff --git a/maxima-lib/src/core/graphql/CancelFriendInvitation.gql b/crates/maxima_server/src/core/graphql/CancelFriendInvitation.gql similarity index 100% rename from maxima-lib/src/core/graphql/CancelFriendInvitation.gql rename to crates/maxima_server/src/core/graphql/CancelFriendInvitation.gql diff --git a/maxima-lib/src/core/graphql/CriticalGetUserPlayer.gql b/crates/maxima_server/src/core/graphql/CriticalGetUserPlayer.gql similarity index 100% rename from maxima-lib/src/core/graphql/CriticalGetUserPlayer.gql rename to crates/maxima_server/src/core/graphql/CriticalGetUserPlayer.gql diff --git a/maxima-lib/src/core/graphql/FullGameOverview.gql b/crates/maxima_server/src/core/graphql/FullGameOverview.gql similarity index 100% rename from maxima-lib/src/core/graphql/FullGameOverview.gql rename to crates/maxima_server/src/core/graphql/FullGameOverview.gql diff --git a/maxima-lib/src/core/graphql/GameBundle.gql b/crates/maxima_server/src/core/graphql/GameBundle.gql similarity index 100% rename from maxima-lib/src/core/graphql/GameBundle.gql rename to crates/maxima_server/src/core/graphql/GameBundle.gql diff --git a/maxima-lib/src/core/graphql/GameImages.gql b/crates/maxima_server/src/core/graphql/GameImages.gql similarity index 100% rename from maxima-lib/src/core/graphql/GameImages.gql rename to crates/maxima_server/src/core/graphql/GameImages.gql diff --git a/maxima-lib/src/core/graphql/GameSystemRequirements.gql b/crates/maxima_server/src/core/graphql/GameSystemRequirements.gql similarity index 100% rename from maxima-lib/src/core/graphql/GameSystemRequirements.gql rename to crates/maxima_server/src/core/graphql/GameSystemRequirements.gql diff --git a/maxima-lib/src/core/graphql/GetActiveSalesWithToast.gql b/crates/maxima_server/src/core/graphql/GetActiveSalesWithToast.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetActiveSalesWithToast.gql rename to crates/maxima_server/src/core/graphql/GetActiveSalesWithToast.gql diff --git a/maxima-lib/src/core/graphql/GetBasicPlayer.gql b/crates/maxima_server/src/core/graphql/GetBasicPlayer.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetBasicPlayer.gql rename to crates/maxima_server/src/core/graphql/GetBasicPlayer.gql diff --git a/maxima-lib/src/core/graphql/GetDlcsForGame.gql b/crates/maxima_server/src/core/graphql/GetDlcsForGame.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetDlcsForGame.gql rename to crates/maxima_server/src/core/graphql/GetDlcsForGame.gql diff --git a/maxima-lib/src/core/graphql/GetGamePlayTimes.gql b/crates/maxima_server/src/core/graphql/GetGamePlayTimes.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetGamePlayTimes.gql rename to crates/maxima_server/src/core/graphql/GetGamePlayTimes.gql diff --git a/maxima-lib/src/core/graphql/GetGameProductOfferIds.gql b/crates/maxima_server/src/core/graphql/GetGameProductOfferIds.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetGameProductOfferIds.gql rename to crates/maxima_server/src/core/graphql/GetGameProductOfferIds.gql diff --git a/maxima-lib/src/core/graphql/GetHeroBackgroundImage.gql b/crates/maxima_server/src/core/graphql/GetHeroBackgroundImage.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetHeroBackgroundImage.gql rename to crates/maxima_server/src/core/graphql/GetHeroBackgroundImage.gql diff --git a/maxima-lib/src/core/graphql/GetInitUserData.gql b/crates/maxima_server/src/core/graphql/GetInitUserData.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetInitUserData.gql rename to crates/maxima_server/src/core/graphql/GetInitUserData.gql diff --git a/maxima-lib/src/core/graphql/GetMyFriendInvitations.gql b/crates/maxima_server/src/core/graphql/GetMyFriendInvitations.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetMyFriendInvitations.gql rename to crates/maxima_server/src/core/graphql/GetMyFriendInvitations.gql diff --git a/maxima-lib/src/core/graphql/GetMyFriends.gql b/crates/maxima_server/src/core/graphql/GetMyFriends.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetMyFriends.gql rename to crates/maxima_server/src/core/graphql/GetMyFriends.gql diff --git a/maxima-lib/src/core/graphql/GetPlayerByPdLite.gql b/crates/maxima_server/src/core/graphql/GetPlayerByPdLite.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetPlayerByPdLite.gql rename to crates/maxima_server/src/core/graphql/GetPlayerByPdLite.gql diff --git a/maxima-lib/src/core/graphql/GetScreenshotsAndVideos.gql b/crates/maxima_server/src/core/graphql/GetScreenshotsAndVideos.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetScreenshotsAndVideos.gql rename to crates/maxima_server/src/core/graphql/GetScreenshotsAndVideos.gql diff --git a/maxima-lib/src/core/graphql/GetSubscriptionStatus.gql b/crates/maxima_server/src/core/graphql/GetSubscriptionStatus.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetSubscriptionStatus.gql rename to crates/maxima_server/src/core/graphql/GetSubscriptionStatus.gql diff --git a/maxima-lib/src/core/graphql/GetUserPdWithGameAndFriendsCount.gql b/crates/maxima_server/src/core/graphql/GetUserPdWithGameAndFriendsCount.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetUserPdWithGameAndFriendsCount.gql rename to crates/maxima_server/src/core/graphql/GetUserPdWithGameAndFriendsCount.gql diff --git a/maxima-lib/src/core/graphql/GetUserPlayer.gql b/crates/maxima_server/src/core/graphql/GetUserPlayer.gql similarity index 100% rename from maxima-lib/src/core/graphql/GetUserPlayer.gql rename to crates/maxima_server/src/core/graphql/GetUserPlayer.gql diff --git a/maxima-lib/src/core/graphql/RejectFriendInvitation.gql b/crates/maxima_server/src/core/graphql/RejectFriendInvitation.gql similarity index 100% rename from maxima-lib/src/core/graphql/RejectFriendInvitation.gql rename to crates/maxima_server/src/core/graphql/RejectFriendInvitation.gql diff --git a/maxima-lib/src/core/graphql/SearchPlayer.gql b/crates/maxima_server/src/core/graphql/SearchPlayer.gql similarity index 100% rename from maxima-lib/src/core/graphql/SearchPlayer.gql rename to crates/maxima_server/src/core/graphql/SearchPlayer.gql diff --git a/maxima-lib/src/core/graphql/SendFriendInvitation.gql b/crates/maxima_server/src/core/graphql/SendFriendInvitation.gql similarity index 100% rename from maxima-lib/src/core/graphql/SendFriendInvitation.gql rename to crates/maxima_server/src/core/graphql/SendFriendInvitation.gql diff --git a/maxima-lib/src/core/graphql/availableBuilds.gql b/crates/maxima_server/src/core/graphql/availableBuilds.gql similarity index 100% rename from maxima-lib/src/core/graphql/availableBuilds.gql rename to crates/maxima_server/src/core/graphql/availableBuilds.gql diff --git a/maxima-lib/src/core/graphql/contentful/GetComposableGameHub.gql b/crates/maxima_server/src/core/graphql/contentful/GetComposableGameHub.gql similarity index 100% rename from maxima-lib/src/core/graphql/contentful/GetComposableGameHub.gql rename to crates/maxima_server/src/core/graphql/contentful/GetComposableGameHub.gql diff --git a/maxima-lib/src/core/graphql/contentful/GetProductFeatureTable.gql b/crates/maxima_server/src/core/graphql/contentful/GetProductFeatureTable.gql similarity index 100% rename from maxima-lib/src/core/graphql/contentful/GetProductFeatureTable.gql rename to crates/maxima_server/src/core/graphql/contentful/GetProductFeatureTable.gql diff --git a/maxima-lib/src/core/graphql/contentful/README.md b/crates/maxima_server/src/core/graphql/contentful/README.md similarity index 100% rename from maxima-lib/src/core/graphql/contentful/README.md rename to crates/maxima_server/src/core/graphql/contentful/README.md diff --git a/maxima-lib/src/core/graphql/downloadUrl.gql b/crates/maxima_server/src/core/graphql/downloadUrl.gql similarity index 100% rename from maxima-lib/src/core/graphql/downloadUrl.gql rename to crates/maxima_server/src/core/graphql/downloadUrl.gql diff --git a/maxima-lib/src/core/graphql/gameAchievements.gql b/crates/maxima_server/src/core/graphql/gameAchievements.gql similarity index 100% rename from maxima-lib/src/core/graphql/gameAchievements.gql rename to crates/maxima_server/src/core/graphql/gameAchievements.gql diff --git a/maxima-lib/src/core/graphql/gameSessionEnd.gql b/crates/maxima_server/src/core/graphql/gameSessionEnd.gql similarity index 100% rename from maxima-lib/src/core/graphql/gameSessionEnd.gql rename to crates/maxima_server/src/core/graphql/gameSessionEnd.gql diff --git a/maxima-lib/src/core/graphql/getGameProducts.gql b/crates/maxima_server/src/core/graphql/getGameProducts.gql similarity index 100% rename from maxima-lib/src/core/graphql/getGameProducts.gql rename to crates/maxima_server/src/core/graphql/getGameProducts.gql diff --git a/maxima-lib/src/core/graphql/getLegacyCatalogDefs.gql b/crates/maxima_server/src/core/graphql/getLegacyCatalogDefs.gql similarity index 100% rename from maxima-lib/src/core/graphql/getLegacyCatalogDefs.gql rename to crates/maxima_server/src/core/graphql/getLegacyCatalogDefs.gql diff --git a/maxima-lib/src/core/graphql/getPreloadedOwnedGames.gql b/crates/maxima_server/src/core/graphql/getPreloadedOwnedGames.gql similarity index 100% rename from maxima-lib/src/core/graphql/getPreloadedOwnedGames.gql rename to crates/maxima_server/src/core/graphql/getPreloadedOwnedGames.gql diff --git a/maxima-lib/src/core/graphql/grantEntitlement.gql b/crates/maxima_server/src/core/graphql/grantEntitlement.gql similarity index 100% rename from maxima-lib/src/core/graphql/grantEntitlement.gql rename to crates/maxima_server/src/core/graphql/grantEntitlement.gql diff --git a/maxima-lib/src/core/graphql/inGamePresenceData.gql b/crates/maxima_server/src/core/graphql/inGamePresenceData.gql similarity index 100% rename from maxima-lib/src/core/graphql/inGamePresenceData.gql rename to crates/maxima_server/src/core/graphql/inGamePresenceData.gql diff --git a/maxima-lib/src/core/graphql/ownedGameAchievements.gql b/crates/maxima_server/src/core/graphql/ownedGameAchievements.gql similarity index 100% rename from maxima-lib/src/core/graphql/ownedGameAchievements.gql rename to crates/maxima_server/src/core/graphql/ownedGameAchievements.gql diff --git a/maxima-lib/src/core/launch.rs b/crates/maxima_server/src/core/launch.rs similarity index 86% rename from maxima-lib/src/core/launch.rs rename to crates/maxima_server/src/core/launch.rs index ceb6bef..1c6426d 100644 --- a/maxima-lib/src/core/launch.rs +++ b/crates/maxima_server/src/core/launch.rs @@ -1,11 +1,11 @@ use base64::{engine::general_purpose, Engine}; use derive_getters::Getters; -use log::{error, info}; use std::{env, fmt::Display, path::PathBuf, sync::Arc}; use tokio::{ process::{Child, Command}, sync::Mutex, }; +use tracing::{error, info}; use uuid::Uuid; use anyhow::{bail, Result}; @@ -21,7 +21,14 @@ use crate::unix::fs::case_insensitive_path; use serde::{Deserialize, Serialize}; -use super::{library::OwnedOffer, Maxima}; +use super::{ + auth::storage::{AuthStorage, LockedAuthStorage}, + cloudsync::CloudSyncClient, + error::CoreError, + library::{GameLibrary, LockedGameLibrary, OwnedOffer}, + user_man::UserManager, + Maxima, +}; pub enum StartupStage { Launch, @@ -112,35 +119,37 @@ impl Display for LaunchMode { } pub async fn start_game( - maxima_arc: Arc>, + auth_storage: &AuthStorage, + library: &mut GameLibrary, + cloud_sync: &CloudSyncClient, + user_man: &UserManager, mode: LaunchMode, game_path_override: Option, mut game_args: Vec, -) -> Result<()> { - let mut maxima = maxima_arc.lock().await; +) -> Result<(), CoreError> { info!("Initiating game launch with {}...", mode); if let LaunchMode::OnlineOffline(ref content_id, _, _) = mode { if game_path_override.is_none() { - bail!("Game path must be specified when launching in OnlineOffline mode"); + return Err(CoreError::LaunchGamePathRequired); } if content_id.starts_with("Origin.OFR") { - bail!("Content ID was specified as an offer ID when launching in OnlineOffline mode"); + return Err(CoreError::LaunchContentIdRequired(content_id.to_owned())); } } let (content_id, online_offline, offer, access_token) = if let LaunchMode::Online(ref offer_id) = mode { - let access_token = &maxima.access_token().await?; - let offer = maxima.mut_library().game_by_base_offer(offer_id).await; + let access_token = &auth_storage.access_token_or_err().await?; + let offer = library.game_by_base_offer(offer_id).await; if offer.is_none() { - bail!("Offer not found"); + return Err(CoreError::OfferNotFound); } let offer = offer.unwrap(); if !offer.installed().await { - bail!("Game is not installed"); + return Err(CoreError::LaunchGameNotInstalled(offer.slug().to_owned())); } let content_id = offer.offer().content_id().to_owned(); @@ -154,7 +163,7 @@ pub async fn start_game( } else if let LaunchMode::OnlineOffline(ref content_id, _, _) = mode { (content_id.to_owned(), true, None, String::new()) } else { - bail!("Offline mode is not yet supported"); + return Err(CoreError::LaunchOfflineUnsupported); }; // Need to move this into Maxima and have a "current game" system @@ -163,7 +172,7 @@ pub async fn start_game( } else if !online_offline { offer.as_ref().unwrap().execute_path(false).await? } else { - bail!("Game path not found"); + return Err(CoreError::LaunchGamePathNotFound); }; let dir = path.parent().unwrap().to_str().unwrap(); @@ -178,7 +187,7 @@ pub async fn start_game( match mode { LaunchMode::Offline(_) => {} LaunchMode::Online(_) => { - let auth = LicenseAuth::AccessToken(maxima.access_token().await?); + let auth = LicenseAuth::AccessToken(auth_storage.access_token_or_err().await?); let offer = offer.as_ref().unwrap(); if needs_license_update(&content_id).await? { @@ -195,10 +204,7 @@ pub async fn start_game( if offer.offer().has_cloud_save() { info!("Syncing with cloud save..."); - let result = maxima - .cloud_sync() - .obtain_lock(offer, CloudSyncLockMode::Read) - .await; + let result = cloud_sync.obtain_lock(offer, CloudSyncLockMode::Read).await; if let Err(err) = result { error!("Failed to obtain CloudSync read lock: {}", err); } else { @@ -237,7 +243,7 @@ pub async fn start_game( let b64 = general_purpose::STANDARD.encode(serde_json::to_string(&bootstrap_args).unwrap()); child.arg(b64); - let user = maxima.local_user().await?; + let user = user_man.local_user().await?; let launch_id = Uuid::new_v4().to_string(); child @@ -251,10 +257,7 @@ pub async fn start_game( .env("EAGameLocale", maxima.locale.full_str()) .env("EAGenericAuthToken", access_token.to_owned()) .env("EALaunchCode", "") - .env( - "EALaunchEAID", - user.player().as_ref().unwrap().display_name(), - ) + .env("EALaunchEAID", user.display_name()) .env("EALaunchEnv", "production") .env("EALaunchOfflineMode", "false") .env("EALsxPort", maxima.lsx_port.to_string()) @@ -287,7 +290,7 @@ pub async fn start_game( let child = child.spawn().expect("Failed to start child"); - maxima.playing = Some(ActiveGameContext::new( + library.add_context(ActiveGameContext::new( &launch_id, dir, &content_id, @@ -301,7 +304,10 @@ pub async fn start_game( #[cfg(unix)] pub async fn mx_linux_setup() -> Result<()> { - use crate::unix::wine::{check_eac_runtime_validity, check_wine_validity, install_eac, install_wine, setup_wine_registry}; + use crate::unix::wine::{ + check_eac_runtime_validity, check_wine_validity, install_eac, install_wine, + setup_wine_registry, + }; info!("Verifying wine dependencies..."); diff --git a/maxima-lib/src/core/library.rs b/crates/maxima_server/src/core/library.rs similarity index 94% rename from maxima-lib/src/core/library.rs rename to crates/maxima_server/src/core/library.rs index 435aa05..630c850 100644 --- a/maxima-lib/src/core/library.rs +++ b/crates/maxima_server/src/core/library.rs @@ -1,7 +1,8 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; use anyhow::{bail, Result}; use derive_getters::Getters; +use tokio::sync::Mutex; use crate::util::registry::{parse_partial_registry_path, parse_registry_path}; @@ -9,16 +10,13 @@ use crate::util::registry::{parse_partial_registry_path, parse_registry_path}; use crate::unix::fs::case_insensitive_path; use super::{ - auth::storage::LockedAuthStorage, - dip::{DiPManifest, DIP_RELATIVE_PATH}, - locale::Locale, - service_layer::{ + auth::storage::LockedAuthStorage, dip::{DiPManifest, DIP_RELATIVE_PATH}, launch::ActiveGameContext, locale::Locale, service_layer::{ ServiceGameProductType, ServiceGetLegacyCatalogDefsRequestBuilder, ServiceGetPreloadedOwnedGamesRequest, ServiceGetPreloadedOwnedGamesRequestBuilder, ServiceLayerClient, ServiceLegacyOffer, ServicePlatform, ServiceStorefront, ServiceUser, ServiceUserGameProduct, SERVICE_REQUEST_GETLEGACYCATALOGDEFS, SERVICE_REQUEST_GETPRELOADEDOWNEDGAMES, - }, + } }; #[derive(Clone, Getters)] @@ -238,15 +236,20 @@ pub struct GameLibrary { service_layer: ServiceLayerClient, library: Vec, last_request: u64, + + contexts: Vec, } +pub type LockedGameLibrary = Arc>; + impl GameLibrary { - pub async fn new(auth: LockedAuthStorage) -> Self { - Self { + pub async fn new(auth: LockedAuthStorage) -> LockedGameLibrary { + Arc::new(Mutex::new(Self { service_layer: ServiceLayerClient::new(auth), library: Vec::new(), last_request: 0, - } + contexts: Vec::new(), + })) } pub async fn games(&mut self) -> &Vec { @@ -297,7 +300,6 @@ impl GameLibrary { let request = GameLibrary::library_request( &locale, ServiceGameProductType::DigitalFullGame, - true, page, )?; @@ -356,7 +358,6 @@ impl GameLibrary { fn library_request( locale: &Locale, r#type: ServiceGameProductType, - entitlement_enabled: bool, page: u32, ) -> Result { Ok(ServiceGetPreloadedOwnedGamesRequestBuilder::default() @@ -374,4 +375,8 @@ impl GameLibrary { .platforms(vec![ServicePlatform::Pc]) .build()?) } + + pub fn add_context(&mut self, context: ActiveGameContext) { + self.contexts.push(context); + } } diff --git a/maxima-lib/src/core/locale.rs b/crates/maxima_server/src/core/locale.rs similarity index 81% rename from maxima-lib/src/core/locale.rs rename to crates/maxima_server/src/core/locale.rs index 1cef249..33bc8d2 100644 --- a/maxima-lib/src/core/locale.rs +++ b/crates/maxima_server/src/core/locale.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] pub enum Locale { + #[default] EnUs, } diff --git a/maxima-lib/src/core/mod.rs b/crates/maxima_server/src/core/mod.rs similarity index 97% rename from maxima-lib/src/core/mod.rs rename to crates/maxima_server/src/core/mod.rs index 3dfde5f..f46081c 100644 --- a/maxima-lib/src/core/mod.rs +++ b/crates/maxima_server/src/core/mod.rs @@ -11,6 +11,7 @@ pub mod library; pub mod locale; pub mod service_layer; pub mod settings; +pub mod user_man; #[cfg(target_os = "windows")] mod background_service_win; @@ -39,7 +40,8 @@ use anyhow::{bail, Result}; use cloudsync::{CloudSyncClient, CloudSyncLockMode}; use derive_builder::Builder; use derive_getters::Getters; -use log::{error, info, warn}; +use library::LockedGameLibrary; +use tracing::{error, info, warn}; use strum_macros::IntoStaticStr; use std::sync::Arc; @@ -87,7 +89,7 @@ pub struct Maxima { service_layer: ServiceLayerClient, #[getter(skip)] - library: GameLibrary, + library: LockedGameLibrary, playing: Option, @@ -370,13 +372,13 @@ impl Maxima { Ok(dir.join(format!("{}_{}x{}.jpg", id, width, height))) } - pub fn library(&self) -> &GameLibrary { - &self.library - } + // pub fn library(&self) -> &GameLibrary { + // &self.library + // } - pub fn mut_library(&mut self) -> &mut GameLibrary { - &mut self.library - } + // pub fn mut_library(&mut self) -> &mut GameLibrary { + // &mut self.library + // } pub fn content_manager(&mut self) -> &mut ContentManager { &mut self.content_manager diff --git a/maxima-lib/src/core/service_layer.rs b/crates/maxima_server/src/core/service_layer.rs similarity index 96% rename from maxima-lib/src/core/service_layer.rs rename to crates/maxima_server/src/core/service_layer.rs index 08f6287..9b2d8b4 100644 --- a/maxima-lib/src/core/service_layer.rs +++ b/crates/maxima_server/src/core/service_layer.rs @@ -2,19 +2,19 @@ use anyhow::{bail, Result}; -use log::debug; use reqwest::{Client, StatusCode}; use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2_const::Sha256; +use tracing::debug; use derive_builder::Builder; use derive_getters::Getters; -use crate::core::endpoints::API_CONTENTFUL_PROXY; - use super::{ - auth::storage::LockedAuthStorage, endpoints::API_SERVICE_AGGREGATION_LAYER, locale::Locale, + auth::storage::LockedAuthStorage, + endpoints::{API_CONTENTFUL_PROXY, API_SERVICE_AGGREGATION_LAYER}, + locale::Locale, }; #[derive(Serialize)] @@ -49,7 +49,7 @@ pub struct ServiceLayerGraphQLRequest { operation: &'static str, key: &'static str, hash: [u8; 32], - r#type: ServiceLayerRequestType + r#type: ServiceLayerRequestType, } macro_rules! load_graphql_request { @@ -61,7 +61,7 @@ macro_rules! load_graphql_request { operation: $operation, key: $key, hash, - r#type: ServiceLayerRequestType::$type + r#type: ServiceLayerRequestType::$type, } }}; } @@ -217,7 +217,7 @@ impl ServiceLayerClient { macro_rules! service_layer_type { ($name:ident, { $($field:tt)* }) => { paste::paste! { - #[derive(Clone, Debug, Serialize, Deserialize, Getters, Builder)] + #[derive(Clone, Debug, Default, Serialize, Deserialize, Getters, Builder)] #[serde(rename_all = "camelCase")] #[repr(C)] pub struct [] { @@ -230,10 +230,11 @@ macro_rules! service_layer_type { macro_rules! service_layer_enum { ($name:ident, { $($field:tt)* }) => { paste::paste! { - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] + #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[repr(C)] pub enum [] { + #[default] $($field)* } } @@ -487,7 +488,9 @@ impl ServiceAvailableBuilds { } pub fn build(&self, id: &str) -> Option<&ServiceAvailableBuild> { - self.builds.iter().find(|b| b.game_version() == &Some(id.to_owned())) + self.builds + .iter() + .find(|b| b.game_version() == &Some(id.to_owned())) } } @@ -622,13 +625,15 @@ service_layer_type!(LegacyOffer, { impl ServiceLegacyOffer { pub fn has_cloud_save(&self) -> bool { - !self.cloud_save_configuration_override.clone().unwrap_or_default().is_empty() + !self + .cloud_save_configuration_override + .clone() + .unwrap_or_default() + .is_empty() } } -service_layer_type!(RecentGames, { - -}); +service_layer_type!(RecentGames, {}); service_layer_type!(HeroBackgroundImageRequest, { game_slug: String, diff --git a/maxima-lib/src/core/settings.rs b/crates/maxima_server/src/core/settings.rs similarity index 100% rename from maxima-lib/src/core/settings.rs rename to crates/maxima_server/src/core/settings.rs diff --git a/crates/maxima_server/src/core/user_man.rs b/crates/maxima_server/src/core/user_man.rs new file mode 100644 index 0000000..89133b9 --- /dev/null +++ b/crates/maxima_server/src/core/user_man.rs @@ -0,0 +1,67 @@ +use std::{sync::Arc, time::Duration}; + +use anyhow::{bail, Result}; +use maxima_proto::models::user::User; +use moka::sync::Cache; +use tokio::sync::Mutex; + +use super::{ + auth::storage::LockedAuthStorage, + service_layer::{ + ServiceGetUserPlayerRequest, ServiceLayerClient, ServiceUser, SERVICE_REQUEST_GETUSERPLAYER, + }, +}; + +fn service_to_proto_user(user: &ServiceUser) -> User { + let player = user.player().clone().unwrap_or_default(); + + User::builder() + .account_id(user.id().to_owned()) + .persona_id(player.psd().to_owned()) + .display_name(player.display_name().to_owned()) + .unique_name(player.unique_name().to_owned()) + .nickname(player.nickname().to_owned()) + .build() +} + +pub struct UserManager { + client: ServiceLayerClient, + cached_users: Cache, +} + +pub type LockedUserManager = Arc>; + +impl UserManager { + pub fn new(auth_storage: LockedAuthStorage) -> LockedUserManager { + let s = Self { + client: ServiceLayerClient::new(auth_storage), + cached_users: Cache::builder() + .max_capacity(128) + .time_to_live(Duration::from_secs(30 * 60)) + .time_to_idle(Duration::from_secs(5 * 60)) + .build(), + }; + + Arc::new(Mutex::new(s)) + } + + pub async fn local_user(&self) -> Result { + if !self.cached_users.contains_key("0") { + let user: ServiceUser = self + .client + .request( + SERVICE_REQUEST_GETUSERPLAYER, + ServiceGetUserPlayerRequest {}, + ) + .await?; + + self.cached_users + .insert("0".to_owned(), service_to_proto_user(&user)); + } + + match self.cached_users.get("0") { + Some(user) => Ok(user), + None => bail!("No user"), + } + } +} diff --git a/maxima-lib/src/lsx/connection.rs b/crates/maxima_server/src/lsx/connection.rs similarity index 99% rename from maxima-lib/src/lsx/connection.rs rename to crates/maxima_server/src/lsx/connection.rs index 7a3cae4..9f0223e 100644 --- a/maxima-lib/src/lsx/connection.rs +++ b/crates/maxima_server/src/lsx/connection.rs @@ -10,7 +10,7 @@ use anyhow::{bail, Result}; use derive_getters::Getters; use lazy_static::lazy_static; -use log::{debug, error, warn}; +use tracing::{debug, error, warn}; use regex::Regex; use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; use tokio::sync::{MutexGuard, RwLock}; diff --git a/maxima-lib/src/lsx/mod.rs b/crates/maxima_server/src/lsx/mod.rs similarity index 100% rename from maxima-lib/src/lsx/mod.rs rename to crates/maxima_server/src/lsx/mod.rs diff --git a/maxima-lib/src/lsx/request/account.rs b/crates/maxima_server/src/lsx/request/account.rs similarity index 100% rename from maxima-lib/src/lsx/request/account.rs rename to crates/maxima_server/src/lsx/request/account.rs diff --git a/maxima-lib/src/lsx/request/auth.rs b/crates/maxima_server/src/lsx/request/auth.rs similarity index 97% rename from maxima-lib/src/lsx/request/auth.rs rename to crates/maxima_server/src/lsx/request/auth.rs index 5cfc1c2..6f794c4 100644 --- a/maxima-lib/src/lsx/request/auth.rs +++ b/crates/maxima_server/src/lsx/request/auth.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use log::{error, info}; +use tracing::{error, info}; use crate::{ core::auth::{context::AuthContext, nucleus_auth_exchange}, diff --git a/maxima-lib/src/lsx/request/challenge.rs b/crates/maxima_server/src/lsx/request/challenge.rs similarity index 98% rename from maxima-lib/src/lsx/request/challenge.rs rename to crates/maxima_server/src/lsx/request/challenge.rs index 220417c..6eb2e7f 100644 --- a/maxima-lib/src/lsx/request/challenge.rs +++ b/crates/maxima_server/src/lsx/request/challenge.rs @@ -1,5 +1,5 @@ use anyhow::{bail, Result}; -use log::{debug, info}; +use tracing::{debug, info}; use crate::{ lsx::{ diff --git a/maxima-lib/src/lsx/request/config.rs b/crates/maxima_server/src/lsx/request/config.rs similarity index 100% rename from maxima-lib/src/lsx/request/config.rs rename to crates/maxima_server/src/lsx/request/config.rs diff --git a/maxima-lib/src/lsx/request/core.rs b/crates/maxima_server/src/lsx/request/core.rs similarity index 100% rename from maxima-lib/src/lsx/request/core.rs rename to crates/maxima_server/src/lsx/request/core.rs diff --git a/maxima-lib/src/lsx/request/game.rs b/crates/maxima_server/src/lsx/request/game.rs similarity index 100% rename from maxima-lib/src/lsx/request/game.rs rename to crates/maxima_server/src/lsx/request/game.rs diff --git a/maxima-lib/src/lsx/request/igo.rs b/crates/maxima_server/src/lsx/request/igo.rs similarity index 96% rename from maxima-lib/src/lsx/request/igo.rs rename to crates/maxima_server/src/lsx/request/igo.rs index 45d88ae..5e8be0b 100644 --- a/maxima-lib/src/lsx/request/igo.rs +++ b/crates/maxima_server/src/lsx/request/igo.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use log::info; +use tracing::info; use crate::lsx::{ connection::LockedConnectionState, diff --git a/maxima-lib/src/lsx/request/license.rs b/crates/maxima_server/src/lsx/request/license.rs similarity index 98% rename from maxima-lib/src/lsx/request/license.rs rename to crates/maxima_server/src/lsx/request/license.rs index 040f332..0be23fc 100644 --- a/maxima-lib/src/lsx/request/license.rs +++ b/crates/maxima_server/src/lsx/request/license.rs @@ -1,5 +1,5 @@ use anyhow::{bail, Result}; -use log::info; +use tracing::info; use crate::{ core::{auth::hardware::HardwareInfo, launch::LaunchMode}, diff --git a/maxima-lib/src/lsx/request/mod.rs b/crates/maxima_server/src/lsx/request/mod.rs similarity index 100% rename from maxima-lib/src/lsx/request/mod.rs rename to crates/maxima_server/src/lsx/request/mod.rs diff --git a/maxima-lib/src/lsx/request/profile.rs b/crates/maxima_server/src/lsx/request/profile.rs similarity index 99% rename from maxima-lib/src/lsx/request/profile.rs rename to crates/maxima_server/src/lsx/request/profile.rs index a59e5a0..c25a439 100644 --- a/maxima-lib/src/lsx/request/profile.rs +++ b/crates/maxima_server/src/lsx/request/profile.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use log::{debug, info}; +use tracing::{debug, info}; use crate::{ lsx::{ diff --git a/maxima-lib/src/lsx/request/progressive_install.rs b/crates/maxima_server/src/lsx/request/progressive_install.rs similarity index 100% rename from maxima-lib/src/lsx/request/progressive_install.rs rename to crates/maxima_server/src/lsx/request/progressive_install.rs diff --git a/maxima-lib/src/lsx/request/voip.rs b/crates/maxima_server/src/lsx/request/voip.rs similarity index 100% rename from maxima-lib/src/lsx/request/voip.rs rename to crates/maxima_server/src/lsx/request/voip.rs diff --git a/maxima-lib/src/lsx/service.rs b/crates/maxima_server/src/lsx/service.rs similarity index 98% rename from maxima-lib/src/lsx/service.rs rename to crates/maxima_server/src/lsx/service.rs index a378968..3b8d718 100644 --- a/maxima-lib/src/lsx/service.rs +++ b/crates/maxima_server/src/lsx/service.rs @@ -3,7 +3,7 @@ use std::{io::ErrorKind, net::TcpListener}; use anyhow::Result; -use log::{info, warn}; +use tracing::{info, warn}; use tokio::time::sleep; use crate::core::LockedMaxima; diff --git a/maxima-lib/src/lsx/types.rs b/crates/maxima_server/src/lsx/types.rs similarity index 100% rename from maxima-lib/src/lsx/types.rs rename to crates/maxima_server/src/lsx/types.rs diff --git a/crates/maxima_server/src/main.rs b/crates/maxima_server/src/main.rs new file mode 100644 index 0000000..3cd3091 --- /dev/null +++ b/crates/maxima_server/src/main.rs @@ -0,0 +1,89 @@ +#![feature(type_ascription)] +#![feature(slice_pattern)] +#![feature(string_remove_matches)] +#![feature(trait_alias)] +#![feature(type_alias_impl_trait)] + +use core::{auth::storage::AuthStorage, user_man::UserManager}; + +use maxima_proto::{ + comm::{router::ProtoRouter, server::ProtoServer}, + comp::{auth::AuthenticationServer, users::UsersServer, util::UtilitiesServer}, +}; +use comp::{auth::AuthComponent, users::UsersComponent, util::UtilComponent}; +use tracing::{error, info, Level}; +use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; +use util::native::maxima_dir; + +pub mod content; +pub mod core; +pub mod lsx; +pub mod ooa; +pub mod rtm; +pub mod comp; +pub mod util; + +#[cfg(unix)] +pub mod unix; + +#[cfg(not(target_arch = "x86_64"))] +compile_error!("Maxima only supports the x86_64 architecture due to the use of __cpuid"); + +#[tokio::main] +async fn main() { + let log_dir = maxima_dir() + .expect("Failed to find a suitable home directory. Please specify HOME, XDG_DATA_HOME, or MAXIMA_HOME") + .join("logs"); + + let file_appender = tracing_appender::rolling::daily(log_dir, "maxima_server.log"); + let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); + + let mut default_layer = fmt::Layer::default(); + + // Waiting on #15701 to + // cook this out in release + if cfg!(debug_assertions) { + default_layer = default_layer + .with_file(true) + .with_line_number(true) + .with_target(false); + } + + tracing_subscriber::registry() + .with(EnvFilter::from_default_env().add_directive(Level::INFO.into())) + .with(default_layer) + .with( + fmt::Layer::default() + .with_writer(non_blocking) + .with_ansi(false), + ) + .init(); + + info!( + "Initializing MAXIMA Server {} ({})...", + env!("CARGO_PKG_VERSION"), + env!("GIT_HASH") + ); + + let auth_storage = match AuthStorage::load() { + Ok(storage) => storage, + Err(err) => { + error!("Failed to load auth storage, using default: {err}"); + AuthStorage::new() + } + }; + + let user_man = UserManager::new(auth_storage.clone()); + + let router = ProtoRouter::builder() + .add_component(AuthenticationServer::new(AuthComponent { + auth_storage: auth_storage.clone(), + })) + .add_component(UsersServer::new(UsersComponent { + auth_storage: auth_storage.clone(), + user_man, + })) + .add_component(UtilitiesServer::new(UtilComponent { auth_storage })); + + ProtoServer::new(router).serve().await.unwrap(); +} diff --git a/maxima-lib/src/ooa/mod.rs b/crates/maxima_server/src/ooa/mod.rs similarity index 98% rename from maxima-lib/src/ooa/mod.rs rename to crates/maxima_server/src/ooa/mod.rs index a9a2fd3..c0366c4 100644 --- a/maxima-lib/src/ooa/mod.rs +++ b/crates/maxima_server/src/ooa/mod.rs @@ -1,6 +1,5 @@ use std::{ fs::create_dir_all, - io::{Read, Write}, path::PathBuf, }; @@ -9,8 +8,8 @@ use aes::cipher::{ }; use anyhow::{bail, Result}; use chrono::{DateTime, Duration, Utc}; -use log::warn; -use tokio::fs::{self, File}; +use tracing::{debug, warn}; +use tokio::fs; use base64::{engine::general_purpose, Engine}; @@ -193,7 +192,8 @@ pub async fn request_license( let mut query = Vec::new(); query.push(("contentId", content_id)); query.push(("machineHash", machine_hash)); - log::debug!("Using hash {}", machine_hash); + + debug!("Using hash {}", machine_hash); match auth { LicenseAuth::AccessToken(access_token) => { diff --git a/maxima-lib/src/rtm/client.rs b/crates/maxima_server/src/rtm/client.rs similarity index 99% rename from maxima-lib/src/rtm/client.rs rename to crates/maxima_server/src/rtm/client.rs index 0b559bf..ed97981 100644 --- a/maxima-lib/src/rtm/client.rs +++ b/crates/maxima_server/src/rtm/client.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Result}; use derive_builder::Builder; use core::future::Future; use derive_getters::Getters; -use log::{debug, info, warn}; +use tracing::{debug, info, warn}; use moka::sync::Cache; use serde::{Deserialize, Serialize}; use tokio::sync::{mpsc, Mutex}; diff --git a/maxima-lib/src/rtm/connection.rs b/crates/maxima_server/src/rtm/connection.rs similarity index 98% rename from maxima-lib/src/rtm/connection.rs rename to crates/maxima_server/src/rtm/connection.rs index affe26b..b6021ba 100644 --- a/maxima-lib/src/rtm/connection.rs +++ b/crates/maxima_server/src/rtm/connection.rs @@ -7,7 +7,7 @@ use std::{ }; use anyhow::Result; -use log::{error, warn}; +use tracing::{error, warn}; use prost::{ bytes::{Buf, BufMut, BytesMut}, Message, @@ -71,12 +71,12 @@ impl RtmConnectionManager { if let Err(e) = RtmConnectionManager::handle_stream(stream, &mut request_rx, &mut update_presence_tx).await { - println!("Stream error: {}", e); + error!("Stream error: {}", e); // Reconnection will be attempted after the delay } } Err(e) => { - println!("Failed to connect: {}", e); + error!("Failed to connect: {}", e); } } diff --git a/maxima-lib/src/rtm/mod.rs b/crates/maxima_server/src/rtm/mod.rs similarity index 100% rename from maxima-lib/src/rtm/mod.rs rename to crates/maxima_server/src/rtm/mod.rs diff --git a/maxima-lib/src/rtm/proto/README.md b/crates/maxima_server/src/rtm/proto/README.md similarity index 100% rename from maxima-lib/src/rtm/proto/README.md rename to crates/maxima_server/src/rtm/proto/README.md diff --git a/maxima-lib/src/rtm/proto/common.proto b/crates/maxima_server/src/rtm/proto/common.proto similarity index 100% rename from maxima-lib/src/rtm/proto/common.proto rename to crates/maxima_server/src/rtm/proto/common.proto diff --git a/maxima-lib/src/rtm/proto/requests.proto b/crates/maxima_server/src/rtm/proto/requests.proto similarity index 100% rename from maxima-lib/src/rtm/proto/requests.proto rename to crates/maxima_server/src/rtm/proto/requests.proto diff --git a/maxima-lib/src/rtm/proto/responses.proto b/crates/maxima_server/src/rtm/proto/responses.proto similarity index 100% rename from maxima-lib/src/rtm/proto/responses.proto rename to crates/maxima_server/src/rtm/proto/responses.proto diff --git a/maxima-lib/src/rtm/proto/rtm.proto b/crates/maxima_server/src/rtm/proto/rtm.proto similarity index 100% rename from maxima-lib/src/rtm/proto/rtm.proto rename to crates/maxima_server/src/rtm/proto/rtm.proto diff --git a/maxima-lib/src/rtm/test.json b/crates/maxima_server/src/rtm/test.json similarity index 100% rename from maxima-lib/src/rtm/test.json rename to crates/maxima_server/src/rtm/test.json diff --git a/maxima-lib/src/unix/fs.rs b/crates/maxima_server/src/unix/fs.rs similarity index 95% rename from maxima-lib/src/unix/fs.rs rename to crates/maxima_server/src/unix/fs.rs index 9c6451d..adbd686 100644 --- a/maxima-lib/src/unix/fs.rs +++ b/crates/maxima_server/src/unix/fs.rs @@ -4,10 +4,12 @@ pub async fn case_insensitive_path(path: PathBuf) -> PathBuf { if path.exists() { return path; } + let mut missing_parts: Vec = Vec::new(); let original_path = path.clone(); let mut path = path; + // Find first existing ancestor // And fill path file names array loop { @@ -18,10 +20,11 @@ pub async fn case_insensitive_path(path: PathBuf) -> PathBuf { break; } } else { - // If we run out of acnestors, return the original path + // If we run out of ancestors, return the original path return original_path; } } + // Reverse the array so we have the proper order of path parts missing_parts.reverse(); @@ -31,6 +34,7 @@ pub async fn case_insensitive_path(path: PathBuf) -> PathBuf { path.push(part); continue; } + let mut found = false; for entry in path.read_dir().unwrap() { if let Ok(entry) = entry { @@ -41,6 +45,7 @@ pub async fn case_insensitive_path(path: PathBuf) -> PathBuf { } } } + // If the path part wasn't found, mark the path as not existing so we can push the rest // of the parts to the end, this will at least allow us to get as close to proper-cased path if !found { diff --git a/maxima-lib/src/unix/mod.rs b/crates/maxima_server/src/unix/mod.rs similarity index 100% rename from maxima-lib/src/unix/mod.rs rename to crates/maxima_server/src/unix/mod.rs diff --git a/maxima-lib/src/unix/wine.rs b/crates/maxima_server/src/unix/wine.rs similarity index 98% rename from maxima-lib/src/unix/wine.rs rename to crates/maxima_server/src/unix/wine.rs index 9d5b794..db4e811 100644 --- a/maxima-lib/src/unix/wine.rs +++ b/crates/maxima_server/src/unix/wine.rs @@ -11,7 +11,7 @@ use std::{ use anyhow::{bail, Context, Result}; use flate2::read::GzDecoder; use lazy_static::lazy_static; -use log::{info, warn}; +use tracing::{info, warn}; use regex::Regex; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -24,7 +24,7 @@ use tokio::{ use xz2::read::XzDecoder; use crate::util::{ - github::{fetch_github_release, fetch_github_releases, github_download_asset, GithubRelease}, + github::{fetch_github_releases, github_download_asset, GithubRelease}, native::maxima_dir, }; @@ -287,7 +287,7 @@ pub(crate) async fn install_wine() -> Result<()> { set_versions(versions)?; if let Err(err) = remove_file(&path) { - log::warn!("Failed to delete {:?} - {:?}", path, err); + warn!("Failed to delete {:?} - {:?}", path, err); } let _ = run_wine_command("", None::<[&str; 0]>, None, false, CommandType::Run).await; diff --git a/maxima-lib/src/util/github.rs b/crates/maxima_server/src/util/github.rs similarity index 99% rename from maxima-lib/src/util/github.rs rename to crates/maxima_server/src/util/github.rs index 9e4629a..0273c3c 100644 --- a/maxima-lib/src/util/github.rs +++ b/crates/maxima_server/src/util/github.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::io::Read; use anyhow::{bail, Result}; -use log::info; +use tracing::info; use reqwest::StatusCode; use serde::Deserialize; diff --git a/maxima-lib/src/util/hash.rs b/crates/maxima_server/src/util/hash.rs similarity index 100% rename from maxima-lib/src/util/hash.rs rename to crates/maxima_server/src/util/hash.rs diff --git a/maxima-lib/src/util/mod.rs b/crates/maxima_server/src/util/mod.rs similarity index 95% rename from maxima-lib/src/util/mod.rs rename to crates/maxima_server/src/util/mod.rs index f6702ef..d244e5e 100644 --- a/maxima-lib/src/util/mod.rs +++ b/crates/maxima_server/src/util/mod.rs @@ -1,6 +1,5 @@ pub mod github; pub mod hash; -pub mod log; pub mod native; pub mod registry; pub mod simple_crypto; diff --git a/maxima-lib/src/util/native.rs b/crates/maxima_server/src/util/native.rs similarity index 96% rename from maxima-lib/src/util/native.rs rename to crates/maxima_server/src/util/native.rs index 4f8da25..6bc5e83 100644 --- a/maxima-lib/src/util/native.rs +++ b/crates/maxima_server/src/util/native.rs @@ -136,6 +136,12 @@ pub fn maxima_dir() -> Result { pub fn maxima_dir() -> Result { use std::{env, fs::create_dir_all}; + if let Ok(path) = env::var("MAXIMA_HOME") { + let path = PathBuf::from(path); + create_dir_all(&path)?; + return Ok(path); + }; + let home = if let Ok(home) = env::var("XDG_DATA_HOME") { home } else if let Ok(home) = env::var("HOME") { diff --git a/maxima-lib/src/util/registry.rs b/crates/maxima_server/src/util/registry.rs similarity index 99% rename from maxima-lib/src/util/registry.rs rename to crates/maxima_server/src/util/registry.rs index 313943d..22dc061 100644 --- a/maxima-lib/src/util/registry.rs +++ b/crates/maxima_server/src/util/registry.rs @@ -301,7 +301,7 @@ pub fn set_up_registry() -> Result<()> { pub fn set_up_registry() -> Result<()> { use std::process::Command; - use log::warn; + use tracing::warn; let bin = bootstrap_path(); diff --git a/maxima-lib/src/util/service_nix.rs b/crates/maxima_server/src/util/service_nix.rs similarity index 100% rename from maxima-lib/src/util/service_nix.rs rename to crates/maxima_server/src/util/service_nix.rs diff --git a/maxima-lib/src/util/service_win.rs b/crates/maxima_server/src/util/service_win.rs similarity index 99% rename from maxima-lib/src/util/service_win.rs rename to crates/maxima_server/src/util/service_win.rs index 9a74a99..8230fe2 100644 --- a/maxima-lib/src/util/service_win.rs +++ b/crates/maxima_server/src/util/service_win.rs @@ -1,5 +1,5 @@ use anyhow::{bail, Result}; -use log::{info, debug}; +use tracing::{info, debug}; use std::ffi::{CString, OsStr, OsString}; use std::path::PathBuf; use std::time::Duration; diff --git a/maxima-lib/src/util/simple_crypto.rs b/crates/maxima_server/src/util/simple_crypto.rs similarity index 100% rename from maxima-lib/src/util/simple_crypto.rs rename to crates/maxima_server/src/util/simple_crypto.rs diff --git a/maxima-lib/src/util/system_profiler_utils.rs b/crates/maxima_server/src/util/system_profiler_utils.rs similarity index 100% rename from maxima-lib/src/util/system_profiler_utils.rs rename to crates/maxima_server/src/util/system_profiler_utils.rs diff --git a/maxima-lib/src/util/wmi_utils.rs b/crates/maxima_server/src/util/wmi_utils.rs similarity index 100% rename from maxima-lib/src/util/wmi_utils.rs rename to crates/maxima_server/src/util/wmi_utils.rs diff --git a/maxima-service/Cargo.toml b/crates/maxima_service/Cargo.toml similarity index 88% rename from maxima-service/Cargo.toml rename to crates/maxima_service/Cargo.toml index 14b05b9..4b7ee03 100644 --- a/maxima-service/Cargo.toml +++ b/crates/maxima_service/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "maxima-service" +name = "maxima_service" version = "0.1.0" authors = ["Sean Kahler "] edition = "2021" [dependencies] -maxima-lib = { path = "../maxima-lib" } +maxima = { path = "../maxima_lib" } tokio = { version = "1.28.2", features = [ "full", "rt", "rt-multi-thread", "time", "net" ] } serde = { version = "1.0.164", features = [ "derive" ] } serde_json = "1.0.97" @@ -29,7 +29,7 @@ windows-service = "0.6.0" dll-syringe = "0.15.2" [build-dependencies] -maxima-resources = { path = "../maxima-resources" } +maxima_resources = { path = "../maxima_resources" } [[bin]] name = "maxima-service" diff --git a/maxima-service/build.rs b/crates/maxima_service/build.rs similarity index 100% rename from maxima-service/build.rs rename to crates/maxima_service/build.rs diff --git a/maxima-service/src/main.rs b/crates/maxima_service/src/main.rs similarity index 100% rename from maxima-service/src/main.rs rename to crates/maxima_service/src/main.rs diff --git a/maxima-service/src/service/error.rs b/crates/maxima_service/src/service/error.rs similarity index 100% rename from maxima-service/src/service/error.rs rename to crates/maxima_service/src/service/error.rs diff --git a/maxima-service/src/service/hash.rs b/crates/maxima_service/src/service/hash.rs similarity index 100% rename from maxima-service/src/service/hash.rs rename to crates/maxima_service/src/service/hash.rs diff --git a/maxima-service/src/service/mod.rs b/crates/maxima_service/src/service/mod.rs similarity index 100% rename from maxima-service/src/service/mod.rs rename to crates/maxima_service/src/service/mod.rs diff --git a/maxima-tui/Cargo.toml b/crates/maxima_tui/Cargo.toml similarity index 89% rename from maxima-tui/Cargo.toml rename to crates/maxima_tui/Cargo.toml index d468b23..4c9c3c6 100644 --- a/maxima-tui/Cargo.toml +++ b/crates/maxima_tui/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Sean Kahler "] edition = "2021" [dependencies] -maxima-lib = { path = "../maxima-lib" } +maxima = { path = "../maxima_lib" } clap = { version = "4.3.5", features = [ "derive" ] } tokio = { version = "1.28.2", features = [ "full", "rt", "rt-multi-thread", "time", "net" ] } serde = { version = "1.0.164", features = [ "derive" ] } @@ -27,7 +27,7 @@ winreg = "0.50.0" is_elevated = "0.1.2" [build-dependencies] -maxima-resources = { path = "../maxima-resources" } +maxima_resources = { path = "../maxima_resources" } [[bin]] name = "maxima-tui" diff --git a/maxima-tui/build.rs b/crates/maxima_tui/build.rs similarity index 100% rename from maxima-tui/build.rs rename to crates/maxima_tui/build.rs diff --git a/maxima-tui/src/main.rs b/crates/maxima_tui/src/main.rs similarity index 100% rename from maxima-tui/src/main.rs rename to crates/maxima_tui/src/main.rs diff --git a/maxima-tui/src/service.rs b/crates/maxima_tui/src/service.rs similarity index 100% rename from maxima-tui/src/service.rs rename to crates/maxima_tui/src/service.rs diff --git a/maxima-ui/Cargo.toml b/crates/maxima_ui/Cargo.toml similarity index 89% rename from maxima-ui/Cargo.toml rename to crates/maxima_ui/Cargo.toml index 6140141..a59ebe9 100644 --- a/maxima-ui/Cargo.toml +++ b/crates/maxima_ui/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "maxima-ui" +name = "maxima_ui" version = "0.1.0" authors = ["Nick Whelan "] edition = "2021" +default-run = "maxima" [dependencies] -maxima-lib = { path = "../maxima-lib" } +maxima = { path = "../maxima_lib" } winapi = { version = "0.3.9", features = [ "memoryapi", "handleapi", "synchapi", "wincon", "consoleapi", "psapi", "wininet", "winerror", "iphlpapi", "tcpmib" ] } anyhow = "1.0.71" clap = { version = "4.3.8", features = ["derive"] } @@ -31,7 +32,7 @@ fuzzy-matcher = "*" is_elevated = "0.1.2" [build-dependencies] -maxima-resources = { path = "../maxima-resources" } +maxima_resources = { path = "../maxima_resources" } [[bin]] name = "maxima" diff --git a/maxima-ui/build.rs b/crates/maxima_ui/build.rs similarity index 100% rename from maxima-ui/build.rs rename to crates/maxima_ui/build.rs diff --git a/maxima-ui/fonts/IBMPlexSans-Regular.ttf b/crates/maxima_ui/fonts/IBMPlexSans-Regular.ttf similarity index 100% rename from maxima-ui/fonts/IBMPlexSans-Regular.ttf rename to crates/maxima_ui/fonts/IBMPlexSans-Regular.ttf diff --git a/maxima-ui/res/achievement.png b/crates/maxima_ui/res/achievement.png similarity index 100% rename from maxima-ui/res/achievement.png rename to crates/maxima_ui/res/achievement.png diff --git a/maxima-ui/res/gamecover_tmp.png b/crates/maxima_ui/res/gamecover_tmp.png similarity index 100% rename from maxima-ui/res/gamecover_tmp.png rename to crates/maxima_ui/res/gamecover_tmp.png diff --git a/maxima-ui/res/locale/en_us.json b/crates/maxima_ui/res/locale/en_us.json similarity index 100% rename from maxima-ui/res/locale/en_us.json rename to crates/maxima_ui/res/locale/en_us.json diff --git a/maxima-ui/res/placeholder.png b/crates/maxima_ui/res/placeholder.png similarity index 100% rename from maxima-ui/res/placeholder.png rename to crates/maxima_ui/res/placeholder.png diff --git a/maxima-ui/res/untitled.blend b/crates/maxima_ui/res/untitled.blend similarity index 100% rename from maxima-ui/res/untitled.blend rename to crates/maxima_ui/res/untitled.blend diff --git a/maxima-ui/res/untitled.blend1 b/crates/maxima_ui/res/untitled.blend1 similarity index 100% rename from maxima-ui/res/untitled.blend1 rename to crates/maxima_ui/res/untitled.blend1 diff --git a/maxima-ui/res/untitled.svg b/crates/maxima_ui/res/untitled.svg similarity index 100% rename from maxima-ui/res/untitled.svg rename to crates/maxima_ui/res/untitled.svg diff --git a/maxima-ui/res/usericon_tmp.png b/crates/maxima_ui/res/usericon_tmp.png similarity index 100% rename from maxima-ui/res/usericon_tmp.png rename to crates/maxima_ui/res/usericon_tmp.png diff --git a/maxima-ui/res/usericon_tmp2.png b/crates/maxima_ui/res/usericon_tmp2.png similarity index 100% rename from maxima-ui/res/usericon_tmp2.png rename to crates/maxima_ui/res/usericon_tmp2.png diff --git a/maxima-ui/shaders/abg.frag b/crates/maxima_ui/shaders/abg.frag similarity index 100% rename from maxima-ui/shaders/abg.frag rename to crates/maxima_ui/shaders/abg.frag diff --git a/maxima-ui/shaders/abg.vert b/crates/maxima_ui/shaders/abg.vert similarity index 100% rename from maxima-ui/shaders/abg.vert rename to crates/maxima_ui/shaders/abg.vert diff --git a/maxima-ui/shaders/gvbg.frag b/crates/maxima_ui/shaders/gvbg.frag similarity index 100% rename from maxima-ui/shaders/gvbg.frag rename to crates/maxima_ui/shaders/gvbg.frag diff --git a/maxima-ui/shaders/gvbg.vert b/crates/maxima_ui/shaders/gvbg.vert similarity index 100% rename from maxima-ui/shaders/gvbg.vert rename to crates/maxima_ui/shaders/gvbg.vert diff --git a/maxima-ui/src/bridge/game_details.rs b/crates/maxima_ui/src/bridge/game_details.rs similarity index 100% rename from maxima-ui/src/bridge/game_details.rs rename to crates/maxima_ui/src/bridge/game_details.rs diff --git a/maxima-ui/src/bridge/get_friends.rs b/crates/maxima_ui/src/bridge/get_friends.rs similarity index 100% rename from maxima-ui/src/bridge/get_friends.rs rename to crates/maxima_ui/src/bridge/get_friends.rs diff --git a/maxima-ui/src/bridge/get_games.rs b/crates/maxima_ui/src/bridge/get_games.rs similarity index 100% rename from maxima-ui/src/bridge/get_games.rs rename to crates/maxima_ui/src/bridge/get_games.rs diff --git a/maxima-ui/src/bridge/login_oauth.rs b/crates/maxima_ui/src/bridge/login_oauth.rs similarity index 100% rename from maxima-ui/src/bridge/login_oauth.rs rename to crates/maxima_ui/src/bridge/login_oauth.rs diff --git a/maxima-ui/src/bridge/mod.rs b/crates/maxima_ui/src/bridge/mod.rs similarity index 100% rename from maxima-ui/src/bridge/mod.rs rename to crates/maxima_ui/src/bridge/mod.rs diff --git a/maxima-ui/src/bridge/start_game.rs b/crates/maxima_ui/src/bridge/start_game.rs similarity index 100% rename from maxima-ui/src/bridge/start_game.rs rename to crates/maxima_ui/src/bridge/start_game.rs diff --git a/maxima-ui/src/bridge_processor.rs b/crates/maxima_ui/src/bridge_processor.rs similarity index 100% rename from maxima-ui/src/bridge_processor.rs rename to crates/maxima_ui/src/bridge_processor.rs diff --git a/maxima-ui/src/bridge_thread.rs b/crates/maxima_ui/src/bridge_thread.rs similarity index 100% rename from maxima-ui/src/bridge_thread.rs rename to crates/maxima_ui/src/bridge_thread.rs diff --git a/maxima-ui/src/desktop.rs b/crates/maxima_ui/src/desktop.rs similarity index 98% rename from maxima-ui/src/desktop.rs rename to crates/maxima_ui/src/desktop.rs index c894898..4290855 100644 --- a/maxima-ui/src/desktop.rs +++ b/crates/maxima_ui/src/desktop.rs @@ -23,7 +23,7 @@ pub fn check_desktop_icon() -> Result<()> { } let png_path_temp = &maxima_dir.join("32.png"); - std::fs::write(png_path_temp, include_bytes!("../../maxima-resources/assets/logo.png"))?; + std::fs::write(png_path_temp, include_bytes!("../../maxima_resources/assets/logo.png"))?; let xdg_register_icon_check = Command::new("xdg-icon-resource") .arg("install") .arg("--size") diff --git a/maxima-ui/src/enum_locale_map.rs b/crates/maxima_ui/src/enum_locale_map.rs similarity index 100% rename from maxima-ui/src/enum_locale_map.rs rename to crates/maxima_ui/src/enum_locale_map.rs diff --git a/maxima-ui/src/event_processor.rs b/crates/maxima_ui/src/event_processor.rs similarity index 100% rename from maxima-ui/src/event_processor.rs rename to crates/maxima_ui/src/event_processor.rs diff --git a/maxima-ui/src/event_thread.rs b/crates/maxima_ui/src/event_thread.rs similarity index 100% rename from maxima-ui/src/event_thread.rs rename to crates/maxima_ui/src/event_thread.rs diff --git a/maxima-ui/src/main.rs b/crates/maxima_ui/src/main.rs similarity index 99% rename from maxima-ui/src/main.rs rename to crates/maxima_ui/src/main.rs index 27be548..31715bd 100644 --- a/maxima-ui/src/main.rs +++ b/crates/maxima_ui/src/main.rs @@ -121,7 +121,7 @@ async fn main() { .with_inner_size([1280.0, 720.0]) .with_min_inner_size([940.0, 480.0]) .with_app_id("io.github.ArmchairDevelopers.Maxima") - .with_icon(eframe::icon_data::from_png_bytes(&include_bytes!("../../maxima-resources/assets/logo.png")[..]).unwrap()), + .with_icon(eframe::icon_data::from_png_bytes(&include_bytes!("../../maxima_resources/assets/logo.png")[..]).unwrap()), ..Default::default() }; eframe::run_native( diff --git a/maxima-ui/src/renderers/app_bg_renderer.rs b/crates/maxima_ui/src/renderers/app_bg_renderer.rs similarity index 100% rename from maxima-ui/src/renderers/app_bg_renderer.rs rename to crates/maxima_ui/src/renderers/app_bg_renderer.rs diff --git a/maxima-ui/src/renderers/game_view_bg_renderer.rs b/crates/maxima_ui/src/renderers/game_view_bg_renderer.rs similarity index 100% rename from maxima-ui/src/renderers/game_view_bg_renderer.rs rename to crates/maxima_ui/src/renderers/game_view_bg_renderer.rs diff --git a/maxima-ui/src/renderers/mod.rs b/crates/maxima_ui/src/renderers/mod.rs similarity index 100% rename from maxima-ui/src/renderers/mod.rs rename to crates/maxima_ui/src/renderers/mod.rs diff --git a/maxima-ui/src/translation_manager.rs b/crates/maxima_ui/src/translation_manager.rs similarity index 100% rename from maxima-ui/src/translation_manager.rs rename to crates/maxima_ui/src/translation_manager.rs diff --git a/maxima-ui/src/ui_image.rs b/crates/maxima_ui/src/ui_image.rs similarity index 100% rename from maxima-ui/src/ui_image.rs rename to crates/maxima_ui/src/ui_image.rs diff --git a/maxima-ui/src/util/markdown.rs b/crates/maxima_ui/src/util/markdown.rs similarity index 100% rename from maxima-ui/src/util/markdown.rs rename to crates/maxima_ui/src/util/markdown.rs diff --git a/maxima-ui/src/util/mod.rs b/crates/maxima_ui/src/util/mod.rs similarity index 100% rename from maxima-ui/src/util/mod.rs rename to crates/maxima_ui/src/util/mod.rs diff --git a/maxima-ui/src/views/debug_view.rs b/crates/maxima_ui/src/views/debug_view.rs similarity index 100% rename from maxima-ui/src/views/debug_view.rs rename to crates/maxima_ui/src/views/debug_view.rs diff --git a/maxima-ui/src/views/downloads_view.rs b/crates/maxima_ui/src/views/downloads_view.rs similarity index 100% rename from maxima-ui/src/views/downloads_view.rs rename to crates/maxima_ui/src/views/downloads_view.rs diff --git a/maxima-ui/src/views/friends_view.rs b/crates/maxima_ui/src/views/friends_view.rs similarity index 100% rename from maxima-ui/src/views/friends_view.rs rename to crates/maxima_ui/src/views/friends_view.rs diff --git a/maxima-ui/src/views/game_view.rs b/crates/maxima_ui/src/views/game_view.rs similarity index 100% rename from maxima-ui/src/views/game_view.rs rename to crates/maxima_ui/src/views/game_view.rs diff --git a/maxima-ui/src/views/mod.rs b/crates/maxima_ui/src/views/mod.rs similarity index 100% rename from maxima-ui/src/views/mod.rs rename to crates/maxima_ui/src/views/mod.rs diff --git a/maxima-ui/src/views/settings_view.rs b/crates/maxima_ui/src/views/settings_view.rs similarity index 100% rename from maxima-ui/src/views/settings_view.rs rename to crates/maxima_ui/src/views/settings_view.rs diff --git a/maxima-ui/src/views/undefinied_view.rs b/crates/maxima_ui/src/views/undefinied_view.rs similarity index 100% rename from maxima-ui/src/views/undefinied_view.rs rename to crates/maxima_ui/src/views/undefinied_view.rs diff --git a/maxima-ui/src/widgets/enum_dropdown.rs b/crates/maxima_ui/src/widgets/enum_dropdown.rs similarity index 100% rename from maxima-ui/src/widgets/enum_dropdown.rs rename to crates/maxima_ui/src/widgets/enum_dropdown.rs diff --git a/maxima-ui/src/widgets/mod.rs b/crates/maxima_ui/src/widgets/mod.rs similarity index 100% rename from maxima-ui/src/widgets/mod.rs rename to crates/maxima_ui/src/widgets/mod.rs diff --git a/maxima-lib/build.rs b/maxima-lib/build.rs deleted file mode 100644 index ea64d75..0000000 --- a/maxima-lib/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() -> std::io::Result<()> { - prost_build::compile_protos(&["src/rtm/proto/rtm.proto"], &["src/rtm/proto/"]) -} diff --git a/maxima-lib/src/core/error.rs b/maxima-lib/src/core/error.rs deleted file mode 100644 index 2cd19a0..0000000 --- a/maxima-lib/src/core/error.rs +++ /dev/null @@ -1 +0,0 @@ -// TODO: Snafu? We shouldn't use anyhow so much. diff --git a/maxima-lib/src/lib.rs b/maxima-lib/src/lib.rs deleted file mode 100644 index 0d878db..0000000 --- a/maxima-lib/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![feature(type_ascription)] -#![feature(slice_pattern)] -#![feature(string_remove_matches)] -#![feature(trait_alias)] -#![feature(type_alias_impl_trait)] - -pub mod content; -pub mod core; -pub mod lsx; -pub mod ooa; -pub mod rtm; -pub mod util; - -#[cfg(unix)] -pub mod unix; - -#[cfg(not(target_arch = "x86_64"))] -compile_error!("Only x86_64 is supported at the moment"); diff --git a/maxima-lib/src/util/log.rs b/maxima-lib/src/util/log.rs deleted file mode 100644 index 80638f2..0000000 --- a/maxima-lib/src/util/log.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::env; - -use log::{Record, Level, Metadata, LevelFilter}; - -pub struct SimpleLogger; -pub static LOGGER: SimpleLogger = SimpleLogger; - -impl log::Log for SimpleLogger { - fn enabled(&self, metadata: &Metadata) -> bool { - let enable_debug = if let Ok(x) = env::var("MAXIMA_LOG_LEVEL") { - x == "debug" - } else { - false - }; - - let log_level = if enable_debug { - Level::Debug - } else { - Level::Info - }; - - metadata.level() <= log_level - } - - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - let level = record.level(); - let color: &str = match level { - Level::Error => "31", // red - Level::Warn => "33", // yellow - Level::Info => "32", // green - Level::Debug => "36", // cyan - Level::Trace => "33", // yellow - }; - - if level == Level::Error { - println!("\u{001b}[{}m{}\u{001b}[37m - [{}:{}] - {}", color, level, record.file_static().unwrap(), record.line().unwrap(), record.args()); - } else { - println!("\u{001b}[{}m{}\u{001b}[37m - [{}] - {}", color, level, record.module_path().unwrap(), record.args()); - } - } - } - - fn flush(&self) {} -} - -pub fn init_logger() { - if enable_ansi_support::enable_ansi_support().is_err() { - println!("ANSI Colors are unsupported in your terminal, things might look a bit off!"); - } - - log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Trace)).ok(); -} \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a932ec7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-07-11" +components = ["rustfmt", "clippy"] +profile = "minimal"