summaryrefslogtreecommitdiffstats
path: root/src/launcher.rs
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-16 02:30:15 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-16 02:30:15 -0600
commit38ab8b70c132150cd974a33d11bdf8048b99fb23 (patch)
tree62f9141c9db23dcb9eb79901a48fe17ed291be87 /src/launcher.rs
parentassets done (diff)
download client jar
Diffstat (limited to 'src/launcher.rs')
-rw-r--r--src/launcher.rs186
1 files changed, 110 insertions, 76 deletions
diff --git a/src/launcher.rs b/src/launcher.rs
index ccb84cb..25cdcc4 100644
--- a/src/launcher.rs
+++ b/src/launcher.rs
@@ -13,9 +13,10 @@ use std::error::Error;
use std::ffi::OsStr;
use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
+use std::io::ErrorKind::AlreadyExists;
use std::path::{Component, Path, PathBuf};
use const_format::formatcp;
-use futures::{stream, StreamExt, TryStreamExt};
+use futures::TryStreamExt;
use log::{debug, info, warn};
use serde::{Deserialize, Serialize};
use sha1_smol::Sha1;
@@ -26,7 +27,7 @@ use tokio::io::AsyncWriteExt;
use download::{MultiDownloader, VerifiedDownload};
use rules::{CompatCheck, IncompatibleError};
use version::{VersionList, VersionResolveError, VersionResult};
-use crate::version::{Logging, Library, OSRestriction, OperatingSystem};
+use crate::version::{Logging, Library, OSRestriction, OperatingSystem, DownloadType, DownloadInfo};
pub use profile::{Instance, Profile};
use crate::launcher::assets::{AssetError, AssetRepository};
@@ -154,8 +155,16 @@ pub enum LaunchError {
LibraryVerifyError(FileVerifyError),
LibraryDownloadError,
+ // ensure file errors
+ MissingURL,
+ IO { what: &'static str, error: io::Error },
+ Offline,
+ Download { url: String, error: reqwest::Error },
+ Integrity(IntegrityError),
+
// log errors
- LogConfig(LogConfigError),
+ UnknownLogType(String),
+ InvalidLogId(Option<String>),
// asset errors
Assets(AssetError)
@@ -171,7 +180,14 @@ impl Display for LaunchError {
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::LogConfig(e) => write!(f, "failed to configure logger: {}", e),
+ LaunchError::MissingURL => f.write_str("cannot download required file, URL is missing"),
+ LaunchError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error),
+ LaunchError::Offline => f.write_str("cannot download file in offline mode"),
+ LaunchError::Download { url, error } => write!(f, "failed to download file ({}): {}", url, error),
+ LaunchError::Integrity(e) => write!(f, "file verify error: {}", e),
+ 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)
}
}
@@ -185,7 +201,9 @@ impl Error for LaunchError {
LaunchError::IncompatibleVersion(e) => Some(e),
LaunchError::LibraryDirError(_, e) => Some(e),
LaunchError::LibraryVerifyError(e) => Some(e),
- LaunchError::LogConfig(e) => Some(e),
+ LaunchError::IO { error: e, .. } => Some(e),
+ LaunchError::Download { error: e, .. } => Some(e),
+ LaunchError::Integrity(e) => Some(e),
LaunchError::Assets(e) => Some(e),
_ => None
}
@@ -200,14 +218,11 @@ impl Launcher {
let versions;
match tokio::fs::create_dir_all(&home).await {
- Ok(_) => (),
- Err(e) => match e.kind() {
- ErrorKind::AlreadyExists => (),
- _ => {
- warn!("Failed to create launcher home directory: {}", e);
- return Err(e.into());
- }
- }
+ Err(e) if e.kind() != AlreadyExists => {
+ warn!("Failed to create launcher home directory: {}", e);
+ return Err(e.into());
+ },
+ _ => ()
}
debug!("Version list online?: {online}");
@@ -224,7 +239,6 @@ impl Launcher {
Ok(Launcher {
online,
- home: home.to_owned(),
versions,
settings_path,
settings,
@@ -232,87 +246,56 @@ impl Launcher {
libraries: LibraryRepository {
home: home.join("libraries"),
},
- assets: AssetRepository::new(online, &assets_path).await?
+ assets: AssetRepository::new(online, &assets_path).await?,
+ home
})
}
- async fn log_config_ensure(&self, config: &Logging) -> Result<Option<String>, LogConfigError> {
- info!("Ensuring log configuration exists and is valid.");
-
- if config.client.log_type != "log4j2-xml" {
- return Err(LogConfigError::UnknownType(config.client.log_type.clone()));
- }
-
- let dlinfo = &config.client.file;
- let Some(id) = dlinfo.id.as_ref() else {
- return Err(LogConfigError::InvalidId(None));
- };
-
- let mut path = self.home.join("logging");
- fs::create_dir_all(path.as_path()).await
- .map_err(|e| LogConfigError::IO{ what: "creating log directory", error: e })?;
-
- let Some(Component::Normal(filename)) = Path::new(id).components().last() else {
- return Err(LogConfigError::InvalidId(Some(id.clone())));
- };
-
- path.push(filename);
-
- debug!("Logger config {} is at {}", id, path.display());
-
- // creates the JVM argument for this logger config
- fn get_arg(arg: &str, path: &Path) -> String {
- strsub::replace_str(arg, |key| match key {
- "path" => Some(path.to_string_lossy().clone()),
- _ => None
- })
- }
-
+ async fn ensure_file(&self, path: &Path, dlinfo: &DownloadInfo) -> Result<(), LaunchError> {
// verify the file
- match util::verify_file(path.as_path(), dlinfo.size, dlinfo.sha1).await {
+ match util::verify_file(path, dlinfo.size, dlinfo.sha1).await {
// integrity passed. return
Ok(_) => {
- info!("Log configuration {} exists and integrity matches. Skipping.", id);
- return Ok(Some(get_arg(config.client.argument.as_str(), path.as_path())));
+ info!("File {} exists and integrity matches. Skipping.", path.display());
+ return Ok(());
},
// ruh roh
Err(e) => match e {
- FileVerifyError::Open(_, ioe) => match ioe.kind() {
- ErrorKind::NotFound => (),
- _ => return Err(LogConfigError::IO{ what: "verify file (open)", error: ioe })
- },
- FileVerifyError::Read(_, ioe) => return Err(LogConfigError::IO{ what: "verify file (read)", error: ioe }),
- FileVerifyError::Integrity(_, ie) => info!("log config failed integrity check: {}", ie)
+ FileVerifyError::Open(_, ioe) if ioe.kind() != ErrorKind::NotFound =>
+ return Err(LaunchError::IO{ what: "verify file (open)", error: ioe }),
+ FileVerifyError::Read(_, ioe) => return Err(LaunchError::IO{ what: "verify file (read)", error: ioe }),
+ FileVerifyError::Integrity(_, ie) => info!("file {} failed integrity check: {}", path.display(), ie),
+ _ => ()
}
}
if !self.online {
- warn!("Cannot download log config! We are offline. Rerun the launcher in online mode to launch this version.");
- return Err(LogConfigError::Offline);
+ warn!("Cannot download file {}! We are offline. Rerun the launcher in online mode to launch this version.", path.display());
+ return Err(LaunchError::Offline);
}
// download it
let Some(url) = dlinfo.url.as_ref() else {
- return Err(LogConfigError::MissingURL);
+ return Err(LaunchError::MissingURL);
};
- let mut file = File::create(path.as_path()).await.map_err(|e| LogConfigError::IO {
- what: "save log config (open)",
+ let mut file = File::create(path).await.map_err(|e| LaunchError::IO {
+ what: "save downloaded file (open)",
error: e
})?;
- debug!("Logger configuration {} must be downloaded ({}).", id, url);
+ debug!("File {} must be downloaded ({}).", path.display(), url);
- let mut response = reqwest::get(url).await.map_err(|e| LogConfigError::Download{ url: url.to_owned(), error: e })?;
+ let mut response = reqwest::get(url).await.map_err(|e| LaunchError::Download{ url: url.to_owned(), error: e })?;
let mut tally = 0usize;
let mut sha1 = Sha1::new();
- while let Some(chunk) = response.chunk().await.map_err(|e| LogConfigError::Download{ url: url.to_owned(), error: e })? {
+ while let Some(chunk) = response.chunk().await.map_err(|e| LaunchError::Download{ url: url.to_owned(), error: e })? {
let slice = chunk.as_ref();
- file.write_all(slice).await.map_err(|e| LogConfigError::IO {
- what: "save log config (write)",
+ file.write_all(slice).await.map_err(|e| LaunchError::IO {
+ what: "save downloaded file (write)",
error: e
})?;
@@ -323,15 +306,15 @@ impl Launcher {
drop(file); // manually close file
let del_file_silent = || async {
- debug!("Deleting downloaded log config {} since its integrity doesn't match :( {}", id, path.display());
- let _ = fs::remove_file(path.as_path()).await.map_err(|e| warn!("failed to delete invalid log config: {}", e));
+ debug!("Deleting downloaded file {} since its integrity doesn't match :(", path.display());
+ let _ = fs::remove_file(path).await.map_err(|e| warn!("failed to delete invalid downloaded file: {}", e));
()
};
if dlinfo.size.is_some_and(|s| s != tally) {
del_file_silent().await;
- return Err(LogConfigError::Integrity(IntegrityError::SizeMismatch {
+ return Err(LaunchError::Integrity(IntegrityError::SizeMismatch {
expect: dlinfo.size.unwrap(),
actual: tally
}));
@@ -342,15 +325,47 @@ impl Launcher {
if dlinfo.sha1.is_some_and(|exp_dig| exp_dig != digest) {
del_file_silent().await;
- return Err(LogConfigError::Integrity(IntegrityError::Sha1Mismatch {
+ return Err(LaunchError::Integrity(IntegrityError::Sha1Mismatch {
expect: dlinfo.sha1.unwrap(),
actual: digest
}));
}
- info!("Log configuration {} downloaded successfully.", id);
+ info!("File {} downloaded successfully.", path.display());
+
+ Ok(())
+ }
+
+ async fn log_config_ensure(&self, config: &Logging) -> Result<String, LaunchError> {
+ info!("Ensuring log configuration exists and is valid.");
- Ok(Some(get_arg(config.client.argument.as_str(), path.as_path())))
+ if config.client.log_type != "log4j2-xml" {
+ return Err(LaunchError::UnknownLogType(config.client.log_type.clone()));
+ }
+
+ let dlinfo = &config.client.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());
+
+ self.ensure_file(&path, dlinfo).await?;
+
+ Ok(strsub::replace_str(config.client.argument.as_str(), |key| match key {
+ "path" => Some(path.to_string_lossy().clone()),
+ _ => None
+ }))
}
pub async fn prepare_launch(&self, profile: &Profile) -> Result<(), LaunchError> {
@@ -373,8 +388,8 @@ impl Launcher {
* - (done) (if offline mode, explode)
* - if virtual or resource-mapped, copy (or perhaps hardlink? that would be cool)
* - the actual client jar
- * - check integriddy and download if needed
- * - (explode if offline mode)
+ * - (done) check integriddy and download if needed
+ * - (done) (explode if offline mode)
* - launch the game
* - build argument list and whatnot also
*/
@@ -428,14 +443,15 @@ impl Launcher {
let log_arg;
if let Some(logging) = ver.logging.as_ref() {
- log_arg = self.log_config_ensure(logging).await
- .map_err(|e| LaunchError::LogConfig(e))?;
+ log_arg = Some(self.log_config_ensure(logging).await?);
} else {
log_arg = None;
}
dbg!(log_arg);
+ // download assets
+
if let Some(idx_download) = ver.asset_index.as_ref() {
let asset_idx = self.assets.load_index(idx_download, ver.assets.as_ref().map(|s| s.as_ref())).await
.map_err(|e| LaunchError::Assets(e))?;
@@ -443,6 +459,24 @@ impl Launcher {
self.assets.ensure_assets(&asset_idx).await.map_err(|e| LaunchError::Assets(e))?;
}
+ // 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());
+
+ self.ensure_file(client_path.as_path(), client).await?;
+
+ client_jar_path = Some(client_path);
+ } else {
+ client_jar_path = None;
+ }
+
//todo!()
Ok(())
}