diff options
| author | 2025-01-22 03:59:51 -0600 | |
|---|---|---|
| committer | 2025-01-22 03:59:51 -0600 | |
| commit | 31207679094b378a2d4b35eb310f679960d6205f (patch) | |
| tree | 2f750cf006ad1e5ff1bdaed908d7b6f621d018a9 /src | |
| parent | get started on downloading JREs (diff) | |
wip: jre download stuff
Diffstat (limited to 'src')
| -rw-r--r-- | src/launcher.rs | 38 | ||||
| -rw-r--r-- | src/launcher/constants.rs | 2 | ||||
| -rw-r--r-- | src/launcher/jre.rs | 83 | ||||
| -rw-r--r-- | src/launcher/jre/manifest.rs | 31 | ||||
| -rw-r--r-- | src/util.rs | 37 |
5 files changed, 129 insertions, 62 deletions
diff --git a/src/launcher.rs b/src/launcher.rs index 19a9fa7..d49556f 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -42,6 +42,7 @@ pub use crate::util::{EnsureFileError, FileVerifyError, IntegrityError}; use crate::assets::AssetIndex; use runner::ArgumentType; use strsub::SubFunc; +use crate::launcher::jre::{JavaRuntimeError, JavaRuntimeRepository}; use crate::version::manifest::VersionType; #[derive(Debug)] @@ -103,7 +104,8 @@ pub struct Launcher { system_info: SystemInfo, libraries: LibraryRepository, - assets: AssetRepository + assets: AssetRepository, + java_runtimes: JavaRuntimeRepository } #[derive(Debug)] @@ -137,7 +139,8 @@ pub enum LaunchError { // java runtime errors ResolveJavaRuntime { what: &'static str, error: io::Error }, - MissingJavaRuntime + MissingJavaRuntime, + JavaRuntimeRepo(JavaRuntimeError) } impl Display for LaunchError { @@ -161,7 +164,8 @@ impl Display for LaunchError { 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::MissingJavaRuntime => f.write_str("suitable java executable not found"), + LaunchError::JavaRuntimeRepo(e) => write!(f, "runtime repository error: {e}") } } } @@ -180,6 +184,7 @@ impl Error for LaunchError { LaunchError::EnsureFile(e) => Some(e), LaunchError::Assets(e) => Some(e), LaunchError::ResolveJavaRuntime { error: e, .. } => Some(e), + LaunchError::JavaRuntimeRepo(e) => Some(e), _ => None } } @@ -247,6 +252,8 @@ impl Launcher { let assets_path = home.join("assets"); + let java_runtimes = JavaRuntimeRepository::new(home.join("jre"), online).await.map_err(LaunchError::JavaRuntimeRepo)?; + Ok(Launcher { online, versions, @@ -256,6 +263,7 @@ impl Launcher { natives: home.join("natives") }, assets: AssetRepository::new(online, &assets_path).await?, + java_runtimes, home }) } @@ -288,7 +296,7 @@ impl Launcher { debug!("Logger config {} is at {}", id, path.display()); - util::ensure_file(&path, dlinfo.url.as_ref().map(|s| s.as_str()), dlinfo.size, dlinfo.sha1, self.online).await + util::ensure_file(&path, dlinfo.url.as_ref().map(|s| s.as_str()), dlinfo.size, dlinfo.sha1, self.online, false).await .map_err(|e| LaunchError::EnsureFile(e))?; struct PathSub<'a>(&'a Path); @@ -443,7 +451,7 @@ impl Launcher { info!("Downloading client jar {}", client_path.display()); - util::ensure_file(client_path.as_path(), client.url.as_ref().map(|s| s.as_str()), client.size, client.sha1, self.online).await + util::ensure_file(client_path.as_path(), client.url.as_ref().map(|s| s.as_str()), client.size, client.sha1, self.online, false).await .map_err(|e| LaunchError::EnsureFile(e))?; client_jar_path = Some(client_path); @@ -481,13 +489,29 @@ impl Launcher { trace!("Classpath: {classpath}"); info!("Resolving java runtime environment path"); - let runtime_path = fs::canonicalize(profile.get_java_runtime().expect("downloading JREs is not supported yet")).await - .map_err(|e| LaunchError::ResolveJavaRuntime {what: "resolving jre path", error: e})?; + 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)?; + dbg!(runtime); + + todo!("download it") + } + 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"); diff --git a/src/launcher/constants.rs b/src/launcher/constants.rs index ec7d6ba..d2f1b4f 100644 --- a/src/launcher/constants.rs +++ b/src/launcher/constants.rs @@ -9,7 +9,7 @@ const CRATE_NAME: &str = env!("CARGO_CRATE_NAME"); pub const USER_AGENT: &str = formatcp!("{PKG_NAME}/{PKG_VERSION} (in {CRATE_NAME})"); pub const URL_VERSION_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; pub const URL_RESOURCE_BASE: &str = "https://resources.download.minecraft.net/"; -pub const URL_JRE_MANIFEST: &str = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"; +pub const URL_JRE_MANIFEST: &str = "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"; pub const NATIVES_PREFIX: &str = "natives-"; diff --git a/src/launcher/jre.rs b/src/launcher/jre.rs index 2979c52..40fd8c1 100644 --- a/src/launcher/jre.rs +++ b/src/launcher/jre.rs @@ -1,8 +1,10 @@ use std::error::Error; -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::path::{Path, PathBuf}; use log::{debug, info, warn}; -use tokio::{fs, io}; +use tokio::{fs, io, io::ErrorKind}; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; mod arch; mod manifest; @@ -10,35 +12,66 @@ mod manifest; use arch::JRE_ARCH; use manifest::JavaRuntimesManifest; use crate::launcher::jre::manifest::JavaRuntimeManifest; +use crate::util; +use crate::util::{EnsureFileError, FileVerifyError, IntegrityError}; +use crate::version::DownloadInfo; use super::constants; pub struct JavaRuntimeRepository { + online: bool, home: PathBuf, manifest: JavaRuntimesManifest } impl JavaRuntimeRepository { - pub async fn new(home: impl AsRef<Path>) -> Result<Self, JavaRuntimeError> { + pub async fn new(home: impl AsRef<Path>, online: bool) -> Result<Self, JavaRuntimeError> { info!("Java runtime architecture is \"{}\".", JRE_ARCH); fs::create_dir_all(&home).await.map_err(|e| JavaRuntimeError::IO { what: "creating home directory", error: e })?; - let manifest: JavaRuntimesManifest = reqwest::get(constants::URL_JRE_MANIFEST).await - .map_err(|e| JavaRuntimeError::Download { - what: "runtime manifest (all.json)", - error: e - })?.json().await - .map_err(|e| JavaRuntimeError::Download { - what: "runtime manifest (all.json)", - error: e - })?; + let manifest_path = home.as_ref().join("manifest.json"); + match util::ensure_file(manifest_path.as_path(), Some(constants::URL_JRE_MANIFEST), None, None, online, true).await { + Ok(_) => (), + Err(EnsureFileError::Offline) => { + info!("Launcher is offline, cannot download runtime manifest."); + }, + Err(e) => return Err(JavaRuntimeError::EnsureFile(e)) + }; + + let manifest_file = fs::read_to_string(&manifest_path).await + .map_err(|e| JavaRuntimeError::IO { what: "reading runtimes manifest", error: e })?; Ok(JavaRuntimeRepository { + online, home: home.as_ref().to_path_buf(), - manifest + manifest: serde_json::from_str(&manifest_file).map_err(|e| JavaRuntimeError::Deserialize { what: "runtimes manifest", error: e })?, }) } + fn get_component_dir(&self, component: &str) -> PathBuf { + [self.home.as_path(), Path::new(JRE_ARCH), Path::new(component)].into_iter().collect() + } + + async fn load_runtime_manifest(&self, component: &str, info: &DownloadInfo) -> Result<JavaRuntimeManifest, JavaRuntimeError> { + let comp_dir = self.get_component_dir(component); + let manifest_path = comp_dir.join("manifest.json"); + + debug!("Ensuring manifest for runtime {JRE_ARCH}.{component}"); + + fs::create_dir_all(comp_dir.as_path()).await + .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 + .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 })?) + } + + // not very descriptive function name pub async fn choose_runtime(&self, component: &str) -> Result<JavaRuntimeManifest, JavaRuntimeError> { let Some(runtime_components) = self.manifest.get(JRE_ARCH) else { return Err(JavaRuntimeError::UnsupportedArch(JRE_ARCH)); @@ -56,36 +89,33 @@ impl JavaRuntimeRepository { return Err(JavaRuntimeError::UnsupportedComponent { arch: JRE_ARCH, component: component.to_owned() }); }; - let Some(ref url) = runtime.manifest.url else { - return Err(JavaRuntimeError::MalformedManifest); - }; - - debug!("Ensuring manifest for runtime {JRE_ARCH}.{component}: {url}"); - - - // hmm maybe - - todo!() + self.load_runtime_manifest(component, &runtime.manifest).await } } #[derive(Debug)] pub enum JavaRuntimeError { + EnsureFile(EnsureFileError), IO { what: &'static str, error: io::Error }, Download { what: &'static str, error: reqwest::Error }, + Deserialize { what: &'static str, error: serde_json::Error }, UnsupportedArch(&'static str), UnsupportedComponent { arch: &'static str, component: String }, - MalformedManifest + MalformedManifest(&'static str), + Integrity(IntegrityError) } impl Display for JavaRuntimeError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + JavaRuntimeError::EnsureFile(e) => std::fmt::Display::fmt(e, f), JavaRuntimeError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error), JavaRuntimeError::Download { what, error } => write!(f, "error downloading {}: {}", what, error), + JavaRuntimeError::Deserialize { what, error } => write!(f, "error deserializing ({what}): {error}"), JavaRuntimeError::UnsupportedArch(arch) => write!(f, r#"unsupported architecture "{arch}""#), JavaRuntimeError::UnsupportedComponent { arch, component } => write!(f, r#"unsupported component "{component}" for architecture "{arch}""#), - JavaRuntimeError::MalformedManifest => f.write_str("malformed runtime manifest (launcher bug?)"), + JavaRuntimeError::MalformedManifest(what) => write!(f, "malformed runtime manifest: {what} (launcher bug?)"), + JavaRuntimeError::Integrity(e) => std::fmt::Display::fmt(e, f) } } } @@ -93,8 +123,11 @@ impl Display for JavaRuntimeError { impl Error for JavaRuntimeError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { + JavaRuntimeError::EnsureFile(error) => Some(error), JavaRuntimeError::IO { error, .. } => Some(error), JavaRuntimeError::Download { error, .. } => Some(error), + JavaRuntimeError::Deserialize { error, .. } => Some(error), + JavaRuntimeError::Integrity(error) => Some(error), _ => None } } diff --git a/src/launcher/jre/manifest.rs b/src/launcher/jre/manifest.rs index 9b84377..ca21a2b 100644 --- a/src/launcher/jre/manifest.rs +++ b/src/launcher/jre/manifest.rs @@ -19,28 +19,29 @@ pub struct JavaRuntimeInfo { // I don't see how half of this information is useful with how the JRE system currently functions -figboot pub availability: Availability, pub manifest: DownloadInfo, - pub version: Version + //pub version: Version } pub type JavaRuntimesManifest = HashMap<String, HashMap<String, Vec<JavaRuntimeInfo>>>; -#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum FileType { - File, - Directory, - Link +#[derive(Debug, Deserialize)] +pub struct FileDownloads { + lzma: Option<DownloadInfo>, + raw: DownloadInfo } #[derive(Debug, Deserialize)] -pub struct JavaRuntimeFile { - #[serde(rename = "type")] - pub file_type: FileType, - #[serde(default)] - pub executable: bool, - - pub lzma: DownloadInfo, - pub raw: DownloadInfo +#[serde(rename_all = "lowercase", tag = "type")] +pub enum JavaRuntimeFile { + File { + #[serde(default)] + executable: bool, + downloads: FileDownloads + }, + Directory, + Link { + target: String + } } #[derive(Debug, Deserialize)] diff --git a/src/util.rs b/src/util.rs index 1184a83..b7b7c6d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -7,7 +7,6 @@ use sha1_smol::{Digest, Sha1}; use tokio::fs::File; use tokio::{fs, io}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use crate::launcher::LaunchError; #[derive(Debug)] pub enum IntegrityError { @@ -69,8 +68,15 @@ pub async fn verify_file(path: impl AsRef<Path>, expect_size: Option<usize>, exp let path = path.as_ref(); if expect_size.is_none() && expect_sha1.is_none() { - debug!("No size or sha1 for {}, have to assume it's good.", path.display()); - return Ok(()); + return match path.metadata() { + Ok(_) => { + debug!("No size or sha1 for {}, have to assume it's good.", path.display()); + Ok(()) + }, + Err(e) => { + Err(FileVerifyError::Open(path.to_path_buf(), e)) + } + } } let mut file = File::open(path).await.map_err(|e| FileVerifyError::Open(path.to_owned(), e))?; @@ -145,19 +151,21 @@ impl Error for EnsureFileError { } } -pub async fn ensure_file(path: impl AsRef<Path>, url: Option<&str>, expect_size: Option<usize>, expect_sha1: Option<Digest>, online: bool) -> Result<bool, EnsureFileError> { +pub async fn ensure_file(path: impl AsRef<Path>, url: Option<&str>, expect_size: Option<usize>, expect_sha1: Option<Digest>, online: bool, force_download: bool) -> Result<bool, EnsureFileError> { let path = path.as_ref(); - match verify_file(path, expect_size, expect_sha1).await { - Ok(_) => { - info!("File {} exists and integrity matches. Skipping.", path.display()); - return Ok(false); - }, - Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => (), - Err(FileVerifyError::Integrity(_, e)) => - info!("File {} on disk failed integrity check: {}", path.display(), e), - Err(FileVerifyError::Open(_, e)) | Err(FileVerifyError::Read(_, e)) => - return Err(EnsureFileError::IO { what: "verifying fileon disk", error: e }) + if !force_download { + match verify_file(path, expect_size, expect_sha1).await { + Ok(_) => { + info!("File {} exists and integrity matches. Skipping.", path.display()); + return Ok(false); + }, + Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => (), + Err(FileVerifyError::Integrity(_, e)) => + info!("File {} on disk failed integrity check: {}", path.display(), e), + Err(FileVerifyError::Open(_, e)) | Err(FileVerifyError::Read(_, e)) => + return Err(EnsureFileError::IO { what: "verifying fileon disk", error: e }) + } } if !online { @@ -273,6 +281,7 @@ impl AsJavaPath for Path { #[cfg(test)] mod tests { + #[allow(unused_imports)] use super::*; #[test] |
