diff options
Diffstat (limited to 'src/launcher.rs')
| -rw-r--r-- | src/launcher.rs | 765 |
1 files changed, 0 insertions, 765 deletions
diff --git a/src/launcher.rs b/src/launcher.rs deleted file mode 100644 index 2836db5..0000000 --- a/src/launcher.rs +++ /dev/null @@ -1,765 +0,0 @@ -mod constants; -mod version; -mod strsub; -mod download; -mod rules; -mod assets; -mod extract; -mod settings; -mod runner; -mod jre; - -use std::borrow::Cow; -use std::cmp::min; -use std::env::consts::{ARCH, OS}; -use std::error::Error; -use std::ffi::{OsStr, OsString}; -use std::fmt::{Display, Formatter}; -use std::io::ErrorKind; -use std::io::ErrorKind::AlreadyExists; -use std::path::{Component, Path, PathBuf}; -use std::{env, process}; -use std::env::JoinPathsError; -use std::time::{Instant, SystemTime, UNIX_EPOCH}; -use const_format::formatcp; -use futures::{StreamExt, TryStreamExt}; -use indexmap::IndexMap; -use log::{debug, info, trace, warn}; -use reqwest::Client; -use sysinfo::System; -use tokio::{fs, io}; -use tokio_stream::wrappers::ReadDirStream; -use download::{MultiDownloader, VerifiedDownload}; -use rules::{CompatCheck, IncompatibleError}; -use version::{VersionList, VersionResolveError, VersionResult}; -use crate::version::{Library, OSRestriction, OperatingSystem, DownloadType, LibraryExtractRule, FeatureMatcher, ClientLogging}; - -use assets::{AssetError, AssetRepository}; -use crate::util::{self, AsJavaPath}; - -pub use settings::*; -pub use runner::run_the_game; -pub use crate::util::{EnsureFileError, FileVerifyError, IntegrityError}; -use crate::assets::AssetIndex; -use runner::ArgumentType; -use strsub::SubFunc; -use crate::launcher::download::FileDownload; -use crate::launcher::jre::{JavaRuntimeError, JavaRuntimeRepository}; -use crate::launcher::version::VersionError; -use crate::version::manifest::VersionType; - -#[derive(Debug)] -pub enum LogConfigError { - UnknownType(String), - InvalidId(Option<String>), - MissingURL, - IO{ what: &'static str, error: io::Error }, - Offline, - Download{ url: String, error: reqwest::Error }, - - Integrity(IntegrityError) -} - -impl Display for LogConfigError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - LogConfigError::UnknownType(log_type) => write!(f, "unknown log type {}", log_type), - LogConfigError::InvalidId(oid) => match oid { - Some(id) => write!(f, "invalid log config id: {}", id), - None => f.write_str("missing log config id") - }, - LogConfigError::MissingURL => f.write_str("missing log config download URL"), - LogConfigError::IO { what, error} => write!(f, "i/o error ({}): {}", what, error), - LogConfigError::Offline => f.write_str("launcher in offline mode"), - LogConfigError::Download { url, error } => write!(f, "failed to download log config ({}): {}", url, error), - LogConfigError::Integrity(e) => write!(f, "log config verify error: {}", e) - } - } -} - -impl Error for LogConfigError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - LogConfigError::IO { error, .. } => Some(error), - LogConfigError::Download {error, ..} => Some(error), - LogConfigError::Integrity(error) => Some(error), - _ => None - } - } -} - -struct SystemInfo { - os: OperatingSystem, - os_version: String, - arch: String -} - -struct LibraryRepository { - home: PathBuf, - natives: PathBuf -} - -pub struct Launcher { - online: bool, - home: PathBuf, - versions: VersionList, - - system_info: SystemInfo, - - libraries: LibraryRepository, - assets: AssetRepository, - java_runtimes: JavaRuntimeRepository -} - -#[derive(Debug)] -pub enum LaunchError { - UnknownInstance(String), - - // version resolution errors - VersionInit(VersionError), - UnknownVersion(String), - LoadVersion(VersionError), - ResolveVersion(VersionResolveError), - IncompatibleVersion(IncompatibleError), - MissingMainClass, - - // library errors - LibraryDirError(PathBuf, io::Error), - LibraryVerifyError(FileVerifyError), - LibraryDownloadError, - LibraryExtractError(extract::ZipExtractError), - LibraryClasspathError(JoinPathsError), - - // ensure file errors - EnsureFile(EnsureFileError), - IO { what: &'static str, error: io::Error }, - - // log errors - UnknownLogType(String), - InvalidLogId(Option<String>), - - // asset errors - Assets(AssetError), - - // java runtime errors - ResolveJavaRuntime { what: &'static str, error: io::Error }, - MissingJavaRuntime, - JavaRuntimeRepo(JavaRuntimeError) -} - -impl Display for LaunchError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match &self { - LaunchError::UnknownInstance(inst) => write!(f, "unknown instance: {inst}"), - LaunchError::VersionInit(e) => write!(f, "initializing version: {e}"), - LaunchError::UnknownVersion(id) => write!(f, "unknown version id: {id}"), - LaunchError::LoadVersion(e) => write!(f, "error loading remote version: {e}"), - LaunchError::ResolveVersion(e) => write!(f, "error resolving remote version: {e}"), - LaunchError::IncompatibleVersion(e) => e.fmt(f), - LaunchError::MissingMainClass => f.write_str("main class not specified"), - LaunchError::LibraryDirError(path, e) => write!(f, "failed to create library directory {}: {}", path.display(), e), - LaunchError::LibraryVerifyError(e) => write!(f, "failed to verify library: {}", e), - LaunchError::LibraryDownloadError => f.write_str("library download failed (see above logs for details)"), // TODO: booo this sucks - LaunchError::LibraryExtractError(e) => write!(f, "library extract zip error: {e}"), - LaunchError::LibraryClasspathError(e) => write!(f, "error building classpath: {e}"), - LaunchError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error), - LaunchError::EnsureFile(e) => e.fmt(f), - LaunchError::UnknownLogType(t) => write!(f, "unknown log type: {}", t), - LaunchError::InvalidLogId(Some(id)) => write!(f, "invalid log id: {}", id), - LaunchError::InvalidLogId(None) => write!(f, "missing log id"), - LaunchError::Assets(e) => write!(f, "failed to fetch assets: {}", e), - LaunchError::ResolveJavaRuntime { what, error } => write!(f, "failed to find java runtime ({}): {}", what, error), - LaunchError::MissingJavaRuntime => f.write_str("suitable java executable not found"), - LaunchError::JavaRuntimeRepo(e) => write!(f, "runtime repository error: {e}") - } - } -} - -impl Error for LaunchError { - fn cause(&self) -> Option<&dyn Error> { - match &self { - LaunchError::VersionInit(e) => Some(e), - LaunchError::LoadVersion(e) => Some(e), - LaunchError::ResolveVersion(e) => Some(e), - LaunchError::IncompatibleVersion(e) => Some(e), - LaunchError::LibraryDirError(_, e) => Some(e), - LaunchError::LibraryVerifyError(e) => Some(e), - LaunchError::LibraryExtractError(e) => Some(e), - LaunchError::LibraryClasspathError(e) => Some(e), - LaunchError::IO { error: e, .. } => Some(e), - LaunchError::EnsureFile(e) => Some(e), - LaunchError::Assets(e) => Some(e), - LaunchError::ResolveJavaRuntime { error: e, .. } => Some(e), - LaunchError::JavaRuntimeRepo(e) => Some(e), - _ => None - } - } -} - -struct LaunchInfo<'l, F: FeatureMatcher> { - launcher: &'l Launcher, - feature_matcher: &'l F, - - asset_index_name: Option<String>, - classpath: String, - virtual_assets_path: Option<PathBuf>, - instance_home: PathBuf, - natives_path: PathBuf, - client_jar: Option<PathBuf>, - version_id: String, - version_type: Option<VersionType>, - asset_index: Option<AssetIndex> -} - -#[derive(Debug)] -pub struct Launch { - jvm_args: Vec<OsString>, - game_args: Vec<OsString>, - main_class: String, - instance_path: PathBuf, - runtime_path: PathBuf, - runtime_legacy_launch: bool -} - -struct ProfileFeatureMatcher<'prof> { - profile: &'prof Profile -} - -impl FeatureMatcher for ProfileFeatureMatcher<'_> { - fn matches(&self, feature: &str) -> bool { - match feature { - "has_custom_resolution" => self.profile.get_resolution().is_some(), - _ => false - } - } -} - -impl Launcher { - // FIXME: more descriptive error type por favor - pub async fn new(home: impl AsRef<Path>, online: bool) -> Result<Launcher, LaunchError> { - match tokio::fs::create_dir_all(home.as_ref()).await { - Err(e) if e.kind() != AlreadyExists => { - warn!("Failed to create launcher home directory: {}", e); - return Err(LaunchError::IO { what: "create launcher home", error: e }); - }, - _ => () - } - - let home = fs::canonicalize(home.as_ref()).await - .map_err(|e| LaunchError::IO { what: "resolve home path", error: e })?; - - let versions_home = home.join("versions"); - - debug!("Version list online?: {online}"); - let versions = if online { - VersionList::online(versions_home.as_ref()).await.map_err(LaunchError::VersionInit)? - } else { - VersionList::offline(versions_home.as_ref()).await.map_err(LaunchError::VersionInit)? - }; - - let assets_path = home.join("assets"); - - let java_runtimes = JavaRuntimeRepository::new(home.join("jre"), online).await.map_err(LaunchError::JavaRuntimeRepo)?; - - Ok(Launcher { - online, - versions, - system_info: SystemInfo::new(), - libraries: LibraryRepository { - home: home.join("libraries"), - natives: home.join("natives") - }, - assets: AssetRepository::new(online, &assets_path).await.map_err(|e| LaunchError::IO { what: "setting up assets", error: e })?, - java_runtimes, - home - }) - } - - fn choose_lib_classifier<'lib>(&self, lib: &'lib Library) -> Option<&'lib str> { - lib.natives.as_ref().and_then(|n| n.get(&self.system_info.os)).map(|s| s.as_str()) - } - - async fn log_config_ensure(&self, config: &ClientLogging) -> Result<String, LaunchError> { - info!("Ensuring log configuration exists and is valid."); - - if config.log_type != "log4j2-xml" { - return Err(LaunchError::UnknownLogType(config.log_type.clone())); - } - - let dlinfo = &config.file; - let Some(id) = dlinfo.id.as_ref() else { - return Err(LaunchError::InvalidLogId(None)); - }; - - let mut path = self.home.join("logging"); - fs::create_dir_all(path.as_path()).await - .map_err(|e| LaunchError::IO{ what: "creating log directory", error: e })?; - - let Some(Component::Normal(filename)) = Path::new(id).components().last() else { - return Err(LaunchError::InvalidLogId(Some(id.clone()))); - }; - - path.push(filename); - - debug!("Logger config {} is at {}", id, path.display()); - - util::ensure_file(&path, dlinfo.url.as_deref(), dlinfo.size, dlinfo.sha1, self.online, false).await - .map_err(LaunchError::EnsureFile)?; - - struct PathSub<'a>(&'a Path); - impl<'a> SubFunc<'a> for PathSub<'a> { - fn substitute(&self, key: &str) -> Option<Cow<'a, str>> { - match key { - "path" => Some(self.0.as_java_path().to_string_lossy()), - _ => None - } - } - } - - Ok(strsub::replace_string(config.argument.as_str(), &PathSub(path.as_ref())).to_string()) - } - - /* TODO: - * - launch game using JNI - * - auth - */ - pub async fn prepare_launch(&self, profile: &Profile, instance: &Instance) -> Result<Launch, LaunchError> { - let start = Instant::now(); - let feature_matcher = ProfileFeatureMatcher { profile }; - let version_id = profile.get_version(); - - let Some(version_id) = self.versions.get_profile_version_id(version_id) else { - // idk how common this use case actually is - warn!("Can't use latest release/snapshot profiles while offline!"); - return Err(LaunchError::UnknownVersion("<latest>".into())); - }; - - info!("Preparing launch for \"{}\"...", version_id); - - let inst_home = instance.get_path(&self.home).await.map_err(|e| LaunchError::IO { - what: "resolving instance directory", - error: e - })?; - - fs::create_dir_all(inst_home.as_path()).await.map_err(|e| LaunchError::IO { - what: "creating instance directory", - error: e - })?; - - info!("Launching the game in {}", inst_home.display()); - - let ver_res = self.versions.get_version_lazy(version_id.as_ref()); - let ver = match ver_res { - VersionResult::Remote(mv) => Cow::Owned(self.versions.load_remote_version(mv).await.map_err(LaunchError::LoadVersion)?), - VersionResult::Complete(cv) => Cow::Borrowed(cv), - VersionResult::None => { - return Err(LaunchError::UnknownVersion(version_id.into_owned())) - } - }; - - let ver = self.versions.resolve_version(ver.as_ref()).await.map_err(LaunchError::ResolveVersion)?; - ver.rules_apply(&self.system_info, &feature_matcher).map_err(LaunchError::IncompatibleVersion)?; - - info!("Resolved launch version {}!", ver.id); - - let mut extract_jobs = Vec::new(); - let mut downloads = IndexMap::new(); - - for lib in ver.libraries.iter() { - if lib.rules_apply(&self.system_info, &feature_matcher).is_err() { - trace!("Skipping library {}, compatibility rules failed", lib.name); - continue; - } - - let classifier = self.choose_lib_classifier(lib); - - if let Some(dl) = self.libraries.create_download(lib, classifier) { - let canon_name = lib.get_canonical_name(); - if downloads.contains_key(&canon_name) { - debug!("Skipping library {}, we already have another version of that library.", lib.name); - continue; - } - - trace!("Using library {} ({})", lib.name, classifier.unwrap_or("None")); - dl.make_dirs().await.map_err(|e| LaunchError::LibraryDirError(dl.get_path().to_path_buf(), e))?; - - if lib.natives.is_some() { - extract_jobs.push(LibraryExtractJob { - source: dl.get_path().to_owned(), - rule: lib.extract.clone() - }); - } - - downloads.insert(canon_name, dl); - } else { - trace!("Skipping library {} ({}), no download", lib.name, classifier.unwrap_or("None")); - } - } - - if self.online { - info!("Downloading {} libraries...", downloads.len()); - let client = Client::new(); - MultiDownloader::new(downloads.values_mut()).perform(&client).await - .inspect_err(|e| warn!("library download failed: {e}")) - .try_fold((), |_, _| async {Ok(())}) - .await - .map_err(|_| LaunchError::LibraryDownloadError)?; - } else { - info!("Verifying {} libraries...", downloads.len()); - download::verify_files(downloads.values_mut()).await.map_err(|e| { - warn!("A library could not be verified: {}", e); - warn!("Since the launcher is in offline mode, libraries cannot be downloaded. Please try again in online mode."); - LaunchError::LibraryVerifyError(e) - })?; - } - - let log_arg; - if let Some(logging) = ver.logging.as_ref().and_then(|l| l.client.as_ref()) { - log_arg = Some(self.log_config_ensure(logging).await?); - } else { - log_arg = None; - } - - // download assets - - let (asset_idx_name, asset_idx) = - if let Some(idx_download) = ver.asset_index.as_ref() { - let asset_idx_name = idx_download.id.as_ref().or(ver.assets.as_ref()).map(String::as_str); - let asset_idx = self.assets.load_index(idx_download, asset_idx_name).await - .map_err(LaunchError::Assets)?; - - self.assets.ensure_assets(&asset_idx).await.map_err(LaunchError::Assets)?; - - (asset_idx_name, Some(asset_idx)) - } else { - (None, None) - }; - - // download client jar - - let client_jar_path; - if let Some(client) = ver.downloads.get(&DownloadType::Client) { - let mut client_path: PathBuf = [self.home.as_ref(), OsStr::new("versions"), OsStr::new(&ver.id)].iter().collect(); - fs::create_dir_all(&client_path).await.map_err(|e| LaunchError::IO{ what: "creating client download directory", error: e })?; - - client_path.push(format!("{}.jar", ver.id)); - - info!("Downloading client jar {}", client_path.display()); - - util::ensure_file(client_path.as_path(), client.url.as_deref(), client.size, client.sha1, self.online, false).await - .map_err(LaunchError::EnsureFile)?; - - client_jar_path = Some(client_path); - } else { - client_jar_path = None; - } - - // clean up old natives - let nnatives = self.libraries.clean_old_natives().await?; - info!("Cleaned up {} old natives directories.", nnatives); - - // extract natives (execute this function unconditionally because we still need the natives dir to exist) - info!("Extracting natives from libraries"); - let natives_dir = self.libraries.extract_natives(extract_jobs).await?; - - let game_assets = if let Some(asset_idx) = asset_idx.as_ref() { - info!("Reconstructing assets"); - self.assets.reconstruct_assets(asset_idx, inst_home.as_path(), asset_idx_name).await - .map_err(LaunchError::Assets)? - } else { - None - }; - - info!("Building classpath"); - let classpath = env::join_paths(downloads.values() - .map(|job| job.get_path().as_java_path()) - .chain(client_jar_path.iter().map(|p| p.as_path().as_java_path()))) - .map_err(LaunchError::LibraryClasspathError)? - .into_string() - .unwrap_or_else(|os| { - warn!("Classpath contains invalid UTF-8. The game may not launch correctly."); - os.to_string_lossy().to_string() - }); - - trace!("Classpath: {classpath}"); - - info!("Resolving java runtime environment path"); - let runtime_path; - - if let Some(ref profile_jre) = profile.get_java_runtime() { - runtime_path = fs::canonicalize(profile_jre).await - .map_err(|e| LaunchError::ResolveJavaRuntime {what: "resolving jre path", error: e})?; - } else { - let Some(ref java_ver) = ver.java_version else { - warn!("Version {} does not specify java version information. You must select a runtime manually.", ver.id); - return Err(LaunchError::MissingJavaRuntime); - }; - - let runtime = self.java_runtimes.choose_runtime(java_ver.component.as_str()).await.map_err(LaunchError::JavaRuntimeRepo)?; - runtime_path = self.java_runtimes.ensure_jre(java_ver.component.as_str(), runtime).await.map_err(LaunchError::JavaRuntimeRepo)?; - } - - let Some(runtime_exe_path) = runner::find_java(runtime_path.as_path(), profile.is_legacy_launch()).await - .map_err(|e| LaunchError::ResolveJavaRuntime {what: "finding java executable", error: e})? else { - return Err(LaunchError::MissingJavaRuntime); - }; - - - debug!("Found runtime exe: {}", runtime_exe_path.display()); - - info!("Deriving launch arguments"); - let info = LaunchInfo { - launcher: self, - feature_matcher: &feature_matcher, - - asset_index_name: asset_idx_name.map(|s| s.to_owned()), - classpath, - virtual_assets_path: game_assets, - instance_home: inst_home.clone(), - natives_path: natives_dir, - client_jar: client_jar_path, - version_id: ver.id.to_string(), - version_type: ver.version_type.clone(), - asset_index: asset_idx - }; - - let Some(ref main_class) = ver.main_class else { - return Err(LaunchError::MissingMainClass); - }; - - // yuck - let jvm_args = profile.iter_arguments().map(OsString::from) - .chain(runner::build_arguments(&info, ver.as_ref(), ArgumentType::Jvm).drain(..)) - .chain(log_arg.iter().map(OsString::from)).collect(); - let game_args = runner::build_arguments(&info, ver.as_ref(), ArgumentType::Game); - - let diff = Instant::now().duration_since(start); - info!("Finished preparing launch for {} in {:.02} seconds!", ver.id, diff.as_secs_f32()); - - Ok(Launch { - jvm_args, - game_args, - main_class: main_class.to_string(), - instance_path: inst_home, - runtime_path: runtime_exe_path, - runtime_legacy_launch: profile.is_legacy_launch() - }) - } -} - -#[derive(Debug)] -enum LibraryError { - InvalidName(String), - IO { what: &'static str, error: io::Error } -} - -impl Display for LibraryError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - LibraryError::InvalidName(name) => write!(f, "invalid name: {name}"), - LibraryError::IO { what, error } => write!(f, "library i/o error ({what}): {error}"), - } - } -} - -impl Error for LibraryError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - LibraryError::IO { error, .. } => Some(error), - _ => None - } - } -} - -#[derive(Debug)] -struct LibraryExtractJob { - source: PathBuf, - rule: Option<LibraryExtractRule> -} - -const ARCH_BITS: &str = formatcp!("{}", usize::BITS); - -impl LibraryRepository { - fn get_artifact_base_dir(name: &str) -> Option<PathBuf> { - let end_of_gid = name.find(':')?; - - Some(name[..end_of_gid].split('.').chain(name.split(':').skip(1).take(2)).collect()) - } - - fn get_artifact_filename(name: &str, classifier: Option<&str>) -> Option<PathBuf> { - let n: Vec<&str> = name.splitn(4, ':').skip(1).collect(); - - struct LibReplace; - impl SubFunc<'static> for LibReplace { - fn substitute(&self, key: &str) -> Option<Cow<'static, str>> { - match key { - "arch" => Some(Cow::Borrowed(ARCH_BITS)), - _ => None - } - } - } - - if let Some(classifier) = classifier { - match n.len() { - 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], classifier).as_str(), &LibReplace).as_ref())), - 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}-{}.jar", n[0], n[1], classifier, n[2]).as_str(), &LibReplace).as_ref())), - _ => None - } - } else { - match n.len() { - 2 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}.jar", n[0], n[1]).as_str(), &LibReplace).as_ref())), - 3 => Some(PathBuf::from(strsub::replace_string(format!("{}-{}-{}.jar", n[0], n[1], n[2]).as_str(), &LibReplace).as_ref())), - _ => None - } - } - } - - fn get_artifact_path(name: &str, classifier: Option<&str>) -> Option<PathBuf> { - let mut p = Self::get_artifact_base_dir(name)?; - - p.push(Self::get_artifact_filename(name, classifier)?); - Some(p) - } - - fn create_download(&self, lib: &Library, classifier: Option<&str>) -> Option<VerifiedDownload> { - if let Some(ref url) = lib.url { - let path = Self::get_artifact_path(lib.name.as_str(), classifier)?; - let url = [url.as_str(), path.to_string_lossy().as_ref()].into_iter().collect::<String>(); - Some(VerifiedDownload::new(url.as_ref(), self.home.join(path).as_path(), lib.size, lib.sha1)) // TODO: could download sha1 - } else if let Some(ref downloads) = lib.downloads { - let dlinfo = downloads.get_download_info(classifier)?; - // drinking game: take a shot once per heap allocation - let path = self.home.join(dlinfo.path.as_ref().map(PathBuf::from).or_else(|| Self::get_artifact_path(lib.name.as_str(), classifier))?); - - Some(VerifiedDownload::new(dlinfo.url.as_ref()?, path.as_path(), dlinfo.size, dlinfo.sha1)) - } else { - let path = Self::get_artifact_path(lib.name.as_str(), classifier)?; - let url = ["https://libraries.minecraft.net/", path.to_string_lossy().as_ref()].into_iter().collect::<String>(); - Some(VerifiedDownload::new(url.as_ref(), self.home.join(path).as_path(), lib.size, lib.sha1)) // TODO: could download sha1 - } - } - - async fn clean_old_natives(&self) -> Result<usize, LaunchError> { - info!("Cleaning up old natives folders..."); - - let boot_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() - min(System::uptime(), 7u64*24*60*60); - - let readdir = match fs::read_dir(&self.natives).await { - Ok(readdir) => readdir, - Err(e) if e.kind() == ErrorKind::NotFound => return Ok(0), - Err(e) => return Err(LaunchError::IO { what: "reading natives directory", error: e }) - }; - - ReadDirStream::new(readdir) - .map(|entry| Ok(async move { - let entry = entry.map_err(|e| LaunchError::IO { what: "reading natives entry", error: e })?; - let ftype = entry.file_type().await.map_err(|e| LaunchError::IO { what: "'stat'ing natives entry", error: e })?; - - if !ftype.is_dir() { return Ok(false); } - - let Some(ftime) = entry.file_name().to_str() - .and_then(|s| constants::NATIVES_DIR_PATTERN.captures(s)) - .and_then(|c| c.get(1)) - .and_then(|cap| cap.as_str().parse::<u64>().ok()) else { - return Ok(false); - }; - - if ftime < boot_time { - let path = entry.path(); - info!("Deleting old natives directory {}", path.display()); - - fs::remove_dir_all(&path).await.map_err(|e| LaunchError::IO { - what: "reading natives entry", - error: e - })?; - - return Ok(true); - } - - Ok(false) - })) - .try_buffer_unordered(32) - .try_fold(0usize, |accum, res| async move { - match res { - true => Ok(accum + 1), - _ => Ok(accum) - } - }).await - } - - async fn extract_natives(&self, libs: Vec<LibraryExtractJob>) -> Result<PathBuf, LaunchError> { - fs::create_dir_all(&self.natives).await.map_err(|e| LaunchError::IO { - what: "creating natives directory", - error: e - })?; - - let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); - let natives_dir = self.natives.join(format!("{}{}-{}", constants::NATIVES_PREFIX, time, process::id())); - - // create_dir_all suppresses "AlreadyExists", but this is a fatal error here. - fs::create_dir(&natives_dir).await.map_err(|e| LaunchError::IO { - what: "creating natives directory", - error: e - })?; - - let (path_again, extracted) = tokio::task::spawn_blocking(move || { - let mut tally = 0usize; - - for job in libs { - debug!("Extracting natives for {}", job.source.display()); - tally += extract::extract_zip(&job.source, &natives_dir, |name| - job.rule.as_ref().is_none_or(|rules| - rules.exclude.iter().any(|ex| - name.starts_with(ex.as_str()))))?; - } - - Ok((natives_dir, tally)) - }).await.unwrap().map_err(LaunchError::LibraryExtractError)?; - - info!("Done extracting natives! Copied {} files.", extracted); - - Ok(path_again) - } -} - -impl SystemInfo { - fn new() -> SystemInfo { - let os = match OS { - "windows" => OperatingSystem::Windows, - "macos" => OperatingSystem::MacOS, - "linux" => OperatingSystem::Linux, - _ => OperatingSystem::Unknown // could probably consider "hurd" and "*bsd" to be linux... - }; - - let mut os_version = System::os_version().unwrap_or_default(); - if os == OperatingSystem::Windows && (os_version.starts_with("10") || os_version.starts_with("11")) { - os_version.replace_range(..2, "10.0"); // minecraft expects this funny business... - } - - let mut arch = ARCH.to_owned(); - if arch == "x86_64" { - // this nomenclature is preferred, since some versions expect the arch containing "x86" to mean 32-bit. - arch.replace_range(.., "amd64"); - } - - SystemInfo { - os, - os_version, - arch - } - } - - fn is_our_os(&self, os: OperatingSystem) -> bool { - if self.os == OperatingSystem::Unknown { - return false; - } - - self.os == os - } - - fn applies(&self, restriction: &OSRestriction) -> bool { - restriction.os.is_none_or(|os| self.is_our_os(os)) - && restriction.version.as_deref().is_none_or(|pat| pat.is_match(&self.os_version)) - && restriction.arch.as_deref().is_none_or(|pat| pat.is_match(&self.arch)) - } -} |
