mod cli; use std::error::Error; use std::path::Path; use std::process::ExitCode; use log::{error, info, trace, LevelFilter}; use clap::Parser; use ozone::launcher::{Instance, JavaRuntimeSetting, Launcher, Settings}; use ozone::launcher::version::{VersionList, VersionResult}; use uuid::Uuid; use ozone::auth::{Account, AuthenticationDatabase}; use crate::cli::{Cli, InstanceCommand, RootCommand}; fn find_account<'a>(auth: &'a AuthenticationDatabase, input: &str) -> Vec<&'a Account> { if let Ok(uuid) = input.parse::() { todo!() } let input = input.to_ascii_lowercase(); auth.users.iter().filter(|a| match *a { Account::Dummy(profile) => profile.name.to_ascii_lowercase().starts_with(&input), Account::MSA(account) => account.player_profile.as_ref().is_some_and(|p| p.name.to_ascii_lowercase().starts_with(&input)) || account.gamertag.as_ref().is_some_and(|p| p.to_ascii_lowercase().starts_with(&input)) }).collect() } fn display_instance(instance: &Instance, id: Uuid, home: impl AsRef, selected: bool, verbose: bool) { println!("Instance `{}':{}", instance.name, if selected { " (selected)" } else { "" }); println!(" UUID: {}", id); println!(" Version: {}", instance.game_version); println!(" Location: {}", home.as_ref().join(Settings::get_instance_path(id)).display()); if !verbose { return; } if let Some(ref args) = instance.jvm_arguments { println!(" JVM arguments: <{} argument{}>", args.len(), if args.len() == 1 { "" } else { "s" }) } if let Some(res) = instance.resolution { println!(" Resolution: {}x{}", res.width, res.height); } match &instance.java_runtime { Some(JavaRuntimeSetting::Component(c)) => println!(" Java runtime component: {c}"), Some(JavaRuntimeSetting::Path(p)) => println!(" Java runtime path: {}", p.display()), _ => () } } async fn main_inner(cli: Cli) -> Result> { let Some(home) = cli.home.or_else(Launcher::sensible_home) else { error!("Could not choose a launcher home directory. Please choose one with `--home'."); return Ok(ExitCode::FAILURE); // we print our own error message }; trace!("Sensible home could be {home:?}"); let mut settings = Settings::load(home.join("ozone.json")).await?; match &cli.subcmd { RootCommand::Instance(p) => match p.command() { InstanceCommand::List => { let mut first = true; if settings.instances.is_empty() { eprintln!("There are no instances. Create one with `profile create '."); return Ok(ExitCode::SUCCESS); } for (cur_id, instance) in settings.instances.iter() { if !first { println!(); } first = false; let cur_id = *cur_id; let sel = settings.selected_instance.is_some_and(|id| id == cur_id); display_instance(instance, cur_id, &home, sel, false); } }, InstanceCommand::Create(args) => { if args.name.is_empty() { eprintln!("The instance must not have an empty name."); return Ok(ExitCode::FAILURE); } let mut inst = if args.clone { if let Some(selected_inst) = settings.get_selected_instance() { let mut inst = selected_inst.clone(); inst.name.replace_range(.., &args.name); inst } else { eprintln!("You do not have an instance selected."); return Ok(ExitCode::FAILURE); } } else { Instance::new(&args.name) }; if let Some(ref ver_name) = args.settings.version { // FIXME: don't hardcode "versions" path let versions = VersionList::new(home.join("versions"), !cli.offline).await?; if matches!(versions.get_version_lazy(ver_name), VersionResult::None) { eprintln!("The version `{}' could not be found.", ver_name); return Ok(ExitCode::FAILURE); } } args.settings.apply_to(&mut inst); let new_id = Uuid::new_v4(); settings.instances.insert(new_id, inst); if !args.no_select { settings.selected_instance = Some(new_id); } settings.save().await?; }, InstanceCommand::Select(args) => { if let Ok(uuid) = args.instance.parse::() { if !settings.instances.contains_key(&uuid) { eprintln!("No instances were found by that UUID."); return Ok(ExitCode::FAILURE); } settings.selected_instance = Some(uuid); settings.save().await?; return Ok(ExitCode::SUCCESS); } let search_norm = args.instance.to_lowercase(); let found: Vec<_> = settings.instances.iter() .filter(|(_, inst)| { // FIXME: find a better way of doing this matching inst.name.to_lowercase().starts_with(&search_norm) }).collect(); if found.is_empty() { eprintln!("No instances were found."); return Ok(ExitCode::FAILURE); } if found.len() > 1 { eprintln!("Ambiguous argument. Found {} instances:", found.len()); for (id, inst) in found { eprintln!("- {} ({id})", inst.name); } return Ok(ExitCode::FAILURE); } let (found_id, found_inst) = found.first().unwrap(); println!("Selected instance {} ({found_id}).", found_inst.name); settings.selected_instance = Some(**found_id); settings.save().await?; }, InstanceCommand::Set(args) => { let Some(inst) = settings.get_selected_instance_mut() else { eprintln!("No instance selected."); return Ok(ExitCode::FAILURE); }; args.apply_to(inst); settings.save().await?; }, InstanceCommand::Delete => { let Some(inst) = settings.selected_instance else { eprintln!("No instance selected."); return Ok(ExitCode::FAILURE); }; settings.instances.remove(&inst); settings.selected_instance = None; settings.save().await?; }, InstanceCommand::Info => { let Some(inst) = settings.get_selected_instance() else { eprintln!("No instance selected."); return Ok(ExitCode::FAILURE); }; display_instance(inst, settings.selected_instance.unwrap(), &home, false, true); }, InstanceCommand::Rename { name } => { if name.is_empty() { eprintln!("The instance must not have an empty name."); return Ok(ExitCode::FAILURE); } let Some(inst) = settings.get_selected_instance_mut() else { eprintln!("No instance selected."); return Ok(ExitCode::FAILURE); }; inst.name.replace_range(.., name); settings.save().await?; } }, RootCommand::Launch => { let Some(selection) = settings.selected_instance else { eprintln!("No instance selected."); return Ok(ExitCode::FAILURE); }; let inst = settings.instances.get(&selection).expect("settings inconsistency"); settings.save().await?; let launcher = Launcher::new(&home, !cli.offline).await?; let launch = launcher.prepare_launch(inst, Settings::get_instance_path(selection), settings.client_id).await.map_err(|e| { error!("error launching: {e}"); e })?; dbg!(&launch); info!("ok"); ozone::launcher::run_the_game(&launch)?; } _ => todo!() } Ok(ExitCode::SUCCESS) } #[tokio::main] async fn main() -> ExitCode { // use Warn as the default level to minimize noise on the command line simple_logger::SimpleLogger::new().with_level(LevelFilter::Warn).env().init().unwrap(); let arg = Cli::parse(); main_inner(arg).await.unwrap_or_else(|e| { error!("Launcher initialization error:"); error!("{e}"); ExitCode::FAILURE }) }