From 7f7e6d7a7028536dbe3d9d6d6cc818d61307b12c Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Tue, 18 Feb 2025 18:02:51 -0600 Subject: launch the game with an account --- ozone-cli/src/main.rs | 20 +++++++++----- ozone/src/auth/types.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++ ozone/src/launcher.rs | 24 +++++++++++----- ozone/src/launcher/runner.rs | 29 ++++++++++++++------ 4 files changed, 116 insertions(+), 22 deletions(-) diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs index 81a44f9..03f249a 100644 --- a/ozone-cli/src/main.rs +++ b/ozone-cli/src/main.rs @@ -429,12 +429,13 @@ async fn main_inner(cli: Cli) -> Result> { let accounts_path = home.join(ACCOUNT_DB_PATH); let mut accounts = AccountStorage::load(&accounts_path).await?; - let Some(account) = accounts.get_selected_account_mut() else { - eprintln!("No account selected."); - return Ok(ExitCode::FAILURE); - }; if !cli.offline { + let Some(account) = accounts.get_selected_account_mut() else { + eprintln!("No account selected."); + return Ok(ExitCode::FAILURE); + }; + if let Account::MSA(msa_acct) = account { let client = MsaAccount::create_client(); @@ -451,15 +452,20 @@ async fn main_inner(cli: Cli) -> Result> { return Ok(ExitCode::FAILURE); } } - - accounts.save(&accounts_path).await?; } + + accounts.save(&accounts_path).await?; } + + let Some((_, account)) = accounts.get_selected_account() else { + eprintln!("No account selected."); + return Ok(ExitCode::FAILURE); + }; println!("Preparing the game files..."); 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| { + let launch = launcher.prepare_launch(inst, Settings::get_instance_path(selection), settings.client_id, account, false).await.map_err(|e| { error!("error launching: {e}"); e })?; diff --git a/ozone/src/auth/types.rs b/ozone/src/auth/types.rs index a2ac7da..3f75387 100644 --- a/ozone/src/auth/types.rs +++ b/ozone/src/auth/types.rs @@ -153,6 +153,13 @@ impl MsaAccount { refresh_token: None } } + + pub fn is_demo(&self) -> bool { + // player_profile is saved - player is permitted to play the game without demo mode as long + // as their profile exists (demo profiles don't seem to be used anymore - you must own the + // game to make a profile). + self.player_profile.is_none() + } } #[derive(Debug, Serialize, Deserialize)] @@ -175,6 +182,64 @@ impl Account { Account::MSA(account) => account.xuid.as_ref().map(|x| format!("msa:xuid:{x}")) } } + + // stuff the game needs to know about this account + pub(crate) fn is_demo(&self) -> bool { + match *self { + Account::MSA(ref msa_acct) => msa_acct.is_demo(), + _ => false + } + } + + pub(crate) fn get_access_token(&self) -> Option<&str> { + match *self { + Account::MSA(ref msa_acct) => msa_acct.mc_token.as_ref().map(|t| t.value.as_str()), + _ => None + } + } + + pub(crate) fn get_player_name(&self) -> Option<&str> { + match *self { + Account::MSA(ref msa_acct) => msa_acct.player_profile.as_ref().map(|i| i.name.as_str()), + Account::Dummy(ref profile) => Some(profile.name.as_str()) + } + } + + pub(crate) fn get_session_token(&self) -> Option { + match *self { + Account::MSA(ref msa_acct) => msa_acct.mc_token.as_ref().zip(msa_acct.player_info.as_ref()) + .map(|(t, p)| format!("token:{}:{}", t.value, p.id.as_simple())), + _ => None + } + } + + pub(crate) fn get_uuid(&self) -> Option { + match *self { + Account::MSA(ref msa_acct) => msa_acct.player_profile.as_ref().map(|p| p.id), + Account::Dummy(ref profile) => Some(profile.id) + } + } + + pub(crate) fn get_xuid(&self) -> Option<&str> { + match *self { + Account::MSA(ref msa_acct) => msa_acct.xuid.as_deref(), + _ => None + } + } + + pub(crate) fn get_profile_properties(&self) -> Option<&PropertyMap> { + match *self { + Account::MSA(ref msa_acct) => msa_acct.player_profile.as_ref().map(|p| &p.properties), + Account::Dummy(ref profile) => Some(&profile.properties) + } + } + + pub(crate) fn get_user_type(&self) -> &'static str { + match *self { + Account::MSA(_) => "msa", + _ => "legacy" + } + } } #[derive(Debug, Default, Serialize, Deserialize)] diff --git a/ozone/src/launcher.rs b/ozone/src/launcher.rs index add9f36..f724e46 100644 --- a/ozone/src/launcher.rs +++ b/ozone/src/launcher.rs @@ -43,6 +43,7 @@ pub use runner::run_the_game; pub use crate::util::{EnsureFileError, FileVerifyError, IntegrityError}; use crate::assets::AssetIndex; use runner::ArgumentType; +use crate::auth::Account; use crate::launcher::download::FileDownload; use crate::launcher::jre::{JavaRuntimeError, JavaRuntimeRepository}; use crate::launcher::version::VersionError; @@ -210,7 +211,10 @@ struct LaunchInfo<'l> { version_type: Option, asset_index: Option, - resolution: Option + resolution: Option, + + account: &'l Account, + force_demo: bool } #[derive(Debug)] @@ -224,14 +228,17 @@ pub struct Launch { } #[derive(Clone, Copy)] -struct InstanceFeatureMatcher<'inst> { - instance: &'inst Instance +struct LaunchFeatureMatcher<'l> { + instance: &'l Instance, + account: &'l Account } -impl<'a> FeatureMatcher<'a> for InstanceFeatureMatcher<'_> { +impl<'a> FeatureMatcher<'a> for LaunchFeatureMatcher<'_> { fn matches(self, feature: &str) -> bool { match feature { "has_custom_resolution" => self.instance.resolution.is_some(), + "__ozone_win10_hack" => false, // todo + "is_demo_user" => self.account.is_demo(), _ => false } } @@ -319,9 +326,9 @@ impl Launcher { /* TODO: * - launch game using JNI */ - pub async fn prepare_launch(&self, instance: &Instance, inst_home: impl AsRef, client_id: Uuid) -> Result { + pub async fn prepare_launch(&self, instance: &Instance, inst_home: impl AsRef, client_id: Uuid, account: &Account, force_demo: bool) -> Result { let start = Instant::now(); - let feature_matcher = InstanceFeatureMatcher { instance }; + let feature_matcher = LaunchFeatureMatcher { instance, account }; let version_id = &instance.game_version; let version_id = self.versions.get_instance_version_id(version_id); @@ -513,7 +520,10 @@ impl Launcher { version_type: ver.version_type.clone(), asset_index: asset_idx, - resolution: instance.resolution + resolution: instance.resolution, + + account, + force_demo }; let Some(ref main_class) = ver.main_class else { diff --git a/ozone/src/launcher/runner.rs b/ozone/src/launcher/runner.rs index 9e7c05e..23c0548 100644 --- a/ozone/src/launcher/runner.rs +++ b/ozone/src/launcher/runner.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use log::{debug, warn}; use tokio::{fs, io}; +use crate::auth::{property_map, PropertyMap}; use crate::util::{self, AsJavaPath}; use crate::version::{CompleteVersion, FeatureMatcher, OperatingSystem}; use super::rules::CompatCheck; @@ -21,16 +22,28 @@ const PATH_SEP: &str = ";"; #[cfg(not(windows))] const PATH_SEP: &str = ":"; +fn serialize_props(props: &PropertyMap, legacy: bool) -> String { + let mut out = Vec::::new(); + + if legacy { + let mut ser = serde_json::Serializer::new(&mut out); + property_map::legacy::serialize(props, &mut ser).expect("serializing property map should be infallible"); + String::from_utf8(out).expect("WTF: serialized property map contained invalid UTF-8") + } else { + serde_json::to_string(props).expect("serializing property map should be infallible") + } +} + impl<'key, 'rep> SubFunc<'key, 'rep> for LaunchArgSub<'rep, '_> { fn substitute(self, key: &'key str) -> Option> { match key { "assets_index_name" => self.0.asset_index_name.as_ref().map(|s| Cow::Borrowed(s.as_str())), "assets_root" => Some(self.0.launcher.assets.get_home().as_java_path().to_string_lossy()), - "auth_access_token" => Some(Cow::Borrowed("-")), // TODO - "auth_player_name" => Some(Cow::Borrowed("Player")), // TODO - "auth_session" => Some(Cow::Borrowed("-")), // TODO - "auth_uuid" => Some(Cow::Borrowed("00000000-0000-0000-0000-000000000000")), // TODO - "auth_xuid" => Some(Cow::Borrowed("0000000000000000")), // TODO + "auth_access_token" => Some(Cow::Borrowed(self.0.account.get_access_token().unwrap_or("-"))), + "auth_player_name" => Some(Cow::Borrowed(self.0.account.get_player_name().unwrap_or("Player"))), + "auth_session" => Some(self.0.account.get_session_token().map(Cow::Owned).unwrap_or(Cow::Borrowed("-"))), + "auth_uuid" => Some(Cow::Owned(self.0.account.get_uuid().unwrap_or_default().as_simple().to_string())), + "auth_xuid" => self.0.account.get_xuid().map(Cow::Borrowed), "classpath" => Some(Cow::Borrowed(self.0.classpath.as_str())), "classpath_separator" => Some(Cow::Borrowed(PATH_SEP)), "clientid" | "client_id" => Some(Cow::Owned(self.0.client_id.to_string())), @@ -49,9 +62,9 @@ impl<'key, 'rep> SubFunc<'key, 'rep> for LaunchArgSub<'rep, '_> { "quickPlaySingleplayer" => None, // TODO "resolution_height" => Some(self.0.resolution.map(|r| Cow::Owned(r.height.to_string())).unwrap_or(Cow::Borrowed(""))), "resolution_width" => Some(self.0.resolution.map(|r| Cow::Owned(r.width.to_string())).unwrap_or(Cow::Borrowed(""))), - "user_properties" => Some(Cow::Borrowed("{}")), // TODO - "user_property_map" => Some(Cow::Borrowed("[]")), // TODO - "user_type" => Some(Cow::Borrowed("legacy")), // TODO + "user_properties" => Some(self.0.account.get_profile_properties().map(|p| Cow::Owned(serialize_props(p, true))).unwrap_or(Cow::Borrowed("{}"))), + "user_property_map" => Some(self.0.account.get_profile_properties().map(|p| Cow::Owned(serialize_props(p, false))).unwrap_or(Cow::Borrowed("[]"))), + "user_type" => Some(Cow::Borrowed(self.0.account.get_user_type())), "version_name" => Some(Cow::Borrowed(self.0.version_id.as_ref())), "version_type" => self.0.version_type.as_ref().map(|s| Cow::Borrowed(s.as_ref())), _ => { -- cgit v1.2.3-70-g09d2