diff --git a/packages/app-lib/src/launcher/args.rs b/packages/app-lib/src/launcher/args.rs index d054dd6e..e7fa746e 100644 --- a/packages/app-lib/src/launcher/args.rs +++ b/packages/app-lib/src/launcher/args.rs @@ -15,7 +15,7 @@ use daedalus::{ }; use dunce::canonicalize; use itertools::Itertools; -use std::io::{BufRead, BufReader}; +use std::io::{BufRead, BufReader, ErrorKind}; use std::net::SocketAddr; use std::{collections::HashMap, path::Path}; use uuid::Uuid; @@ -60,7 +60,11 @@ pub fn get_class_paths( return None; } - Some(get_lib_path(libraries_path, &library.name, false)) + Some(get_lib_path( + libraries_path, + &library.name, + library.natives_os_key_and_classifiers(java_arch).is_some(), + )) })) .process_results(|iter| { iter.unique().join(classpath_separator(java_arch)) @@ -85,21 +89,21 @@ pub fn get_lib_path( lib: &str, allow_not_exist: bool, ) -> crate::Result { - let path = libraries_path - .to_path_buf() - .join(get_path_from_artifact(lib)?); + let path = libraries_path.join(get_path_from_artifact(lib)?); - if !path.exists() && allow_not_exist { - return Ok(path.to_string_lossy().to_string()); - } - - let path = &canonicalize(&path).map_err(|_| { - crate::ErrorKind::LauncherError(format!( - "Library file at path {} does not exist", - path.to_string_lossy() - )) - .as_error() - })?; + let path = match canonicalize(&path) { + Ok(p) => p, + Err(err) if err.kind() == ErrorKind::NotFound && allow_not_exist => { + path + } + Err(err) => { + return Err(crate::ErrorKind::LauncherError(format!( + "Could not canonicalize library path {}: {err}", + path.display() + )) + .as_error()); + } + }; Ok(path.to_string_lossy().to_string()) } diff --git a/packages/app-lib/src/launcher/download.rs b/packages/app-lib/src/launcher/download.rs index 8fbece3e..5d911df5 100644 --- a/packages/app-lib/src/launcher/download.rs +++ b/packages/app-lib/src/launcher/download.rs @@ -8,13 +8,13 @@ use crate::{ emit::{emit_loading, loading_try_for_each_concurrent}, }, state::State, - util::{fetch::*, io, platform::OsExt}, + util::{fetch::*, io}, }; use daedalus::minecraft::{LoggingConfiguration, LoggingSide}; use daedalus::{ self as d, minecraft::{ - Asset, AssetsIndex, Library, Os, Version as GameVersion, + Asset, AssetsIndex, Library, Version as GameVersion, VersionInfo as GameVersionInfo, }, modded::LoaderVersion, @@ -288,90 +288,132 @@ pub async fn download_libraries( }?; let num_files = libraries.len(); loading_try_for_each_concurrent( - stream::iter(libraries.iter()) - .map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move { - if let Some(rules) = &library.rules - && !parse_rules(rules, java_arch, &QuickPlayType::None, minecraft_updated) { - tracing::trace!("Skipped library {}", &library.name); - return Ok(()); - } + stream::iter(libraries.iter()).map(Ok::<&Library, crate::Error>), + None, + loading_bar, + loading_amount, + num_files, + None, + |library| async move { + if let Some(rules) = &library.rules + && !parse_rules( + rules, + java_arch, + &QuickPlayType::None, + minecraft_updated, + ) + { + tracing::trace!("Skipped library {}", &library.name); + return Ok(()); + } - if !library.downloadable { - tracing::trace!("Skipped non-downloadable library {}", &library.name); + if !library.downloadable { + tracing::trace!( + "Skipped non-downloadable library {}", + &library.name + ); + return Ok(()); + } + + // When a library has natives, we only need to download such natives, as PrismLauncher does + if let Some((os_key, classifiers)) = + library.natives_os_key_and_classifiers(java_arch) + { + let parsed_key = os_key + .replace("${arch}", crate::util::platform::ARCH_WIDTH); + + if let Some(native) = classifiers.get(&parsed_key) { + let data = fetch( + &native.url, + Some(&native.sha1), + &st.fetch_semaphore, + &st.pool, + ) + .await?; + + if let Ok(mut archive) = + zip::ZipArchive::new(std::io::Cursor::new(&data)) + { + match archive.extract( + st.directories.version_natives_dir(version), + ) { + Ok(_) => tracing::debug!( + "Fetched native {}", + &library.name + ), + Err(err) => tracing::error!( + "Failed extracting native {}. err: {err}", + &library.name + ), + } + } else { + tracing::error!( + "Failed extracting native {}", + &library.name + ); + } + } + } else { + let artifact_path = d::get_path_from_artifact(&library.name)?; + let path = st.directories.libraries_dir().join(&artifact_path); + + if path.exists() && !force { return Ok(()); } - tokio::try_join! { - async { - let artifact_path = d::get_path_from_artifact(&library.name)?; - let path = st.directories.libraries_dir().join(&artifact_path); + if let Some(d::minecraft::LibraryDownloads { + artifact: Some(ref artifact), + .. + }) = library.downloads + && !artifact.url.is_empty() + { + let bytes = fetch( + &artifact.url, + Some(&artifact.sha1), + &st.fetch_semaphore, + &st.pool, + ) + .await?; + write(&path, &bytes, &st.io_semaphore).await?; - if path.exists() && !force { - return Ok(()); - } + tracing::trace!( + "Fetched library {} to path {:?}", + &library.name, + &path + ); + } else { + // We lack an artifact URL, so fall back to constructing one ourselves. + // PrismLauncher just ignores the library if this is the case, so it's + // probably not needed, but previous code revisions of the Modrinth App + // intended to do this, so we keep that behavior for compatibility. - if let Some(d::minecraft::LibraryDownloads { artifact: Some(ref artifact), ..}) = library.downloads - && !artifact.url.is_empty(){ - let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore, &st.pool) - .await?; - write(&path, &bytes, &st.io_semaphore).await?; - tracing::trace!("Fetched library {} to path {:?}", &library.name, &path); - return Ok::<_, crate::Error>(()); - } + let url = format!( + "{}{artifact_path}", + library + .url + .as_deref() + .unwrap_or("https://libraries.minecraft.net/") + ); - let url = [ - library - .url - .as_deref() - .unwrap_or("https://libraries.minecraft.net/"), - &artifact_path - ].concat(); + let bytes = + fetch(&url, None, &st.fetch_semaphore, &st.pool) + .await?; - let bytes = fetch(&url, None, &st.fetch_semaphore, &st.pool).await?; - write(&path, &bytes, &st.io_semaphore).await?; - tracing::trace!("Fetched library {} to path {:?}", &library.name, &path); - Ok::<_, crate::Error>(()) - }, - async { - // HACK: pseudo try block using or else - if let Some((os_key, classifiers)) = None.or_else(|| Some(( - library - .natives - .as_ref()? - .get(&Os::native_arch(java_arch))?, - library - .downloads - .as_ref()? - .classifiers - .as_ref()? - ))) { - let parsed_key = os_key.replace( - "${arch}", - crate::util::platform::ARCH_WIDTH, - ); + write(&path, &bytes, &st.io_semaphore).await?; - if let Some(native) = classifiers.get(&parsed_key) { - let data = fetch(&native.url, Some(&native.sha1), &st.fetch_semaphore, &st.pool).await?; - let reader = std::io::Cursor::new(&data); - if let Ok(mut archive) = zip::ZipArchive::new(reader) { - match archive.extract(st.directories.version_natives_dir(version)) { - Ok(_) => tracing::debug!("Fetched native {}", &library.name), - Err(err) => tracing::error!("Failed extracting native {}. err: {}", &library.name, err) - } - } else { - tracing::error!("Failed extracting native {}", &library.name) - } - } - } - - Ok(()) - } - }?; - - tracing::debug!("Loaded library {}", library.name); - Ok(()) + tracing::trace!( + "Fetched library {} to path {:?}", + &library.name, + &path + ); + } } - ).await?; + + tracing::debug!("Loaded library {}", library.name); + Ok(()) + }, + ) + .await?; tracing::debug!("Done loading libraries!"); Ok(()) diff --git a/packages/app-lib/src/util/platform.rs b/packages/app-lib/src/util/platform.rs index 3eb57e2a..3932cfea 100644 --- a/packages/app-lib/src/util/platform.rs +++ b/packages/app-lib/src/util/platform.rs @@ -1,65 +1,6 @@ //! Platform-related code use daedalus::minecraft::{Os, OsRule}; -// OS detection -pub trait OsExt { - /// Get the OS of the current system - fn native() -> Self; - - /// Gets the OS + Arch of the current system - fn native_arch(java_arch: &str) -> Self; - - /// Gets the OS from an OS + Arch - fn get_os(&self) -> Self; -} - -impl OsExt for Os { - fn native() -> Self { - match std::env::consts::OS { - "windows" => Self::Windows, - "macos" => Self::Osx, - "linux" => Self::Linux, - _ => Self::Unknown, - } - } - - fn native_arch(java_arch: &str) -> Self { - if std::env::consts::OS == "windows" { - if java_arch == "aarch64" { - Os::WindowsArm64 - } else { - Os::Windows - } - } else if std::env::consts::OS == "linux" { - if java_arch == "aarch64" { - Os::LinuxArm64 - } else if java_arch == "arm" { - Os::LinuxArm32 - } else { - Os::Linux - } - } else if std::env::consts::OS == "macos" { - if java_arch == "aarch64" { - Os::OsxArm64 - } else { - Os::Osx - } - } else { - Os::Unknown - } - } - - fn get_os(&self) -> Self { - match self { - Os::OsxArm64 => Os::Osx, - Os::LinuxArm32 => Os::Linux, - Os::LinuxArm64 => Os::Linux, - Os::WindowsArm64 => Os::Windows, - _ => self.clone(), - } - } -} - // Bit width #[cfg(target_pointer_width = "64")] pub const ARCH_WIDTH: &str = "64"; diff --git a/packages/daedalus/src/minecraft.rs b/packages/daedalus/src/minecraft.rs index 24e94a67..45488426 100644 --- a/packages/daedalus/src/minecraft.rs +++ b/packages/daedalus/src/minecraft.rs @@ -179,6 +179,56 @@ pub enum Os { Unknown, } +impl Os { + /// Returns the native OS of the build + pub fn native() -> Self { + match std::env::consts::OS { + "windows" => Self::Windows, + "macos" => Self::Osx, + "linux" => Self::Linux, + _ => Self::Unknown, + } + } + + /// Returns the native OS variant of the build, taking into account the architecture of its Java runtime + pub fn native_arch(java_arch: &str) -> Self { + if std::env::consts::OS == "windows" { + if java_arch == "aarch64" { + Os::WindowsArm64 + } else { + Os::Windows + } + } else if std::env::consts::OS == "linux" { + if java_arch == "aarch64" { + Os::LinuxArm64 + } else if java_arch == "arm" { + Os::LinuxArm32 + } else { + Os::Linux + } + } else if std::env::consts::OS == "macos" { + if java_arch == "aarch64" { + Os::OsxArm64 + } else { + Os::Osx + } + } else { + Os::Unknown + } + } + + /// Returns the base OS of a variant (e.g. OsxArm64 -> Osx) + pub fn get_os(&self) -> Self { + match self { + Os::OsxArm64 => Os::Osx, + Os::LinuxArm32 => Os::Linux, + Os::LinuxArm64 => Os::Linux, + Os::WindowsArm64 => Os::Windows, + _ => self.clone(), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] /// A rule which depends on what OS the user is on pub struct OsRule { @@ -277,6 +327,24 @@ pub struct Library { pub downloadable: bool, } +impl Library { + /// Returns the OS key and classifiers for downloading natives, if applicable + pub fn natives_os_key_and_classifiers( + &self, + java_arch: &str, + ) -> Option<(&str, &HashMap)> { + self.natives + .as_ref() + .and_then(|natives| natives.get(&Os::native_arch(java_arch))) + .and_then(|natives| { + self.downloads + .as_ref() + .and_then(|downloads| downloads.classifiers.as_ref()) + .map(|classifiers| (natives.as_str(), classifiers)) + }) + } +} + #[derive(Deserialize, Debug, Clone)] /// A partial library which should be merged with a full library pub struct PartialLibrary {