diff options
| author | 2025-01-31 02:32:19 -0600 | |
|---|---|---|
| committer | 2025-01-31 02:32:19 -0600 | |
| commit | cdeee17c2be5b8b9a333b977b3e2d373b94dfe0a (patch) | |
| tree | 58ec48b5bfa9afe03ebbd9716f1f90841af914e9 /src/launcher | |
| parent | Remove some unused imports but not all of them (diff) | |
do clippy stuff and change line endings
Diffstat (limited to 'src/launcher')
| -rw-r--r-- | src/launcher/assets.rs | 644 | ||||
| -rw-r--r-- | src/launcher/constants.rs | 4 | ||||
| -rw-r--r-- | src/launcher/download.rs | 10 | ||||
| -rw-r--r-- | src/launcher/jre.rs | 19 | ||||
| -rw-r--r-- | src/launcher/jre/manifest.rs | 2 | ||||
| -rw-r--r-- | src/launcher/rules.rs | 2 | ||||
| -rw-r--r-- | src/launcher/runner.rs | 17 | ||||
| -rw-r--r-- | src/launcher/settings.rs | 4 | ||||
| -rw-r--r-- | src/launcher/strsub.rs | 4 | ||||
| -rw-r--r-- | src/launcher/version.rs | 22 |
10 files changed, 360 insertions, 368 deletions
diff --git a/src/launcher/assets.rs b/src/launcher/assets.rs index dacd01d..7c5dcf3 100644 --- a/src/launcher/assets.rs +++ b/src/launcher/assets.rs @@ -1,322 +1,322 @@ -use std::error::Error;
-use std::ffi::OsStr;
-use std::fmt::{Display, Formatter};
-use std::io::ErrorKind;
-use std::path::{Path, PathBuf};
-use std::path::Component::Normal;
-use futures::{stream, TryStreamExt};
-use log::{debug, info, warn};
-use reqwest::Client;
-use sha1_smol::Sha1;
-use tokio::{fs, io};
-use tokio::fs::File;
-use crate::assets::{Asset, AssetIndex};
-use crate::launcher::download::{MultiDownloader, VerifiedDownload};
-use crate::util;
-use crate::util::{FileVerifyError, IntegrityError};
-use crate::version::DownloadInfo;
-
-const INDEX_PATH: &'static str = "indexes";
-const OBJECT_PATH: &'static str = "objects";
-
-pub struct AssetRepository {
- online: bool,
- home: PathBuf
-}
-
-#[derive(Debug)]
-pub enum AssetError {
- InvalidId(Option<String>),
- IO { what: &'static str, error: io::Error },
- IndexParse(serde_json::Error),
- Offline,
- MissingURL,
- DownloadIndex(reqwest::Error),
- Integrity(IntegrityError),
- AssetObjectDownload,
- AssetVerifyError(FileVerifyError),
- AssetNameError(&'static str)
-}
-
-impl Display for AssetError {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- AssetError::InvalidId(None) => f.write_str("missing asset index id"),
- AssetError::InvalidId(Some(id)) => write!(f, "invalid asset index id: {}", id),
- AssetError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error),
- AssetError::IndexParse(error) => write!(f, "error parsing asset index: {}", error),
- AssetError::Offline => f.write_str("cannot download asset index while offline"),
- AssetError::MissingURL => f.write_str("missing asset index URL"),
- AssetError::DownloadIndex(e) => write!(f, "error downloading asset index: {}", e),
- AssetError::Integrity(e) => write!(f, "asset index integrity error: {}", e),
- AssetError::AssetObjectDownload => f.write_str("asset object download failed"),
- AssetError::AssetVerifyError(e) => write!(f, "error verifying asset object: {e}"),
- AssetError::AssetNameError(e) => write!(f, "invalid asset name: {e}")
- }
- }
-}
-
-impl Error for AssetError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- AssetError::IO { error, .. } => Some(error),
- AssetError::IndexParse(error) => Some(error),
- AssetError::DownloadIndex(error) => Some(error),
- AssetError::Integrity(error) => Some(error),
- AssetError::AssetVerifyError(error) => Some(error),
- _ => None
- }
- }
-}
-
-impl From<(&'static str, io::Error)> for AssetError {
- fn from((what, error): (&'static str, io::Error)) -> Self {
- AssetError::IO { what, error }
- }
-}
-
-impl AssetRepository {
- pub async fn new(online: bool, home: impl AsRef<Path>) -> Result<AssetRepository, io::Error> {
- let home = home.as_ref().to_owned();
-
- match fs::create_dir_all(&home).await {
- Ok(_) => (),
- Err(e) => match e.kind() {
- ErrorKind::AlreadyExists => (),
- _ => return Err(e)
- }
- };
-
- Ok(AssetRepository {
- online,
- home
- })
- }
-
- pub fn get_home(&self) -> &Path {
- self.home.as_path()
- }
-
- fn get_index_path(&self, id: &str) -> Result<PathBuf, AssetError> {
- let mut indexes_path: PathBuf = [self.home.as_ref(), OsStr::new(INDEX_PATH)].iter().collect();
- let Some(Normal(path)) = Path::new(id).components().last() else {
- return Err(AssetError::InvalidId(Some(id.into())));
- };
-
- let path = path.to_str().ok_or(AssetError::InvalidId(Some(path.to_string_lossy().into())))?;
-
- // FIXME: change this once "add_extension" is stabilized
- indexes_path.push(format!("{}.json", path));
-
- Ok(indexes_path)
- }
-
- pub async fn load_index(&self, index: &DownloadInfo, id: Option<&str>) -> Result<AssetIndex, AssetError> {
- let Some(id) = id else {
- return Err(AssetError::InvalidId(None));
- };
-
- info!("Loading asset index {}", id);
-
- let path = self.get_index_path(id)?;
- debug!("Asset index {} is located at {}", id, path.display());
-
- match util::verify_file(&path, index.size, index.sha1).await {
- Ok(_) => {
- debug!("Asset index {} verified on disk. Loading it.", id);
- let idx_data = fs::read_to_string(&path).await.map_err(|e| AssetError::IO {
- what: "reading asset index",
- error: e
- })?;
-
- return Ok(serde_json::from_str(&idx_data).map_err(|e| AssetError::IndexParse(e))?);
- },
- Err(FileVerifyError::Open(_, e)) => match e.kind() {
- ErrorKind::NotFound => {
- debug!("Asset index {} not found on disk. Must download it.", id);
- },
- _ => return Err(("opening asset index", e).into())
- },
- Err(FileVerifyError::Integrity(_, e)) => {
- info!("Asset index {} has mismatched integrity: {}, must download it.", id, e);
- let _ = fs::remove_file(&path).await.map_err(|e| warn!("Error deleting modified index {}: {} (ignored)", id, e));
- },
- Err(FileVerifyError::Read(_, e)) => return Err(("reading asset index", e).into())
- }
-
- if !self.online {
- warn!("Must download asset index {}, but the launcher is in offline mode. Please try again in online mode.", id);
- return Err(AssetError::Offline);
- }
-
- let Some(url) = index.url.as_ref() else {
- return Err(AssetError::MissingURL);
- };
-
- debug!("Downloading asset index {} from {}", id, url);
-
- if let Some(parent) = path.parent() {
- fs::create_dir_all(parent).await.map_err(|e| AssetError::IO {
- what: "creating asset index folder",
- error: e
- })?;
- }
-
- let idx_text = reqwest::get(url).await
- .map_err(|e| AssetError::DownloadIndex(e))?
- .text().await
- .map_err(|e| AssetError::DownloadIndex(e))?;
-
- if index.size.is_some_and(|s| s != idx_text.len()) {
- return Err(AssetError::Integrity(IntegrityError::SizeMismatch {
- expect: index.size.unwrap(),
- actual: idx_text.len()
- }));
- }
-
- if let Some(expect) = index.sha1 {
- let actual = Sha1::from(&idx_text).digest();
-
- if actual != expect {
- return Err(AssetError::Integrity(IntegrityError::Sha1Mismatch { expect, actual }));
- }
- }
-
- debug!("Saving downloaded asset index to {}", path.display());
- fs::write(&path, &idx_text).await.map_err(|e| AssetError::IO {
- what: "writing asset index",
- error: e
- })?;
-
- Ok(serde_json::from_str(&idx_text).map_err(|e| AssetError::IndexParse(e))?)
- }
-
- fn get_object_url(obj: &Asset) -> String {
- format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash)
- }
-
- pub fn get_object_path(&self, obj: &Asset) -> PathBuf {
- let hex_digest = obj.hash.to_string();
- [self.home.as_ref(), OsStr::new(OBJECT_PATH), OsStr::new(&hex_digest[..2]), OsStr::new(&hex_digest)].iter().collect()
- }
-
- async fn ensure_dir(path: impl AsRef<Path>) -> Result<(), io::Error> {
- match fs::create_dir(path).await {
- Ok(_) => Ok(()),
- Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()),
- Err(e) => Err(e)
- }
- }
-
- pub async fn ensure_assets(&self, index: &AssetIndex) -> Result<(), AssetError> {
- let mut downloads = Vec::new();
- let objects_path = [self.home.as_ref(), OsStr::new(OBJECT_PATH)].iter().collect::<PathBuf>();
-
- Self::ensure_dir(&objects_path).await.map_err(|e| AssetError::IO {
- what: "creating objects directory",
- error: e
- })?;
-
- for object in index.objects.values() {
- let path = self.get_object_path(object);
-
- Self::ensure_dir(path.parent().unwrap()).await.map_err(|error| AssetError::IO { error, what: "creating directory for object" })?;
-
- downloads.push(VerifiedDownload::new(&Self::get_object_url(object), &path, Some(object.size), Some(object.hash)));
- }
-
- if self.online {
- info!("Downloading {} asset objects...", downloads.len());
- let client = Client::new();
- MultiDownloader::with_concurrent(downloads.iter_mut(), 32).perform(&client).await
- .inspect_err(|e| warn!("asset download failed: {e}"))
- .try_fold((), |_, _| async {Ok(())})
- .await
- .map_err(|_| AssetError::AssetObjectDownload)?;
- } else {
- info!("Verifying {} asset objects...", downloads.len());
- super::download::verify_files(downloads.iter_mut()).await.map_err(|e| AssetError::AssetVerifyError(e))?;
- }
-
- Ok(())
- }
-
- pub async fn reconstruct_assets(&self, index: &AssetIndex, instance_path: &Path, index_id: Option<&str>) -> Result<Option<PathBuf>, AssetError> {
- let target_path: PathBuf;
- let Some(index_id) = index_id else {
- return Err(AssetError::InvalidId(None));
- };
-
- if index.virtual_assets {
- target_path = [self.home.as_ref(), OsStr::new("virtual"), OsStr::new(index_id)].iter().collect();
- } else if index.map_to_resources {
- target_path = [instance_path, Path::new("resources")].iter().collect();
- } else {
- info!("This asset index does not request a virtual assets folder. Nothing to be done.");
- return Ok(None);
- }
-
- info!("Reconstructing virtual assets for {}", index_id);
-
- fs::create_dir_all(&target_path).await.map_err(|e| AssetError::from(("creating virtual assets directory", e)))?;
-
- stream::iter(index.objects.values()
- .map(|object| {
- let obj_path = util::check_path(object.name.as_str()).map_err(AssetError::AssetNameError)?;
- let obj_path = target_path.join(obj_path);
-
- Ok((object, obj_path))
- }))
- .try_filter_map(|(object, obj_path)| async move {
- match util::verify_file(&obj_path, Some(object.size), Some(object.hash)).await {
- Ok(_) => {
- debug!("Not copying asset {}, integrity matches.", object.name);
- Ok(None)
- }
- Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => {
- debug!("Copying asset {}, file does not exist.", object.name);
- Ok(Some((object, obj_path)))
- },
- Err(FileVerifyError::Integrity(_, e)) => {
- debug!("Copying asset {}: {}", object.name, e);
- Ok(Some((object, obj_path)))
- },
- Err(e) => {
- debug!("Error while reconstructing assets: {e}");
- Err(AssetError::AssetVerifyError(e))
- }
- }
- })
- .try_for_each_concurrent(32, |(object, obj_path)| async move {
- if let Some(parent) = obj_path.parent() {
- fs::create_dir_all(parent).await
- .inspect_err(|e| debug!("Error creating directory for asset object {}: {e}", object.name))
- .map_err(|e| AssetError::from(("creating asset object directory", e)))?;
- }
-
- let mut fromfile = File::open(self.get_object_path(object)).await
- .map_err(|e| AssetError::from(("opening source object", e)))?;
- let mut tofile = File::create(&obj_path).await
- .map_err(|e| AssetError::from(("creating target object", e)))?;
-
- io::copy(&mut fromfile, &mut tofile).await.map_err(|e| AssetError::from(("copying asset object", e)))?;
- debug!("Copied object {} to {}.", object.name, obj_path.display());
- Ok(())
- }).await.map(|_| Some(target_path))
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_it() {
- let digest_str = "ad1115931887a73cd596300f2c93f84adf39521d";
- assert_eq!(AssetRepository::get_object_url(&Asset {
- name: String::from("test"),
- hash: digest_str.parse().unwrap(),
- size: 0usize
- }), "https://resources.download.minecraft.net/ad/ad1115931887a73cd596300f2c93f84adf39521d");
- }
-}
+use std::error::Error; +use std::ffi::OsStr; +use std::fmt::{Display, Formatter}; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; +use std::path::Component::Normal; +use futures::{stream, TryStreamExt}; +use log::{debug, info, warn}; +use reqwest::Client; +use sha1_smol::Sha1; +use tokio::{fs, io}; +use tokio::fs::File; +use crate::assets::{Asset, AssetIndex}; +use crate::launcher::download::{MultiDownloader, VerifiedDownload}; +use crate::util; +use crate::util::{FileVerifyError, IntegrityError}; +use crate::version::DownloadInfo; + +const INDEX_PATH: &str = "indexes"; +const OBJECT_PATH: &str = "objects"; + +pub struct AssetRepository { + online: bool, + home: PathBuf +} + +#[derive(Debug)] +pub enum AssetError { + InvalidId(Option<String>), + IO { what: &'static str, error: io::Error }, + IndexParse(serde_json::Error), + Offline, + MissingURL, + DownloadIndex(reqwest::Error), + Integrity(IntegrityError), + AssetObjectDownload, + AssetVerifyError(FileVerifyError), + AssetNameError(&'static str) +} + +impl Display for AssetError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AssetError::InvalidId(None) => f.write_str("missing asset index id"), + AssetError::InvalidId(Some(id)) => write!(f, "invalid asset index id: {}", id), + AssetError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error), + AssetError::IndexParse(error) => write!(f, "error parsing asset index: {}", error), + AssetError::Offline => f.write_str("cannot download asset index while offline"), + AssetError::MissingURL => f.write_str("missing asset index URL"), + AssetError::DownloadIndex(e) => write!(f, "error downloading asset index: {}", e), + AssetError::Integrity(e) => write!(f, "asset index integrity error: {}", e), + AssetError::AssetObjectDownload => f.write_str("asset object download failed"), + AssetError::AssetVerifyError(e) => write!(f, "error verifying asset object: {e}"), + AssetError::AssetNameError(e) => write!(f, "invalid asset name: {e}") + } + } +} + +impl Error for AssetError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + AssetError::IO { error, .. } => Some(error), + AssetError::IndexParse(error) => Some(error), + AssetError::DownloadIndex(error) => Some(error), + AssetError::Integrity(error) => Some(error), + AssetError::AssetVerifyError(error) => Some(error), + _ => None + } + } +} + +impl From<(&'static str, io::Error)> for AssetError { + fn from((what, error): (&'static str, io::Error)) -> Self { + AssetError::IO { what, error } + } +} + +impl AssetRepository { + pub async fn new(online: bool, home: impl AsRef<Path>) -> Result<AssetRepository, io::Error> { + let home = home.as_ref().to_owned(); + + match fs::create_dir_all(&home).await { + Ok(_) => (), + Err(e) => match e.kind() { + ErrorKind::AlreadyExists => (), + _ => return Err(e) + } + }; + + Ok(AssetRepository { + online, + home + }) + } + + pub fn get_home(&self) -> &Path { + self.home.as_path() + } + + fn get_index_path(&self, id: &str) -> Result<PathBuf, AssetError> { + let mut indexes_path: PathBuf = [self.home.as_ref(), OsStr::new(INDEX_PATH)].iter().collect(); + let Some(Normal(path)) = Path::new(id).components().last() else { + return Err(AssetError::InvalidId(Some(id.into()))); + }; + + let path = path.to_str().ok_or(AssetError::InvalidId(Some(path.to_string_lossy().into())))?; + + // FIXME: change this once "add_extension" is stabilized + indexes_path.push(format!("{}.json", path)); + + Ok(indexes_path) + } + + pub async fn load_index(&self, index: &DownloadInfo, id: Option<&str>) -> Result<AssetIndex, AssetError> { + let Some(id) = id else { + return Err(AssetError::InvalidId(None)); + }; + + info!("Loading asset index {}", id); + + let path = self.get_index_path(id)?; + debug!("Asset index {} is located at {}", id, path.display()); + + match util::verify_file(&path, index.size, index.sha1).await { + Ok(_) => { + debug!("Asset index {} verified on disk. Loading it.", id); + let idx_data = fs::read_to_string(&path).await.map_err(|e| AssetError::IO { + what: "reading asset index", + error: e + })?; + + return serde_json::from_str(&idx_data).map_err(AssetError::IndexParse); + }, + Err(FileVerifyError::Open(_, e)) => match e.kind() { + ErrorKind::NotFound => { + debug!("Asset index {} not found on disk. Must download it.", id); + }, + _ => return Err(("opening asset index", e).into()) + }, + Err(FileVerifyError::Integrity(_, e)) => { + info!("Asset index {} has mismatched integrity: {}, must download it.", id, e); + let _ = fs::remove_file(&path).await.map_err(|e| warn!("Error deleting modified index {}: {} (ignored)", id, e)); + }, + Err(FileVerifyError::Read(_, e)) => return Err(("reading asset index", e).into()) + } + + if !self.online { + warn!("Must download asset index {}, but the launcher is in offline mode. Please try again in online mode.", id); + return Err(AssetError::Offline); + } + + let Some(url) = index.url.as_ref() else { + return Err(AssetError::MissingURL); + }; + + debug!("Downloading asset index {} from {}", id, url); + + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).await.map_err(|e| AssetError::IO { + what: "creating asset index folder", + error: e + })?; + } + + let idx_text = reqwest::get(url).await + .map_err(AssetError::DownloadIndex)? + .text().await + .map_err(AssetError::DownloadIndex)?; + + if index.size.is_some_and(|s| s != idx_text.len()) { + return Err(AssetError::Integrity(IntegrityError::SizeMismatch { + expect: index.size.unwrap(), + actual: idx_text.len() + })); + } + + if let Some(expect) = index.sha1 { + let actual = Sha1::from(&idx_text).digest(); + + if actual != expect { + return Err(AssetError::Integrity(IntegrityError::Sha1Mismatch { expect, actual })); + } + } + + debug!("Saving downloaded asset index to {}", path.display()); + fs::write(&path, &idx_text).await.map_err(|e| AssetError::IO { + what: "writing asset index", + error: e + })?; + + serde_json::from_str(&idx_text).map_err(AssetError::IndexParse) + } + + fn get_object_url(obj: &Asset) -> String { + format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash) + } + + pub fn get_object_path(&self, obj: &Asset) -> PathBuf { + let hex_digest = obj.hash.to_string(); + [self.home.as_ref(), OsStr::new(OBJECT_PATH), OsStr::new(&hex_digest[..2]), OsStr::new(&hex_digest)].iter().collect() + } + + async fn ensure_dir(path: impl AsRef<Path>) -> Result<(), io::Error> { + match fs::create_dir(path).await { + Ok(_) => Ok(()), + Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), + Err(e) => Err(e) + } + } + + pub async fn ensure_assets(&self, index: &AssetIndex) -> Result<(), AssetError> { + let mut downloads = Vec::new(); + let objects_path = [self.home.as_ref(), OsStr::new(OBJECT_PATH)].iter().collect::<PathBuf>(); + + Self::ensure_dir(&objects_path).await.map_err(|e| AssetError::IO { + what: "creating objects directory", + error: e + })?; + + for object in index.objects.values() { + let path = self.get_object_path(object); + + Self::ensure_dir(path.parent().unwrap()).await.map_err(|error| AssetError::IO { error, what: "creating directory for object" })?; + + downloads.push(VerifiedDownload::new(&Self::get_object_url(object), &path, Some(object.size), Some(object.hash))); + } + + if self.online { + info!("Downloading {} asset objects...", downloads.len()); + let client = Client::new(); + MultiDownloader::with_concurrent(downloads.iter_mut(), 32).perform(&client).await + .inspect_err(|e| warn!("asset download failed: {e}")) + .try_fold((), |_, _| async {Ok(())}) + .await + .map_err(|_| AssetError::AssetObjectDownload)?; + } else { + info!("Verifying {} asset objects...", downloads.len()); + super::download::verify_files(downloads.iter_mut()).await.map_err(AssetError::AssetVerifyError)?; + } + + Ok(()) + } + + pub async fn reconstruct_assets(&self, index: &AssetIndex, instance_path: &Path, index_id: Option<&str>) -> Result<Option<PathBuf>, AssetError> { + let target_path: PathBuf; + let Some(index_id) = index_id else { + return Err(AssetError::InvalidId(None)); + }; + + if index.virtual_assets { + target_path = [self.home.as_ref(), OsStr::new("virtual"), OsStr::new(index_id)].iter().collect(); + } else if index.map_to_resources { + target_path = [instance_path, Path::new("resources")].iter().collect(); + } else { + info!("This asset index does not request a virtual assets folder. Nothing to be done."); + return Ok(None); + } + + info!("Reconstructing virtual assets for {}", index_id); + + fs::create_dir_all(&target_path).await.map_err(|e| AssetError::from(("creating virtual assets directory", e)))?; + + stream::iter(index.objects.values() + .map(|object| { + let obj_path = util::check_path(object.name.as_str()).map_err(AssetError::AssetNameError)?; + let obj_path = target_path.join(obj_path); + + Ok((object, obj_path)) + })) + .try_filter_map(|(object, obj_path)| async move { + match util::verify_file(&obj_path, Some(object.size), Some(object.hash)).await { + Ok(_) => { + debug!("Not copying asset {}, integrity matches.", object.name); + Ok(None) + } + Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => { + debug!("Copying asset {}, file does not exist.", object.name); + Ok(Some((object, obj_path))) + }, + Err(FileVerifyError::Integrity(_, e)) => { + debug!("Copying asset {}: {}", object.name, e); + Ok(Some((object, obj_path))) + }, + Err(e) => { + debug!("Error while reconstructing assets: {e}"); + Err(AssetError::AssetVerifyError(e)) + } + } + }) + .try_for_each_concurrent(32, |(object, obj_path)| async move { + if let Some(parent) = obj_path.parent() { + fs::create_dir_all(parent).await + .inspect_err(|e| debug!("Error creating directory for asset object {}: {e}", object.name)) + .map_err(|e| AssetError::from(("creating asset object directory", e)))?; + } + + let mut fromfile = File::open(self.get_object_path(object)).await + .map_err(|e| AssetError::from(("opening source object", e)))?; + let mut tofile = File::create(&obj_path).await + .map_err(|e| AssetError::from(("creating target object", e)))?; + + io::copy(&mut fromfile, &mut tofile).await.map_err(|e| AssetError::from(("copying asset object", e)))?; + debug!("Copied object {} to {}.", object.name, obj_path.display()); + Ok(()) + }).await.map(|_| Some(target_path)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_it() { + let digest_str = "ad1115931887a73cd596300f2c93f84adf39521d"; + assert_eq!(AssetRepository::get_object_url(&Asset { + name: String::from("test"), + hash: digest_str.parse().unwrap(), + size: 0usize + }), "https://resources.download.minecraft.net/ad/ad1115931887a73cd596300f2c93f84adf39521d"); + } +} diff --git a/src/launcher/constants.rs b/src/launcher/constants.rs index db90d2f..4506ab5 100644 --- a/src/launcher/constants.rs +++ b/src/launcher/constants.rs @@ -7,8 +7,8 @@ pub const URL_JRE_MANIFEST: &str = "https://piston-meta.mojang.com/v1/products/j pub const NATIVES_PREFIX: &str = "natives-"; -pub const DEF_INSTANCE_NAME: &'static str = "default"; -pub const DEF_PROFILE_NAME: &'static str = "default"; +pub const DEF_INSTANCE_NAME: &str = "default"; +pub const DEF_PROFILE_NAME: &str = "default"; // https://github.com/unmojang/FjordLauncher/pull/14/files // https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&redirect_uri=ms-xal-00000000402b5328://auth&response_type=token&display=touch&scope=service::user.auth.xboxlive.com::MBI_SSL%20offline_access&prompt=select_account diff --git a/src/launcher/download.rs b/src/launcher/download.rs index 3a89d79..132cd7f 100644 --- a/src/launcher/download.rs +++ b/src/launcher/download.rs @@ -55,7 +55,7 @@ pub struct PhaseDownloadError<'j, T: Download> { job: &'j T } -impl<'j, T: Download> Debug for PhaseDownloadError<'j, T> { +impl<T: Download> Debug for PhaseDownloadError<'_, T> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("PhaseDownloadError") .field("phase", &self.phase) @@ -65,13 +65,13 @@ impl<'j, T: Download> Debug for PhaseDownloadError<'j, T> { } } -impl<'j, T: Download> Display for PhaseDownloadError<'j, T> { +impl<T: Download> Display for PhaseDownloadError<'_, T> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "error while {} ({}): {}", self.phase, self.job, self.inner) } } -impl<'j, T: Download> Error for PhaseDownloadError<'j, T> { +impl<T: Download> Error for PhaseDownloadError<'_, T> { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&*self.inner) } @@ -100,7 +100,7 @@ impl<'j, T: Download + 'j, I: Iterator<Item = &'j mut T>> MultiDownloader<'j, T, } pub async fn perform(self, client: &'j Client) -> impl TryStream<Ok = (), Error = PhaseDownloadError<'j, T>> { - stream::iter(self.jobs.into_iter()).map(move |job| Ok(async move { + stream::iter(self.jobs).map(move |job| Ok(async move { macro_rules! map_err { ($result:expr, $phase:expr, $job:expr) => { match $result { @@ -124,7 +124,7 @@ impl<'j, T: Download + 'j, I: Iterator<Item = &'j mut T>> MultiDownloader<'j, T, map_err!(job.handle_chunk(bytes.as_ref()).await, Phase::HandleChunk, job); } - job.finish().await.map_err(|e| PhaseDownloadError::new(Phase::Finish, e.into(), job))?; + job.finish().await.map_err(|e| PhaseDownloadError::new(Phase::Finish, e, job))?; Ok(()) })).try_buffer_unordered(self.nconcurrent) diff --git a/src/launcher/jre.rs b/src/launcher/jre.rs index 0b92c20..31034b5 100644 --- a/src/launcher/jre.rs +++ b/src/launcher/jre.rs @@ -67,13 +67,13 @@ impl JavaRuntimeRepository { .inspect_err(|e| warn!("Failed to create directory for JRE component {}: {}", component, e)) .map_err(|e| JavaRuntimeError::IO { what: "creating component directory", error: e })?; - util::ensure_file(&manifest_path, info.url.as_ref().map(|s| s.as_str()), info.size, info.sha1, self.online, false).await + util::ensure_file(&manifest_path, info.url.as_deref(), info.size, info.sha1, self.online, false).await .map_err(JavaRuntimeError::EnsureFile)?; let manifest_file = fs::read_to_string(&manifest_path).await .map_err(|e| JavaRuntimeError::IO { what: "reading runtimes manifest", error: e })?; - Ok(serde_json::from_str(&manifest_file).map_err(|e| JavaRuntimeError::Deserialize { what: "runtime manifest", error: e })?) + serde_json::from_str(&manifest_file).map_err(|e| JavaRuntimeError::Deserialize { what: "runtime manifest", error: e }) } // not very descriptive function name @@ -86,7 +86,7 @@ impl JavaRuntimeRepository { return Err(JavaRuntimeError::UnsupportedComponent { arch: JRE_ARCH, component: component.to_owned() }); }; - let Some(runtime) = runtime_component.iter().filter(|r| r.availability.progress == 100).next() else { + let Some(runtime) = runtime_component.iter().find(|r| r.availability.progress == 100) else { if !runtime_components.is_empty() { warn!("Weird: the only java runtimes in {JRE_ARCH}.{component} has a progress of less than 100!"); } @@ -102,17 +102,16 @@ impl JavaRuntimeRepository { let entry = entry?; let rel_path = entry.path().strip_prefix(path).expect("walkdir escaped root (???)"); - if rel_path.components().filter(|c| !matches!(c, Component::CurDir)).next().is_none() { + if !rel_path.components().any(|c| !matches!(&c, Component::CurDir)) { // if this path is trivial (points at the root), ignore it continue; } - let rel_path_str; - if std::path::MAIN_SEPARATOR != '/' { - rel_path_str = rel_path.to_str().map(|s| s.replace(std::path::MAIN_SEPARATOR, "/")); + let rel_path_str = if std::path::MAIN_SEPARATOR != '/' { + rel_path.to_str().map(|s| s.replace(std::path::MAIN_SEPARATOR, "/")) } else { - rel_path_str = rel_path.to_str().map(String::from); - } + rel_path.to_str().map(String::from) + }; if !rel_path_str.as_ref().is_some_and(|s| manifest.files.get(s) .is_some_and(|f| (f.is_file() == entry.file_type().is_file()) @@ -175,7 +174,7 @@ impl JavaRuntimeRepository { Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), Err(e) => { warn!("Could not create directory {} for JRE!", ent_path.display()); - return Err(JavaRuntimeError::IO { what: "creating directory", error: e }); + Err(JavaRuntimeError::IO { what: "creating directory", error: e }) } } }).await diff --git a/src/launcher/jre/manifest.rs b/src/launcher/jre/manifest.rs index 887871a..3fd6484 100644 --- a/src/launcher/jre/manifest.rs +++ b/src/launcher/jre/manifest.rs @@ -37,7 +37,7 @@ pub enum JavaRuntimeFile { File { #[serde(default)] executable: bool, - downloads: FileDownloads + downloads: Box<FileDownloads> }, Directory, Link { diff --git a/src/launcher/rules.rs b/src/launcher/rules.rs index 69c967d..29a36d1 100644 --- a/src/launcher/rules.rs +++ b/src/launcher/rules.rs @@ -80,7 +80,7 @@ impl seal::CompatCheckInner for CompleteVersion { } fn get_incompatibility_reason(&self) -> Option<&str> { - self.incompatibility_reason.as_ref().map(|s| s.as_str()) + self.incompatibility_reason.as_deref() } } diff --git a/src/launcher/runner.rs b/src/launcher/runner.rs index f7fd025..afdfc7f 100644 --- a/src/launcher/runner.rs +++ b/src/launcher/runner.rs @@ -21,7 +21,7 @@ const PATH_SEP: &str = ";"; #[cfg(not(windows))] const PATH_SEP: &str = ":"; -impl<'rep, 'l, F: FeatureMatcher> SubFunc<'rep> for LaunchArgSub<'rep, 'l, F> { +impl<'rep, F: FeatureMatcher> SubFunc<'rep> for LaunchArgSub<'rep, '_, F> { fn substitute(&self, key: &str) -> Option<Cow<'rep, str>> { match key { "assets_index_name" => self.0.asset_index_name.as_ref().map(|s| Cow::Borrowed(s.as_str())), @@ -51,12 +51,11 @@ impl<'rep, 'l, F: FeatureMatcher> SubFunc<'rep> for LaunchArgSub<'rep, 'l, F> { "user_properties" => Some(Cow::Borrowed("{}")), // TODO "user_property_map" => Some(Cow::Borrowed("[]")), // TODO "user_type" => Some(Cow::Borrowed("legacy")), // TODO - "version_name" => Some(Cow::Borrowed(&self.0.version_id.as_ref())), + "version_name" => Some(Cow::Borrowed(self.0.version_id.as_ref())), "version_type" => self.0.version_type.as_ref().map(|s| Cow::Borrowed(s.to_str())), _ => { if let Some(asset_key) = key.strip_prefix("asset=") { - return self.0.asset_index.as_ref() - .map_or(None, |idx| idx.objects.get(asset_key)) + return self.0.asset_index.as_ref().and_then(|idx| idx.objects.get(asset_key)) .map(|obj| Cow::Owned(self.0.launcher.assets.get_object_path(obj).as_java_path().to_string_lossy().into_owned())) } @@ -68,16 +67,16 @@ impl<'rep, 'l, F: FeatureMatcher> SubFunc<'rep> for LaunchArgSub<'rep, 'l, F> { #[derive(Clone, Copy)] pub enum ArgumentType { - JVM, + Jvm, Game } -pub fn build_arguments<'l, F: FeatureMatcher>(launch: &LaunchInfo<'l, F>, version: &CompleteVersion, arg_type: ArgumentType) -> Vec<OsString> { +pub fn build_arguments<F: FeatureMatcher>(launch: &LaunchInfo<'_, F>, version: &CompleteVersion, arg_type: ArgumentType) -> Vec<OsString> { let sub = LaunchArgSub(launch); let system_info = &launch.launcher.system_info; - if let Some(arguments) = version.arguments.as_ref().map_or(None, |args| match arg_type { - ArgumentType::JVM => args.jvm.as_ref(), + if let Some(arguments) = version.arguments.as_ref().and_then(|args| match arg_type { + ArgumentType::Jvm => args.jvm.as_ref(), ArgumentType::Game => args.game.as_ref() }) { arguments.iter() @@ -86,7 +85,7 @@ pub fn build_arguments<'l, F: FeatureMatcher>(launch: &LaunchInfo<'l, F>, versio .map(|s| OsString::from(strsub::replace_string(s, &sub).into_owned())).collect() } else if let Some(arguments) = version.minecraft_arguments.as_ref() { match arg_type { - ArgumentType::JVM => { + ArgumentType::Jvm => { [ "-Djava.library.path=${natives_directory}", "-Dminecraft.launcher.brand=${launcher_name}", diff --git a/src/launcher/settings.rs b/src/launcher/settings.rs index 4dfc4ac..8453653 100644 --- a/src/launcher/settings.rs +++ b/src/launcher/settings.rs @@ -89,7 +89,7 @@ impl Settings { } pub fn get_path(&self) -> Option<&Path> { - self.path.as_ref().map(|p| p.as_path()) + self.path.as_deref() } pub async fn save_to(&self, path: impl AsRef<Path>) -> Result<(), SettingsError> { @@ -184,7 +184,7 @@ impl Instance { } } -const DEF_JVM_ARGUMENTS: [&'static str; 7] = [ +const DEF_JVM_ARGUMENTS: [&str; 7] = [ "-Xmx2G", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseG1GC", diff --git a/src/launcher/strsub.rs b/src/launcher/strsub.rs index 0d2357d..5764405 100644 --- a/src/launcher/strsub.rs +++ b/src/launcher/strsub.rs @@ -69,7 +69,7 @@ pub fn replace_string<'inp, 'rep>(input: &'inp str, sub: &impl SubFunc<'rep>) -> let spec_start = idx + VAR_BEGIN.len(); // the start of the "spec" (area inside {}) // first, check if this is escaped - if let Some((prev_idx, ESCAPE)) = input[..idx].char_indices().rev().next() { + if let Some((prev_idx, ESCAPE)) = input[..idx].char_indices().next_back() { let s = ret.get_or_insert_default(); s.push_str(&input[cursor..prev_idx]); @@ -94,7 +94,7 @@ pub fn replace_string<'inp, 'rep>(input: &'inp str, sub: &impl SubFunc<'rep>) -> }; let after = spec_end + VAR_END.len(); - if let Some(subst) = sub.substitute(name).map_or_else(|| def.map(|d| Cow::Borrowed(d)), |v| Some(v)) { + if let Some(subst) = sub.substitute(name).map_or_else(|| def.map(Cow::Borrowed), Some) { let s = ret.get_or_insert_default(); s.push_str(&input[cursor..idx]); s.push_str(subst.as_ref()); diff --git a/src/launcher/version.rs b/src/launcher/version.rs index 328f0a9..49525b0 100644 --- a/src/launcher/version.rs +++ b/src/launcher/version.rs @@ -40,14 +40,9 @@ impl RemoteVersionList { async fn download_version(&self, ver: &VersionManifestVersion, path: &Path) -> Result<CompleteVersion, Box<dyn Error>> { // ensure parent directory exists info!("Downloading version {}.", ver.id); - match tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await { - Err(e) => { - if e.kind() != ErrorKind::AlreadyExists { - warn!("failed to create {} parent dirs: {e}", path.display()); - return Err(e.into()); - } - }, - Ok(()) => {} + if let Err(e) = tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await { + warn!("failed to create {} parent dirs: {e}", path.display()); + return Err(e.into()); } // download it @@ -254,7 +249,7 @@ impl VersionList { Self::create_dir_for(home).await?; let remote = RemoteVersionList::new().await?; - let local = LocalVersionList::load_versions(home.as_ref(), |s| remote.versions.contains_key(s)).await?; + let local = LocalVersionList::load_versions(home, |s| remote.versions.contains_key(s)).await?; Ok(VersionList { remote: Some(remote), @@ -280,16 +275,15 @@ impl VersionList { } pub fn get_version_lazy(&self, id: &str) -> VersionResult { - self.remote.as_ref() - .map_or(None, |r| r.versions.get(id).map(VersionResult::from)) + self.remote.as_ref().and_then(|r| r.versions.get(id).map(VersionResult::from)) .or_else(|| self.local.versions.get(id).map(VersionResult::from)) .unwrap_or(VersionResult::None) } pub fn get_profile_version_id<'v>(&self, ver: &'v ProfileVersion) -> Option<Cow<'v, str>> { match ver { - ProfileVersion::LatestRelease => self.remote.as_ref().map_or(None, |r| Some(Cow::Owned(r.latest.release.clone()))), - ProfileVersion::LatestSnapshot => self.remote.as_ref().map_or(None, |r| Some(Cow::Owned(r.latest.snapshot.clone()))), + ProfileVersion::LatestRelease => self.remote.as_ref().map(|r| Cow::Owned(r.latest.release.clone())), + ProfileVersion::LatestSnapshot => self.remote.as_ref().map(|r| Cow::Owned(r.latest.snapshot.clone())), ProfileVersion::Specific(ver) => Some(Cow::Borrowed(ver)) } } @@ -346,7 +340,7 @@ impl VersionList { let inherited_ver = match self.get_version_lazy(inherit.as_str()) { VersionResult::Complete(v) => Cow::Borrowed(v), VersionResult::Remote(v) => - Cow::Owned(self.load_remote_version(v).await.map_err(|e| VersionResolveError::Unknown(e))?), + Cow::Owned(self.load_remote_version(v).await.map_err(VersionResolveError::Unknown)?), VersionResult::None => { warn!("Cannot resolve version {}, it inherits an unknown version {inherit}", ver.id); return Err(VersionResolveError::MissingVersion(inherit)); |
