From e607dfe6b64fb9d7f85ac3749ecf6fe193544549 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Thu, 28 Mar 2024 20:32:16 +0100 Subject: [PATCH 01/10] feat: Fixes --- Cargo.toml | 5 +- onelauncher/Cargo.toml | 12 -- onelauncher/src/error.rs | 8 - onelauncher/src/game/minecraft.rs | 238 ++++++++++++++++-------------- src-tauri/Cargo.toml | 2 +- src-tauri/src/api/auth.rs | 2 +- src-tauri/src/api/game.rs | 2 +- src-tauri/src/api/mod.rs | 2 +- src-tauri/src/error.rs | 2 +- src-tauri/src/main.rs | 8 +- 10 files changed, 139 insertions(+), 142 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14f3fb2b..bcab4e32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,10 @@ tracing-subscriber = { version = "0.3", features = [ tracing-error = { version = "0.2" } tracing-appender = { version = "0.2" } -uuid = { version = "1.8", features = [ "serde" ] } +uuid = { version = "1.8", features = [ + "serde", + "v4" +] } tokio = { version = "1", features = [ "full" ] } serde = { version = "1", features = [ "derive" ] } chrono = { version = "0.4", features = [ "serde" ] } diff --git a/onelauncher/Cargo.toml b/onelauncher/Cargo.toml index b522b5c5..1527adf7 100644 --- a/onelauncher/Cargo.toml +++ b/onelauncher/Cargo.toml @@ -10,14 +10,7 @@ readme = { workspace = true } homepage = { workspace = true } authors = { workspace = true } -[features] -tauri = [ "dep:tauri" ] -cli = [ "dep:indicatif" ] - [dependencies] -tauri = { workspace = true, optional = true } -indicatif = { workspace = true, optional = true } - tracing = { workspace = true } tracing-subscriber = { workspace = true } tracing-error = { workspace = true } @@ -39,8 +32,3 @@ dirs = { workspace = true } open = { workspace = true } sha1 = { workspace = true } discord-rich-presence = { workspace = true } -notify = { workspace = true } -notify-debouncer-mini = { workspace = true } - -[target.'cfg(windows)'.dependencies] -winreg = { workspace = true } diff --git a/onelauncher/src/error.rs b/onelauncher/src/error.rs index 829d6247..bf77a290 100644 --- a/onelauncher/src/error.rs +++ b/onelauncher/src/error.rs @@ -15,10 +15,6 @@ pub enum ErrorKind { #[error("tokio join handle error: {0}")] JoinError(#[from] tokio::task::JoinError), - /// Wrapper around [`notify::Error`] to handle file watching errors. - #[error("failed to watch file: {0}")] - NotifyError(#[from] notify::Error), - /// Wrapper around [`flate2::CompressError`] to handle flate compression errors. #[error("failed to compress a file with flate: {0}")] FlateError(#[from] flate2::CompressError), @@ -70,10 +66,6 @@ pub enum ErrorKind { /// Wrapper around [`crate::game::client::ClientManagerError`] to handle directory errors. #[error("failed to manage clients: {0}")] ClientManagerError(#[from] crate::game::client::ClientManagerError), - - #[cfg(feature = "tauri")] - #[error("failed to launch tauri: {0}")] - TauriError(#[from] tauri::Error), } #[derive(Debug)] diff --git a/onelauncher/src/game/minecraft.rs b/onelauncher/src/game/minecraft.rs index 217059f0..079f23d6 100644 --- a/onelauncher/src/game/minecraft.rs +++ b/onelauncher/src/game/minecraft.rs @@ -1,209 +1,223 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; +use core::fmt; +use std::{collections::HashMap, marker::PhantomData}; + +use serde::{de::{self, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize}; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MinecraftManifest { - pub asset_index: AssetIndex, - pub downloads: Downloads, - #[serde(rename = "id")] - pub version: String, - pub java_version: JavaVersion, - pub libraries: Vec, - pub logging: Logging, - pub main_class: String, - pub arguments: Arguments, - pub release_time: String, - #[serde(rename = "type")] - pub release_type: ReleaseType, + pub asset_index: AssetIndex, + pub downloads: Downloads, + pub java_version: JavaVersion, + pub libraries: Vec, + pub logging: Logging, + pub main_class: String, + pub release_time: String, + + #[serde(rename = "id")] + pub version: String, + + #[serde(rename = "type")] + pub release_type: ReleaseType, + + #[serde(skip_serializing_if = "Option::is_none")] + pub minecraft_arguments: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub arguments: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub enum Arguments { - // TODO: https://serde.rs/string-or-struct.html - MinecraftArguments(String), - Arguments { - game: Vec, - jvm: Vec, - }, -} - -impl Default for Arguments { - fn default() -> Self { - Arguments::MinecraftArguments(String::new()) - } +pub struct ModernArguments { + game: Vec, + jvm: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(untagged)] pub enum ModernArgumentsItem { - Simple(String), - Rule { - rules: Vec, - value: ArgumentRuleValue, - }, + Simple(String), + Rule(ArgumentRule), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub enum ArgumentRuleValue { - String(String), - List(Vec), +pub struct ArgumentRule { + #[serde(skip_serializing_if = "Vec::is_empty")] + rules: Vec, + + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(deserialize_with = "string_or_seq")] + value: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ReleaseType { - Snapshot, - Release, - OldBeta, - OldAlpha, + Snapshot, + Release, + OldBeta, + OldAlpha, } impl Default for ReleaseType { - fn default() -> Self { - ReleaseType::Release - } + fn default() -> Self { ReleaseType::Release } } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AssetIndex { - pub id: String, - pub sha1: String, - pub url: String, + pub id: String, + pub sha1: String, + pub url: String, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Downloads { - pub client: Client, + pub client: Client, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Client { - pub sha1: String, - pub url: String, + pub sha1: String, + pub url: String, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JavaVersion { - pub major_version: u8, + pub major_version: u8, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Library { - pub downloads: Downloads2, - pub name: String, - #[serde(default)] - pub rules: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub natives: Option, + pub downloads: LibraryDownload, + pub name: String, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub rules: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub natives: Option>, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Downloads2 { - #[serde(skip_serializing_if = "Option::is_none")] - pub artifact: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub classifiers: Option, +pub struct LibraryDownload { + #[serde(skip_serializing_if = "Option::is_none")] + pub artifact: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub classifiers: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Artifact { - pub path: String, - pub sha1: String, - pub url: String, + pub path: String, + pub sha1: String, + pub url: String, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Classifiers { - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "natives-osx")] - pub natives_osx: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "natives-osx")] + pub natives_osx: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "natives-linux")] + pub natives_linux: Option, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "natives-linux")] - pub natives_linux: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "natives-windows")] - pub natives_windows: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Native { - pub path: String, - pub sha1: String, - pub url: String, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "natives-windows")] + pub natives_windows: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Rule { - pub action: RuleAction, + pub action: RuleAction, + + #[serde(skip_serializing_if = "Option::is_none")] + pub os: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub os: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub features: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub features: Option>, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[serde(untagged)] pub enum RuleAction { - Allow, - Disallow, + #[serde(rename = "allow")] + Allow, + #[serde(rename = "disallow")] + Disallow, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Os { - pub name: Option, - pub arch: Option, - pub version: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Natives { - pub windows: Option, - pub linux: Option, - pub osx: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub arch: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Logging { - pub client: Client2, + pub client: Client2, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Client2 { - pub argument: String, - pub file: File, - #[serde(rename = "type")] - pub type_field: String, + pub argument: String, + pub file: File, + #[serde(rename = "type")] + pub type_field: String, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct File { - pub id: String, - pub sha1: String, - pub url: String, + pub id: String, + pub sha1: String, + pub url: String, } + +fn string_or_seq<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct StringOrVec(PhantomData>); + + impl<'de> Visitor<'de> for StringOrVec { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string or list of strings") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(vec![value.into()]) + } + + fn visit_seq(self, seq: S) -> Result + where + S: SeqAccess<'de>, + { + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq)) + } + } + + deserializer.deserialize_any(StringOrVec(PhantomData)) +} \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2bb64d9d..732f0018 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,7 +14,7 @@ authors = { workspace = true } tauri-build = { workspace = true } [dependencies] -onelauncher = { path = "../onelauncher", features = [ "tauri" ] } +onelauncher = { path = "../onelauncher" } tauri = { workspace = true } tauri-plugin-shell = { workspace = true } diff --git a/src-tauri/src/api/auth.rs b/src-tauri/src/api/auth.rs index 67e81ca7..55814eb7 100644 --- a/src-tauri/src/api/auth.rs +++ b/src-tauri/src/api/auth.rs @@ -1,4 +1,4 @@ -use launcher_core::auth::{ +use onelauncher::auth::{ microsoft_auth::MicrosoftAuthenticationMethod, Account, AuthenticationMethod, }; use tauri::Manager; diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index 48820062..41eebbaf 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -1,4 +1,4 @@ -use launcher_core::game::client::{ClientType, Instance, Manifest}; +use onelauncher::game::client::{ClientType, Instance, Manifest}; use tauri::State; use tokio::sync::Mutex; use uuid::Uuid; diff --git a/src-tauri/src/api/mod.rs b/src-tauri/src/api/mod.rs index de534859..ecbe4aeb 100644 --- a/src-tauri/src/api/mod.rs +++ b/src-tauri/src/api/mod.rs @@ -1,4 +1,4 @@ -use launcher_core::game::client::ClientManager; +use onelauncher::game::client::ClientManager; use tauri::{plugin::TauriPlugin, Manager, State}; use tokio::sync::Mutex; diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index be05d925..0964d4fd 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -1,6 +1,6 @@ use tracing_error::ExtractSpanTrace; -pub fn display_tracing_error(err: &launcher_core::Error) { +pub fn display_tracing_error(err: &onelauncher::Error) { match get_span_trace(err) { Some(span_trace) => { tracing::error!(error = %err, span_trace = %span_trace); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 82f9f15b..0218fda6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,8 +3,8 @@ use std::error::Error; -use polyfrost_launcher::api; -use tauri::{menu::Menu, App}; +use onelauncher_gui::api; +use tauri::{menu::Menu, App, Manager}; #[derive(Clone, serde::Serialize)] struct Payload { @@ -14,7 +14,7 @@ struct Payload { #[tokio::main] async fn main() { - let _log_guard = launcher_core::start_logger(); + let _log_guard = onelauncher::start_logger(); tracing::info!("initialized tracing subscriber. loading OneLauncher"); tauri::Builder::default() @@ -22,7 +22,7 @@ async fn main() { .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { println!("{}, {argv:?}, {cwd}", app.package_info().name); - app.emit_all("single-instance", Payload { args: argv, cwd }) + app.emit("single-instance", Payload { args: argv, cwd }) .unwrap(); })) .plugin(tauri_plugin_window_state::Builder::default().build()) From 0dd1365fa41945eb2acfcbdc1e5a986bafc831e2 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Thu, 28 Mar 2024 21:12:32 +0100 Subject: [PATCH 02/10] feat: Rebrand 'Instance' to 'Cluster' --- onelauncher/src/game/client.rs | 65 ++++++++++--------- onelauncher/src/game/vanilla.rs | 6 +- onelauncher/src/utils/dirs.rs | 4 +- src-tauri/build.rs | 6 +- .../permissions/onelauncher/default.toml | 6 +- src-tauri/src/api/game.rs | 18 ++--- src-tauri/src/api/mod.rs | 6 +- src/bridge/game.ts | 49 +++++++------- src/index.tsx | 12 ++-- src/types/game.d.ts | 6 +- src/ui/pages/Home.tsx | 58 ++++++++--------- .../ClusterMods.tsx} | 4 +- .../ClusterOverview.tsx} | 4 +- src/ui/pages/{instances => cluster}/index.tsx | 8 +-- 14 files changed, 123 insertions(+), 129 deletions(-) rename src/ui/pages/{instances/InstanceMods.tsx => cluster/ClusterMods.tsx} (55%) rename src/ui/pages/{instances/InstanceOverview.tsx => cluster/ClusterOverview.tsx} (51%) rename src/ui/pages/{instances => cluster}/index.tsx (79%) diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index 30ed7444..68c6af43 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -32,7 +32,7 @@ pub struct Manifest { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Instance { +pub struct Cluster { pub id: Uuid, pub created_at: DateTime, pub name: String, @@ -57,17 +57,17 @@ impl ClientType { pub fn get_impl<'a>( client: &'a ClientType, - instance: &'a Instance, + cluster: &'a Cluster, manifest: &'a Manifest, ) -> Box + 'a> { Box::new(match client { - ClientType::Vanilla(_) => vanilla::VanillaClient::new(instance, manifest), + ClientType::Vanilla(_) => vanilla::VanillaClient::new(cluster, manifest), }) } #[async_trait] pub trait ClientTrait<'a>: Send + Sync { - fn new(instance: &'a Instance, manifest: &'a Manifest) -> Self + fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self where Self: Sized; @@ -79,9 +79,10 @@ pub trait ClientTrait<'a>: Send + Sync { async fn install_natives(&self) -> crate::Result<()>; async fn install_assets(&self) -> crate::Result<()>; - fn get_instance(&self) -> &'a Instance + fn get_cluster(&self) -> &'a Cluster where Self: Sized; + fn get_manifest(&self) -> &'a Manifest where Self: Sized; @@ -97,7 +98,7 @@ macro_rules! create_game_client { #[derive(Debug, Clone)] pub struct $client<'a> { - pub instance: &'a crate::game::client::Instance, + pub cluster: &'a crate::game::client::Cluster, pub manifest: &'a crate::game::client::Manifest, $(pub $name: $type),* } @@ -107,11 +108,11 @@ macro_rules! create_game_client { #[macro_export] macro_rules! impl_game_client { () => { - fn get_instance(&self) -> &'a crate::game::client::Instance + fn get_cluster(&self) -> &'a crate::game::client::Cluster where Self: Sized, { - self.instance + self.cluster } fn get_manifest(&self) -> &'a crate::game::client::Manifest @@ -125,54 +126,54 @@ macro_rules! impl_game_client { #[derive(Debug, thiserror::Error)] pub enum ClientManagerError { - #[error("Instance not found")] - InstanceNotFound, + #[error("Cluster not found")] + ClusterNotFound, #[error("Manifest not found")] ManifestNotFound, } pub struct ClientManager { - instances: HashMap, + clusters: HashMap, manifests: HashMap, } impl ClientManager { pub fn new() -> crate::Result { - let instances = load_and_serialize::(&dirs::instances_dir()?)?; + let clusters = load_and_serialize::(&dirs::clusters_dir()?)?; let manifests = load_and_serialize::(&dirs::manifests_dir()?)?; Ok(Self { - instances, + clusters, manifests, }) } pub fn get_impl_uuid<'a>(&'a self, uuid: Uuid) -> crate::Result + 'a>> { - let instance = self.get_instance(uuid)?; + let cluster = self.get_cluster(uuid)?; let manifest = self.get_manifest(uuid)?; - Ok(get_impl(&instance.client, &instance, &manifest)) + Ok(get_impl(&cluster.client, &cluster, &manifest)) } - pub fn get_instances(&self) -> Vec<&Instance> { - self.instances.values().collect() + pub fn get_clusters(&self) -> Vec<&Cluster> { + self.clusters.values().collect() } - pub fn get_instances_owned(&self) -> Vec { - self.instances.values().cloned().collect() + pub fn get_clusters_owned(&self) -> Vec { + self.clusters.values().cloned().collect() } - pub fn get_instance(&self, uuid: Uuid) -> crate::Result<&Instance> { - self.instances + pub fn get_cluster(&self, uuid: Uuid) -> crate::Result<&Cluster> { + self.clusters .get(&uuid) - .ok_or(ClientManagerError::InstanceNotFound.into()) + .ok_or(ClientManagerError::ClusterNotFound.into()) } - pub fn get_instance_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Instance> { - self.instances + pub fn get_cluster_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Cluster> { + self.clusters .get_mut(&uuid) - .ok_or(ClientManagerError::InstanceNotFound.into()) + .ok_or(ClientManagerError::ClusterNotFound.into()) } pub fn get_manifest(&self, uuid: Uuid) -> crate::Result<&Manifest> { @@ -187,7 +188,7 @@ impl ClientManager { .ok_or(ClientManagerError::ManifestNotFound.into()) } - pub async fn create_instance( + pub async fn create_cluster( &mut self, name: String, version: String, @@ -218,8 +219,8 @@ impl ClientManager { )?; self.manifests.insert(uuid, manifest); - // Save the instance - let instance = Instance { + // Save the cluster + let cluster = Cluster { id: uuid, created_at: Local::now(), name, @@ -229,10 +230,10 @@ impl ClientManager { }; save( - &dirs::instances_dir()?.join(format!("{}.json", uuid)), - &instance, + &dirs::clusters_dir()?.join(format!("{}.json", uuid)), + &cluster, )?; - self.instances.insert(uuid, instance); + self.clusters.insert(uuid, cluster); Ok(uuid) } @@ -292,7 +293,7 @@ where let parsed = match serde_json::from_str::(&content) { Ok(parsed) => parsed, Err(_) => { - eprintln!("Couldn't parse file content as JSON: '{:?}'", content); + eprintln!("Couldn't parse file '{}' content as JSON", path.display()); continue; } }; diff --git a/onelauncher/src/game/vanilla.rs b/onelauncher/src/game/vanilla.rs index 1f563394..97b7f4c4 100644 --- a/onelauncher/src/game/vanilla.rs +++ b/onelauncher/src/game/vanilla.rs @@ -1,5 +1,5 @@ use super::{ - client::{ClientTrait, Instance, Manifest, Version}, + client::{ClientTrait, Cluster, Manifest, Version}, minecraft::MinecraftManifest, }; use crate::{ @@ -22,8 +22,8 @@ create_game_client! { impl<'a> ClientTrait<'a> for VanillaClient<'a> { impl_game_client!(); - fn new(instance: &'a Instance, manifest: &'a Manifest) -> Self { - VanillaClient { instance, manifest } + fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self { + VanillaClient { cluster, manifest } } async fn launch(&self) -> crate::Result<()> { diff --git a/onelauncher/src/utils/dirs.rs b/onelauncher/src/utils/dirs.rs index 54edbff5..bce9f719 100644 --- a/onelauncher/src/utils/dirs.rs +++ b/onelauncher/src/utils/dirs.rs @@ -22,8 +22,8 @@ pub fn java_dir() -> crate::Result { Ok(app_config_dir()?.join("java")) } -pub fn instances_dir() -> crate::Result { - Ok(app_config_dir()?.join("instances")) +pub fn clusters_dir() -> crate::Result { + Ok(app_config_dir()?.join("clusters")) } pub fn manifests_dir() -> crate::Result { diff --git a/src-tauri/build.rs b/src-tauri/build.rs index e739fd85..6ccf4925 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -6,9 +6,9 @@ fn main() { InlinedPlugin::new().commands(&[ "login_msa", "refresh_client_manager", - "create_instance", - "get_instances", - "get_instance", + "create_cluster", + "get_clusters", + "get_cluster", "get_manifest", ]), )); diff --git a/src-tauri/permissions/onelauncher/default.toml b/src-tauri/permissions/onelauncher/default.toml index af03ad78..1361f0e9 100644 --- a/src-tauri/permissions/onelauncher/default.toml +++ b/src-tauri/permissions/onelauncher/default.toml @@ -2,9 +2,9 @@ description = "Default permissions for the plugin" permissions = [ "allow-refresh-client-manager", - "allow-create-instance", - "allow-get-instances", - "allow-get-instance", + "allow-create-cluster", + "allow-get-clusters", + "allow-get-cluster", "allow-get-manifest", "allow-login-msa", ] diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index 41eebbaf..c72a65be 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -1,4 +1,4 @@ -use onelauncher::game::client::{ClientType, Instance, Manifest}; +use onelauncher::game::client::{ClientType, Cluster, Manifest}; use tauri::State; use tokio::sync::Mutex; use uuid::Uuid; @@ -6,7 +6,7 @@ use uuid::Uuid; use super::GameManagerState; #[tauri::command] -pub async fn create_instance( +pub async fn create_cluster( state: State<'_, Mutex>, name: String, version: String, @@ -16,26 +16,26 @@ pub async fn create_instance( ) -> Result { let manager = &mut state.lock().await.client_manager; let uuid = manager - .create_instance(name, version, cover, group, client) + .create_cluster(name, version, cover, group, client) .await?; Ok(uuid) } #[tauri::command] -pub async fn get_instances( +pub async fn get_clusters( state: State<'_, Mutex>, -) -> Result, String> { +) -> Result, String> { let manager = &mut state.lock().await.client_manager; - Ok(manager.get_instances_owned()) + Ok(manager.get_clusters_owned()) } #[tauri::command] -pub async fn get_instance( +pub async fn get_cluster( state: State<'_, Mutex>, uuid: Uuid, -) -> Result { +) -> Result { let manager = &mut state.lock().await.client_manager; - let instance = manager.get_instance(uuid)?; + let instance = manager.get_cluster(uuid)?; Ok(instance.clone()) } diff --git a/src-tauri/src/api/mod.rs b/src-tauri/src/api/mod.rs index ecbe4aeb..485e6a53 100644 --- a/src-tauri/src/api/mod.rs +++ b/src-tauri/src/api/mod.rs @@ -23,9 +23,9 @@ pub fn init() -> TauriPlugin { }) .invoke_handler(tauri::generate_handler![ auth::login_msa, - game::create_instance, - game::get_instances, - game::get_instance, + game::create_cluster, + game::get_clusters, + game::get_cluster, game::get_manifest, refresh_client_manager, ]) diff --git a/src/bridge/game.ts b/src/bridge/game.ts index 6d4005a7..2e726cc6 100644 --- a/src/bridge/game.ts +++ b/src/bridge/game.ts @@ -1,7 +1,7 @@ import { invoke } from '@tauri-apps/api/core'; -export async function getInstance(uuid: string): Promise { - return await invoke('plugin:onelauncher|get_instance', { +export async function getCluster(uuid: string): Promise { + return await invoke('plugin:onelauncher|get_cluster', { uuid, }); } @@ -12,22 +12,22 @@ export async function getManifest(uuid: string): Promise { }); } -export async function getInstancesWithManifests(): Promise { - const instances = await getInstances(); +export async function getClustersWithManifests(): Promise { + const clusters = await getClusters(); - return Promise.all(instances.map(async instance => ({ - instance, - manifest: await getManifest(instance.id), + return Promise.all(clusters.map(async cluster => ({ + cluster, + manifest: await getManifest(cluster.id), }))); } -export async function getGroupedInstances(): Promise> { - const instances = await getInstancesWithManifests(); - const map = new Map(); +export async function getClustersGrouped(): Promise> { + const clusters = await getClustersWithManifests(); + const map = new Map(); - instances.forEach((instance, index) => { - const groupName = instance.instance.group || 'Unnamed'; - const value = { ...instance, index }; + clusters.forEach((cluster, index) => { + const groupName = cluster.cluster.group || 'Unnamed'; + const value = { ...cluster, index }; if (map.has(groupName)) map.get(groupName)!.push(value); @@ -38,24 +38,19 @@ export async function getGroupedInstances(): Promise { - return await invoke('plugin:onelauncher|get_instances'); +export async function getClusters(): Promise { + return await invoke('plugin:onelauncher|get_clusters'); } -export async function createInstance(instance: Omit & { version: string }): Promise { +export async function createCluster(cluster: Omit & { version: string }): Promise { return await invoke( - 'plugin:onelauncher|create_instance', + 'plugin:onelauncher|create_cluster', { - name: instance.name, - version: instance.version, - client: instance.client, - // cover: instance.cover, - // group: instance.group, - }, - { - headers: { - Accept: 'text/plain', - }, + name: cluster.name, + version: cluster.version, + client: cluster.client, + // cover: cluster.cover, + // group: cluster.group, }, ); } diff --git a/src/index.tsx b/src/index.tsx index d696ff77..d92951fc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,18 +8,18 @@ import App from './ui/App'; import HomePage from './ui/pages/Home'; import BrowserPage from './ui/pages/Browser'; import UpdatesPage from './ui/pages/Updates'; -import InstancePage from './ui/pages/instances'; -import InstanceOverview from './ui/pages/instances/InstanceOverview'; -import InstanceMods from './ui/pages/instances/InstanceMods'; +import ClusterPage from './ui/pages/cluster'; +import ClusterOverview from './ui/pages/cluster/ClusterOverview'; +import ClusterMods from './ui/pages/cluster/ClusterMods'; render(() => ( - - - + + + ), document.getElementById('root') as HTMLElement); diff --git a/src/types/game.d.ts b/src/types/game.d.ts index 7c3ca779..5e25c09c 100644 --- a/src/types/game.d.ts +++ b/src/types/game.d.ts @@ -1,6 +1,6 @@ declare namespace Core { - export interface InstanceWithManifest { - instance: Instance, + export interface ClusterWithManifest { + cluster: Cluster, manifest: Manifest, } @@ -16,7 +16,7 @@ declare namespace Core { }, } - export interface Instance { + export interface Cluster { id: string, createdAt: number, name: string, diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index e4052ee1..9799fc22 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -1,7 +1,6 @@ import { CheckIcon, DotsVerticalIcon, - Download01Icon, EyeIcon, PlayIcon, PlusIcon, @@ -16,8 +15,7 @@ import Tag from '../components/base/Tag'; import TextField from '../components/base/TextField'; import defaultCover from '../../assets/images/default_instance_cover.jpg'; import ContextMenu from '../components/overlay/ContextMenu'; -import { createInstance, getGroupedInstances, getInstancesWithManifests } from '../../bridge/game'; -import Modal from '../components/overlay/Modal'; +import { createCluster, getClustersGrouped } from '../../bridge/game'; // TODO: Replace this into it's own component // possibly with the core ui lirbary with autogenerated and optimized svgs pls @@ -63,14 +61,14 @@ function Banner() { ); } -interface InstanceCardContextMenuProps { +interface ClusterCardContextMenuProps { pos: Accessor<{ x: number; y: number }>; contextMenuVisible: Accessor; setContextMenuVisible: Setter; id: string; } -function InstanceCardContextMenu(props: InstanceCardContextMenuProps) { +function ClusterCardContextMenu(props: ClusterCardContextMenuProps) { const navigate = useNavigate(); return ( @@ -83,7 +81,7 @@ function InstanceCardContextMenu(props: InstanceCardContextMenuProps) { icon={} text="View" onClick={() => { - navigate(`/instances/?id=${props.id}`); + navigate(`/clusters/?id=${props.id}`); }} /> @@ -106,14 +104,14 @@ declare module 'solid-js' { } } -function InstanceCard(props: Core.InstanceWithManifest) { +function ClusterCard(props: Core.ClusterWithManifest) { const navigate = useNavigate(); const [pos, setPos] = createSignal({ x: 0, y: 0 }); const [contextMenuVisible, setContextMenuVisible] = createSignal(false); let ref!: HTMLDivElement; - function openInstancePage(_e: MouseEvent) { - navigate(`/instances/?id=${props.instance.id}`); + function openClusterPage(_e: MouseEvent) { + navigate(`/clusters/?id=${props.cluster.id}`); } function openContextMenu(e: MouseEvent) { @@ -129,7 +127,7 @@ function InstanceCard(props: Core.InstanceWithManifest) { return ( <>
openInstancePage(e)} + onClick={e => openClusterPage(e)} onMouseDown={e => onMouseDown(e)} onContextMenu={e => openContextMenu(e)} ref={ref} @@ -141,16 +139,16 @@ function InstanceCard(props: Core.InstanceWithManifest) { style={{ '-webkit-transform': 'translateZ(0)' }} >
-

{props.instance.name}

+

{props.cluster.name}

- {props.instance.client.type} + {props.cluster.client.type} {' '} {props.manifest.manifest.id} {/* {' '} @@ -162,38 +160,38 @@ function InstanceCard(props: Core.InstanceWithManifest) {

- ); } -interface InstanceGroupProps { +interface ClusterGroupProps { title: string; - instances: Core.InstanceWithManifest[]; + clusters: Core.ClusterWithManifest[]; } -function InstanceGroup(props: InstanceGroupProps) { +function ClusterGroup(props: ClusterGroupProps) { return (

{props.title}

- {item => } + {item => }
); } function HomePage() { - const [instances, { refetch }] = createResource(getGroupedInstances); + const [clusters, { refetch }] = createResource(getClustersGrouped); async function newInstance() { - await createInstance({ - name: 'New Instance', + await createCluster({ + name: 'New Cluster', version: '1.8.9', client: { type: 'Vanilla', @@ -217,22 +215,22 @@ function HomePage() { + Date: Sat, 30 Mar 2024 19:40:59 +0100 Subject: [PATCH 04/10] feat: Lots of changes --- .idea/.gitignore | 8 + .idea/Launcher.iml | 12 + .idea/discord.xml | 7 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + onelauncher/src/constants.rs | 36 ++- onelauncher/src/error.rs | 4 + onelauncher/src/game/client.rs | 23 +- onelauncher/src/game/client_manager.rs | 11 +- onelauncher/src/game/clients/mod.rs | 8 +- onelauncher/src/game/clients/vanilla.rs | 161 ++++++---- onelauncher/src/game/minecraft.rs | 124 ++++++-- onelauncher/src/utils/file.rs | 9 +- pnpm-lock.yaml | 312 +++++-------------- src/ui/components/game/PlayerHead.tsx | 2 +- 16 files changed, 395 insertions(+), 342 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/Launcher.iml create mode 100644 .idea/discord.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Launcher.iml b/.idea/Launcher.iml new file mode 100644 index 00000000..ba207acf --- /dev/null +++ b/.idea/Launcher.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 00000000..d8e95616 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..03d9549e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..24e7e3a1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/onelauncher/src/constants.rs b/onelauncher/src/constants.rs index fad91da7..5b1cc774 100644 --- a/onelauncher/src/constants.rs +++ b/onelauncher/src/constants.rs @@ -2,16 +2,44 @@ pub const NAME: &str = "OneLauncher"; pub const VERSION: &str = "0.1.0"; + // Authentication and HTTP (TODO: env?) pub const APP_CONFIG_DIR: &str = "org.polyfrost.launcher"; pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)"; + // Microsoft Authentication pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56"; pub const MSA_PORT: u16 = 13523; -// Minecraft -pub const MINECRAFT_VERSIONS_MANIFEST: &str = - "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; -// Java Downloading +// Minecraft +pub const MINECRAFT_VERSIONS_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; + + +// OS constants which match Minecraft's/Mojang's scheme +#[cfg(target_os = "windows")] +pub const TARGET_OS: &str = "windows"; + +#[cfg(target_os = "macos")] +pub const TARGET_OS: &str = "osx"; + +#[cfg(target_os = "linux")] +pub const TARGET_OS: &str = "linux"; + +#[cfg(target_arch = "x86")] +pub const NATIVE_ARCH: &str = "32"; + +#[cfg(target_arch = "x86_64")] +pub const NATIVE_ARCH: &str = "64"; + +// TODO: Add more architectures +#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] +pub const NATIVE_ARCH: &str = "64"; + +// Minecraft library splitter +#[cfg(target_os = "windows")] +pub const LIBRARY_SPLITTER: &str = ";"; + +#[cfg(not(target_os = "windows"))] +pub const LIBRARY_SPLITTER: &str = ":"; diff --git a/onelauncher/src/error.rs b/onelauncher/src/error.rs index ad5812a6..e5b478b4 100644 --- a/onelauncher/src/error.rs +++ b/onelauncher/src/error.rs @@ -66,6 +66,10 @@ pub enum ErrorKind { /// Wrapper around [`crate::game::client::ClientManagerError`] to handle directory errors. #[error("failed to manage clients: {0}")] ClientManagerError(#[from] crate::game::client_manager::ClientManagerError), + + /// Wrapper around [`reqwest::header::ToStrError`] to handle header conversion errors. + #[error("failed to convert header to string: {0}")] + HeaderToStrError(#[from] reqwest::header::ToStrError), } #[derive(Debug)] diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index 79e01c26..1b3c6160 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -1,27 +1,15 @@ -use std::path::PathBuf; - use async_trait::async_trait; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use super::{clients::ClientType, minecraft::{MinecraftManifest, ReleaseType}}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MinecraftVersion { - pub id: String, - pub url: String, - #[serde(default)] - pub release_type: ReleaseType, - #[serde(default)] - pub release_time: chrono::DateTime, -} +use super::{clients::ClientType, minecraft::MinecraftManifest}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Manifest { pub id: Uuid, - pub manifest: MinecraftManifest, + #[serde(rename = "manifest")] + pub minecraft_manifest: MinecraftManifest, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -44,11 +32,6 @@ pub trait ClientTrait<'a>: Send + Sync { async fn launch(&self) -> crate::Result<()>; async fn setup(&self) -> crate::Result<()>; - async fn install_game(&self) -> crate::Result; - async fn install_libraries(&self) -> crate::Result; - async fn install_natives(&self) -> crate::Result<()>; - async fn install_assets(&self) -> crate::Result<()>; - fn get_cluster(&self) -> &'a Cluster where Self: Sized; diff --git a/onelauncher/src/game/client_manager.rs b/onelauncher/src/game/client_manager.rs index 1aa42388..2f67d3bb 100644 --- a/onelauncher/src/game/client_manager.rs +++ b/onelauncher/src/game/client_manager.rs @@ -13,6 +13,7 @@ use super::{client::{ClientTrait, Cluster, Manifest}, clients::{self, vanilla, C pub enum ClientManagerError { #[error("Cluster not found")] ClusterNotFound, + #[error("Manifest not found")] ManifestNotFound, } @@ -24,13 +25,9 @@ pub struct ClientManager { impl ClientManager { pub fn new() -> crate::Result { - let clusters = load_and_serialize::(&dirs::clusters_dir()?)?; - - let manifests = load_and_serialize::(&dirs::manifests_dir()?)?; - Ok(Self { - clusters, - manifests, + clusters: load_and_serialize::(&dirs::clusters_dir()?)?, + manifests: load_and_serialize::(&dirs::manifests_dir()?)?, }) } @@ -108,7 +105,7 @@ impl ClientManager { let minecraft_manifest = vanilla::retrieve_version_manifest(url).await?; let manifest = Manifest { id: uuid, - manifest: minecraft_manifest, + minecraft_manifest, }; save( diff --git a/onelauncher/src/game/clients/mod.rs b/onelauncher/src/game/clients/mod.rs index 1f42aef6..7faf775b 100644 --- a/onelauncher/src/game/clients/mod.rs +++ b/onelauncher/src/game/clients/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use self::vanilla::VanillaClientProps; -use super::client::{ClientTrait, Cluster, Manifest, MinecraftVersion}; +use super::{client::{ClientTrait, Cluster, Manifest}, minecraft::MinecraftVersion}; pub mod vanilla; @@ -14,6 +14,12 @@ pub enum ClientType { Vanilla(VanillaClientProps), } +#[derive(Debug)] +pub struct LaunchInfo { + // This is where things like account info, features etc will go + pub java: PathBuf +} + pub async fn get_versions(client: &ClientType, file: Option<&PathBuf>) -> crate::Result> { match client { ClientType::Vanilla(_) => vanilla::get_versions(file).await, diff --git a/onelauncher/src/game/clients/vanilla.rs b/onelauncher/src/game/clients/vanilla.rs index 70362414..96518591 100644 --- a/onelauncher/src/game/clients/vanilla.rs +++ b/onelauncher/src/game/clients/vanilla.rs @@ -1,7 +1,7 @@ use crate::{ - constants::MINECRAFT_VERSIONS_MANIFEST, + constants::{self, MINECRAFT_VERSIONS_MANIFEST}, create_game_client, - game::{client::{ClientTrait, Cluster, Manifest, MinecraftVersion}, minecraft::MinecraftManifest}, + game::{client::{ClientTrait, Cluster, Manifest}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}}, impl_game_client, utils::{dirs, http} }; @@ -10,9 +10,12 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; use std::{fs, path::PathBuf}; +pub fn vanilla_dir() -> crate::Result { + Ok(dirs::clients_dir()?.join("vanilla")) +} + create_game_client! { VanillaClientProps {} - VanillaClient {} } @@ -29,38 +32,94 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> { } async fn setup(&self) -> crate::Result<()> { - let client_path = self.install_game().await?; - println!("{:?}", client_path); - Ok(()) - } + let manifest = &self.manifest.minecraft_manifest; - async fn install_game(&self) -> crate::Result { - let manifest = &self.manifest.manifest; - let file = dirs::clients_dir()?.join(format!("{}.jar", manifest.version)); - - if !file.exists() { - fs::create_dir_all(&file)?; - let artifact = &manifest.downloads.client; - - http::download_file_sha1_check(&artifact.url, &file, &artifact.sha1).await?; - } - - Ok(file) - } - - async fn install_libraries(&self) -> crate::Result { - Ok("".to_string()) - } - - async fn install_natives(&self) -> crate::Result<()> { - Ok(()) - } - - async fn install_assets(&self) -> crate::Result<()> { + let client_path = install_game(manifest).await?; + let libraries = install_libraries(manifest).await?; Ok(()) } } +async fn install_game(manifest: &MinecraftManifest) -> crate::Result { + let file_name = format!("{}.jar", manifest.version); + let file = vanilla_dir()?.join(file_name); + + if !file.exists() { + fs::create_dir_all(&file.parent().ok_or(anyhow!("Couldn't take client jar parent folder"))?)?; + + let artifact = &manifest.downloads.client; + http::download_file_sha1_check(&artifact.url, &file, &artifact.sha1).await?; + } + + Ok(file) +} + +pub async fn install_libraries(manifest: &MinecraftManifest) -> crate::Result { + let libraries = &manifest.libraries; + + let mut natives_ret: Vec<&Library> = vec![]; + let mut libraries_ret: Vec = vec![]; + + for library in libraries { + if !&library.rules.check() { + continue; + } + + if let Some(_) = library.natives { + natives_ret.push(library); + continue; + } + + let artifact = library.downloads.artifact.clone().ok_or(anyhow!("No artifact object"))?; + let path = artifact.path; + let url = artifact.url; + + let dest = dirs::libraries_dir()?.join(path); + fs::create_dir_all(dest.parent().ok_or(anyhow!("Couldn't get library parent"))?)?; + + if !dest.exists() { + http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?; + } + + libraries_ret.push(dest.to_str().unwrap().to_string()); + } + + install_natives(natives_ret).await?; + Ok(libraries_ret.join(constants::LIBRARY_SPLITTER)) +} + +pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> { + for native in natives { + let classifiers = native.natives.as_ref().unwrap(); + let classifier = match classifiers.get(constants::TARGET_OS) { + Some(classifier) => classifier.replace("${arch}", constants::NATIVE_ARCH), + None => continue, + }; + + let artifact = native.downloads.classifiers.as_ref() + .ok_or(anyhow!("No classifiers object"))? + .get(&classifier) + .ok_or(anyhow!("No classifier object"))?; + + let path = artifact.path.clone(); + let url = artifact.url.clone(); + + let dest = dirs::libraries_dir()?.join(path); + fs::create_dir_all(dest.parent().ok_or(anyhow!("Couldn't get native parent"))?)?; + + if !dest.exists() { + http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?; + } + } + + Ok(()) +} + +pub async fn install_assets() -> crate::Result<()> { + Ok(()) +} + + #[derive(Clone, Serialize, Deserialize)] struct VersionList { versions: Vec, @@ -75,9 +134,22 @@ struct CachedVersions { pub async fn get_versions(file: Option<&PathBuf>) -> crate::Result> { if let Some(file) = file { if file.exists() && file.is_file() { - if let Some(versions) = get_cached_versions(file).await? { - return Ok(versions); - } + let cached = serde_json::from_str::(&fs::read_to_string(file)?)?; + let head_request = http::create_client()? + .head(MINECRAFT_VERSIONS_MANIFEST) + .send() + .await?; + + let last_updated = head_request + .headers() + .get("Last-Modified") + .ok_or(anyhow!("Last-Modified header not found"))?; + + let last_updated = chrono::DateTime::parse_from_rfc2822(last_updated.to_str()?)?; + + if cached.last_updated > last_updated.timestamp() { + return Ok(cached.versions); + } } } @@ -100,29 +172,6 @@ pub async fn get_versions(file: Option<&PathBuf>) -> crate::Result crate::Result>> { - let cached = serde_json::from_str::(&fs::read_to_string(file)?)?; - let head_request = http::create_client()? - .head(MINECRAFT_VERSIONS_MANIFEST) - .send() - .await?; - - let last_updated = head_request - .headers() - .get("Last-Modified") - .ok_or(anyhow!("Last-Modified header not found"))?; - let last_updated = last_updated - .to_str() - .map_err(|_| anyhow!("Failed to convert Last-Modified header to string"))?; - let last_updated = chrono::DateTime::parse_from_rfc2822(last_updated)?; - - if cached.last_updated < last_updated.timestamp() { - Ok(None) - } else { - Ok(Some(cached.versions)) - } -} - pub async fn retrieve_version_manifest(url: &str) -> crate::Result { let manifest = http::create_client()? .get(url) diff --git a/onelauncher/src/game/minecraft.rs b/onelauncher/src/game/minecraft.rs index bb49ecb0..7b346590 100644 --- a/onelauncher/src/game/minecraft.rs +++ b/onelauncher/src/game/minecraft.rs @@ -3,6 +3,19 @@ use std::{collections::HashMap, marker::PhantomData}; use serde::{de::{self, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize}; +use crate::constants; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MinecraftVersion { + pub id: String, + pub url: String, + #[serde(default)] + pub release_type: ReleaseType, + #[serde(default)] + pub release_time: chrono::DateTime, +} + #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MinecraftManifest { @@ -34,17 +47,52 @@ pub struct ModernArguments { pub jvm: Vec, } +pub trait ModernArgumentsExt { + fn build(&self) -> String; +} + +impl ModernArgumentsExt for ModernArguments { + fn build(&self) -> String { + // TODO: Implement the custom rules + let mut builder = String::new(); + + for item in &self.game { + match item { + ModernArgumentsItem::Simple(s) => { + builder.push_str(s); + builder.push(' '); + }, + + ModernArgumentsItem::Rule(_) => {} + } + } + + for item in &self.jvm { + match item { + ModernArgumentsItem::Simple(s) => { + builder.push_str(s); + builder.push(' '); + }, + + ModernArgumentsItem::Rule(_) => {} + } + } + + builder + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(untagged)] pub enum ModernArgumentsItem { Simple(String), - Rule(ArgumentRule), + Rule(ModernArgumentRuleItem), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ArgumentRule { +pub struct ModernArgumentRuleItem { #[serde(skip_serializing_if = "Vec::is_empty")] pub rules: Vec, @@ -110,7 +158,7 @@ pub struct LibraryDownload { #[serde(skip_serializing_if = "Option::is_none")] pub artifact: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub classifiers: Option, + pub classifiers: Option>, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -121,22 +169,6 @@ pub struct Artifact { pub url: String, } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Classifiers { - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "natives-osx")] - pub natives_osx: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "natives-linux")] - pub natives_linux: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "natives-windows")] - pub natives_windows: Option, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Rule { @@ -149,6 +181,60 @@ pub struct Rule { pub features: Option>, } +pub trait RuleExt { + fn check(&self) -> bool; +} + +pub trait RuleListExt { + fn check(&self) -> bool; +} + +impl RuleListExt for Vec { + fn check(&self) -> bool { + for rule in self { + if !rule.check() { + return false; + } + } + + true + } +} + +impl RuleExt for Rule { + fn check(&self) -> bool { + if let Some(os) = &self.os { + match &self.action { + RuleAction::Allow => { + // os name check + if let Some(name) = &os.name { + if name != constants::TARGET_OS { + return false; + } + } + + // TODO: os version check + // os version check + + // TODO: os arch check + // os arch check + }, + + RuleAction::Disallow => { + // os name check + if let Some(name) = &os.name { + if name == constants::TARGET_OS { + return false; + } + } + } + } + } + + true + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum RuleAction { #[serde(rename = "allow")] diff --git a/onelauncher/src/utils/file.rs b/onelauncher/src/utils/file.rs index 94e08365..f4083e0b 100644 --- a/onelauncher/src/utils/file.rs +++ b/onelauncher/src/utils/file.rs @@ -9,12 +9,11 @@ use std::{ use crate::ErrorKind; pub fn file_sha1(file: &PathBuf) -> crate::Result { - let file = File::open(file)?; - let reader = BufReader::new(file); - + let mut file = File::open(file)?; let mut hasher = Sha1::new(); - hasher.update(&reader.buffer()); - let hash = hasher.finalize(); + let mut reader = BufReader::new(&mut file); + std::io::copy(&mut reader, &mut hasher)?; + let hash = hasher.finalize(); Ok(format!("{:x}", hash)) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d02519ef..b67ee7f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ dependencies: '@fontsource/poppins': specifier: ^5.0.12 version: 5.0.12 - '@sentry/vite-plugin': - specifier: ^2.16.0 - version: 2.16.0 '@solidjs/router': specifier: ^0.13.1 version: 0.13.1(solid-js@1.8.16) @@ -104,6 +101,7 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + dev: true /@antfu/eslint-define-config@1.23.0-2: resolution: {integrity: sha512-LvxY21+ZhpuBf/aHeBUtGQhSEfad4PkNKXKvDOSvukaM3XVTfBhwmHX2EKwAsdq5DlfjbT3qqYyMiueBIO5iDQ==} @@ -122,6 +120,7 @@ packages: dependencies: '@babel/highlight': 7.23.4 chalk: 2.4.2 + dev: true /@babel/code-frame@7.24.2: resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} @@ -134,6 +133,7 @@ packages: /@babel/compat-data@7.23.5: resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} engines: {node: '>=6.9.0'} + dev: true /@babel/core@7.24.0: resolution: {integrity: sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==} @@ -156,6 +156,7 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true /@babel/generator@7.23.6: resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} @@ -165,6 +166,7 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 + dev: true /@babel/helper-compilation-targets@7.23.6: resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} @@ -175,10 +177,12 @@ packages: browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 + dev: true /@babel/helper-environment-visitor@7.22.20: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} engines: {node: '>=6.9.0'} + dev: true /@babel/helper-function-name@7.23.0: resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} @@ -186,12 +190,14 @@ packages: dependencies: '@babel/template': 7.24.0 '@babel/types': 7.24.0 + dev: true /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.24.0 + dev: true /@babel/helper-module-imports@7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} @@ -205,6 +211,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.24.0 + dev: true /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0): resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} @@ -218,6 +225,7 @@ packages: '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 + dev: true /@babel/helper-plugin-utils@7.24.0: resolution: {integrity: sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==} @@ -229,24 +237,29 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.24.0 + dev: true /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.24.0 + dev: true /@babel/helper-string-parser@7.23.4: resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} engines: {node: '>=6.9.0'} + dev: true /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} + dev: true /@babel/helper-validator-option@7.23.5: resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} + dev: true /@babel/helpers@7.24.0: resolution: {integrity: sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==} @@ -257,6 +270,7 @@ packages: '@babel/types': 7.24.0 transitivePeerDependencies: - supports-color + dev: true /@babel/highlight@7.23.4: resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} @@ -265,6 +279,7 @@ packages: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 + dev: true /@babel/highlight@7.24.2: resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} @@ -282,6 +297,7 @@ packages: hasBin: true dependencies: '@babel/types': 7.24.0 + dev: true /@babel/parser@7.24.1: resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} @@ -315,6 +331,7 @@ packages: '@babel/code-frame': 7.23.5 '@babel/parser': 7.24.0 '@babel/types': 7.24.0 + dev: true /@babel/traverse@7.24.0: resolution: {integrity: sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==} @@ -332,6 +349,7 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color + dev: true /@babel/types@7.24.0: resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} @@ -340,6 +358,7 @@ packages: '@babel/helper-string-parser': 7.23.4 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 + dev: true /@es-joy/jsdoccomment@0.42.0: resolution: {integrity: sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==} @@ -741,23 +760,28 @@ packages: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.25 + dev: true /@jridgewell/resolve-uri@3.1.2: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} + dev: true /@jridgewell/set-array@1.2.1: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} + dev: true /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true /@jridgewell/trace-mapping@0.3.25: resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + dev: true /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -891,125 +915,6 @@ packages: dev: true optional: true - /@sentry/babel-plugin-component-annotate@2.16.0: - resolution: {integrity: sha512-+uy1qPkA5MSNgJ0L9ur/vNTydfdHwHnBX2RQ+0thsvkqf90fU788YjkkXwUiBBNuqNyI69JiOW6frixAWy7oUg==} - engines: {node: '>= 14'} - dev: false - - /@sentry/bundler-plugin-core@2.16.0: - resolution: {integrity: sha512-dhgIZsIR3L9KnE2OO5JJm6hPtStAjEPYKQsZzxRr69uVhd9xAvfXeXr0afKVNVEcIDksas6yMgHqwQ2wOXFIAg==} - engines: {node: '>= 14'} - dependencies: - '@babel/core': 7.24.0 - '@sentry/babel-plugin-component-annotate': 2.16.0 - '@sentry/cli': 2.30.2 - dotenv: 16.4.5 - find-up: 5.0.0 - glob: 9.3.5 - magic-string: 0.27.0 - unplugin: 1.0.1 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@sentry/cli-darwin@2.30.2: - resolution: {integrity: sha512-lZkKXMt0HUAwLQuPpi/DM3CsdCCp+6B2cdur+8fAq7uARXTOsTKVDxv9pkuJHCgHUnguh8ittP5GMr0baTxmMg==} - engines: {node: '>=10'} - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@sentry/cli-linux-arm64@2.30.2: - resolution: {integrity: sha512-IWassuXggNhHOPCNrORNmd5SrAx5rU4XDlgOWBJr/ez7DvlPrr9EhV1xsdht6K4mPXhCGJq3rtRdCoWGJQW6Uw==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux, freebsd] - requiresBuild: true - dev: false - optional: true - - /@sentry/cli-linux-arm@2.30.2: - resolution: {integrity: sha512-H7hqiLpEL7w/EHdhuUGatwg9O080mdujq4/zS96buKIHXxZE6KqMXGtMVIAvTl1+z6BlBEnfvZGI19MPw3t/7w==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux, freebsd] - requiresBuild: true - dev: false - optional: true - - /@sentry/cli-linux-i686@2.30.2: - resolution: {integrity: sha512-gZIq131M4TJTG1lX9uvpoaGWaEXCEfdDXrXu/z/YZmAKBcThpMYChodXmm8FB6X4xb0TPXzIFqdzlLdglFK46g==} - engines: {node: '>=10'} - cpu: [x86, ia32] - os: [linux, freebsd] - requiresBuild: true - dev: false - optional: true - - /@sentry/cli-linux-x64@2.30.2: - resolution: {integrity: sha512-NmTAIl7aW9OHxwB4149sBfvCbTyK9T/CvBX38keaD2yIThet9gZ4koP49hBDxYF99aQX3E+LIAqWwnkV9W72Sw==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux, freebsd] - requiresBuild: true - dev: false - optional: true - - /@sentry/cli-win32-i686@2.30.2: - resolution: {integrity: sha512-SBR/Q3T6o+7uHwHNdjcG9GA3R++9w8oi778b95GuOC3dh0WOU6hXaKwQWe95ZcuSd2rKpouH7dhMjqqNM4HxOA==} - engines: {node: '>=10'} - cpu: [x86, ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@sentry/cli-win32-x64@2.30.2: - resolution: {integrity: sha512-gF9wSZxzXFgakkC+uKVLAAYlbYj13e1gTsNm3gm+ODfpV+rbHwvbKoLfNsbVCFVCEZxIV2rXEP5WmTr0kiMvWQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@sentry/cli@2.30.2: - resolution: {integrity: sha512-jQ/RBJ3bZ4PFbfOsGq8EykygHHmXXPw+i6jqsnQfAPIeZoX+DsqpAZbYubQEZKekmQ8EVGFxGHzUVkd6hLVMbA==} - engines: {node: '>= 10'} - hasBin: true - requiresBuild: true - dependencies: - https-proxy-agent: 5.0.1 - node-fetch: 2.7.0 - progress: 2.0.3 - proxy-from-env: 1.1.0 - which: 2.0.2 - optionalDependencies: - '@sentry/cli-darwin': 2.30.2 - '@sentry/cli-linux-arm': 2.30.2 - '@sentry/cli-linux-arm64': 2.30.2 - '@sentry/cli-linux-i686': 2.30.2 - '@sentry/cli-linux-x64': 2.30.2 - '@sentry/cli-win32-i686': 2.30.2 - '@sentry/cli-win32-x64': 2.30.2 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@sentry/vite-plugin@2.16.0: - resolution: {integrity: sha512-/7rFTpasql9fJ1KbruF0yDESKV/ojvOP9pL/qqwUffvA9iy9Bvw3kYzzyzd1YsiCiVBgftoCsFTKPcvL3A4rwQ==} - engines: {node: '>= 14'} - dependencies: - '@sentry/bundler-plugin-core': 2.16.0 - unplugin: 1.0.1 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - /@solid-primitives/refs@1.0.8(solid-js@1.8.16): resolution: {integrity: sha512-+jIsWG8/nYvhaCoG2Vg6CJOLgTmPKFbaCrNQKWfChalgUf9WrVxWw0CdJb3yX15n5lUcQ0jBo6qYtuVVmBLpBw==} peerDependencies: @@ -1630,15 +1535,7 @@ packages: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true - - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false + dev: true /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1664,6 +1561,7 @@ packages: engines: {node: '>=4'} dependencies: color-convert: 1.9.3 + dev: true /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -1687,6 +1585,7 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + dev: true /are-docs-informative@0.0.2: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} @@ -1828,10 +1727,12 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true /binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + dev: true /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1848,12 +1749,14 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 + dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 + dev: true /browserslist@4.23.0: resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} @@ -1864,6 +1767,7 @@ packages: electron-to-chromium: 1.4.715 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) + dev: true /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} @@ -1899,6 +1803,7 @@ packages: /caniuse-lite@1.0.30001600: resolution: {integrity: sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==} + dev: true /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -1907,6 +1812,7 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 + dev: true /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1941,6 +1847,7 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: true /ci-info@4.0.0: resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} @@ -1958,6 +1865,7 @@ packages: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 + dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1968,6 +1876,7 @@ packages: /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1989,6 +1898,7 @@ packages: /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true /core-js-compat@3.36.1: resolution: {integrity: sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==} @@ -2066,6 +1976,7 @@ packages: optional: true dependencies: ms: 2.1.2 + dev: true /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2116,17 +2027,13 @@ packages: esutils: 2.0.3 dev: true - /dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - dev: false - /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true /electron-to-chromium@1.4.715: resolution: {integrity: sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==} + dev: true /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2296,10 +2203,12 @@ packages: /escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} + dev: true /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + dev: true /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -2853,6 +2762,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /find-up-simple@1.0.0: resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} @@ -2873,6 +2783,7 @@ packages: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 + dev: true /find-up@7.0.0: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} @@ -2916,12 +2827,14 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true + dev: true optional: true /function-bind@1.1.2: @@ -2945,6 +2858,7 @@ packages: /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + dev: true /get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} @@ -2982,6 +2896,7 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -3013,19 +2928,10 @@ packages: path-is-absolute: 1.0.1 dev: true - /glob@9.3.5: - resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} - engines: {node: '>=16 || 14 >=14.17'} - dependencies: - fs.realpath: 1.0.0 - minimatch: 8.0.4 - minipass: 4.2.8 - path-scurry: 1.10.1 - dev: false - /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + dev: true /globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -3075,6 +2981,7 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -3131,16 +3038,6 @@ packages: engines: {node: '>=8'} dev: true - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -3243,6 +3140,7 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.3.0 + dev: true /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -3291,6 +3189,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-finalizationregistry@1.0.2: resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} @@ -3315,6 +3214,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-hexadecimal@1.0.4: resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} @@ -3347,6 +3247,7 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} @@ -3429,6 +3330,7 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true /iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} @@ -3456,6 +3358,7 @@ packages: /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} @@ -3478,6 +3381,7 @@ packages: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true + dev: true /jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} @@ -3505,6 +3409,7 @@ packages: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + dev: true /jsonc-eslint-parser@2.4.0: resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} @@ -3602,6 +3507,7 @@ packages: engines: {node: '>=10'} dependencies: p-locate: 5.0.0 + dev: true /locate-path@7.2.0: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} @@ -3621,11 +3527,13 @@ packages: /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} + dev: true /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: yallist: 3.1.1 + dev: true /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} @@ -3634,13 +3542,6 @@ packages: yallist: 4.0.0 dev: true - /magic-string@0.27.0: - resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - dev: false - /magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} @@ -3713,13 +3614,6 @@ packages: brace-expansion: 1.1.11 dev: true - /minimatch@8.0.4: - resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} - engines: {node: '>=16 || 14 >=14.17'} - dependencies: - brace-expansion: 2.0.1 - dev: false - /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} @@ -3727,14 +3621,10 @@ packages: brace-expansion: 2.0.1 dev: true - /minipass@4.2.8: - resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} - engines: {node: '>=8'} - dev: false - /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} + dev: true /mlly@1.6.1: resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} @@ -3747,6 +3637,7 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3774,20 +3665,9 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - dependencies: - whatwg-url: 5.0.0 - dev: false - /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + dev: true /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -3811,6 +3691,7 @@ packages: /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + dev: true /normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} @@ -3938,6 +3819,7 @@ packages: engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 + dev: true /p-limit@4.0.0: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} @@ -3958,6 +3840,7 @@ packages: engines: {node: '>=10'} dependencies: p-limit: 3.1.0 + dev: true /p-locate@6.0.0: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} @@ -4016,6 +3899,7 @@ packages: /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + dev: true /path-exists@5.0.0: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} @@ -4047,6 +3931,7 @@ packages: dependencies: lru-cache: 10.2.0 minipass: 7.0.4 + dev: true /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -4059,10 +3944,12 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + dev: true /picomatch@4.0.1: resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} @@ -4182,11 +4069,6 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - dev: false - /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -4195,10 +4077,6 @@ packages: sisteransi: 1.0.5 dev: true - /proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - dev: false - /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -4258,6 +4136,7 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: true /reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} @@ -4394,6 +4273,7 @@ packages: /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + dev: true /semver@7.6.0: resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} @@ -4642,6 +4522,7 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 + dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -4713,12 +4594,14 @@ packages: /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} + dev: true /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true /toml-eslint-parser@0.9.3: resolution: {integrity: sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==} @@ -4727,10 +4610,6 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: false - /ts-api-utils@1.3.0(typescript@5.4.3): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -4867,15 +4746,6 @@ packages: '@types/unist': 2.0.10 dev: true - /unplugin@1.0.1: - resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} - dependencies: - acorn: 8.11.3 - chokidar: 3.6.0 - webpack-sources: 3.2.3 - webpack-virtual-modules: 0.5.0 - dev: false - /update-browserslist-db@1.0.13(browserslist@4.23.0): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -4885,6 +4755,7 @@ packages: browserslist: 4.23.0 escalade: 3.1.2 picocolors: 1.0.0 + dev: true /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -5000,26 +4871,6 @@ packages: - supports-color dev: true - /webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: false - - /webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} - dev: false - - /webpack-virtual-modules@0.5.0: - resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} - dev: false - - /whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - dev: false - /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -5075,6 +4926,7 @@ packages: hasBin: true dependencies: isexe: 2.0.0 + dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} @@ -5105,6 +4957,7 @@ packages: /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -5128,6 +4981,7 @@ packages: /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + dev: true /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} diff --git a/src/ui/components/game/PlayerHead.tsx b/src/ui/components/game/PlayerHead.tsx index 3a9e54c0..a9b3eb75 100644 --- a/src/ui/components/game/PlayerHead.tsx +++ b/src/ui/components/game/PlayerHead.tsx @@ -1,6 +1,6 @@ import { createResource } from 'solid-js'; import type { JSX } from 'solid-js'; -import auth from '../../../bridge/auth'; +import * as auth from '../../../bridge/auth'; import steveSrc from '../../../assets/images/steve.png'; type PlayerHeadProps = JSX.IntrinsicElements['img'] & { uuid: string }; From b77f48ed907ae6ae0ee58b04cba19f3aabdd543f Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 31 Mar 2024 13:25:05 +0200 Subject: [PATCH 05/10] feat: it launches! --- onelauncher/src/auth/mod.rs | 1 + onelauncher/src/constants.rs | 1 + onelauncher/src/game/client.rs | 89 +++++++++++++++++++++---- onelauncher/src/game/client_manager.rs | 25 +++++-- onelauncher/src/game/clients/mod.rs | 6 -- onelauncher/src/game/clients/vanilla.rs | 28 +++++--- onelauncher/src/game/java.rs | 2 +- onelauncher/src/game/minecraft.rs | 17 +---- onelauncher/src/utils/dirs.rs | 8 +++ onelauncher/src/utils/file.rs | 8 +-- src-tauri/src/api/game.rs | 2 +- src/ui/pages/Home.tsx | 4 +- 12 files changed, 138 insertions(+), 53 deletions(-) diff --git a/onelauncher/src/auth/mod.rs b/onelauncher/src/auth/mod.rs index 0ad3eb44..2dc70a36 100644 --- a/onelauncher/src/auth/mod.rs +++ b/onelauncher/src/auth/mod.rs @@ -16,6 +16,7 @@ pub struct Account { pub username: String, pub uuid: String, pub skins: Vec, + pub access_token: String, // TODO deal with this properly } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/onelauncher/src/constants.rs b/onelauncher/src/constants.rs index 5b1cc774..8bc1a9b6 100644 --- a/onelauncher/src/constants.rs +++ b/onelauncher/src/constants.rs @@ -37,6 +37,7 @@ pub const NATIVE_ARCH: &str = "64"; #[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] pub const NATIVE_ARCH: &str = "64"; + // Minecraft library splitter #[cfg(target_os = "windows")] pub const LIBRARY_SPLITTER: &str = ";"; diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index 1b3c6160..94fc8093 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -1,9 +1,15 @@ +use std::{io::{stderr, stdout}, path::PathBuf}; + +use anyhow::anyhow; use async_trait::async_trait; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; +use tokio::process; use uuid::Uuid; -use super::{clients::ClientType, minecraft::MinecraftManifest}; +use crate::auth::Account; + +use super::{clients::ClientType, minecraft::{MinecraftManifest, ModernArgumentsItemExt}}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Manifest { @@ -23,22 +29,81 @@ pub struct Cluster { pub client: ClientType, } +#[derive(Debug)] +pub struct LaunchInfo { + pub java: PathBuf, + pub account: Account, + pub mem_min: u32, + pub mem_max: u32, + pub setup: SetupInfo, +} + +#[derive(Debug)] +pub struct SetupInfo { + pub version: String, + pub libraries: String, + pub game_dir: PathBuf, + pub natives_dir: PathBuf, +} + #[async_trait] pub trait ClientTrait<'a>: Send + Sync { - fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self - where - Self: Sized; + fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self where Self: Sized; - async fn launch(&self) -> crate::Result<()>; - async fn setup(&self) -> crate::Result<()>; + async fn launch(&self, info: LaunchInfo) -> crate::Result<()> { + let manifest = &self.get_manifest().minecraft_manifest; - fn get_cluster(&self) -> &'a Cluster - where - Self: Sized; + let args = get_arguments(manifest)? + .replace("${auth_player_name}", &info.account.username) + .replace("${version_name}", &info.setup.version) + .replace("${game_directory}", info.setup.game_dir.to_str().unwrap()) + .replace("${assets_root}", format!("{}/assets", info.setup.game_dir.to_str().unwrap()).as_str()) + .replace("${assets_index_name}", &manifest.asset_index.id) + .replace("${auth_uuid}", &info.account.uuid) + .replace("${auth_access_token}", &info.account.access_token) + .replace("${auth_session}", "0") // TODO: Figure out how to get this session ID. + .replace("${user_properties}", "{}") // TODO: Figure out these properties. + .replace("${user_type}", "1"); - fn get_manifest(&self) -> &'a Manifest - where - Self: Sized; + process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy") + .arg("-XX:-OmitStackTraceInFastThrow") + .arg("-Dminecraft.launcher.brand=onelauncher") + .arg("-Dminecraft.launcher.version=1") + .arg(format!("-Djava.library.path={}", info.setup.natives_dir.to_str().unwrap())) + .arg(format!("-Xms{}M", info.mem_min)) + .arg(format!("-Xmx{}M", info.mem_max)) + .arg("-cp") + .arg(info.setup.libraries) + .arg(manifest.main_class.clone()) + .args(args.split_whitespace()) + .stdout(stdout()) // TODO: Implement better logging + .stderr(stderr()) + .spawn()? + .wait() + .await?; + + Ok(()) + } + + async fn setup(&self) -> crate::Result; + + fn get_cluster(&self) -> &'a Cluster; + + fn get_manifest(&self) -> &'a Manifest; +} + +pub fn get_arguments(manifest: &MinecraftManifest) -> crate::Result { + // Modern + if let Some(arguments) = &manifest.arguments { + return Ok(arguments.game.build()); + } + + // Legacy + if let Some(arguments) = &manifest.minecraft_arguments { + return Ok(arguments.clone()); + } + + Err(anyhow!("No arguments found").into()) } #[macro_export] diff --git a/onelauncher/src/game/client_manager.rs b/onelauncher/src/game/client_manager.rs index 2f67d3bb..9b87f873 100644 --- a/onelauncher/src/game/client_manager.rs +++ b/onelauncher/src/game/client_manager.rs @@ -5,9 +5,9 @@ use chrono::Local; use serde::{de::DeserializeOwned, Serialize}; use uuid::Uuid; -use crate::utils::dirs; +use crate::{auth::Account, utils::dirs}; -use super::{client::{ClientTrait, Cluster, Manifest}, clients::{self, vanilla, ClientType}}; +use super::{client::{ClientTrait, Cluster, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java}; #[derive(Debug, thiserror::Error)] pub enum ClientManagerError { @@ -77,8 +77,25 @@ impl ClientManager { uuid: Uuid, ) -> crate::Result<()> { let client = self.get_impl_uuid(uuid)?; - client.setup().await?; - client.launch().await?; + + let java = java::download_java(&dirs::java_dir()?, client.get_manifest().minecraft_manifest.java_version.major_version).await?; + let setup = client.setup().await?; + + // TODO: Make this configurable + let info = LaunchInfo { + java, + setup, + account: Account { + username: "Player 0".to_string(), + access_token: "0".to_string(), + uuid: Uuid::new_v4().to_string(), + skins: vec![] + }, + mem_min: 1024, + mem_max: 4096, + }; + + client.launch(info).await?; Ok(()) } diff --git a/onelauncher/src/game/clients/mod.rs b/onelauncher/src/game/clients/mod.rs index 7faf775b..47cd7b2d 100644 --- a/onelauncher/src/game/clients/mod.rs +++ b/onelauncher/src/game/clients/mod.rs @@ -14,12 +14,6 @@ pub enum ClientType { Vanilla(VanillaClientProps), } -#[derive(Debug)] -pub struct LaunchInfo { - // This is where things like account info, features etc will go - pub java: PathBuf -} - pub async fn get_versions(client: &ClientType, file: Option<&PathBuf>) -> crate::Result> { match client { ClientType::Vanilla(_) => vanilla::get_versions(file).await, diff --git a/onelauncher/src/game/clients/vanilla.rs b/onelauncher/src/game/clients/vanilla.rs index 96518591..89f542c0 100644 --- a/onelauncher/src/game/clients/vanilla.rs +++ b/onelauncher/src/game/clients/vanilla.rs @@ -1,9 +1,9 @@ use crate::{ constants::{self, MINECRAFT_VERSIONS_MANIFEST}, create_game_client, - game::{client::{ClientTrait, Cluster, Manifest}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}}, + game::{client::{ClientTrait, Cluster, Manifest, SetupInfo}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}}, impl_game_client, - utils::{dirs, http} + utils::{dirs, file, http} }; use anyhow::anyhow; use async_trait::async_trait; @@ -27,16 +27,25 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> { VanillaClient { cluster, manifest } } - async fn launch(&self) -> crate::Result<()> { - Ok(()) - } - - async fn setup(&self) -> crate::Result<()> { + async fn setup(&self) -> crate::Result { let manifest = &self.manifest.minecraft_manifest; let client_path = install_game(manifest).await?; - let libraries = install_libraries(manifest).await?; - Ok(()) + let mut libraries = install_libraries(manifest).await?; + + // Append client path to the libraries string at the end + libraries.push_str(constants::LIBRARY_SPLITTER); + libraries.push_str(client_path.to_str().unwrap()); + + let game_dir = dirs::game_dir()?.join(&self.cluster.id.to_string()); + fs::create_dir_all(&game_dir)?; + + Ok(SetupInfo { + version: manifest.version.clone(), + libraries, + natives_dir: dirs::natives_dir()?, + game_dir, + }) } } @@ -109,6 +118,7 @@ pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> { if !dest.exists() { http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?; + file::extract_zip(&dest, &dirs::natives_dir()?)?; // TODO: Handle this properly } } diff --git a/onelauncher/src/game/java.rs b/onelauncher/src/game/java.rs index 304ba616..10a81b3e 100644 --- a/onelauncher/src/game/java.rs +++ b/onelauncher/src/game/java.rs @@ -112,7 +112,7 @@ async fn download( } fn extract(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { - if let Err(err) = file::extract_archive(archive.as_path(), dest.as_path()) { + if let Err(err) = file::extract_archive(archive, dest) { let _ = fs::remove_file(dest.as_path()); return Err(ErrorKind::JavaError(JavaDownloadError::ExtractError(err.to_string())).into()); } diff --git a/onelauncher/src/game/minecraft.rs b/onelauncher/src/game/minecraft.rs index 7b346590..bb200b47 100644 --- a/onelauncher/src/game/minecraft.rs +++ b/onelauncher/src/game/minecraft.rs @@ -47,27 +47,16 @@ pub struct ModernArguments { pub jvm: Vec, } -pub trait ModernArgumentsExt { +pub trait ModernArgumentsItemExt { fn build(&self) -> String; } -impl ModernArgumentsExt for ModernArguments { +impl ModernArgumentsItemExt for [ModernArgumentsItem] { fn build(&self) -> String { // TODO: Implement the custom rules let mut builder = String::new(); - for item in &self.game { - match item { - ModernArgumentsItem::Simple(s) => { - builder.push_str(s); - builder.push(' '); - }, - - ModernArgumentsItem::Rule(_) => {} - } - } - - for item in &self.jvm { + for item in self { match item { ModernArgumentsItem::Simple(s) => { builder.push_str(s); diff --git a/onelauncher/src/utils/dirs.rs b/onelauncher/src/utils/dirs.rs index bce9f719..83914640 100644 --- a/onelauncher/src/utils/dirs.rs +++ b/onelauncher/src/utils/dirs.rs @@ -18,6 +18,10 @@ pub fn libraries_dir() -> crate::Result { Ok(app_config_dir()?.join("libraries")) } +pub fn natives_dir() -> crate::Result { + Ok(app_config_dir()?.join("natives")) +} + pub fn java_dir() -> crate::Result { Ok(app_config_dir()?.join("java")) } @@ -33,3 +37,7 @@ pub fn manifests_dir() -> crate::Result { pub fn clients_dir() -> crate::Result { Ok(app_config_dir()?.join("clients")) } + +pub fn game_dir() -> crate::Result { + Ok(app_config_dir()?.join("games")) +} diff --git a/onelauncher/src/utils/file.rs b/onelauncher/src/utils/file.rs index f4083e0b..8b8d1012 100644 --- a/onelauncher/src/utils/file.rs +++ b/onelauncher/src/utils/file.rs @@ -3,7 +3,7 @@ use sha1::{Digest, Sha1}; use std::{ fs::File, io::BufReader, - path::{Path, PathBuf}, + path::PathBuf, }; use crate::ErrorKind; @@ -18,7 +18,7 @@ pub fn file_sha1(file: &PathBuf) -> crate::Result { Ok(format!("{:x}", hash)) } -pub fn extract_archive(archive: &Path, dest: &Path) -> crate::Result<()> { +pub fn extract_archive(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { let ext = match archive.extension() { Some(ext) => ext.to_str().unwrap(), None => return Err(ErrorKind::AnyhowError(anyhow!("unsupported operating system")).into()), @@ -31,7 +31,7 @@ pub fn extract_archive(archive: &Path, dest: &Path) -> crate::Result<()> { } } -pub fn extract_zip(archive: &Path, dest: &Path) -> crate::Result<()> { +pub fn extract_zip(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { let file = File::open(archive).map_err(|err| ErrorKind::IOError(err))?; let archive = zip::ZipArchive::new(file).map_err(|err| ErrorKind::ZipError(err)); archive? @@ -40,7 +40,7 @@ pub fn extract_zip(archive: &Path, dest: &Path) -> crate::Result<()> { Ok(()) } -pub fn extract_tar_gz(archive: &Path, dest: &Path) -> crate::Result<()> { +pub fn extract_tar_gz(archive: &PathBuf, dest: &PathBuf) -> crate::Result<()> { let file = File::open(archive).map_err(|err| ErrorKind::IOError(err))?; let tar_gz = flate2::read::GzDecoder::new(file); let mut archive = tar::Archive::new(tar_gz); diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index c395c782..b2aa982b 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -45,7 +45,7 @@ pub async fn launch_cluster( uuid: Uuid, ) -> Result<(), String> { let manager = &mut state.lock().await.client_manager; - manager.launch_cluster(uuid).await?; // TODO: Change how this works + manager.launch_cluster(uuid).await?; Ok(()) } diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index da25dbe5..b1b990a3 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -190,8 +190,8 @@ function HomePage() { async function newInstance() { await createCluster({ - name: 'New Cluster', - version: '1.8.9', + name: 'Modern cluster', + version: '1.20.4', client: { type: 'Vanilla', }, From fbab841bbd750358fc75604eae83aa79e232c38f Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:24:41 +0200 Subject: [PATCH 06/10] feat: Pass stdout + stderr + pid callbacks to frontend --- onelauncher/src/game/client.rs | 52 ++++++++++++++++++++------ onelauncher/src/game/client_manager.rs | 9 +++-- src-tauri/src/api/game.rs | 30 ++++++++++++--- src/bridge/game.ts | 21 ++++++++++- src/ui/pages/Home.tsx | 8 +++- 5 files changed, 97 insertions(+), 23 deletions(-) diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index 94fc8093..7d6d4902 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -1,10 +1,10 @@ -use std::{io::{stderr, stdout}, path::PathBuf}; +use std::{path::PathBuf, process::Stdio}; use anyhow::anyhow; use async_trait::async_trait; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; -use tokio::process; +use tokio::{io::{AsyncBufReadExt, BufReader}, process}; use uuid::Uuid; use crate::auth::Account; @@ -46,11 +46,17 @@ pub struct SetupInfo { pub natives_dir: PathBuf, } +pub struct LaunchCallbacks { + pub on_launch: Box, + pub on_stdout: Box, + pub on_stderr: Box, +} + #[async_trait] pub trait ClientTrait<'a>: Send + Sync { fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self where Self: Sized; - async fn launch(&self, info: LaunchInfo) -> crate::Result<()> { + async fn launch(&self, info: LaunchInfo, mut callbacks: LaunchCallbacks) -> crate::Result { let manifest = &self.get_manifest().minecraft_manifest; let args = get_arguments(manifest)? @@ -63,9 +69,10 @@ pub trait ClientTrait<'a>: Send + Sync { .replace("${auth_access_token}", &info.account.access_token) .replace("${auth_session}", "0") // TODO: Figure out how to get this session ID. .replace("${user_properties}", "{}") // TODO: Figure out these properties. - .replace("${user_type}", "1"); + .replace("${user_type}", "1") + .replace("${version_type}", "OneLauncher"); - process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy") + let mut process = process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy") .arg("-XX:-OmitStackTraceInFastThrow") .arg("-Dminecraft.launcher.brand=onelauncher") .arg("-Dminecraft.launcher.version=1") @@ -76,13 +83,36 @@ pub trait ClientTrait<'a>: Send + Sync { .arg(info.setup.libraries) .arg(manifest.main_class.clone()) .args(args.split_whitespace()) - .stdout(stdout()) // TODO: Implement better logging - .stderr(stderr()) - .spawn()? - .wait() - .await?; + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let pid = process.id().unwrap_or(0); + (callbacks.on_launch)(pid); + + let stdout = process.stdout.take().ok_or(anyhow!("Couldn't take stdout"))?; + let stderr = process.stderr.take().ok_or(anyhow!("Couldn't take stderr"))?; - Ok(()) + let mut stdout_lines = BufReader::new(stdout).lines(); + let mut stderr_lines = BufReader::new(stderr).lines(); + + let stdout_task = tokio::spawn(async move { + while let Some(line) = stdout_lines.next_line().await.expect("Couldn't read line") { + (callbacks.on_stdout)(line); + } + }); + + let stderr_task = tokio::spawn(async move { + while let Some(line) = stderr_lines.next_line().await.expect("Couldn't read line") { + (callbacks.on_stderr)(line); + } + }); + + let status = process.wait().await?; + tokio::try_join!(stdout_task, stderr_task)?; + + let exit_code = status.code().unwrap_or(0); + Ok(exit_code) } async fn setup(&self) -> crate::Result; diff --git a/onelauncher/src/game/client_manager.rs b/onelauncher/src/game/client_manager.rs index 9b87f873..bbb71f19 100644 --- a/onelauncher/src/game/client_manager.rs +++ b/onelauncher/src/game/client_manager.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use crate::{auth::Account, utils::dirs}; -use super::{client::{ClientTrait, Cluster, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java}; +use super::{client::{ClientTrait, Cluster, LaunchCallbacks, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java}; #[derive(Debug, thiserror::Error)] pub enum ClientManagerError { @@ -75,7 +75,8 @@ impl ClientManager { pub async fn launch_cluster( &mut self, uuid: Uuid, - ) -> crate::Result<()> { + callbacks: LaunchCallbacks, + ) -> crate::Result { let client = self.get_impl_uuid(uuid)?; let java = java::download_java(&dirs::java_dir()?, client.get_manifest().minecraft_manifest.java_version.major_version).await?; @@ -95,8 +96,8 @@ impl ClientManager { mem_max: 4096, }; - client.launch(info).await?; - Ok(()) + let exit_code = client.launch(info, callbacks).await?; + Ok(exit_code) } pub async fn create_cluster( diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index b2aa982b..3f7ab183 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -1,5 +1,5 @@ -use onelauncher::game::{client::{Cluster, Manifest}, clients::ClientType}; -use tauri::State; +use onelauncher::game::{client::{Cluster, LaunchCallbacks, Manifest}, clients::ClientType}; +use tauri::{Manager, State}; use tokio::sync::Mutex; use uuid::Uuid; @@ -41,12 +41,32 @@ pub async fn get_cluster( #[tauri::command] pub async fn launch_cluster( + app: tauri::AppHandle, state: State<'_, Mutex>, uuid: Uuid, -) -> Result<(), String> { +) -> Result { let manager = &mut state.lock().await.client_manager; - manager.launch_cluster(uuid).await?; - Ok(()) + + let on_launch_app = app.clone(); + let on_stdout_app = app.clone(); + let on_stderr_app = app.clone(); + + let callbacks = LaunchCallbacks { + on_launch: Box::new(move |pid| { + on_launch_app.emit("game:launch", pid).expect("Failed to emit game:launched"); + }), + + on_stdout: Box::new(move |line| { + on_stdout_app.emit("game:stdout", line).expect("Failed to emit game:stdout"); + }), + + on_stderr: Box::new(move |line| { + on_stderr_app.emit("game:stderr", line).expect("Failed to emit game:stderr"); + }) + }; + + let exit_code = manager.launch_cluster(uuid, callbacks).await?; + Ok(exit_code) } #[tauri::command] diff --git a/src/bridge/game.ts b/src/bridge/game.ts index 258d5f9f..d7625dba 100644 --- a/src/bridge/game.ts +++ b/src/bridge/game.ts @@ -1,4 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; +import { listen } from '@tauri-apps/api/event'; export async function getCluster(uuid: string): Promise { return await invoke( @@ -61,9 +62,25 @@ export async function refreshClientManager(): Promise { return await invoke('plugin:onelauncher|refresh_client_manager'); } -export async function launchCluster(uuid: string): Promise { - return await invoke( +interface LaunchCallbacks { + on_launch: (pid: number) => any; + on_stdout: (line: string) => any; + on_stderr: (line: string) => any; +}; + +export async function launchCluster(uuid: string, callbacks: LaunchCallbacks): Promise { + const unlisten_launch = await listen('game:launch', e => callbacks.on_launch(e.payload)); + const unlisten_stdout = await listen('game:stdout', e => callbacks.on_stdout(e.payload)); + const unlisten_stderr = await listen('game:stderr', e => callbacks.on_stderr(e.payload)); + + const exit_code = await invoke( 'plugin:onelauncher|launch_cluster', { uuid }, ); + + unlisten_launch(); + unlisten_stdout(); + unlisten_stderr(); + + return exit_code; } diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index b1b990a3..d2bdfee4 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -76,7 +76,13 @@ function ClusterCardContextMenu(props: ClusterCardContextMenuProps) { } function launch() { - launchCluster(props.id); + const exit_code = launchCluster(props.id, { + on_launch: pid => console.log('Launched with PID:', pid), + on_stdout: line => console.log('STDOUT:', line), + on_stderr: line => console.log('STDERR:', line), + }); + + console.log('Exit with code: ', exit_code); } return ( From e12045697ed326c8442ab1449939ab0e6303449e Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 31 Mar 2024 22:00:42 +0200 Subject: [PATCH 07/10] feat: Settings manager --- onelauncher/src/lib.rs | 18 +++++++++++ onelauncher/src/settings.rs | 61 +++++++++++++++++++++++++++++++++++++ src-tauri/src/api/game.rs | 24 +++++++-------- src-tauri/src/api/mod.rs | 17 +++-------- 4 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 onelauncher/src/settings.rs diff --git a/onelauncher/src/lib.rs b/onelauncher/src/lib.rs index 2e6fa70e..3a2e2bbb 100644 --- a/onelauncher/src/lib.rs +++ b/onelauncher/src/lib.rs @@ -7,6 +7,24 @@ pub mod error; pub mod game; pub mod logger; pub mod utils; +pub mod settings; pub use error::*; pub use logger::start_logger; + +pub struct AppState { + pub settings: settings::SettingsManager, + pub clients: game::client_manager::ClientManager, +} + +unsafe impl Send for AppState {} +unsafe impl Sync for AppState {} + +impl AppState { + pub fn new() -> crate::Result { + Ok(Self { + settings: settings::SettingsManager::new()?, + clients: game::client_manager::ClientManager::new()?, + }) + } +} diff --git a/onelauncher/src/settings.rs b/onelauncher/src/settings.rs new file mode 100644 index 00000000..14a274e0 --- /dev/null +++ b/onelauncher/src/settings.rs @@ -0,0 +1,61 @@ +use std::fs; + +use serde::{Deserialize, Serialize}; + +use crate::utils::dirs; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct Setings { + pub discord_rpc: bool, +} + +impl Default for Setings { + fn default() -> Self { + Self { + discord_rpc: true, + } + } +} + +pub struct SettingsManager { + pub settings: Setings, +} + +impl SettingsManager { + pub fn new() -> crate::Result { + let path = dirs::app_config_dir()?.join("settings.json"); + + if path.exists() { + let file = fs::read_to_string(path)?; + let settings: Setings = serde_json::from_str(&file)?; + + return Ok(Self { + settings + }); + } + + let this = Self { + settings: Setings::default() + }; + + this.save()?; + Ok(this) + } + + pub fn get_settings(&self) -> &Setings { + &self.settings + } + + pub fn get_settings_mut(&mut self) -> &mut Setings { + &mut self.settings + } + + pub fn save(&self) -> crate::Result<()> { + let path = dirs::app_config_dir()?.join("settings.json"); + fs::create_dir_all(path.parent().unwrap())?; + fs::write(path, serde_json::to_string(&self.settings)?)?; + + Ok(()) + } +} \ No newline at end of file diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index 3f7ab183..86d4b2b0 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -1,20 +1,18 @@ -use onelauncher::game::{client::{Cluster, LaunchCallbacks, Manifest}, clients::ClientType}; +use onelauncher::{game::{client::{Cluster, LaunchCallbacks, Manifest}, clients::ClientType}, AppState}; use tauri::{Manager, State}; use tokio::sync::Mutex; use uuid::Uuid; -use super::GameManagerState; - #[tauri::command] pub async fn create_cluster( - state: State<'_, Mutex>, + state: State<'_, Mutex>, name: String, version: String, client: ClientType, cover: Option, group: Option, ) -> Result { - let manager = &mut state.lock().await.client_manager; + let manager = &mut state.lock().await.clients; let uuid = manager .create_cluster(name, version, cover, group, client) .await?; @@ -23,18 +21,18 @@ pub async fn create_cluster( #[tauri::command] pub async fn get_clusters( - state: State<'_, Mutex>, + state: State<'_, Mutex>, ) -> Result, String> { - let manager = &mut state.lock().await.client_manager; + let manager = &mut state.lock().await.clients; Ok(manager.get_clusters_owned()) } #[tauri::command] pub async fn get_cluster( - state: State<'_, Mutex>, + state: State<'_, Mutex>, uuid: Uuid, ) -> Result { - let manager = &mut state.lock().await.client_manager; + let manager = &mut state.lock().await.clients; let instance = manager.get_cluster(uuid)?; Ok(instance.clone()) } @@ -42,10 +40,10 @@ pub async fn get_cluster( #[tauri::command] pub async fn launch_cluster( app: tauri::AppHandle, - state: State<'_, Mutex>, + state: State<'_, Mutex>, uuid: Uuid, ) -> Result { - let manager = &mut state.lock().await.client_manager; + let manager = &mut state.lock().await.clients; let on_launch_app = app.clone(); let on_stdout_app = app.clone(); @@ -71,10 +69,10 @@ pub async fn launch_cluster( #[tauri::command] pub async fn get_manifest( - state: State<'_, Mutex>, + state: State<'_, Mutex>, uuid: Uuid, ) -> Result { - let manager = &mut state.lock().await.client_manager; + let manager = &mut state.lock().await.clients; let manifest = manager.get_manifest(uuid)?; Ok(manifest.clone()) } diff --git a/src-tauri/src/api/mod.rs b/src-tauri/src/api/mod.rs index f81d57fd..1b72849c 100644 --- a/src-tauri/src/api/mod.rs +++ b/src-tauri/src/api/mod.rs @@ -1,23 +1,14 @@ -use onelauncher::game::client_manager::ClientManager; +use onelauncher::{game::client_manager::ClientManager, AppState}; use tauri::{plugin::TauriPlugin, Manager, State}; use tokio::sync::Mutex; mod auth; mod game; -pub struct GameManagerState { - pub client_manager: ClientManager, -} - -unsafe impl Send for GameManagerState {} -unsafe impl Sync for GameManagerState {} - pub fn init() -> TauriPlugin { tauri::plugin::Builder::new("onelauncher") .setup(|app, _| { - app.manage(Mutex::new(GameManagerState { - client_manager: ClientManager::new().expect("Failed to initialize client manager"), - })); + app.manage(Mutex::new(AppState::new()?)); Ok(()) }) @@ -35,8 +26,8 @@ pub fn init() -> TauriPlugin { #[tauri::command] #[tracing::instrument(skip_all)] -async fn refresh_client_manager(state: State<'_, Mutex>) -> Result<(), String> { +async fn refresh_client_manager(state: State<'_, Mutex>) -> Result<(), String> { let mut state = state.lock().await; - state.client_manager = ClientManager::new()?; + state.clients = ClientManager::new()?; Ok(()) } From 025ffa03e57eca795196ad354e628f3455f3cde6 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:30:00 +0200 Subject: [PATCH 08/10] refactor: Change cluster file structure + use ClusterWithManifest over singular Cluster and Manifest types --- onelauncher/src/game/client_manager.rs | 137 ++++++++++-------------- onelauncher/src/game/clients/vanilla.rs | 32 +++++- onelauncher/src/game/minecraft.rs | 11 ++ onelauncher/src/utils/dirs.rs | 12 +-- src-tauri/src/api/game.rs | 20 +--- src-tauri/src/api/mod.rs | 1 - src/bridge/game.ts | 33 ++---- src/types/game.d.ts | 6 +- src/types/global.d.ts | 3 + src/ui/pages/Home.tsx | 4 +- 10 files changed, 124 insertions(+), 135 deletions(-) create mode 100644 src/types/global.d.ts diff --git a/onelauncher/src/game/client_manager.rs b/onelauncher/src/game/client_manager.rs index bbb71f19..54cd6e25 100644 --- a/onelauncher/src/game/client_manager.rs +++ b/onelauncher/src/game/client_manager.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, fs, path::PathBuf}; use anyhow::anyhow; use chrono::Local; -use serde::{de::DeserializeOwned, Serialize}; +use serde::Serialize; use uuid::Uuid; use crate::{auth::Account, utils::dirs}; @@ -18,57 +18,42 @@ pub enum ClientManagerError { ManifestNotFound, } +#[derive(Debug, Clone, Serialize)] +pub struct ClusterWithManifest { + pub cluster: Cluster, + pub manifest: Manifest +} + pub struct ClientManager { - clusters: HashMap, - manifests: HashMap, + clusters: HashMap, } impl ClientManager { pub fn new() -> crate::Result { Ok(Self { - clusters: load_and_serialize::(&dirs::clusters_dir()?)?, - manifests: load_and_serialize::(&dirs::manifests_dir()?)?, - }) + clusters: load_and_serialize()? + }) } pub fn get_impl_uuid<'a>(&'a self, uuid: Uuid) -> crate::Result + 'a>> { - let cluster = self.get_cluster(uuid)?; - let manifest = self.get_manifest(uuid)?; + let ClusterWithManifest { cluster, manifest } = self.get_cluster(uuid)?; - Ok(clients::get_impl(&cluster.client, &cluster, &manifest)) + Ok(clients::get_impl(&cluster.client, cluster, manifest)) } - pub fn get_clusters(&self) -> Vec<&Cluster> { + pub fn get_clusters(&self) -> Vec<&ClusterWithManifest> { self.clusters.values().collect() } - pub fn get_clusters_owned(&self) -> Vec { + pub fn get_clusters_owned(&self) -> Vec { self.clusters.values().cloned().collect() } - pub fn get_cluster(&self, uuid: Uuid) -> crate::Result<&Cluster> { - self.clusters - .get(&uuid) - .ok_or(ClientManagerError::ClusterNotFound.into()) - } - - pub fn get_cluster_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Cluster> { - self.clusters - .get_mut(&uuid) - .ok_or(ClientManagerError::ClusterNotFound.into()) - } - - pub fn get_manifest(&self, uuid: Uuid) -> crate::Result<&Manifest> { - self.manifests - .get(&uuid) - .ok_or(ClientManagerError::ManifestNotFound.into()) - } - - pub fn get_manifest_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Manifest> { - self.manifests - .get_mut(&uuid) - .ok_or(ClientManagerError::ManifestNotFound.into()) - } + pub fn get_cluster(&self, uuid: Uuid) -> crate::Result<&ClusterWithManifest> { + self.clusters + .get(&uuid) + .ok_or(ClientManagerError::ClusterNotFound.into()) + } } impl ClientManager { @@ -117,6 +102,7 @@ impl ClientManager { .url; let uuid = Uuid::new_v4(); + let dir = &dirs::cluster_dir(uuid.to_string())?; // Save the manifest // TODO: Is this correct? @@ -127,10 +113,9 @@ impl ClientManager { }; save( - &dirs::manifests_dir()?.join(format!("{}.json", uuid)), + &dir.join("manifest.json"), &manifest, )?; - self.manifests.insert(uuid, manifest); // Save the cluster let cluster = Cluster { @@ -143,10 +128,11 @@ impl ClientManager { }; save( - &dirs::clusters_dir()?.join(format!("{}.json", uuid)), + &dir.join("cluster.json"), &cluster, )?; - self.clusters.insert(uuid, cluster); + + self.clusters.insert(uuid, ClusterWithManifest { cluster, manifest }); Ok(uuid) } @@ -168,51 +154,40 @@ where Ok(()) } -fn load_and_serialize(dir: &PathBuf) -> crate::Result> -where - T: DeserializeOwned, -{ - let mut result = HashMap::::new(); +fn load_and_serialize() -> crate::Result> { + let mut result = HashMap::::new(); - if !dir.exists() { - fs::create_dir_all(dir)?; - return Ok(result); - } + let dir = dirs::clusters_dir()?; + if !dir.exists() { + fs::create_dir_all(&dir)?; + return Ok(result); + } + + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); - for file in fs::read_dir(dir)? { - let file = file?; - let file_name = file.file_name(); - let file_name = file_name - .to_str() - .ok_or(anyhow!("Couldn't convert OsStr to str"))? - .replace(".json", ""); - let uuid = match Uuid::parse_str(&file_name) { - Ok(uuid) => uuid, - Err(_) => { - eprintln!("Couldn't parse file name as UUID: '{:?}'", file_name); - continue; - } - }; - - let path = file.path(); - let content = match fs::read_to_string(&path) { - Ok(content) => content, - Err(_) => { - eprintln!("Couldn't read file: '{:?}'", path); - continue; - } - }; - - let parsed = match serde_json::from_str::(&content) { - Ok(parsed) => parsed, - Err(_) => { - eprintln!("Couldn't parse file '{}' content as JSON", path.display()); - continue; - } - }; - - result.insert(uuid, parsed); - } + if path.is_dir() { + match get_entry(&path) { + Ok((uuid, cluster)) => { + result.insert(uuid, cluster); + } + Err(e) => { + eprintln!("Failed to load cluster: {}", e); + } + } + } + } Ok(result) } + +fn get_entry(path: &PathBuf) -> crate::Result<(Uuid, ClusterWithManifest)> { + let manifest_path = path.join("manifest.json"); + let cluster_path = path.join("cluster.json"); + + let manifest: Manifest = serde_json::from_str(&fs::read_to_string(manifest_path)?)?; + let cluster: Cluster = serde_json::from_str(&fs::read_to_string(cluster_path)?)?; + + Ok((cluster.id, ClusterWithManifest { cluster, manifest })) +} diff --git a/onelauncher/src/game/clients/vanilla.rs b/onelauncher/src/game/clients/vanilla.rs index 89f542c0..ca79437f 100644 --- a/onelauncher/src/game/clients/vanilla.rs +++ b/onelauncher/src/game/clients/vanilla.rs @@ -1,7 +1,7 @@ use crate::{ constants::{self, MINECRAFT_VERSIONS_MANIFEST}, create_game_client, - game::{client::{ClientTrait, Cluster, Manifest, SetupInfo}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}}, + game::{client::{ClientTrait, Cluster, Manifest, SetupInfo}, minecraft::{AssetIndexFile, Library, MinecraftManifest, MinecraftVersion, RuleListExt}}, impl_game_client, utils::{dirs, file, http} }; @@ -37,7 +37,7 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> { libraries.push_str(constants::LIBRARY_SPLITTER); libraries.push_str(client_path.to_str().unwrap()); - let game_dir = dirs::game_dir()?.join(&self.cluster.id.to_string()); + let game_dir = dirs::cluster_dir(self.cluster.id.to_string())?.join("game"); fs::create_dir_all(&game_dir)?; Ok(SetupInfo { @@ -125,11 +125,35 @@ pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> { Ok(()) } -pub async fn install_assets() -> crate::Result<()> { +pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result<()> { + let assets = dirs::assets_dir()?; + let objects = assets.join("objects"); + let indexes = assets.join("indexes"); + let index = indexes.join(format!("{}.json", manifest.asset_index.id)); + + fs::create_dir_all(&objects)?; + fs::create_dir_all(&indexes)?; + + if !index.exists() { + let artifact = &manifest.asset_index; + http::download_file_sha1_check(&artifact.url, &index, &artifact.sha1).await?; + } + + let contents = serde_json::from_str::(&fs::read_to_string(&index)?)?; + for (hash, asset) in contents.objects { + let short = &hash[..2]; + let file = objects.join(&short).join(&hash); + fs::create_dir_all(file.parent().ok_or(anyhow!("Couldn't get asset parent"))?)?; + + if !file.exists() { + let url = format!("https://resources.download.minecraft.net/{}/{}", &short, hash); + http::download_file_sha1_check(&url, &file, &asset.hash).await?; + } + } + Ok(()) } - #[derive(Clone, Serialize, Deserialize)] struct VersionList { versions: Vec, diff --git a/onelauncher/src/game/minecraft.rs b/onelauncher/src/game/minecraft.rs index bb200b47..b08a9357 100644 --- a/onelauncher/src/game/minecraft.rs +++ b/onelauncher/src/game/minecraft.rs @@ -266,6 +266,17 @@ pub struct File { pub url: String, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AssetIndexFile { + pub objects: HashMap, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AssetObject { + pub hash: String, + pub size: u64, +} + fn string_or_seq<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, diff --git a/onelauncher/src/utils/dirs.rs b/onelauncher/src/utils/dirs.rs index 83914640..7274aec0 100644 --- a/onelauncher/src/utils/dirs.rs +++ b/onelauncher/src/utils/dirs.rs @@ -14,6 +14,10 @@ pub fn app_config_dir() -> crate::Result { .ok_or(DirectoryError::ConfigDir.into()) } +pub fn assets_dir() -> crate::Result { + Ok(app_config_dir()?.join("assets")) +} + pub fn libraries_dir() -> crate::Result { Ok(app_config_dir()?.join("libraries")) } @@ -30,14 +34,10 @@ pub fn clusters_dir() -> crate::Result { Ok(app_config_dir()?.join("clusters")) } -pub fn manifests_dir() -> crate::Result { - Ok(app_config_dir()?.join("manifests")) +pub fn cluster_dir(uuid: String) -> crate::Result { + Ok(clusters_dir()?.join(uuid)) } pub fn clients_dir() -> crate::Result { Ok(app_config_dir()?.join("clients")) } - -pub fn game_dir() -> crate::Result { - Ok(app_config_dir()?.join("games")) -} diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index 86d4b2b0..666c9e7b 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -1,4 +1,4 @@ -use onelauncher::{game::{client::{Cluster, LaunchCallbacks, Manifest}, clients::ClientType}, AppState}; +use onelauncher::{game::{client::LaunchCallbacks, client_manager::ClusterWithManifest, clients::ClientType}, AppState}; use tauri::{Manager, State}; use tokio::sync::Mutex; use uuid::Uuid; @@ -22,7 +22,7 @@ pub async fn create_cluster( #[tauri::command] pub async fn get_clusters( state: State<'_, Mutex>, -) -> Result, String> { +) -> Result, String> { let manager = &mut state.lock().await.clients; Ok(manager.get_clusters_owned()) } @@ -31,10 +31,10 @@ pub async fn get_clusters( pub async fn get_cluster( state: State<'_, Mutex>, uuid: Uuid, -) -> Result { +) -> Result { let manager = &mut state.lock().await.clients; - let instance = manager.get_cluster(uuid)?; - Ok(instance.clone()) + let cluster = manager.get_cluster(uuid)?; + Ok(cluster.clone()) } #[tauri::command] @@ -66,13 +66,3 @@ pub async fn launch_cluster( let exit_code = manager.launch_cluster(uuid, callbacks).await?; Ok(exit_code) } - -#[tauri::command] -pub async fn get_manifest( - state: State<'_, Mutex>, - uuid: Uuid, -) -> Result { - let manager = &mut state.lock().await.clients; - let manifest = manager.get_manifest(uuid)?; - Ok(manifest.clone()) -} diff --git a/src-tauri/src/api/mod.rs b/src-tauri/src/api/mod.rs index 1b72849c..b1d04ecf 100644 --- a/src-tauri/src/api/mod.rs +++ b/src-tauri/src/api/mod.rs @@ -17,7 +17,6 @@ pub fn init() -> TauriPlugin { game::create_cluster, game::get_clusters, game::get_cluster, - game::get_manifest, game::launch_cluster, refresh_client_manager, ]) diff --git a/src/bridge/game.ts b/src/bridge/game.ts index d7625dba..745292d1 100644 --- a/src/bridge/game.ts +++ b/src/bridge/game.ts @@ -1,36 +1,23 @@ import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; -export async function getCluster(uuid: string): Promise { - return await invoke( +export async function getCluster(uuid: string): Promise { + return await invoke( 'plugin:onelauncher|get_cluster', { uuid }, ); } -export async function getManifest(uuid: string): Promise { - return await invoke( - 'plugin:onelauncher|get_manifest', - { uuid }, - ); -} - -export async function getClustersWithManifests(): Promise { +export async function getClustersGrouped(): Promise[]>> { const clusters = await getClusters(); - - return Promise.all(clusters.map(async cluster => ({ - cluster, - manifest: await getManifest(cluster.id), - }))); -} - -export async function getClustersGrouped(): Promise> { - const clusters = await getClustersWithManifests(); - const map = new Map(); + const map = new Map[]>(); clusters.forEach((cluster, index) => { const groupName = cluster.cluster.group || 'Unnamed'; - const value = { ...cluster, index }; + const value: WithIndex = { + ...cluster, + index, + }; if (map.has(groupName)) map.get(groupName)!.push(value); @@ -41,8 +28,8 @@ export async function getClustersGrouped(): Promise { - return await invoke('plugin:onelauncher|get_clusters'); +export async function getClusters(): Promise { + return await invoke('plugin:onelauncher|get_clusters'); } export async function createCluster(cluster: Omit & { version: string }): Promise { diff --git a/src/types/game.d.ts b/src/types/game.d.ts index 5e25c09c..37e039d2 100644 --- a/src/types/game.d.ts +++ b/src/types/game.d.ts @@ -1,7 +1,7 @@ declare namespace Core { - export interface ClusterWithManifest { - cluster: Cluster, - manifest: Manifest, + export type ClusterWithManifest = { + cluster: Cluster, + manifest: Manifest } export interface Manifest { diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 00000000..5fbb961c --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,3 @@ +declare namespace globalThis { + export type WithIndex = T & { index: number } +} \ No newline at end of file diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index d2bdfee4..d1194eb4 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -75,8 +75,8 @@ function ClusterCardContextMenu(props: ClusterCardContextMenuProps) { navigate(`/clusters/?id=${props.id}`); } - function launch() { - const exit_code = launchCluster(props.id, { + async function launch() { + const exit_code = await launchCluster(props.id, { on_launch: pid => console.log('Launched with PID:', pid), on_stdout: line => console.log('STDOUT:', line), on_stderr: line => console.log('STDERR:', line), From 940e41e291940069e3957552e610f2d7f3f9017d Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:21:28 +0200 Subject: [PATCH 09/10] feat: Downloading assets --- onelauncher/src/game/client.rs | 11 ++++++++++- onelauncher/src/game/clients/vanilla.rs | 14 ++++++++++---- src/ui/pages/Home.tsx | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index 7d6d4902..d8cc47da 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -5,6 +5,7 @@ use async_trait::async_trait; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; use tokio::{io::{AsyncBufReadExt, BufReader}, process}; +use tracing::info; use uuid::Uuid; use crate::auth::Account; @@ -29,6 +30,12 @@ pub struct Cluster { pub client: ClientType, } +impl Cluster { + pub fn dir(&self) -> crate::Result { + Ok(crate::utils::dirs::cluster_dir(self.id.to_string())?) + } +} + #[derive(Debug)] pub struct LaunchInfo { pub java: PathBuf, @@ -44,6 +51,8 @@ pub struct SetupInfo { pub libraries: String, pub game_dir: PathBuf, pub natives_dir: PathBuf, + pub assets_dir: PathBuf, + pub asset_index: String, } pub struct LaunchCallbacks { @@ -63,7 +72,7 @@ pub trait ClientTrait<'a>: Send + Sync { .replace("${auth_player_name}", &info.account.username) .replace("${version_name}", &info.setup.version) .replace("${game_directory}", info.setup.game_dir.to_str().unwrap()) - .replace("${assets_root}", format!("{}/assets", info.setup.game_dir.to_str().unwrap()).as_str()) + .replace("${assets_root}", info.setup.assets_dir.to_str().unwrap()) .replace("${assets_index_name}", &manifest.asset_index.id) .replace("${auth_uuid}", &info.account.uuid) .replace("${auth_access_token}", &info.account.access_token) diff --git a/onelauncher/src/game/clients/vanilla.rs b/onelauncher/src/game/clients/vanilla.rs index ca79437f..4d206488 100644 --- a/onelauncher/src/game/clients/vanilla.rs +++ b/onelauncher/src/game/clients/vanilla.rs @@ -30,14 +30,17 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> { async fn setup(&self) -> crate::Result { let manifest = &self.manifest.minecraft_manifest; + // Install everything let client_path = install_game(manifest).await?; let mut libraries = install_libraries(manifest).await?; + let assets = install_assets(manifest).await?; // Append client path to the libraries string at the end libraries.push_str(constants::LIBRARY_SPLITTER); libraries.push_str(client_path.to_str().unwrap()); - let game_dir = dirs::cluster_dir(self.cluster.id.to_string())?.join("game"); + // Create game directory + let game_dir = self.cluster.dir()?.join("game"); fs::create_dir_all(&game_dir)?; Ok(SetupInfo { @@ -45,6 +48,8 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> { libraries, natives_dir: dirs::natives_dir()?, game_dir, + assets_dir: dirs::assets_dir()?, + asset_index: assets, }) } } @@ -125,7 +130,7 @@ pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> { Ok(()) } -pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result<()> { +pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result { let assets = dirs::assets_dir()?; let objects = assets.join("objects"); let indexes = assets.join("indexes"); @@ -140,7 +145,8 @@ pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result<()> { } let contents = serde_json::from_str::(&fs::read_to_string(&index)?)?; - for (hash, asset) in contents.objects { + for (_, asset) in contents.objects { + let hash = asset.hash.clone(); let short = &hash[..2]; let file = objects.join(&short).join(&hash); fs::create_dir_all(file.parent().ok_or(anyhow!("Couldn't get asset parent"))?)?; @@ -151,7 +157,7 @@ pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result<()> { } } - Ok(()) + Ok(manifest.asset_index.id.clone()) } #[derive(Clone, Serialize, Deserialize)] diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index d1194eb4..07836348 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -197,7 +197,7 @@ function HomePage() { async function newInstance() { await createCluster({ name: 'Modern cluster', - version: '1.20.4', + version: '1.8.9', client: { type: 'Vanilla', }, From ffbab6cb5f0e616b9d3158377d63041cb1b4602f Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:12:58 +0200 Subject: [PATCH 10/10] fix: Changed way of downloading and using assets in pre 1.6 versions --- onelauncher/src/game/client.rs | 1 + onelauncher/src/game/clients/vanilla.rs | 44 +++++++++++++++++-------- onelauncher/src/game/minecraft.rs | 12 +++++-- src/ui/pages/Home.tsx | 4 +-- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index d8cc47da..b34aff99 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -69,6 +69,7 @@ pub trait ClientTrait<'a>: Send + Sync { let manifest = &self.get_manifest().minecraft_manifest; let args = get_arguments(manifest)? + .replace("${game_assets}", "${assets_root}") .replace("${auth_player_name}", &info.account.username) .replace("${version_name}", &info.setup.version) .replace("${game_directory}", info.setup.game_dir.to_str().unwrap()) diff --git a/onelauncher/src/game/clients/vanilla.rs b/onelauncher/src/game/clients/vanilla.rs index 4d206488..8e16133f 100644 --- a/onelauncher/src/game/clients/vanilla.rs +++ b/onelauncher/src/game/clients/vanilla.rs @@ -30,26 +30,31 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> { async fn setup(&self) -> crate::Result { let manifest = &self.manifest.minecraft_manifest; + // Create game directory + let game_dir = self.cluster.dir()?.join("game"); + fs::create_dir_all(&game_dir)?; + // Install everything let client_path = install_game(manifest).await?; let mut libraries = install_libraries(manifest).await?; - let assets = install_assets(manifest).await?; + + // Resources dir used for legacy versions + // with map_to_resources set to true in their asset indexes + let resources_dir = game_dir.join("resources"); + let (asset_index, assets_dir) = install_assets(manifest, &resources_dir).await?; // Append client path to the libraries string at the end libraries.push_str(constants::LIBRARY_SPLITTER); libraries.push_str(client_path.to_str().unwrap()); - // Create game directory - let game_dir = self.cluster.dir()?.join("game"); - fs::create_dir_all(&game_dir)?; Ok(SetupInfo { version: manifest.version.clone(), libraries, natives_dir: dirs::natives_dir()?, game_dir, - assets_dir: dirs::assets_dir()?, - asset_index: assets, + assets_dir, + asset_index, }) } } @@ -130,7 +135,7 @@ pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> { Ok(()) } -pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result { +pub async fn install_assets(manifest: &MinecraftManifest, resources_dir: &PathBuf) -> crate::Result<(String, PathBuf)> { let assets = dirs::assets_dir()?; let objects = assets.join("objects"); let indexes = assets.join("indexes"); @@ -145,10 +150,16 @@ pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result(&fs::read_to_string(&index)?)?; - for (_, asset) in contents.objects { + for (path, asset) in contents.objects { let hash = asset.hash.clone(); let short = &hash[..2]; - let file = objects.join(&short).join(&hash); + + let file = if contents.map_to_resources { + resources_dir.join(path) + } else { + objects.join(&short).join(&hash) + }; + fs::create_dir_all(file.parent().ok_or(anyhow!("Couldn't get asset parent"))?)?; if !file.exists() { @@ -157,7 +168,10 @@ pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result crate::Result() - .await?; - Ok(manifest) + .json::().await; + + if let Err(err) = manifest { + println!("{:#?}", err); + return Err(err.into()); + } + Ok(manifest.unwrap()) } diff --git a/onelauncher/src/game/minecraft.rs b/onelauncher/src/game/minecraft.rs index b08a9357..434b34d6 100644 --- a/onelauncher/src/game/minecraft.rs +++ b/onelauncher/src/game/minecraft.rs @@ -23,12 +23,14 @@ pub struct MinecraftManifest { pub downloads: Downloads, pub java_version: JavaVersion, pub libraries: Vec, - pub logging: Logging, pub main_class: String, pub release_time: String, - + #[serde(rename = "id")] pub version: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub logging: Option, #[serde(rename = "type")] pub release_type: ReleaseType, @@ -91,7 +93,7 @@ pub struct ModernArgumentRuleItem { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "snake_case")] pub enum ReleaseType { Snapshot, Release, @@ -268,9 +270,13 @@ pub struct File { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AssetIndexFile { + #[serde(default = "default_false")] + pub map_to_resources: bool, pub objects: HashMap, } +fn default_false() -> bool { false } + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct AssetObject { pub hash: String, diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index 07836348..19cb1162 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -196,8 +196,8 @@ function HomePage() { async function newInstance() { await createCluster({ - name: 'Modern cluster', - version: '1.8.9', + name: '1.0 cluster', + version: 'a1.0.11', client: { type: 'Vanilla', },