From 601586bc1263cb0e746181f8750a443ab0d4aaf1 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Tue, 21 Jan 2025 19:10:44 -0600 Subject: support jre specified in profile --- src/launcher.rs | 33 ++++++++++++--- src/launcher/runner.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++--- src/launcher/settings.rs | 11 +++++ 3 files changed, 135 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/launcher.rs b/src/launcher.rs index 40d9742..f8be78b 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -22,7 +22,7 @@ use std::env::JoinPathsError; use std::time::{Instant, SystemTime, UNIX_EPOCH}; use const_format::formatcp; use futures::{StreamExt, TryStreamExt}; -use log::{debug, info, warn}; +use log::{debug, info, trace, warn}; use reqwest::Client; use sha1_smol::Sha1; use sysinfo::System; @@ -137,7 +137,11 @@ pub enum LaunchError { InvalidLogId(Option), // asset errors - Assets(AssetError) + Assets(AssetError), + + // java runtime errors + ResolveJavaRuntime { what: &'static str, error: io::Error }, + MissingJavaRuntime } impl Display for LaunchError { @@ -162,7 +166,9 @@ impl Display for LaunchError { 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::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") } } } @@ -181,6 +187,7 @@ impl Error for LaunchError { LaunchError::Download { error: e, .. } => Some(e), LaunchError::Integrity(e) => Some(e), LaunchError::Assets(e) => Some(e), + LaunchError::ResolveJavaRuntime { error: e, .. } => Some(e), _ => None } } @@ -206,7 +213,9 @@ pub struct Launch { jvm_args: Vec, game_args: Vec, main_class: String, - instance_path: PathBuf + instance_path: PathBuf, + runtime_path: PathBuf, + runtime_legacy_launch: bool } struct ProfileFeatureMatcher<'prof> { @@ -560,7 +569,17 @@ impl Launcher { os.to_string_lossy().to_string() }); - info!("Classpath: {classpath}"); + 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 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 { @@ -593,7 +612,9 @@ impl Launcher { jvm_args, game_args, main_class: main_class.to_string(), - instance_path: inst_home + instance_path: inst_home, + runtime_path: runtime_exe_path, + runtime_legacy_launch: profile.is_legacy_launch() }) } } diff --git a/src/launcher/runner.rs b/src/launcher/runner.rs index 41b3ed1..50a9ff8 100644 --- a/src/launcher/runner.rs +++ b/src/launcher/runner.rs @@ -1,7 +1,10 @@ use std::borrow::Cow; use std::ffi::{OsStr, OsString}; use std::iter; +use std::path::{Path, PathBuf}; use std::process::Command; +use log::{debug, warn}; +use tokio::{fs, io}; use crate::util::AsJavaPath; use crate::version::{CompleteVersion, FeatureMatcher, OperatingSystem}; use super::rules::CompatCheck; @@ -113,12 +116,100 @@ pub fn build_arguments<'l, F: FeatureMatcher>(launch: &LaunchInfo<'l, F>, versio } pub fn run_the_game(launch: &Launch) -> Result<(), Box> { - Command::new("java") - .args(launch.jvm_args.iter() - .map(|o| o.as_os_str()) - .chain(iter::once(OsStr::new(launch.main_class.as_str()))) - .chain(launch.game_args.iter().map(|o| o.as_os_str()))) - .current_dir(launch.instance_path.as_path().as_java_path()).spawn()?.wait()?; + if launch.runtime_legacy_launch { + Command::new(launch.runtime_path.as_path().as_java_path()) + .args(launch.jvm_args.iter() + .map(|o| o.as_os_str()) + .chain(iter::once(OsStr::new(launch.main_class.as_str()))) + .chain(launch.game_args.iter().map(|o| o.as_os_str()))) + .current_dir(launch.instance_path.as_path().as_java_path()).spawn()?.wait()?; + } else { + todo!("jni launch not supported :(") + } Ok(()) } + +#[allow(dead_code)] +mod windows { + pub const JNI_SEARCH_PATH: Option<&str> = Some("server/jvm.dll"); + pub const JAVA_SEARCH_PATH: Option<&str> = Some("bin/java.exe"); + pub const JRE_PLATFORM_KNOWN: bool = true; +} + +#[allow(dead_code)] +mod linux { + pub const JNI_SEARCH_PATH: Option<&str> = Some("server/libjvm.so"); + pub const JAVA_SEARCH_PATH: Option<&str> = Some("bin/java"); + pub const JRE_PLATFORM_KNOWN: bool = true; +} + +#[allow(dead_code)] +mod macos { + pub const JNI_SEARCH_PATH: Option<&str> = Some("server/libjvm.dylib"); + pub const JAVA_SEARCH_PATH: Option<&str> = Some("bin/java"); + pub const JRE_PLATFORM_KNOWN: bool = true; +} + +#[allow(dead_code)] +mod unknown { + pub const JNI_SEARCH_PATH: Option<&str> = None; + pub const JAVA_SEARCH_PATH: Option<&str> = None; + pub const JRE_PLATFORM_KNOWN: bool = false; +} + +#[cfg(target_os = "windows")] +use self::windows::*; +#[cfg(target_os = "linux")] +use self::linux::*; +#[cfg(target_os = "macos")] +use self::macos::*; +#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] +use self::unknown::*; + +fn search_java_sync(base: impl AsRef, legacy: bool) -> Result, io::Error> { + assert!(JRE_PLATFORM_KNOWN); + let search_path = Path::new(match legacy { + true => JAVA_SEARCH_PATH, + _ => JNI_SEARCH_PATH + }.unwrap()); + + let walker = walkdir::WalkDir::new(base.as_ref()).into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_dir()); + + for entry in walker { + let check_path = [base.as_ref(), entry.path(), Path::new(search_path)].into_iter().collect::(); + match std::fs::metadata(check_path.as_path()) { + Err(e) if e.kind() == io::ErrorKind::NotFound => (), + Err(e) => return Err(e), + Ok(meta) if meta.is_file() => return Ok(Some(check_path)), + _ => () + } + } + + Ok(None) // not found (sadface) +} + +pub async fn find_java(base: impl AsRef, legacy: bool) -> Result, io::Error> { + let meta = fs::metadata(&base).await?; + if meta.is_dir() { // do search + if !JRE_PLATFORM_KNOWN { + warn!("Unknown platform! Cannot search for java executable in {}. Please specify the executable file manually.", base.as_ref().display()); + return Ok(None); + } + + let (tx, rx) = tokio::sync::oneshot::channel(); + let base = base.as_ref().to_path_buf(); // idc + + tokio::task::spawn_blocking(move || { + let res = search_java_sync(base, legacy); + let _ = tx.send(res); // I really don't care if the reader hung up + }).await.expect("jre search panicked"); + + rx.await.expect("jre search didn't send us a result") + } else { // we are pointed directly at a file. assume it's what we want + debug!("JRE path {} is a file ({}). Assuming it's what we want.", base.as_ref().display(), legacy); + Ok(Some(base.as_ref().to_path_buf())) + } +} diff --git a/src/launcher/settings.rs b/src/launcher/settings.rs index e65b8b3..5c8cb27 100644 --- a/src/launcher/settings.rs +++ b/src/launcher/settings.rs @@ -161,6 +161,8 @@ pub struct Profile { #[serde(default)] jvm_arguments: Vec, + #[serde(default)] + legacy_launch: bool, resolution: Option } @@ -200,6 +202,7 @@ impl Profile { java_runtime: None, instance: instance_name.into(), jvm_arguments: DEF_JVM_ARGUMENTS.iter().map(|s| String::from(*s)).collect(), + legacy_launch: false, resolution: None } } @@ -219,4 +222,12 @@ impl Profile { pub fn get_resolution(&self) -> Option { self.resolution } + + pub fn get_java_runtime(&self) -> Option<&String> { + self.java_runtime.as_ref() + } + + pub fn is_legacy_launch(&self) -> bool { + self.legacy_launch + } } -- cgit v1.2.3-70-g09d2