summaryrefslogtreecommitdiffstats
path: root/ozone-cli/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ozone-cli/src/main.rs')
-rw-r--r--ozone-cli/src/main.rs192
1 files changed, 182 insertions, 10 deletions
diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs
index 62f7155..f033a19 100644
--- a/ozone-cli/src/main.rs
+++ b/ozone-cli/src/main.rs
@@ -5,22 +5,23 @@ 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::{Instance, JavaRuntimeSetting, Launcher, Settings, ALT_CLIENT_ID, MAIN_CLIENT_ID};
use ozone::launcher::version::{VersionList, VersionResult};
use uuid::Uuid;
-use ozone::auth::{Account, AuthenticationDatabase};
+use ozone::auth::{Account, AccountStorage, MsaAccount};
+use ozone::auth::error::AuthErrorKind;
use crate::cli::{AccountCommand, Cli, InstanceCommand, ProfileSelectArgs, RootCommand};
-fn find_account<'a>(auth: &'a AuthenticationDatabase, input: &ProfileSelectArgs) -> Result<Vec<&'a Account>, ()> {
+fn find_account<'a>(auth: &'a AccountStorage, input: &ProfileSelectArgs) -> Result<Vec<(&'a String, &'a Account)>, ()> {
if let Some(uuid) = input.uuid {
- Ok(auth.users.iter().filter(|a| match *a {
+ Ok(auth.iter_accounts().filter(|(_, a)| match *a {
Account::Dummy(p) => p.id == uuid,
Account::MSA(account) => account.player_profile.as_ref().is_some_and(|p| p.id == uuid)
}).collect())
} else if let Some(ref name) = input.name {
let name = name.to_ascii_lowercase();
- Ok(auth.users.iter().filter(|a| match *a {
+ Ok(auth.iter_accounts().filter(|(_, a)| match *a {
Account::Dummy(profile) => profile.name.to_ascii_lowercase().starts_with(&name),
Account::MSA(account) =>
account.player_profile.as_ref().is_some_and(|p| p.name.to_ascii_lowercase().starts_with(&name))
@@ -28,12 +29,12 @@ fn find_account<'a>(auth: &'a AuthenticationDatabase, input: &ProfileSelectArgs)
} else if let Some(ref gt) = input.gamertag {
let gt = gt.to_ascii_lowercase();
- Ok(auth.users.iter().filter(|a| match *a {
+ Ok(auth.iter_accounts().filter(|(_, a)| match *a {
Account::MSA(account) => account.gamertag.as_ref().is_some_and(|g| g.to_ascii_lowercase().starts_with(&gt)),
_ => false
}).collect())
} else if let Some(ref xuid) = input.xuid {
- Ok(auth.users.iter().filter(|a| match *a {
+ Ok(auth.iter_accounts().filter(|(_, a)| match *a {
Account::MSA(account) => account.xuid.as_ref().is_some_and(|x| x == xuid),
_ => false
}).collect())
@@ -66,6 +67,45 @@ fn display_instance(instance: &Instance, id: Uuid, home: impl AsRef<Path>, selec
}
}
+fn display_account(account: &Account, selected: bool, verbose: bool) {
+ let selected = if selected { " (selected)" } else { "" };
+
+ match *account {
+ Account::Dummy(ref profile) => {
+ println!("Dummy account:{selected}");
+ println!(" Username: {}", profile.name);
+ println!(" UUID: {}", profile.id);
+ if verbose {
+ println!(" Properties: <{} propert{}>", profile.properties.len(), if profile.properties.len() == 1 { "y" } else { "ies" });
+ }
+ },
+ Account::MSA(ref msa_acct) => {
+ println!("Microsoft account:{selected}");
+
+ if let Some(ref profile) = msa_acct.player_profile {
+ println!(" Username: {}", profile.name);
+ println!(" UUID: {}", profile.id);
+
+ if verbose {
+ println!(" Properties: <{} propert{}>", profile.properties.len(), if profile.properties.len() == 1 { "y" } else { "ies" });
+ }
+ } else {
+ println!(" Username: <no profile>");
+ println!(" UUID: <no profile>");
+
+ if verbose {
+ println!(" Properties: <no profile>");
+ }
+ }
+
+ println!(" Xbox Gamertag: {}", msa_acct.gamertag.as_deref().unwrap_or("<unknown>"));
+ if verbose {
+ println!(" XUID: {}", msa_acct.xuid.as_deref().unwrap_or("<unknown>"));
+ }
+ }
+ }
+}
+
async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> {
let Some(home) = cli.home.or_else(Launcher::sensible_home) else {
error!("Could not choose a launcher home directory. Please choose one with `--home'.");
@@ -81,7 +121,7 @@ async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> {
let mut first = true;
if settings.instances.is_empty() {
- eprintln!("There are no instances. Create one with `profile create <name>'.");
+ eprintln!("There are no instances. Create one with `instance create <name>'.");
return Ok(ExitCode::SUCCESS);
}
@@ -220,17 +260,149 @@ async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> {
}
},
RootCommand::Account(account_args) => {
- // TODO: load auth db and do stuff with it
+ let accounts_path = home.join("ozone_accounts.json");
+ let mut accounts = AccountStorage::load(&accounts_path).await?;
match &account_args.command {
AccountCommand::Select(args) => {
+ let Ok(results) = find_account(&accounts, args) else {
+ return Ok(ExitCode::FAILURE);
+ };
+
+ if results.is_empty() {
+ eprintln!("No account was found.");
+ return Ok(ExitCode::FAILURE);
+ }
+
+ if results.len() > 1 {
+ eprintln!("Ambiguous argument. Found {} accounts:", results.len());
+ for (_, account) in results {
+ eprintln!("- {account}");
+ }
+ return Ok(ExitCode::FAILURE);
+ }
+
+ let (key, account) = results.into_iter().next().unwrap();
+ println!("Selected account: {account}");
+ let key = key.clone();
+ accounts.set_selected_account(key);
+ accounts.save(&accounts_path).await?;
},
AccountCommand::Forget => {
+ if accounts.pop_selected_account().is_none() {
+ eprintln!("No account selected.");
+ return Ok(ExitCode::FAILURE);
+ }
+
+ accounts.save(&accounts_path).await?;
+ },
+ AccountCommand::SignIn(args) => {
+ let (client_id, azure) = if args.use_alt_client_id {
+ (ALT_CLIENT_ID, false)
+ } else {
+ (MAIN_CLIENT_ID, true)
+ };
+
+ let client = MsaAccount::create_client();
+
+ let mut acct = MsaAccount::with_client_id(client_id, azure);
+ acct.xbl_login_device(&client, |d| async move {
+ if let Some(uri_complete) = d.verification_uri_complete() {
+ println!("In a browser, please navigate to the following URL:");
+ println!("{}", uri_complete.secret());
+ } else {
+ println!("In a browser, please navigate to the following URL: {}", d.verification_uri());
+ println!("Use the following device code: {}", d.user_code().secret())
+ }
+ }).await?;
+
+ println!("Authentication success! Logging in...");
+
+ match acct.log_in_silent(&client).await {
+ Ok(_) => (),
+ Err(e) => match e.kind() {
+ AuthErrorKind::NotOnXbox => {
+ eprintln!("This Microsoft account is not on Xbox. Please make sure you are using the correct Microsoft account.");
+ return Ok(ExitCode::FAILURE);
+ },
+ AuthErrorKind::TooYoung => {
+ eprintln!("Currently, Microsoft accounts held by minors (under 18 years old) cannot sign into third party applications (such as olauncher) unless they are in a family.");
+ eprintln!("If you do not wish to configure a family, try running this command with the `--use-alt-client-id' flag.");
+ return Ok(ExitCode::FAILURE);
+ },
+ AuthErrorKind::NotEntitled => {
+ eprintln!("Warning: This Microsoft account does not seem to own the game.");
+ eprintln!("This account will only be able to play the demo version of the game.");
+ },
+ AuthErrorKind::NoProfile => {
+ eprintln!("Warning: It appears that you own the game but have not yet created a profile.");
+ eprintln!("Visit https://minecraft.net to choose a name. Until then, you will only be able to play the demo version of the game.");
+ },
+ _ => {
+ eprintln!("An unknown error occurred while signing into the account:");
+ eprintln!("{e}");
+ return Ok(ExitCode::FAILURE);
+ }
+ }
+ }
+
+ let key = accounts.add_account(acct.into()).expect("authentication succeeded but xuid missing????");
+ if !args.no_select {
+ accounts.set_selected_account(key);
+ }
+
+ accounts.save(&accounts_path).await?;
+
+ println!("Success! Account added.");
+ },
+ AccountCommand::List => {
+ let iter = accounts.iter_accounts();
+ if iter.len() == 0 {
+ eprintln!("There are no accounts.");
+ return Ok(ExitCode::FAILURE);
+ }
+
+ let sel_id = accounts.get_selected_account().map(|(id, _)| id);
+ let mut first = true;
+ for (id, account) in iter {
+ if !first {
+ println!();
+ }
+
+ first = false;
+ display_account(account, sel_id.is_some_and(|s| s == id), false);
+ }
},
- AccountCommand::SignIn => {
+ AccountCommand::Info => {
+ let Some((_, account)) = accounts.get_selected_account() else {
+ eprintln!("No account selected.");
+ return Ok(ExitCode::FAILURE);
+ };
+ display_account(account, false, true);
+ },
+ AccountCommand::Refresh => {
+ let Some(account) = accounts.get_selected_account_mut() else {
+ eprintln!("No account selected.");
+ return Ok(ExitCode::FAILURE);
+ };
+
+ let client = MsaAccount::create_client();
+
+ match account {
+ Account::MSA(msa_acct) => {
+ msa_acct.log_in_silent(&client).await?;
+ println!("Successfully refreshed account: {}", account);
+ },
+ _ => {
+ eprintln!("Cannot refresh non-MSA account.");
+ return Ok(ExitCode::FAILURE);
+ }
+ }
+
+ accounts.save(&accounts_path).await?;
}
}
}