summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-22 03:59:51 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-22 03:59:51 -0600
commit31207679094b378a2d4b35eb310f679960d6205f (patch)
tree2f750cf006ad1e5ff1bdaed908d7b6f621d018a9
parentget started on downloading JREs (diff)
wip: jre download stuff
-rw-r--r--src/launcher.rs38
-rw-r--r--src/launcher/constants.rs2
-rw-r--r--src/launcher/jre.rs83
-rw-r--r--src/launcher/jre/manifest.rs31
-rw-r--r--src/util.rs37
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]