use std::path::PathBuf; use clap::{Args, Parser, Subcommand}; use clap::error::ErrorKind; use uuid::Uuid; use ozone::launcher::{Instance, InstanceVersion, JavaRuntimeSetting, Resolution}; #[derive(Args, Debug)] pub struct InstanceSelectArgs { /// The name of the instance to select. #[arg(index = 1)] pub instance: String } fn parse_resolution_argument(val: &str) -> Result, clap::Error> { match val.to_ascii_lowercase().as_str() { "default" => Ok(None), s => { let Some(idx) = s.find('x') else { return Err(clap::Error::raw(ErrorKind::InvalidValue, "malformed resolution argument (expected 'x')")); }; let width: u32 = s[..idx].parse().map_err(|e| clap::Error::raw(ErrorKind::InvalidValue, e))?; let height: u32 = s[idx+1..].parse().map_err(|e| clap::Error::raw(ErrorKind::InvalidValue, e))?; if width == 0 || height == 0 { return Err(clap::Error::raw(ErrorKind::InvalidValue, "malformed resolution argument (width and height must be positive)")); } Ok(Some(Resolution { width, height })) } } } #[derive(Args, Debug)] pub struct InstanceSettingsArgs { /// Specify a version of the game which this profile should launch. #[arg(long, short = 'v', conflicts_with_all = ["latest_release", "latest_snapshot"])] pub version: Option, /// If set, this profile should always use the latest release (default). /// /// **Note**: Whenever a new version of Minecraft is released, this profile will /// automatically switch to that version. This could cause unintended upgrades of settings and /// worlds. #[arg(long, conflicts_with_all = ["version", "latest_snapshot"])] pub latest_release: bool, /// If set, this profile should always use the latest snapshot. /// /// **Note**: Whenever a new version of Minecraft is released, this profile will /// automatically switch to that version. This could cause unintended upgrades of settings and /// worlds. #[arg(long, short = 's', conflicts_with_all = ["version", "latest_release"])] pub latest_snapshot: bool, /// Specify the path to the java runtime that this profile should use to run the game. /// /// By default, the launcher will attempt to download a compatible java runtime before running the game. #[arg(long, short = 'j', conflicts_with_all = ["jre_component", "jre_default"])] pub jre_path: Option, /// Specify the java runtime component which this profile should download and use when launching /// the game. If you don't understand what this means, you probably don't need this argument. #[arg(long, conflicts_with_all = ["jre_path", "jre_default"])] pub jre_component: Option, /// If set, configures this profile to automatically determine the java runtime to download and /// use when launching the game. This is the default behavior. #[arg(long, conflicts_with_all = ["jre_path", "jre_component"])] pub jre_default: bool, /// Specify the resolution of the Minecraft game window. Note that this argument is a suggestion /// to the game process, and could be ignored or unsupported on some versions. /// /// Pass the resolution as WIDTHxHEIGHT (like `800x600'). /// /// Use the special value `default' to reset to the default value. #[arg(long, short = 'r', value_parser = parse_resolution_argument)] pub resolution: Option>, } impl InstanceSettingsArgs { pub fn apply_to(&self, inst: &mut Instance) { if let Some(ref ver) = self.version { inst.game_version = InstanceVersion::Specific(ver.clone()); } else if self.latest_release { inst.game_version = InstanceVersion::LatestRelease; } else if self.latest_snapshot { inst.game_version = InstanceVersion::LatestSnapshot; } if let Some(ref path) = self.jre_path { inst.java_runtime = Some(JavaRuntimeSetting::Path(path.clone())); } else if let Some(ref comp) = self.jre_component { inst.java_runtime = Some(JavaRuntimeSetting::Component(comp.clone())); } else if self.jre_default { inst.java_runtime = None; } if let Some(res) = self.resolution { inst.resolution = res; } } } #[derive(Args, Debug)] pub struct InstanceCreateArgs { /// The name of the new instance. Must not be an empty string. #[arg(index = 1)] pub name: String, /// If set, clones settings from the selected instance. #[arg(long, short = 'c')] pub clone: bool, /// If set, don't select the newly created instance. #[arg(long)] pub no_select: bool, #[command(flatten)] pub settings: InstanceSettingsArgs } #[derive(Subcommand, Debug)] pub enum InstanceCommand { /// Selects an instance. Select(InstanceSelectArgs), /// Creates an instance, optionally cloning the selected instance. Create(InstanceCreateArgs), /// Change the configuration of an instance. Set(InstanceSettingsArgs), /// Rename an instance. Rename { /// The new name for the instance #[arg(index = 1)] name: String }, /// Deletes the selected instance. Delete, /// Lists known instances. List, /// Shows complete information about the selected instance. Info } #[derive(Args, Debug)] pub struct InstanceArgs { #[command(subcommand)] subcmd: Option } impl InstanceArgs { pub fn command(&self) -> &InstanceCommand { self.subcmd.as_ref().unwrap_or(&InstanceCommand::List) } } #[derive(Args, Debug)] pub struct ProfileSelectArgs { #[arg(index = 1, conflicts_with_all = ["uuid", "xuid", "gamertag"])] pub name: Option, #[arg(long, conflicts_with_all = ["name", "xuid", "gamertag"])] pub uuid: Option, #[arg(long, conflicts_with_all = ["name", "uuid", "gamertag"])] pub xuid: Option, #[arg(long, short = 'g', alias = "gt", conflicts_with_all = ["name", "uuid", "xuid"])] pub gamertag: Option } #[derive(Subcommand, Debug)] pub enum AccountCommand { Select(ProfileSelectArgs), Forget, SignIn } #[derive(Args, Debug)] pub struct AccountArgs { #[command(subcommand)] pub command: AccountCommand } #[derive(Subcommand, Debug)] pub enum RootCommand { /// Manages instances. /// /// An instance in terms of this launcher is a set of settings which decides how to launch the /// game. This includes a version of the game, a set of JVM arguments, and other miscellaneous /// settings. Each instance runs the game in a separate directory. Instance(InstanceArgs), /// Manages accounts. Account(AccountArgs), /// Launches the selected instance with the selected account. Launch } #[derive(Parser, Debug)] #[clap(version)] pub struct Cli { /// Run the launcher in offline mode. The launcher will not attempt to make any requests using /// the network. The launcher WILL verify the integrity of files required to launch the game, /// and refuse to launch the game with an error if it must download a file. #[arg(long, global = true)] pub offline: bool, /// Directory which the launcher will perform its work in. Defaults to an application-specific /// directory based on your OS. #[arg(long, global = true, value_hint = clap::ValueHint::DirPath)] pub home: Option, #[command(subcommand)] pub subcmd: RootCommand }