summaryrefslogtreecommitdiffstats
path: root/src/launcher.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher.rs')
-rw-r--r--src/launcher.rs765
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))
- }
-}