mod client;
mod configuration;
mod process;
mod server;
use std::fs::File;
use std::io::Write;
use std::ops::DerefMut;
use std::path::{Path, PathBuf};
use anyhow::{Context, bail};
use nitro_shared::java_args::MemoryArg;
use nitro_shared::output::{MessageContents, NitroOutput};
use nitro_shared::versions::VersionName;
use nitro_shared::{Side, translate};
use self::client::create_quick_play_args;
use self::process::{LaunchGameProcessParameters, launch_game_process};
use crate::account::AccountManager;
use crate::account::auth::check_game_ownership;
use crate::config::BrandingProperties;
use crate::instance::InstanceKind;
use crate::io::files::paths::Paths;
use crate::io::java::classpath::Classpath;
use crate::io::java::install::JavaInstallation;
use crate::net::game_files::client_meta::ClientMeta;
use crate::net::game_files::version_manifest::VersionManifestAndList;
pub use self::configuration::{
LaunchConfigBuilder, LaunchConfiguration, QuickPlayType, WrapperCommand,
};
pub use self::process::get_stdio_file_path;
pub use self::process::launch_process;
pub use self::process::{LaunchProcessParameters, LaunchProcessProperties};
pub(crate) async fn launch(
params: LaunchParameters<'_>,
o: &mut impl NitroOutput,
) -> anyhow::Result<InstanceHandle> {
let command = params.java.get_jvm_path();
if let InstanceKind::Client { .. } = ¶ms.side {
let mut process = o.get_process();
let message = translate!(process, StartAuthenticating);
process.display(MessageContents::StartProcess(message));
params
.accounts
.authenticate(
params.offline_auth,
params.paths,
params.req_client,
process.deref_mut(),
)
.await
.context("Failed to ensure authentication")?;
let owns_game =
check_game_ownership(params.paths).context("Failed to check for game ownership")?;
if !owns_game {
bail!(
"Could not prove game ownership. If using an alternative auth system, like from a plugin, you must login with a Microsoft account that owns Minecraft first."
);
}
let message = translate!(process, FinishAuthenticating);
process.display(MessageContents::Success(message));
}
let props = match params.side.get_side() {
Side::Client => self::client::get_launch_props(¶ms).await,
Side::Server => self::server::get_launch_props(¶ms),
}
.context("Failed to generate side-specific launch properties")?;
let account_access_token = params
.accounts
.get_chosen_account()
.and_then(|x| x.get_access_token());
let proc_params = LaunchGameProcessParameters {
command: command.as_os_str(),
cwd: params.launch_dir,
main_class: Some(params.main_class),
classpath: params.classpath.clone(),
paths: params.paths,
props,
launch_config: params.launch_config,
version: params.version,
version_list: ¶ms.version_manifest.list,
side: params.side,
account_access_token,
censor_secrets: params.censor_secrets,
pipe_stdin: params.pipe_stdin,
};
launch_game_process(proc_params, o).context("Failed to launch game process")
}
pub(crate) struct LaunchParameters<'a> {
pub version: &'a VersionName,
pub version_manifest: &'a VersionManifestAndList,
pub side: &'a InstanceKind,
pub launch_dir: &'a Path,
pub java: &'a JavaInstallation,
pub classpath: &'a Classpath,
pub jar_path: &'a Path,
pub main_class: &'a str,
pub launch_config: &'a LaunchConfiguration,
pub paths: &'a Paths,
pub req_client: &'a reqwest::Client,
pub client_meta: &'a ClientMeta,
pub accounts: &'a mut AccountManager,
pub offline_auth: bool,
pub censor_secrets: bool,
pub branding: &'a BrandingProperties,
pub pipe_stdin: bool,
}
impl LaunchConfiguration {
pub fn generate_jvm_args(&self) -> Vec<String> {
let mut out = self.jvm_args.clone();
if let Some(n) = &self.min_mem {
out.push(MemoryArg::Min.to_string(n));
}
if let Some(n) = &self.max_mem {
out.push(MemoryArg::Max.to_string(n));
}
out
}
pub fn generate_game_args(
&self,
version: &str,
version_list: &[String],
side: Side,
o: &mut impl NitroOutput,
) -> Vec<String> {
let mut out = self.game_args.clone();
out.extend(self.generate_additional_game_args(version, version_list, side, o));
out
}
pub fn generate_additional_game_args(
&self,
version: &str,
version_list: &[String],
side: Side,
o: &mut impl NitroOutput,
) -> Vec<String> {
let mut out = Vec::new();
if let Side::Client = side {
out.extend(create_quick_play_args(
&self.quick_play,
version,
version_list,
o,
));
}
out
}
}
#[derive(Debug)]
pub struct InstanceHandle {
process: std::process::Child,
stdout: File,
stdout_path: PathBuf,
stdin: Option<File>,
stdin_path: Option<PathBuf>,
classpath: Classpath,
}
impl InstanceHandle {
fn new(
process: std::process::Child,
stdout: File,
stdout_path: PathBuf,
stdin: Option<File>,
stdin_path: Option<PathBuf>,
classpath: Classpath,
) -> Self {
Self {
process,
stdout,
stdout_path,
stdin,
stdin_path,
classpath,
}
}
pub fn wait(&mut self) -> std::io::Result<std::process::ExitStatus> {
self.process.wait()
}
pub fn kill(&mut self) -> std::io::Result<()> {
self.process.kill()
}
pub fn try_wait(&mut self) -> std::io::Result<Option<std::process::ExitStatus>> {
self.process.try_wait()
}
pub fn get_process(self) -> std::process::Child {
self.process
}
pub fn get_pid(&self) -> u32 {
self.process.id()
}
pub fn stdout(&mut self) -> &mut File {
&mut self.stdout
}
pub fn stdout_path(&self) -> &Path {
&self.stdout_path
}
pub fn stdin(&mut self) -> Option<&mut File> {
self.stdin.as_mut()
}
pub fn stdin_path(&self) -> Option<&Path> {
self.stdin_path.as_deref()
}
pub fn write_stdin(&mut self, data: &[u8]) -> std::io::Result<()> {
if let Some(stdin) = &mut self.stdin {
stdin.write_all(data)
} else {
Ok(())
}
}
pub fn classpath(&self) -> &Classpath {
&self.classpath
}
}