//! Minecraft CLI argument logic use crate::launcher::parse_rules; use crate::profile::QuickPlayType; use crate::state::Credentials; use crate::{ state::{MemorySettings, WindowSize}, util::{io::IOError, platform::classpath_separator}, }; use daedalus::minecraft::LoggingConfiguration; use daedalus::{ get_path_from_artifact, minecraft::{Argument, ArgumentValue, Library, VersionType}, modded::SidedDataEntry, }; use dunce::canonicalize; use hashlink::LinkedHashSet; use std::io::{BufRead, BufReader}; use std::{collections::HashMap, path::Path}; use uuid::Uuid; // Replaces the space separator with a newline character, as to not split the arguments const TEMPORARY_REPLACE_CHAR: &str = "\n"; pub fn get_class_paths( libraries_path: &Path, libraries: &[Library], launcher_class_path: &[&Path], java_arch: &str, minecraft_updated: bool, ) -> crate::Result { let mut cps = libraries .iter() .filter_map(|library| { if let Some(rules) = &library.rules { if !parse_rules( rules, java_arch, &QuickPlayType::None, minecraft_updated, ) { return None; } } if !library.include_in_classpath { return None; } Some(get_lib_path(libraries_path, &library.name, false)) }) .collect::, _>>()?; for launcher_path in launcher_class_path { cps.insert( canonicalize(launcher_path) .map_err(|_| { crate::ErrorKind::LauncherError(format!( "Specified class path {} does not exist", launcher_path.to_string_lossy() )) .as_error() })? .to_string_lossy() .to_string(), ); } Ok(cps .into_iter() .collect::>() .join(classpath_separator(java_arch))) } pub fn get_class_paths_jar>( libraries_path: &Path, libraries: &[T], java_arch: &str, ) -> crate::Result { let cps = libraries .iter() .map(|library| get_lib_path(libraries_path, library.as_ref(), false)) .collect::, _>>()?; Ok(cps.join(classpath_separator(java_arch))) } pub fn get_lib_path( libraries_path: &Path, lib: &str, allow_not_exist: bool, ) -> crate::Result { let path = libraries_path .to_path_buf() .join(get_path_from_artifact(lib)?); if !path.exists() && allow_not_exist { return Ok(path.to_string_lossy().to_string()); } let path = &canonicalize(&path).map_err(|_| { crate::ErrorKind::LauncherError(format!( "Library file at path {} does not exist", path.to_string_lossy() )) .as_error() })?; Ok(path.to_string_lossy().to_string()) } #[allow(clippy::too_many_arguments)] pub fn get_jvm_arguments( arguments: Option<&[Argument]>, natives_path: &Path, libraries_path: &Path, log_configs_path: &Path, class_paths: &str, version_name: &str, memory: MemorySettings, custom_args: Vec, java_arch: &str, quick_play_type: &QuickPlayType, log_config: Option<&LoggingConfiguration>, ) -> crate::Result> { let mut parsed_arguments = Vec::new(); if let Some(args) = arguments { parse_arguments( args, &mut parsed_arguments, |arg| { parse_jvm_argument( arg.to_string(), natives_path, libraries_path, class_paths, version_name, java_arch, ) }, java_arch, quick_play_type, )?; } else { parsed_arguments.push(format!( "-Djava.library.path={}", canonicalize(natives_path) .map_err(|_| crate::ErrorKind::LauncherError(format!( "Specified natives path {} does not exist", natives_path.to_string_lossy() )) .as_error())? .to_string_lossy() )); parsed_arguments.push("-cp".to_string()); parsed_arguments.push(class_paths.to_string()); } parsed_arguments.push(format!("-Xmx{}M", memory.maximum)); if let Some(LoggingConfiguration::Log4j2Xml { argument, file }) = log_config { let full_path = log_configs_path.join(&file.id); let full_path = full_path.to_string_lossy(); parsed_arguments.push(argument.replace("${path}", &full_path)); } for arg in custom_args { if !arg.is_empty() { parsed_arguments.push(arg); } } Ok(parsed_arguments) } fn parse_jvm_argument( mut argument: String, natives_path: &Path, libraries_path: &Path, class_paths: &str, version_name: &str, java_arch: &str, ) -> crate::Result { argument.retain(|c| !c.is_whitespace()); Ok(argument .replace( "${natives_directory}", &canonicalize(natives_path) .map_err(|_| { crate::ErrorKind::LauncherError(format!( "Specified natives path {} does not exist", natives_path.to_string_lossy() )) .as_error() })? .to_string_lossy(), ) .replace( "${library_directory}", &canonicalize(libraries_path) .map_err(|_| { crate::ErrorKind::LauncherError(format!( "Specified libraries path {} does not exist", libraries_path.to_string_lossy() )) .as_error() })? .to_string_lossy(), ) .replace("${classpath_separator}", classpath_separator(java_arch)) .replace("${launcher_name}", "theseus") .replace("${launcher_version}", env!("CARGO_PKG_VERSION")) .replace("${version_name}", version_name) .replace("${classpath}", class_paths)) } #[allow(clippy::too_many_arguments)] pub async fn get_minecraft_arguments( arguments: Option<&[Argument]>, legacy_arguments: Option<&str>, credentials: &Credentials, version: &str, asset_index_name: &str, game_directory: &Path, assets_directory: &Path, version_type: &VersionType, resolution: WindowSize, java_arch: &str, quick_play_type: &QuickPlayType, ) -> crate::Result> { let access_token = credentials.access_token.clone(); let profile = credentials.maybe_online_profile().await; if let Some(arguments) = arguments { let mut parsed_arguments = Vec::new(); parse_arguments( arguments, &mut parsed_arguments, |arg| { parse_minecraft_argument( arg, &access_token, &profile.name, profile.id, version, asset_index_name, game_directory, assets_directory, version_type, resolution, quick_play_type, ) }, java_arch, quick_play_type, )?; Ok(parsed_arguments) } else if let Some(legacy_arguments) = legacy_arguments { let mut parsed_arguments = Vec::new(); for x in legacy_arguments.split(' ') { parsed_arguments.push(parse_minecraft_argument( &x.replace(' ', TEMPORARY_REPLACE_CHAR), &access_token, &profile.name, profile.id, version, asset_index_name, game_directory, assets_directory, version_type, resolution, quick_play_type, )?); } Ok(parsed_arguments) } else { Ok(Vec::new()) } } #[allow(clippy::too_many_arguments)] fn parse_minecraft_argument( argument: &str, access_token: &str, username: &str, uuid: Uuid, version: &str, asset_index_name: &str, game_directory: &Path, assets_directory: &Path, version_type: &VersionType, resolution: WindowSize, quick_play_type: &QuickPlayType, ) -> crate::Result { Ok(argument .replace("${accessToken}", access_token) .replace("${auth_access_token}", access_token) .replace("${auth_session}", access_token) .replace("${auth_player_name}", username) // TODO: add auth xuid eventually .replace("${auth_xuid}", "0") .replace("${auth_uuid}", &uuid.simple().to_string()) .replace("${uuid}", &uuid.simple().to_string()) .replace("${clientid}", "c4502edb-87c6-40cb-b595-64a280cf8906") .replace("${user_properties}", "{}") .replace("${user_type}", "msa") .replace("${version_name}", version) .replace("${assets_index_name}", asset_index_name) .replace( "${game_directory}", &canonicalize(game_directory) .map_err(|_| { crate::ErrorKind::LauncherError(format!( "Specified game directory {} does not exist", game_directory.to_string_lossy() )) .as_error() })? .to_string_lossy(), ) .replace( "${assets_root}", &canonicalize(assets_directory) .map_err(|_| { crate::ErrorKind::LauncherError(format!( "Specified assets directory {} does not exist", assets_directory.to_string_lossy() )) .as_error() })? .to_string_lossy(), ) .replace( "${game_assets}", &canonicalize(assets_directory) .map_err(|_| { crate::ErrorKind::LauncherError(format!( "Specified assets directory {} does not exist", assets_directory.to_string_lossy() )) .as_error() })? .to_string_lossy(), ) .replace("${version_type}", version_type.as_str()) .replace("${resolution_width}", &resolution.0.to_string()) .replace("${resolution_height}", &resolution.1.to_string()) .replace( "${quickPlaySingleplayer}", match quick_play_type { QuickPlayType::Singleplayer(world) => world, _ => "", }, ) .replace( "${quickPlayMultiplayer}", match quick_play_type { QuickPlayType::Server(address) => address, _ => "", }, )) } fn parse_arguments( arguments: &[Argument], parsed_arguments: &mut Vec, parse_function: F, java_arch: &str, quick_play_type: &QuickPlayType, ) -> crate::Result<()> where F: Fn(&str) -> crate::Result, { for argument in arguments { match argument { Argument::Normal(arg) => { let parsed = parse_function(&arg.replace(' ', TEMPORARY_REPLACE_CHAR))?; for arg in parsed.split(TEMPORARY_REPLACE_CHAR) { parsed_arguments.push(arg.to_string()); } } Argument::Ruled { rules, value } => { if parse_rules(rules, java_arch, quick_play_type, true) { match value { ArgumentValue::Single(arg) => { parsed_arguments.push(parse_function( &arg.replace(' ', TEMPORARY_REPLACE_CHAR), )?); } ArgumentValue::Many(args) => { for arg in args { parsed_arguments.push(parse_function( &arg.replace(' ', TEMPORARY_REPLACE_CHAR), )?); } } } } } } } Ok(()) } pub fn get_processor_arguments>( libraries_path: &Path, arguments: &[T], data: &HashMap, ) -> crate::Result> { let mut new_arguments = Vec::new(); for argument in arguments { let trimmed_arg = &argument.as_ref()[1..argument.as_ref().len() - 1]; if argument.as_ref().starts_with('{') { if let Some(entry) = data.get(trimmed_arg) { new_arguments.push(if entry.client.starts_with('[') { get_lib_path( libraries_path, &entry.client[1..entry.client.len() - 1], true, )? } else { entry.client.clone() }) } } else if argument.as_ref().starts_with('[') { new_arguments.push(get_lib_path(libraries_path, trimmed_arg, true)?) } else { new_arguments.push(argument.as_ref().to_string()) } } Ok(new_arguments) } pub async fn get_processor_main_class( path: String, ) -> crate::Result> { let main_class = tokio::task::spawn_blocking(move || { let zipfile = std::fs::File::open(&path) .map_err(|e| IOError::with_path(e, &path))?; let mut archive = zip::ZipArchive::new(zipfile).map_err(|_| { crate::ErrorKind::LauncherError(format!( "Cannot read processor at {path}" )) .as_error() })?; let file = archive.by_name("META-INF/MANIFEST.MF").map_err(|_| { crate::ErrorKind::LauncherError(format!( "Cannot read processor manifest at {path}" )) .as_error() })?; let reader = BufReader::new(file); for line in reader.lines() { let mut line = line.map_err(IOError::from)?; line.retain(|c| !c.is_whitespace()); if line.starts_with("Main-Class:") { if let Some(class) = line.split(':').nth(1) { return Ok(Some(class.to_string())); } } } Ok::, crate::Error>(None) }) .await??; Ok(main_class) }