use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::Path;
use std::process::{Child, Command};
use anyhow::Context;
use mcvm_auth::mc::AccessToken;
use mcvm_shared::output::{MCVMOutput, MessageContents, MessageLevel};
use mcvm_shared::translate;
use crate::instance::InstanceKind;
use crate::util::versions::VersionName;
use crate::WrapperCommand;
use super::LaunchConfiguration;
pub(crate) fn launch_game_process(
mut params: LaunchGameProcessParameters<'_>,
o: &mut impl MCVMOutput,
) -> anyhow::Result<std::process::Child> {
let previous_game_args = params.props.game_args.clone();
params.props.game_args = params.launch_config.generate_game_args(
params.version,
params.version_list,
params.side.get_side(),
o,
);
params.props.game_args.extend(previous_game_args);
let proc_params = LaunchProcessParameters {
command: params.command,
cwd: params.cwd,
main_class: params.main_class,
props: params.props,
launch_config: params.launch_config,
};
o.display(
MessageContents::Success(translate!(o, Launch)),
MessageLevel::Important,
);
let mut cmd = get_process_launch_command(proc_params)
.context("Failed to create process launch command")?;
output_launch_command(&cmd, params.user_access_token, params.censor_secrets, o)?;
let child = cmd.spawn().context("Failed to spawn child process")?;
Ok(child)
}
pub fn launch_process(params: LaunchProcessParameters<'_>) -> anyhow::Result<Child> {
let mut cmd =
get_process_launch_command(params).context("Failed to create process launch command")?;
cmd.spawn().context("Failed to spawn child process")
}
pub fn get_process_launch_command(params: LaunchProcessParameters<'_>) -> anyhow::Result<Command> {
let mut cmd = create_wrapped_command(params.command, ¶ms.launch_config.wrappers);
cmd.current_dir(params.cwd);
cmd.envs(params.launch_config.env.clone());
cmd.envs(params.props.additional_env_vars);
cmd.args(params.launch_config.generate_jvm_args());
cmd.args(params.props.jvm_args);
if let Some(main_class) = params.main_class {
cmd.arg(main_class);
}
cmd.args(params.props.game_args);
Ok(cmd)
}
fn output_launch_command(
command: &Command,
access_token: Option<&AccessToken>,
censor_secrets: bool,
o: &mut impl MCVMOutput,
) -> anyhow::Result<()> {
o.end_process();
let access_token = if censor_secrets { access_token } else { None };
o.display(
MessageContents::Property(
"Launch command".into(),
Box::new(MessageContents::Simple(
command.get_program().to_string_lossy().into(),
)),
),
MessageLevel::Debug,
);
o.display(
MessageContents::Header("Launch command arguments".into()),
MessageLevel::Debug,
);
const CENSOR_STR: &str = "***";
for arg in command.get_args() {
let mut arg = arg.to_string_lossy().to_string();
if let Some(access_token) = &access_token {
arg = arg.replace(&access_token.0, CENSOR_STR);
}
o.display(
MessageContents::ListItem(Box::new(MessageContents::Simple(arg))),
MessageLevel::Debug,
);
}
o.display(
MessageContents::Header("Launch command environment".into()),
MessageLevel::Debug,
);
for (env, val) in command.get_envs() {
let Some(val) = val else { continue };
let env = env.to_string_lossy().to_string();
let val = val.to_string_lossy().to_string();
o.display(
MessageContents::ListItem(Box::new(MessageContents::Property(
env,
Box::new(MessageContents::Simple(val)),
))),
MessageLevel::Debug,
);
}
if let Some(dir) = command.get_current_dir() {
o.display(
MessageContents::Property(
"Launch command directory".into(),
Box::new(MessageContents::Simple(dir.to_string_lossy().into())),
),
MessageLevel::Debug,
);
}
Ok(())
}
fn create_wrapped_command(command: &OsStr, wrappers: &[WrapperCommand]) -> Command {
let mut cmd = Command::new(command);
for wrapper in wrappers {
cmd = wrap_single(cmd, wrapper);
}
cmd
}
fn wrap_single(command: Command, wrapper: &WrapperCommand) -> Command {
let mut new_cmd = Command::new(&wrapper.cmd);
new_cmd.args(&wrapper.args);
new_cmd.arg(command.get_program());
new_cmd.args(command.get_args());
new_cmd
}
pub(crate) struct LaunchGameProcessParameters<'a> {
pub command: &'a OsStr,
pub cwd: &'a Path,
pub main_class: Option<&'a str>,
pub props: LaunchProcessProperties,
pub launch_config: &'a LaunchConfiguration,
pub version: &'a VersionName,
pub version_list: &'a [String],
pub side: &'a InstanceKind,
pub user_access_token: Option<&'a AccessToken>,
pub censor_secrets: bool,
}
pub struct LaunchProcessParameters<'a> {
pub command: &'a OsStr,
pub cwd: &'a Path,
pub main_class: Option<&'a str>,
pub props: LaunchProcessProperties,
pub launch_config: &'a LaunchConfiguration,
}
#[derive(Default)]
pub struct LaunchProcessProperties {
pub jvm_args: Vec<String>,
pub game_args: Vec<String>,
pub additional_env_vars: HashMap<String, String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wrappers() {
let wrappers = vec![
WrapperCommand {
cmd: "hello".into(),
args: Vec::new(),
},
WrapperCommand {
cmd: "world".into(),
args: vec!["foo".into(), "bar".into()],
},
];
let cmd = create_wrapped_command(OsStr::new("run"), &wrappers);
dbg!(&cmd);
assert_eq!(cmd.get_program(), OsStr::new("world"));
let mut args = cmd.get_args();
assert_eq!(args.next(), Some(OsStr::new("foo")));
assert_eq!(args.next(), Some(OsStr::new("bar")));
assert_eq!(args.next(), Some(OsStr::new("hello")));
assert_eq!(args.next(), Some(OsStr::new("run")));
}
}