diff options
| -rw-r--r-- | ozone-cli/src/main.rs | 6 | ||||
| -rw-r--r-- | src/launcher.rs | 58 | ||||
| -rw-r--r-- | src/launcher/runner.rs | 70 | ||||
| -rw-r--r-- | src/launcher/settings.rs | 42 |
4 files changed, 124 insertions, 52 deletions
diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs index bb72ce3..b811f9a 100644 --- a/ozone-cli/src/main.rs +++ b/ozone-cli/src/main.rs @@ -20,11 +20,15 @@ async fn main() -> Result<(), Box<dyn Error>> { let profile = settings.get_profile("default").unwrap(); - launcher.prepare_launch(profile.get_version(), settings.get_instance_for(profile)).await.map_err(|e| { + let launch = launcher.prepare_launch(profile, settings.get_instance_for(profile)).await.map_err(|e| { error!("error launching: {e}"); e })?; + + dbg!(&launch); info!("ok"); + o3launcher::launcher::run_the_game(&launch); + Ok(()) } diff --git a/src/launcher.rs b/src/launcher.rs index a21a422..f148653 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -12,7 +12,7 @@ use std::borrow::Cow; use std::cmp::min; use std::env::consts::{ARCH, OS}; use std::error::Error; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fmt::{Display, Formatter}; use std::io::ErrorKind; use std::io::ErrorKind::AlreadyExists; @@ -39,6 +39,7 @@ use assets::{AssetError, AssetRepository}; use crate::util::{self, FileVerifyError, IntegrityError}; pub use settings::*; +pub use runner::run_the_game; use crate::assets::AssetIndex; use crate::launcher::runner::ArgumentType; use crate::launcher::strsub::SubFunc; @@ -115,6 +116,7 @@ pub enum LaunchError { LoadVersion(Box<dyn Error>), ResolveVersion(VersionResolveError), IncompatibleVersion(IncompatibleError), + MissingMainClass, // library errors LibraryDirError(PathBuf, io::Error), @@ -146,6 +148,7 @@ impl Display for LaunchError { LaunchError::LoadVersion(e) => write!(f, "error loading remote version: {e}"), LaunchError::ResolveVersion(e) => write!(f, "error resolving remote version: {e}"), LaunchError::IncompatibleVersion(e) => e.fmt(f), + LaunchError::MissingMainClass => f.write_str("main class not specified"), 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 @@ -198,10 +201,24 @@ struct LaunchInfo<'l, F: FeatureMatcher> { asset_index: Option<AssetIndex> } -struct FMTrivial; -impl FeatureMatcher for FMTrivial { - fn matches(&self, _feature: &str) -> bool { - false +#[derive(Debug)] +pub struct Launch { + jvm_args: Vec<OsString>, + game_args: Vec<OsString>, + main_class: String, + instance_path: PathBuf +} + +struct ProfileFeatureMatcher<'prof> { + profile: &'prof Profile +} + +impl FeatureMatcher for ProfileFeatureMatcher<'_> { + fn matches(&self, feature: &str) -> bool { + match feature { + "has_custom_resolution" => self.profile.get_resolution().is_some(), + _ => false + } } } @@ -246,10 +263,6 @@ impl Launcher { lib.natives.as_ref().map_or(None, |n| n.get(&self.system_info.os)).map(|s| s.as_str()) } - fn get_feature_matcher(&self) -> &impl FeatureMatcher { - &FMTrivial - } - async fn ensure_file(&self, path: &Path, dlinfo: &DownloadInfo) -> Result<(), LaunchError> { // verify the file match util::verify_file(path, dlinfo.size, dlinfo.sha1).await { @@ -374,7 +387,7 @@ impl Launcher { Ok(strsub::replace_string(config.client.argument.as_str(), &PathSub(path.as_ref())).to_string()) } - pub async fn prepare_launch(&self, version_id: &ProfileVersion, instance: &Instance) -> Result<(), LaunchError> { + pub async fn prepare_launch(&self, profile: &Profile, instance: &Instance) -> Result<Launch, LaunchError> { /* tasks 2 l;aunch the gayme!!!! :3 * - java runtime * - normal process (good research, past figboot :3) @@ -399,6 +412,9 @@ impl Launcher { * - launch the game * - build argument list and whatnot also */ + let feature_matcher = ProfileFeatureMatcher { profile }; + let version_id = profile.get_version(); + let Some(version_id) = self.versions.get_profile_version_id(version_id) else { // idk how common this use case actually is warn!("Can't use latest release/snapshot profiles while offline!"); @@ -429,7 +445,7 @@ impl Launcher { }; let ver = self.versions.resolve_version(ver.as_ref()).await.map_err(|e| LaunchError::ResolveVersion(e))?; - ver.rules_apply(&self.system_info, self.get_feature_matcher()).map_err(|e| LaunchError::IncompatibleVersion(e))?; + ver.rules_apply(&self.system_info, &feature_matcher).map_err(|e| LaunchError::IncompatibleVersion(e))?; info!("Resolved launch version {}!", ver.id); @@ -438,7 +454,7 @@ impl Launcher { let mut downloads = Vec::new(); for lib in ver.libraries.values() { - if lib.rules_apply(&self.system_info, self.get_feature_matcher()).is_err() { + if lib.rules_apply(&self.system_info, &feature_matcher).is_err() { continue; } @@ -548,12 +564,12 @@ impl Launcher { info!("Deriving launch arguments"); let info = LaunchInfo { launcher: self, - feature_matcher: self.get_feature_matcher(), + feature_matcher: &feature_matcher, asset_index_name: asset_idx_name.map(|s| s.to_owned()), classpath, virtual_assets_path: game_assets, - instance_home: inst_home, + instance_home: inst_home.clone(), natives_path: natives_dir, client_jar: client_jar_path, version_id: ver.id.to_string(), @@ -561,10 +577,20 @@ impl Launcher { asset_index: asset_idx }; - let jvm_args = runner::build_arguments(&info, ver.as_ref(), ArgumentType::JVM); + let Some(ref main_class) = ver.main_class else { + return Err(LaunchError::MissingMainClass); + }; + + // yuck + let jvm_args = profile.iter_arguments().map(OsString::from).chain(runner::build_arguments(&info, ver.as_ref(), ArgumentType::JVM).drain(..)).collect(); let game_args = runner::build_arguments(&info, ver.as_ref(), ArgumentType::Game); - todo!() + Ok(Launch { + jvm_args, + game_args, + main_class: main_class.to_string(), + instance_path: inst_home + }) } } diff --git a/src/launcher/runner.rs b/src/launcher/runner.rs index 4d07f20..8c7af3d 100644 --- a/src/launcher/runner.rs +++ b/src/launcher/runner.rs @@ -1,13 +1,14 @@ use std::borrow::Cow; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::iter; -use crate::version::{CompleteVersion, FeatureMatcher}; +use std::process::Command; +use crate::version::{CompleteVersion, FeatureMatcher, OperatingSystem}; use super::rules::CompatCheck; use super::strsub::{self, SubFunc}; -use super::LaunchInfo; +use super::{Launch, LaunchInfo}; #[derive(Clone, Copy)] -struct LaunchArgSub<'a: 'l, 'l, F: FeatureMatcher>(&'a LaunchInfo<'l, F>); +struct LaunchArgSub<'a, 'l, F: FeatureMatcher>(&'a LaunchInfo<'l, F>); impl<'rep, 'l, F: FeatureMatcher> SubFunc<'rep> for LaunchArgSub<'rep, 'l, F> { fn substitute(&self, key: &str) -> Option<Cow<'rep, str>> { @@ -59,32 +60,6 @@ pub enum ArgumentType { Game } -struct OptionalIterator<I> -where - I: Iterator -{ - opt: Option<I> -} - -impl<I: Iterator> Iterator for OptionalIterator<I> { - type Item = I::Item; - - fn next(&mut self) -> Option<Self::Item> { - match self.opt { - Some(ref mut i) => i.next(), - None => None - } - } -} - -impl<II: IntoIterator> From<Option<II>> for OptionalIterator<II::IntoIter> { - fn from(opt: Option<II>) -> Self { - OptionalIterator { - opt: opt.map(IntoIterator::into_iter) - } - } -} - pub fn build_arguments<'l, F: FeatureMatcher>(launch: &LaunchInfo<'l, F>, version: &CompleteVersion, arg_type: ArgumentType) -> Vec<OsString> { let sub = LaunchArgSub(launch); let system_info = &launch.launcher.system_info; @@ -94,13 +69,31 @@ pub fn build_arguments<'l, F: FeatureMatcher>(launch: &LaunchInfo<'l, F>, versio ArgumentType::Game => args.game.as_ref() }) { arguments.iter() - .flat_map(|wa| OptionalIterator::from(wa.rules_apply(system_info, launch.feature_matcher).ok().map(|_| &wa.value))) + .filter(|wa| wa.rules_apply(system_info, launch.feature_matcher).is_ok()) + .flat_map(|wa| &wa.value) .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 => { - todo!() - } + [ + "-Djava.library.path=${natives_directory}", + "-Dminecraft.launcher.brand=${launcher_name}", + "-Dminecraft.launcher.version=${launcher_version}", + "-Dminecraft.client.jar=${primary_jar}", + "-cp", + "${classpath}" + ].into_iter() + .chain(iter::once("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump") + .take_while(|_| system_info.os == OperatingSystem::Windows)) + .chain(iter::once(["-Dos.name=Windows 10", "-Dos.version=10.0"]) + .take_while(|_| launch.feature_matcher.matches("__ozone_win10_hack")) + .flatten()) + .chain(iter::once(["-Xdock:icon=${asset=icons/minecraft.icns}", "-Xdock:name=Minecraft"]) + .take_while(|_| system_info.os == OperatingSystem::MacOS) + .flatten()) + .map(|s| OsString::from(strsub::replace_string(s, &sub).into_owned())) + .collect() + }, ArgumentType::Game => { arguments.split(' ') .chain(iter::once("--demo") @@ -116,3 +109,14 @@ pub fn build_arguments<'l, F: FeatureMatcher>(launch: &LaunchInfo<'l, F>, versio Vec::default() } } + +pub fn run_the_game(launch: &Launch) -> Result<(), Box<dyn std::error::Error>> { + Command::new("/usr/lib/jvm/java-8-openjdk-amd64/bin/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()).spawn()?.wait()?; + + Ok(()) +} diff --git a/src/launcher/settings.rs b/src/launcher/settings.rs index 5a96589..29d9220 100644 --- a/src/launcher/settings.rs +++ b/src/launcher/settings.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::error::Error; +use std::ffi::{OsStr, OsString}; use std::fmt::{Display, Formatter}; use std::io::ErrorKind; use std::path::{Path, PathBuf}; @@ -140,11 +141,28 @@ pub enum ProfileVersion { Specific(String) } +#[derive(Deserialize, Serialize, Debug, Clone, Copy)] +pub struct Resolution { + width: u32, + height: u32 +} + +impl Default for Resolution { + fn default() -> Self { + Resolution { width: 864, height: 480 } + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Profile { game_version: ProfileVersion, java_runtime: Option<String>, - instance: String // ugly that this is a string instead of reference to an Instance but whatever I'm lazy + instance: String, // ugly that this is a string instead of reference to an Instance but whatever I'm lazy + + #[serde(default)] + jvm_arguments: Vec<OsString>, + + resolution: Option<Resolution> } impl<P: AsRef<Path>> From<P> for Instance { @@ -165,12 +183,24 @@ impl Instance { } } +const DEF_JVM_ARGUMENTS: [&'static str; 7] = [ + "-Xmx2G", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=32M" +]; + impl Profile { fn new(instance_name: &str) -> Self { Self { game_version: ProfileVersion::LatestRelease, java_runtime: None, - instance: instance_name.into() + instance: instance_name.into(), + jvm_arguments: DEF_JVM_ARGUMENTS.iter().map(OsString::from).collect(), + resolution: None } } @@ -181,4 +211,12 @@ impl Profile { pub fn get_instance_name(&self) -> &str { &self.instance } + + pub fn iter_arguments(&self) -> impl Iterator<Item = &OsString> { + self.jvm_arguments.iter() + } + + pub fn get_resolution(&self) -> Option<Resolution> { + self.resolution + } } |
