use crate::error::{Result, LateJavaCoreError};
use crate::launcher::LaunchOptions;
use crate::auth::AuthResponse;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LaunchArguments {
pub game: Vec<String>,
pub jvm: Vec<String>,
pub classpath: Vec<String>,
pub main_class: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionJson {
pub id: String,
pub r#type: String,
pub asset_index: AssetIndex,
pub assets: Option<String>,
pub main_class: Option<String>,
pub minecraft_arguments: Option<String>,
pub arguments: Option<Arguments>,
pub libraries: Option<Vec<Library>>,
pub natives_list: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetIndex {
pub id: String,
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Arguments {
pub game: Option<Vec<serde_json::Value>>,
pub jvm: Option<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Library {
pub name: String,
pub loader: Option<String>,
pub natives: Option<HashMap<String, String>>,
pub rules: Option<Vec<Rule>>,
pub downloads: Option<LibraryDownloads>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Rule {
pub action: String,
pub os: Option<OsRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OsRule {
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LibraryDownloads {
pub artifact: Option<Artifact>,
pub classifiers: Option<HashMap<String, Artifact>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Artifact {
pub sha1: Option<String>,
pub size: Option<u64>,
pub path: Option<String>,
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoaderJson {
pub id: Option<String>,
pub main_class: Option<String>,
pub libraries: Option<Vec<Library>>,
pub minecraft_arguments: Option<String>,
pub is_old_forge: Option<bool>,
pub jar_path: Option<String>,
pub arguments: Option<Arguments>,
}
pub struct MinecraftArguments {
options: LaunchOptions,
authenticator: AuthResponse,
}
impl MinecraftArguments {
pub fn new(options: LaunchOptions, authenticator: AuthResponse) -> Self {
Self {
options,
authenticator,
}
}
pub async fn get_arguments(&self, version_json: &VersionJson, loader_json: Option<&LoaderJson>) -> Result<LaunchArguments> {
let game_arguments = self.get_game_arguments(version_json, loader_json).await?;
let jvm_arguments = self.get_jvm_arguments(version_json).await?;
let classpath_data = self.get_classpath(version_json, loader_json).await?;
Ok(LaunchArguments {
game: game_arguments,
jvm: jvm_arguments,
classpath: classpath_data.classpath,
main_class: classpath_data.main_class,
})
}
pub async fn get_game_arguments(&self, version_json: &VersionJson, loader_json: Option<&LoaderJson>) -> Result<Vec<String>> {
let mut game_args = if let Some(minecraft_args) = &version_json.minecraft_arguments {
minecraft_args.split(' ').map(|s| s.to_string()).collect()
} else if let Some(args) = &version_json.arguments {
args.game.as_ref()
.map(|g| g.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
.unwrap_or_default()
} else {
Vec::new()
};
if let Some(loader) = loader_json {
if let Some(loader_args) = &loader.minecraft_arguments {
let loader_game_args: Vec<String> = loader_args.split(' ')
.map(|s| s.to_string())
.collect();
game_args.extend(loader_game_args);
game_args.sort();
game_args.dedup();
}
}
let user_type = if version_json.id.starts_with("1.16") {
"Xbox"
} else {
match self.authenticator.meta.auth_type.as_str() {
"Xbox" => "msa",
_ => &self.authenticator.meta.auth_type,
}
};
let placeholder_map: HashMap<String, String> = [
("${auth_access_token}", self.authenticator.access_token.clone()),
("${auth_session}", self.authenticator.access_token.clone()),
("${auth_player_name}", self.authenticator.name.clone()),
("${auth_uuid}", self.authenticator.uuid.clone()),
("${auth_xuid}", self.authenticator.xbox_account.as_ref()
.map(|x| x.xuid.clone())
.unwrap_or_else(|| self.authenticator.access_token.clone())),
("${user_properties}", self.authenticator.user_properties.clone()),
("${user_type}", user_type.to_string()),
("${version_name}", loader_json
.and_then(|l| l.id.as_ref())
.unwrap_or(&version_json.id)
.clone()),
("${assets_index_name}", version_json.asset_index.id.clone()),
("${game_directory}", if let Some(instance) = &self.options.instance {
format!("{}/instances/{}", self.options.path, instance)
} else {
self.options.path.clone()
}),
("${assets_root}", if self.is_old(version_json) {
format!("{}/resources", self.options.path)
} else {
format!("{}/assets", self.options.path)
}),
("${game_assets}", if self.is_old(version_json) {
format!("{}/resources", self.options.path)
} else {
format!("{}/assets", self.options.path)
}),
("${version_type}", version_json.r#type.clone()),
("${clientid}", self.authenticator.client_token.clone()),
].iter().cloned().collect();
for arg in &mut game_args {
if let Some(replacement) = placeholder_map.get(arg) {
*arg = replacement.clone();
}
}
if let Some(width) = self.options.screen.width {
if let Some(height) = self.options.screen.height {
game_args.push("--width".to_string());
game_args.push(width.to_string());
game_args.push("--height".to_string());
game_args.push(height.to_string());
}
}
game_args.extend(self.options.game_args.clone());
game_args.retain(|arg| !arg.is_empty());
Ok(game_args)
}
pub async fn get_jvm_arguments(&self, version_json: &VersionJson) -> Result<Vec<String>> {
let mut jvm_args = vec![
format!("-Xms{}", self.options.memory.min),
format!("-Xmx{}", self.options.memory.max),
"-XX:+UnlockExperimentalVMOptions".to_string(),
"-XX:G1NewSizePercent=20".to_string(),
"-XX:G1ReservePercent=20".to_string(),
"-XX:MaxGCPauseMillis=50".to_string(),
"-XX:G1HeapRegionSize=32M".to_string(),
"-Dfml.ignoreInvalidMinecraftCertificates=true".to_string(),
format!("-Djna.tmpdir={}/versions/{}/natives", self.options.path, version_json.id),
format!("-Dorg.lwjgl.system.SharedLibraryExtractPath={}/versions/{}/natives", self.options.path, version_json.id),
format!("-Dio.netty.native.workdir={}/versions/{}/natives", self.options.path, version_json.id),
];
let os_specific_opts: HashMap<&str, &str> = [
("windows", "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"),
("macos", "-XstartOnFirstThread"),
("linux", "-Xss1M"),
].iter().cloned().collect();
if version_json.minecraft_arguments.is_none() {
let current_os = std::env::consts::OS;
if let Some(opt) = os_specific_opts.get(current_os) {
jvm_args.push(opt.to_string());
}
}
if self.options.bypass_offline.unwrap_or(false) {
jvm_args.push("-Dminecraft.api.auth.host=https://nope.invalid/".to_string());
jvm_args.push("-Dminecraft.api.account.host=https://nope.invalid/".to_string());
jvm_args.push("-Dminecraft.api.session.host=https://nope.invalid/".to_string());
jvm_args.push("-Dminecraft.api.services.host=https://nope.invalid/".to_string());
}
if version_json.natives_list.unwrap_or(false) {
jvm_args.push(format!("-Djava.library.path={}/versions/{}/natives", self.options.path, version_json.id));
}
if std::env::consts::OS == "macos" {
if let Some(assets) = &version_json.assets {
let assets_path = format!("{}/assets/indexes/{}.json", self.options.path, assets);
if std::path::Path::new(&assets_path).exists() {
if let Ok(assets_content) = std::fs::read_to_string(&assets_path) {
if let Ok(assets_json) = serde_json::from_str::<serde_json::Value>(&assets_content) {
if let Some(icon_hash) = assets_json["objects"]["icons/minecraft.icns"]["hash"].as_str() {
jvm_args.push("-Xdock:name=Minecraft".to_string());
jvm_args.push(format!("-Xdock:icon={}/assets/objects/{}/{}",
self.options.path,
&icon_hash[0..2],
icon_hash));
}
}
}
}
}
}
jvm_args.extend(self.options.jvm_args.clone());
Ok(jvm_args)
}
pub async fn get_classpath(&self, version_json: &VersionJson, loader_json: Option<&LoaderJson>) -> Result<ClasspathData> {
let mut combined_libraries = version_json.libraries.clone().unwrap_or_default();
if let Some(loader) = loader_json {
if let Some(loader_libs) = &loader.libraries {
combined_libraries.extend(loader_libs.clone());
}
}
let mut libraries_list = Vec::new();
for lib in &combined_libraries {
if self.should_skip_library(lib) {
continue;
}
let lib_path = self.get_library_path(lib);
if let Some(loader_path) = &lib.loader {
libraries_list.push(format!("{}/libraries/{}", loader_path, lib_path));
} else {
libraries_list.push(format!("{}/libraries/{}", self.options.path, lib_path));
}
}
if let Some(loader) = loader_json {
if loader.is_old_forge.unwrap_or(false) {
if let Some(jar_path) = &loader.jar_path {
libraries_list.push(jar_path.clone());
}
}
} else if let Some(mcp) = &self.options.mcp {
libraries_list.push(mcp.clone());
} else {
libraries_list.push(format!("{}/versions/{}/{}.jar", self.options.path, version_json.id, version_json.id));
}
libraries_list.sort();
libraries_list.dedup();
let cp_separator = if std::env::consts::OS == "windows" { ";" } else { ":" };
let cp_argument = if !libraries_list.is_empty() {
libraries_list.join(cp_separator)
} else {
String::new()
};
Ok(ClasspathData {
classpath: vec!["-cp".to_string(), cp_argument],
main_class: loader_json
.and_then(|l| l.main_class.clone())
.or_else(|| version_json.main_class.clone()),
})
}
fn is_old(&self, version_json: &VersionJson) -> bool {
version_json.assets.as_ref()
.map(|assets| assets == "legacy" || assets == "pre-1.6")
.unwrap_or(false)
}
fn should_skip_library(&self, lib: &Library) -> bool {
if let Some(rules) = &lib.rules {
let current_os = std::env::consts::OS;
let mojang_os = match current_os {
"windows" => "windows",
"macos" => "osx",
"linux" => "linux",
_ => current_os,
};
for rule in rules {
if let Some(os_rule) = &rule.os {
if let Some(os_name) = &os_rule.name {
match rule.action.as_str() {
"allow" => {
if os_name != mojang_os {
return true;
}
}
"disallow" => {
if os_name == mojang_os {
return true;
}
}
_ => {}
}
}
}
}
}
false
}
fn get_library_path(&self, lib: &Library) -> String {
let parts: Vec<&str> = lib.name.split(':').collect();
if parts.len() < 3 {
return lib.name.clone();
}
let group = parts[0].replace('.', "/");
let artifact = parts[1];
let version = parts[2];
format!("{}/{}/{}/{}-{}.jar", group, artifact, version, artifact, version)
}
}
#[derive(Debug, Clone)]
pub struct ClasspathData {
pub classpath: Vec<String>,
pub main_class: Option<String>,
}