diff --git a/core/src/implementations/minecraft/configurable.rs b/core/src/implementations/minecraft/configurable.rs index 9f27a800..4622837f 100644 --- a/core/src/implementations/minecraft/configurable.rs +++ b/core/src/implementations/minecraft/configurable.rs @@ -276,6 +276,7 @@ pub(super) enum CmdArgSetting { MinRam(u32), MaxRam(u32), JavaCmd(String), + CustomCmd(String), Args(Vec), } @@ -288,6 +289,7 @@ impl CmdArgSetting { CmdArgSetting::MinRam(_) => "min_ram", CmdArgSetting::MaxRam(_) => "max_ram", CmdArgSetting::JavaCmd(_) => "java_cmd", + CmdArgSetting::CustomCmd(_) => "custom_cmd", CmdArgSetting::Args(_) => "cmd_args", } } @@ -296,6 +298,7 @@ impl CmdArgSetting { CmdArgSetting::MinRam(_) => "Minimum RAM", CmdArgSetting::MaxRam(_) => "Maximum RAM", CmdArgSetting::JavaCmd(_) => "Java command", + CmdArgSetting::CustomCmd(_) => "Custom command", CmdArgSetting::Args(_) => "Command line arguments", } } @@ -308,6 +311,7 @@ impl CmdArgSetting { "The maximum amount of RAM to allocate to the server instance" } CmdArgSetting::JavaCmd(_) => "The command to use to run the java executable", + CmdArgSetting::CustomCmd(_) => "A custom command to use instead of the Java command", CmdArgSetting::Args(_) => "The command line arguments to pass to the server", } } @@ -320,6 +324,7 @@ impl CmdArgSetting { val.parse().context("Invalid value. Expected a u32")?, )), "java_cmd" => Ok(CmdArgSetting::JavaCmd(val.to_string())), + "custom_cmd" => Ok(CmdArgSetting::CustomCmd(val.to_string())), "cmd_args" => Ok(CmdArgSetting::Args( val.split(' ').map(|s| s.to_string()).collect(), )), @@ -330,7 +335,10 @@ impl CmdArgSetting { } } pub fn is_key_valid(key: &str) -> bool { - matches!(key, "min_ram" | "max_ram" | "java_cmd" | "cmd_args") + matches!( + key, + "min_ram" | "max_ram" | "java_cmd" | "cmd_args" | "custom_cmd" + ) } } @@ -373,6 +381,16 @@ impl From for SettingManifest { false, true, ), + CmdArgSetting::CustomCmd(ref custom_cmd) => SettingManifest::new_optional_value( + value.get_identifier().to_owned(), + value.get_name().to_owned(), + value.get_description().to_owned(), + Some(ConfigurableValue::String(custom_cmd.to_owned())), + ConfigurableValueType::String { regex: None }, + None, + false, + true, + ), CmdArgSetting::Args(ref args) => SettingManifest::new_optional_value( value.get_identifier().to_owned(), value.get_name().to_owned(), @@ -411,6 +429,13 @@ impl TryFrom for CmdArgSetting { .try_as_string()? .to_owned(), )), + "custom_cmd" => Ok(CmdArgSetting::CustomCmd( + value + .get_value() + .context("Expected a value")? + .try_as_string()? + .to_owned(), + )), "cmd_args" => Ok(CmdArgSetting::Args( value .get_value() diff --git a/core/src/implementations/minecraft/mod.rs b/core/src/implementations/minecraft/mod.rs index 72d36a52..023c97a7 100644 --- a/core/src/implementations/minecraft/mod.rs +++ b/core/src/implementations/minecraft/mod.rs @@ -159,6 +159,7 @@ pub struct RestoreConfig { pub description: String, pub cmd_args: Vec, pub java_cmd: Option, + pub custom_cmd: Option, pub port: u32, pub min_ram: u32, pub max_ram: u32, @@ -396,6 +397,9 @@ impl MinecraftInstance { cmd_args_config_map.insert(max_ram.get_identifier().to_owned(), max_ram.into()); let java_cmd = CmdArgSetting::JavaCmd(java_cmd); cmd_args_config_map.insert(java_cmd.get_identifier().to_owned(), java_cmd.into()); + let custom_cmd = + CmdArgSetting::CustomCmd(restore_config.custom_cmd.clone().unwrap_or(String::new())); + cmd_args_config_map.insert(custom_cmd.get_identifier().to_owned(), custom_cmd.into()); let cmd_line_section_manifest = SectionManifest::new( CmdArgSetting::get_section_id().to_string(), @@ -638,12 +642,34 @@ impl MinecraftInstance { 1.0, )); + // let custom_args = if let Some(custom_cmd) = config.custom_cmd.clone() { + // let mut split: Vec = custom_cmd.split(" ").map(|c| String::from(c)).collect(); + // if split.len() != 0 { + // split.remove(0); + // } + // split + // } else { + // vec![] + // }; + // + // let custom_cmd = if let Some(custom_cmd) = config.custom_cmd.clone() { + // let split: Vec = custom_cmd.split(" ").map(|c| String::from(c)).collect(); + // if split.len() == 0 { + // None + // } else { + // Some(split[0].clone()) + // } + // } else { + // None + // }; + let restore_config = RestoreConfig { name: config.name, version: config.version, flavour, description: config.description.unwrap_or_default(), cmd_args: config.cmd_args, + custom_cmd: None, port: config.port, min_ram: config.min_ram.unwrap_or(2048), max_ram: config.max_ram.unwrap_or(4096), @@ -884,6 +910,18 @@ impl MinecraftInstance { .expect("Programming error, value is not a string") .to_owned(), ); + + config_lock.custom_cmd = Some( + configurable_map + .get(CmdArgSetting::CustomCmd(Default::default()).get_identifier()) + .expect("Programming error, value is not set") + .get_value() + .expect("Programming error, value is not set") + .clone() + .try_as_string() + .expect("Programming error, value is not a string") + .to_owned(), + ); } pub fn get_rcon(&self) -> Arc>>> { diff --git a/core/src/implementations/minecraft/server.rs b/core/src/implementations/minecraft/server.rs index 5194a990..2d560059 100644 --- a/core/src/implementations/minecraft/server.rs +++ b/core/src/implementations/minecraft/server.rs @@ -21,10 +21,11 @@ use crate::traits::t_macro::TaskEntry; use crate::traits::t_server::{MonitorReport, State, StateAction, TServer}; use crate::types::Snowflake; -use crate::util::{dont_spawn_terminal, list_dir}; +use crate::util::dont_spawn_terminal; use super::r#macro::resolve_macro_invocation; -use super::{Flavour, ForgeBuildVersion, MinecraftInstance}; +use super::util::create_java_launch_cmd; +use super::MinecraftInstance; use tracing::{error, info, warn}; #[async_trait::async_trait] @@ -113,98 +114,31 @@ impl TServer for MinecraftInstance { .join("java") }; - let mut server_start_command = Command::new(&jre); - let server_start_command = server_start_command - .arg(format!("-Xmx{}M", config.max_ram)) - .arg(format!("-Xms{}M", config.min_ram)) - .args( - &config - .cmd_args - .iter() + let mut server_start_command = if config.custom_cmd.is_none() + || (config.custom_cmd.is_some() && config.custom_cmd.clone().unwrap().is_empty()) + { + create_java_launch_cmd(config.clone(), jre, self.path_to_instance.clone()) + .await + .unwrap() + } else { + let mut args: Vec = config + .custom_cmd + .unwrap() + .split(' ') + .map(String::from) + .collect(); + let mut server_start_command = Command::new(args.remove(0)); + server_start_command.args( + args.iter() .filter(|s| !s.is_empty()) .collect::>(), ); - - let server_start_command = match &config.flavour { - Flavour::Forge { build_version } => { - let ForgeBuildVersion(build_version) = build_version - .as_ref() - .ok_or_else(|| eyre!("Forge version not found"))?; - let version_parts: Vec<&str> = config.version.split('.').collect(); - let major_version: i32 = version_parts[1] - .parse() - .context("Unable to parse major Minecraft version for Forge")?; - - if 17 <= major_version { - let forge_args = match std::env::consts::OS { - "windows" => "win_args.txt", - _ => "unix_args.txt", - }; - - let mut full_forge_args = std::ffi::OsString::from("@"); - full_forge_args.push( - self.path_to_instance - .join("libraries") - .join("net") - .join("minecraftforge") - .join("forge") - .join(build_version.as_str()) - .join(forge_args) - .into_os_string() - .as_os_str(), - ); - - server_start_command.arg(full_forge_args) - } else if (7..=16).contains(&major_version) { - let files = list_dir(&self.path_to_instance, Some(false)) - .await - .context("Failed to find forge.jar")?; - let forge_jar_name = files - .iter() - .find(|p| { - p.extension().unwrap_or_default() == "jar" - && p.file_name() - .unwrap_or_default() - .to_str() - .unwrap_or_default() - .starts_with(format!("forge-{}-", config.version,).as_str()) - }) - .ok_or_else(|| eyre!("Failed to find forge.jar"))?; - server_start_command - .arg("-jar") - .arg(&self.path_to_instance.join(forge_jar_name)) - } else { - // 1.5 doesn't work due to JRE issues - // 1.4 doesn't work since forge doesn't provide an installer - let files = list_dir(&self.path_to_instance, Some(false)) - .await - .context("Failed to find minecraftforge.jar")?; - let server_jar_name = files - .iter() - .find(|p| { - p.extension().unwrap_or_default() == "jar" - && p.file_name() - .unwrap_or_default() - .to_str() - .unwrap_or_default() - .starts_with("minecraftforge") - }) - .ok_or_else(|| eyre!("Failed to find minecraftforge.jar"))?; - server_start_command - .arg("-jar") - .arg(&self.path_to_instance.join(server_jar_name)) - } - } - _ => server_start_command - .arg("-jar") - .arg(&self.path_to_instance.join("server.jar")), + server_start_command }; - let server_start_command = server_start_command - .arg("nogui") - .current_dir(&self.path_to_instance); + server_start_command.current_dir(&self.path_to_instance); - match dont_spawn_terminal(server_start_command) + match dont_spawn_terminal(&mut server_start_command) .stdout(Stdio::piped()) .stdin(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/core/src/implementations/minecraft/util.rs b/core/src/implementations/minecraft/util.rs index 052c4f0a..0f732086 100644 --- a/core/src/implementations/minecraft/util.rs +++ b/core/src/implementations/minecraft/util.rs @@ -1,13 +1,115 @@ use color_eyre::eyre::{eyre, Context, ContextCompat}; use indexmap::IndexMap; use serde_json::{self, Value}; -use std::{collections::BTreeMap, path::Path, str::FromStr}; -use tokio::io::AsyncBufReadExt; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + str::FromStr, +}; +use tokio::{io::AsyncBufReadExt, process::Command}; use super::{ FabricInstallerVersion, FabricLoaderVersion, Flavour, ForgeBuildVersion, PaperBuildVersion, + RestoreConfig, }; -use crate::error::Error; +use crate::{error::Error, util::list_dir}; + +pub async fn create_java_launch_cmd( + config: RestoreConfig, + jre: PathBuf, + path_to_instance: PathBuf, +) -> Result { + let mut server_start_command = Command::new(&jre); + server_start_command + .arg(format!("-Xmx{}M", config.max_ram)) + .arg(format!("-Xms{}M", config.min_ram)) + .args( + &config + .cmd_args + .iter() + .filter(|s| !s.is_empty()) + .collect::>(), + ); + + match &config.flavour { + Flavour::Forge { build_version } => { + let ForgeBuildVersion(build_version) = build_version + .as_ref() + .ok_or_else(|| eyre!("Forge version not found"))?; + let version_parts: Vec<&str> = config.version.split('.').collect(); + let major_version: i32 = version_parts[1] + .parse() + .context("Unable to parse major Minecraft version for Forge")?; + + if 17 <= major_version { + let forge_args = match std::env::consts::OS { + "windows" => "win_args.txt", + _ => "unix_args.txt", + }; + + let mut full_forge_args = std::ffi::OsString::from("@"); + full_forge_args.push( + path_to_instance + .join("libraries") + .join("net") + .join("minecraftforge") + .join("forge") + .join(build_version.as_str()) + .join(forge_args) + .into_os_string() + .as_os_str(), + ); + + server_start_command.arg(full_forge_args) + } else if (7..=16).contains(&major_version) { + let files = list_dir(&path_to_instance, Some(false)) + .await + .context("Failed to find forge.jar")?; + let forge_jar_name = files + .iter() + .find(|p| { + p.extension().unwrap_or_default() == "jar" + && p.file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .starts_with(format!("forge-{}-", config.version,).as_str()) + }) + .ok_or_else(|| eyre!("Failed to find forge.jar"))?; + server_start_command + .arg("-jar") + .arg(&path_to_instance.join(forge_jar_name)) + } else { + // 1.5 doesn't work due to JRE issues + // 1.4 doesn't work since forge doesn't provide an installer + let files = list_dir(&path_to_instance, Some(false)) + .await + .context("Failed to find minecraftforge.jar")?; + let server_jar_name = files + .iter() + .find(|p| { + p.extension().unwrap_or_default() == "jar" + && p.file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .starts_with("minecraftforge") + }) + .ok_or_else(|| eyre!("Failed to find minecraftforge.jar"))?; + server_start_command + .arg("-jar") + .arg(&path_to_instance.join(server_jar_name)) + } + } + _ => server_start_command + .arg("-jar") + .arg(&path_to_instance.join("server.jar")), + }; + + server_start_command.arg("nogui"); + + Ok(server_start_command) +} pub async fn read_properties_from_path( path_to_properties: &Path, diff --git a/core/src/migration/v042_to_v044.rs b/core/src/migration/v042_to_v044.rs index 6a8e7acb..67034ec4 100644 --- a/core/src/migration/v042_to_v044.rs +++ b/core/src/migration/v042_to_v044.rs @@ -25,6 +25,7 @@ impl From for RestoreConfig { jre_major_version: config.jre_major_version, has_started: config.has_started, java_cmd: None, + custom_cmd: None, } } }