summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-20 04:33:28 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-20 04:33:28 -0600
commitb317a8a652d2e3a48c8b9f4940c7933d3802cfc3 (patch)
treeec06208dd483d17eb7c764587c7629e4e4f0d100 /src
parentget rid of wacky closure business (diff)
it runs the game
Diffstat (limited to 'src')
-rw-r--r--src/launcher.rs58
-rw-r--r--src/launcher/runner.rs70
-rw-r--r--src/launcher/settings.rs42
3 files changed, 119 insertions, 51 deletions
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
+ }
}