diff --git a/app/native/src/api/download.rs b/app/native/src/api/download.rs index cea5ad42..1472d0e1 100644 --- a/app/native/src/api/download.rs +++ b/app/native/src/api/download.rs @@ -1,6 +1,6 @@ use std::{ collections::VecDeque, - path::PathBuf, + path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, @@ -73,8 +73,9 @@ pub fn extract_filename(url: &str) -> Result { Ok(filename.to_owned()) } -pub async fn validate_sha1(file_path: &PathBuf, sha1: &str) -> Result<()> { +pub async fn validate_sha1(file_path: impl AsRef + Send + Sync, sha1: &str) -> Result<()> { use sha1::{Digest, Sha1}; + let file_path = file_path.as_ref(); let file = File::open(file_path).await?; let mut buffer = Vec::new(); let mut reader = BufReader::new(file); diff --git a/app/native/src/api/vanilla.rs b/app/native/src/api/vanilla.rs index e0bec48c..b9f1b9f5 100644 --- a/app/native/src/api/vanilla.rs +++ b/app/native/src/api/vanilla.rs @@ -9,11 +9,11 @@ use futures::Future; use log::error; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::path::Path; pub use std::path::PathBuf; use std::pin::Pin; use std::sync::{atomic::AtomicUsize, atomic::Ordering, Arc}; -use tokio::fs::{self, create_dir_all, File}; -use tokio::io::AsyncWriteExt; +use tokio::fs::{self, create_dir_all}; use crate::api::vanilla::library::os_match; use crate::api::vanilla::rules::OsName; @@ -165,26 +165,11 @@ pub async fn prepare_vanilla_download() -> Result<(DownloadArgs, ProcessedArgume ) .await?; - let game_argument = game_manifest["arguments"]["game"] - .as_array() - .context("Failure to parse contents[\"arguments\"][\"game\"]")?; - - let mut game_flags = GameFlags { - rules: get_rules(game_argument)?, - arguments: game_argument - .iter() - .filter_map(Value::as_str) - .map(std::string::ToString::to_string) - .collect::>(), - additional_arguments: None, - }; - - let game_directory = PathBuf::from("downloads/.minecraft"); - let asset_directory = PathBuf::from("downloads/.minecraft/assets"); - let library_directory = PathBuf::from("downloads/library"); - let native_directory = PathBuf::from(format!( - "downloads/.minecraft/versions/{current_version}/natives" - )); + let base_path = PathBuf::from("downloads/"); + let game_directory = base_path.join(".minecraft"); + let asset_directory = game_directory.join("assets"); + let library_directory = base_path.join("library"); + let native_directory = game_directory.join(format!("versions/{current_version}/natives")); create_dir_all(&library_directory) .await @@ -199,46 +184,18 @@ pub async fn prepare_vanilla_download() -> Result<(DownloadArgs, ProcessedArgume .await .context("fail to create native_directory(vanilla)")?; - let storage = STORAGE.account_storage.read().await; + let game_flags = setup_game_flags(&game_manifest)?; - // NOTE: have to check in frontend - let uuid = storage - .main_account - .context("Can't launch game without main account")?; - let minecraft_account = storage - .accounts - .iter() - .find(|x| x.uuid == uuid) - .context("Somehow fail to find the main account uuid in accounts")?; + let jvm_flags = setup_jvm_flags(&game_manifest)?; - let game_options = GameOptions { - auth_player_name: minecraft_account.username.clone(), - game_version_name: current_version, - game_directory: RustOpaque::new( - game_directory - .canonicalize() - .context("Fail to canonicalize game_directory(game_options)")?, - ), - assets_root: RustOpaque::new( - asset_directory - .canonicalize() - .context("Fail to canonicalize asset_directory(game_options)")?, - ), - assets_index_name: game_manifest["assetIndex"]["id"] - .as_str() - .context("assetindex id doesn't exist")? - .to_owned(), - auth_access_token: minecraft_account.access_token.token.clone(), - auth_uuid: uuid.to_string(), - user_type: String::from("mojang"), - version_type: String::from("release"), - }; - - game_flags.arguments = game_args_parse(&game_flags, &game_options); - - if let Some(x) = game_flags.additional_arguments { - game_flags.arguments.extend(x); - } + let (game_options, game_flags) = setup_game_option( + current_version, + &game_directory, + &asset_directory, + &game_manifest, + game_flags, + ) + .await?; let asset_index = AssetIndex::deserialize(&game_manifest["assetIndex"]) .context("Failed to Serialize assetIndex")?; @@ -256,61 +213,88 @@ pub async fn prepare_vanilla_download() -> Result<(DownloadArgs, ProcessedArgume let main_class: String = String::deserialize(&game_manifest["mainClass"]).context("Failed to get MainClass")?; - let client_jar = std::env::current_dir()?.join(extract_filename(&downloads_list.client.url)?); + let (client_jar, jvm_options) = setup_jvm_options( + &base_path, + &downloads_list, + &library_directory, + &game_directory, + &native_directory, + )?; + + let (jvm_options, mut jvm_flags) = add_jvm_rules( + &library_list, + &library_directory, + &client_jar, + jvm_options, + jvm_flags, + )?; - let jvm_argument = game_manifest["arguments"]["jvm"] - .as_array() - .context("Failure to parse contents[\"arguments\"][\"jvm\"]")?; - let mut jvm_flags = JvmFlags { - rules: get_rules(jvm_argument)?, - arguments: jvm_argument - .iter() - .filter_map(Value::as_str) - .map(std::string::ToString::to_string) - .collect::>(), - additional_arguments: None, - }; - - let mut jvm_options = JvmOptions { - launcher_name: String::from("era-connect"), - launcher_version: String::from("0.0.1"), - classpath: String::new(), - classpath_separator: String::from(":"), - primary_jar: client_jar.to_string_lossy().to_string(), - library_directory: RustOpaque::new( - library_directory - .canonicalize() - .context("Fail to canonicalize library_directory(jvm_options)")?, - ), - game_directory: RustOpaque::new( - game_directory - .canonicalize() - .context("Fail to canonicalize game_directory(jvm_options)")?, - ), - native_directory: RustOpaque::new( - native_directory - .canonicalize() - .context("Fail to canonicalize native_directory(jvm_options)")?, - ), - }; + jvm_flags.arguments = jvm_args_parse(&jvm_flags.arguments, &jvm_options); let mut handles = Vec::new(); let current_size = Arc::new(AtomicUsize::new(0)); + let (library_path, native_library_path) = ( - Arc::new(jvm_options.library_directory.clone()), - Arc::new(jvm_options.native_directory.clone()), + jvm_options.library_directory.clone(), + jvm_options.native_directory.clone(), ); let total_size = parallel_library( Arc::clone(&library_list), - Arc::clone(&library_path), - Arc::clone(&native_library_path), + library_path.clone(), + native_library_path.clone(), Arc::clone(¤t_size), &mut handles, ) .await?; + let client_jar_filename = PathBuf::from(extract_filename(&downloads_list.client.url)?); + + if !client_jar_filename.exists() { + let bytes = + download_file(downloads_list.client.url, Some(Arc::clone(¤t_size))).await?; + fs::write(client_jar_filename, &bytes).await?; + total_size.fetch_add(downloads_list.client.size, Ordering::Relaxed); + } else if let Err(x) = validate_sha1(&client_jar_filename, &downloads_list.client.sha1).await { + error!("{x}\n redownloading."); + let bytes = + download_file(downloads_list.client.url, Some(Arc::clone(¤t_size))).await?; + fs::write(client_jar_filename, &bytes).await?; + total_size.fetch_add(downloads_list.client.size, Ordering::Relaxed); + } + + let asset_settings = extract_assets(asset_index, asset_directory).await?; + + parallel_assets(asset_settings, ¤t_size, &total_size, &mut handles).await?; + + let launch_args = LaunchArgs { + jvm_args: jvm_flags.arguments, + main_class, + game_args: game_flags.arguments, + }; + + Ok(( + DownloadArgs { + current_size: Arc::clone(¤t_size), + total_size: Arc::clone(&total_size), + handles, + }, + ProcessedArguments { + launch_args, + jvm_args: jvm_options, + game_args: game_options, + }, + )) +} + +fn add_jvm_rules( + library_list: &Arc<[Library]>, + library_path: impl AsRef, + client_jar: impl AsRef, + mut jvm_options: JvmOptions, + mut jvm_flags: JvmFlags, +) -> Result<(JvmOptions, JvmFlags), anyhow::Error> { let current_os = os_version::detect()?; let current_os_type = match current_os { os_version::OsVersion::Linux(_) => OsName::Linux, @@ -330,70 +314,147 @@ pub async fn prepare_vanilla_download() -> Result<(DownloadArgs, ProcessedArgume let mut classpath_list = parsed_library_list .iter() .map(|x| { - Arc::clone(&library_path) + library_path + .as_ref() .join(&x.downloads.artifact.path) .to_string_lossy() .to_string() }) .collect::>(); - classpath_list.push(client_jar.to_string_lossy().to_string()); + classpath_list.push(client_jar.as_ref().to_string_lossy().to_string()); jvm_options.classpath = classpath_list.join(":"); - - for x in jvm_flags.rules { + for x in &jvm_flags.rules { if x.action == ActionType::Allow - && x.os.map_or(false, |os| os.name == Some(current_os_type)) + && x.os + .as_ref() + .map_or(false, |os| os.name == Some(current_os_type)) { jvm_flags .arguments - .extend(x.value.context("rules value doesn't exist")?); + .extend_from_slice(x.value.as_ref().context("rules value doesn't exist")?); } } + Ok((jvm_options, jvm_flags)) +} - jvm_flags.arguments = jvm_args_parse(&jvm_flags.arguments, &jvm_options); +fn setup_jvm_options( + base_path: impl AsRef, + downloads_list: &Downloads, + library_directory: impl AsRef, + game_directory: impl AsRef, + native_directory: impl AsRef, +) -> Result<(PathBuf, JvmOptions), anyhow::Error> { + let client_jar = base_path + .as_ref() + .join(extract_filename(&downloads_list.client.url)?); + let jvm_options = JvmOptions { + launcher_name: String::from("era-connect"), + launcher_version: String::from("0.0.1"), + classpath: String::new(), + classpath_separator: String::from(":"), + primary_jar: client_jar.to_string_lossy().to_string(), + library_directory: RustOpaque::new( + library_directory + .as_ref() + .canonicalize() + .context("Fail to canonicalize library_directory(jvm_options)")?, + ), + game_directory: RustOpaque::new( + game_directory + .as_ref() + .canonicalize() + .context("Fail to canonicalize game_directory(jvm_options)")?, + ), + native_directory: RustOpaque::new( + native_directory + .as_ref() + .canonicalize() + .context("Fail to canonicalize native_directory(jvm_options)")?, + ), + }; + Ok((client_jar, jvm_options)) +} - let client_jar_filename = PathBuf::from(extract_filename(&downloads_list.client.url)?); +fn setup_jvm_flags(game_manifest: &Value) -> Result { + let jvm_argument = game_manifest["arguments"]["jvm"] + .as_array() + .context("Failure to parse contents[\"arguments\"][\"jvm\"]")?; - if !client_jar_filename.exists() { - let bytes = - download_file(downloads_list.client.url, Some(Arc::clone(¤t_size))).await?; - fs::write(client_jar_filename, &bytes).await?; - total_size.fetch_add(downloads_list.client.size, Ordering::Relaxed); - } else if let Err(x) = validate_sha1( - &PathBuf::from(&extract_filename(&downloads_list.client.url)?), - &downloads_list.client.sha1, - ) - .await - { - error!("{x}\n redownloading."); - let bytes = - download_file(downloads_list.client.url, Some(Arc::clone(¤t_size))).await?; - let mut f = File::create(client_jar_filename).await?; - f.write_all(&bytes).await?; - total_size.fetch_add(downloads_list.client.size, Ordering::Relaxed); - } - let asset_settings = extract_assets(asset_index, asset_directory).await?; + let jvm_flags = JvmFlags { + rules: get_rules(jvm_argument)?, + arguments: jvm_argument + .iter() + .filter_map(Value::as_str) + .map(ToString::to_string) + .collect::>(), + additional_arguments: None, + }; + Ok(jvm_flags) +} - parallel_assets(asset_settings, ¤t_size, &total_size, &mut handles).await?; +fn setup_game_flags(game_manifest: &Value) -> Result { + let game_argument = game_manifest["arguments"]["game"] + .as_array() + .context("Failure to parse contents[\"arguments\"][\"game\"]")?; + let game_flags = GameFlags { + rules: get_rules(game_argument)?, + arguments: game_argument + .iter() + .filter_map(Value::as_str) + .map(std::string::ToString::to_string) + .collect::>(), + additional_arguments: None, + }; + Ok(game_flags) +} - let launch_args = LaunchArgs { - jvm_args: jvm_flags.arguments, - main_class, - game_args: game_flags.arguments, +async fn setup_game_option( + current_version: String, + game_directory: impl AsRef + Send + Sync, + asset_directory: impl AsRef + Send + Sync, + game_manifest: &Value, + mut game_flags: GameFlags, +) -> Result<(GameOptions, GameFlags), anyhow::Error> { + let storage = STORAGE.account_storage.read().await; + let uuid = storage + .main_account + .context("Can't launch game without main account")?; + let minecraft_account = storage + .accounts + .iter() + .find(|x| x.uuid == uuid) + .context("Somehow fail to find the main account uuid in accounts")?; + let game_options = GameOptions { + auth_player_name: minecraft_account.username.clone(), + game_version_name: current_version, + game_directory: RustOpaque::new( + game_directory + .as_ref() + .canonicalize() + .context("Fail to canonicalize game_directory(game_options)")?, + ), + assets_root: RustOpaque::new( + asset_directory + .as_ref() + .canonicalize() + .context("Fail to canonicalize asset_directory(game_options)")?, + ), + assets_index_name: game_manifest["assetIndex"]["id"] + .as_str() + .context("assetindex id doesn't exist")? + .to_owned(), + auth_access_token: minecraft_account.access_token.token.clone(), + auth_uuid: uuid.to_string(), + user_type: String::from("mojang"), + version_type: String::from("release"), }; + game_flags.arguments = game_args_parse(&game_flags, &game_options); - Ok(( - DownloadArgs { - current_size: Arc::clone(¤t_size), - total_size: Arc::clone(&total_size), - handles, - }, - ProcessedArguments { - launch_args, - jvm_args: jvm_options, - game_args: game_options, - }, - )) + if let Some(x) = game_flags.additional_arguments.as_ref() { + game_flags.arguments.extend_from_slice(x); + } + Ok((game_options, game_flags)) } fn jvm_args_parse(jvm_flags: &[String], jvm_options: &JvmOptions) -> Vec { diff --git a/app/native/src/api/vanilla/library.rs b/app/native/src/api/vanilla/library.rs index 89858bff..c3ead82d 100644 --- a/app/native/src/api/vanilla/library.rs +++ b/app/native/src/api/vanilla/library.rs @@ -65,8 +65,8 @@ pub fn os_match<'a>(library: &Library, current_os_type: &'a OsName) -> (bool, bo } pub async fn parallel_library( library_list_arc: Arc<[Library]>, - folder: Arc>, - native_folder: Arc>, + folder: RustOpaque, + native_folder: RustOpaque, current: Arc, library_download_handles: &mut HandlesType, ) -> Result> { @@ -87,9 +87,9 @@ pub async fn parallel_library( let library_list_clone = Arc::clone(&library_list_arc); let counter_clone = Arc::clone(&index_counter); let current_size_clone = Arc::clone(¤t_size); - let folder_clone = Arc::clone(&folder); + let folder_clone = folder.clone(); let download_total_size_clone = Arc::clone(&download_total_size); - let native_folder_clone = Arc::clone(&native_folder); + let native_folder_clone = native_folder.clone(); let handle = Box::pin(async move { let index = counter_clone.fetch_add(1, Ordering::SeqCst); if index < num_libraries { @@ -158,7 +158,7 @@ pub async fn parallel_library( async fn native_download( url: &String, current_size_clone: &Arc, - native_folder_clone: Arc>, + native_folder_clone: RustOpaque, library_extension: &str, ) -> Result<()> { let bytes = download_file(url, Some(Arc::clone(current_size_clone))).await?;