From 6a1bb0980facadcc22cbae27f57782050e7924a1 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Sat, 1 Feb 2025 00:20:54 -0600 Subject: random changes --- src/auth.rs | 9 ++-- src/auth/msa.rs | 4 +- src/auth/types.rs | 4 +- src/auth/types/property_map.rs | 27 +++++++---- src/launcher.rs | 22 +++++---- src/launcher/version.rs | 105 ++++++++++++++++++++++++++++------------- src/util.rs | 10 ++-- src/util/progress.rs | 3 ++ 8 files changed, 122 insertions(+), 62 deletions(-) create mode 100644 src/util/progress.rs (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index 7336e64..057cceb 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -87,7 +87,7 @@ macro_rules! create_oauth_client { } const AZURE_TOKEN_URL: &str = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; -const AZURE_DEVICE_CODE_URL: &str = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; +const AZURE_DEVICE_CODE_URL: &str = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"; const NON_AZURE_TOKEN_URL: &str = "https://login.live.com/oauth20_token.srf"; const NON_AZURE_DEVICE_CODE_URL: &str = "https://login.live.com/oauth20_connect.srf"; @@ -241,7 +241,7 @@ impl MsaUser { return Err(AuthError::Internal("missing xuid for user".into())); }; - self.xuid = Some(xuid); + self.xuid = Some(xuid.to_owned()); self.gamertag = res.get_gamertag().map(|s| s.to_owned()); debug!("Xbox info loaded: (xuid {xuid}, gamertag {})", res.get_gamertag().unwrap_or("")); @@ -298,8 +298,9 @@ mod test { xuid: None, gamertag: None, player_info: None, - client_id: ClientId::new("00000000402b5328".into()), - is_azure_client_id: false, + //client_id: ClientId::new("00000000402b5328".into()), + client_id: ClientId::new("60b6cc54-fc07-4bab-bca9-cbe9aa713c80".into()), + is_azure_client_id: true, mc_token: None, xbl_token: None, refresh_token: None diff --git a/src/auth/msa.rs b/src/auth/msa.rs index 404329b..add345c 100644 --- a/src/auth/msa.rs +++ b/src/auth/msa.rs @@ -119,8 +119,8 @@ impl XSTSAuthSuccessResponse { self.get_display_claim("uhs") } - pub(super) fn get_xuid(&self) -> Option { - self.get_display_claim("xid").and_then(|s| s.parse().ok()).map(|n| Uuid::from_u64_pair(0, n)) + pub(super) fn get_xuid(&self) -> Option<&str> { + self.get_display_claim("xid") } pub(super) fn get_gamertag(&self) -> Option<&str> { diff --git a/src/auth/types.rs b/src/auth/types.rs index 79b89c9..b9cdaad 100644 --- a/src/auth/types.rs +++ b/src/auth/types.rs @@ -12,6 +12,8 @@ use uuid::Uuid; pub struct Property { pub name: String, pub value: String, + + #[serde(skip_serializing_if = "Option::is_none")] pub signature: Option } @@ -99,7 +101,7 @@ pub struct MinecraftPlayerInfo { pub struct MsaUser { #[serde(skip_serializing_if = "Option::is_none")] pub player_profile: Option, - pub xuid: Option, + pub xuid: Option, pub gamertag: Option, #[serde(skip)] // this information is transient diff --git a/src/auth/types/property_map.rs b/src/auth/types/property_map.rs index 964b06f..ddfc9ce 100644 --- a/src/auth/types/property_map.rs +++ b/src/auth/types/property_map.rs @@ -2,22 +2,33 @@ use std::fmt::Formatter; use multimap::MultiMap; use serde::de::{SeqAccess, Visitor}; use serde::{Deserializer, Serializer}; -use serde::ser::SerializeSeq; use crate::auth::Property; pub type PropertyMap = MultiMap; +pub mod legacy { + use serde::Serializer; + use super::PropertyMap; + + pub fn serialize(value: &PropertyMap, serializer: S) -> Result + where S: Serializer + { + serializer.collect_map(value.iter_all() + .filter_map(|(k, v)| { + if v.is_empty() { + None + } else { + Some((k, v.iter().map(|p| &p.value).collect::>())) + } + })) + } +} + pub fn serialize(value: &PropertyMap, serializer: S) -> Result where S: Serializer { - let mut seq = serializer.serialize_seq(Some(value.keys().len()))?; - - for (_, prop) in value.flat_iter() { - seq.serialize_element(prop)?; - } - - seq.end() + serializer.collect_seq(value.flat_iter().map(|(_, v)| v)) } pub fn deserialize<'de, D>(deserializer: D) -> Result diff --git a/src/launcher.rs b/src/launcher.rs index 0f2b442..2836db5 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -45,6 +45,7 @@ use runner::ArgumentType; use strsub::SubFunc; use crate::launcher::download::FileDownload; use crate::launcher::jre::{JavaRuntimeError, JavaRuntimeRepository}; +use crate::launcher::version::VersionError; use crate::version::manifest::VersionType; #[derive(Debug)] @@ -115,8 +116,9 @@ pub enum LaunchError { UnknownInstance(String), // version resolution errors + VersionInit(VersionError), UnknownVersion(String), - LoadVersion(Box), + LoadVersion(VersionError), ResolveVersion(VersionResolveError), IncompatibleVersion(IncompatibleError), MissingMainClass, @@ -149,6 +151,7 @@ impl Display for LaunchError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match &self { LaunchError::UnknownInstance(inst) => write!(f, "unknown instance: {inst}"), + LaunchError::VersionInit(e) => write!(f, "initializing version: {e}"), LaunchError::UnknownVersion(id) => write!(f, "unknown version id: {id}"), LaunchError::LoadVersion(e) => write!(f, "error loading remote version: {e}"), LaunchError::ResolveVersion(e) => write!(f, "error resolving remote version: {e}"), @@ -175,7 +178,8 @@ impl Display for LaunchError { impl Error for LaunchError { fn cause(&self) -> Option<&dyn Error> { match &self { - LaunchError::LoadVersion(e) => Some(e.as_ref()), + LaunchError::VersionInit(e) => Some(e), + LaunchError::LoadVersion(e) => Some(e), LaunchError::ResolveVersion(e) => Some(e), LaunchError::IncompatibleVersion(e) => Some(e), LaunchError::LibraryDirError(_, e) => Some(e), @@ -232,23 +236,25 @@ impl FeatureMatcher for ProfileFeatureMatcher<'_> { impl Launcher { // FIXME: more descriptive error type por favor - pub async fn new(home: impl AsRef, online: bool) -> Result> { + pub async fn new(home: impl AsRef, online: bool) -> Result { match tokio::fs::create_dir_all(home.as_ref()).await { Err(e) if e.kind() != AlreadyExists => { warn!("Failed to create launcher home directory: {}", e); - return Err(e.into()); + return Err(LaunchError::IO { what: "create launcher home", error: e }); }, _ => () } - let home = fs::canonicalize(home.as_ref()).await?; + let home = fs::canonicalize(home.as_ref()).await + .map_err(|e| LaunchError::IO { what: "resolve home path", error: e })?; + let versions_home = home.join("versions"); debug!("Version list online?: {online}"); let versions = if online { - VersionList::online(versions_home.as_ref()).await? + VersionList::online(versions_home.as_ref()).await.map_err(LaunchError::VersionInit)? } else { - VersionList::offline(versions_home.as_ref()).await? + VersionList::offline(versions_home.as_ref()).await.map_err(LaunchError::VersionInit)? }; let assets_path = home.join("assets"); @@ -263,7 +269,7 @@ impl Launcher { home: home.join("libraries"), natives: home.join("natives") }, - assets: AssetRepository::new(online, &assets_path).await?, + assets: AssetRepository::new(online, &assets_path).await.map_err(|e| LaunchError::IO { what: "setting up assets", error: e })?, java_runtimes, home }) diff --git a/src/launcher/version.rs b/src/launcher/version.rs index 49525b0..0f55223 100644 --- a/src/launcher/version.rs +++ b/src/launcher/version.rs @@ -13,17 +13,51 @@ use crate::version::{*, manifest::*}; use super::constants::*; +#[derive(Debug)] +pub enum VersionError { + IO { what: String, error: io::Error }, + Request { what: String, error: reqwest::Error }, + MalformedObject { what: String, error: serde_json::Error }, + VersionIntegrity { id: String, expect: Digest, got: Digest } +} + +impl Display for VersionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + VersionError::IO { what, error } => write!(f, "i/o error ({what}): {error}"), + VersionError::Request { what, error } => write!(f, "request error ({what}): {error}"), + VersionError::MalformedObject { what, error } => write!(f, "malformed {what}: {error}"), + VersionError::VersionIntegrity { id, expect, got } => write!(f, "version {id} integrity mismatch (expect {expect}, got {got})") + } + } +} + +impl Error for VersionError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + VersionError::IO { error, .. } => Some(error), + VersionError::Request { error, .. } => Some(error), + VersionError::MalformedObject { error, .. } => Some(error), + _ => None + } + } +} + struct RemoteVersionList { versions: HashMap, latest: LatestVersions } impl RemoteVersionList { - async fn new() -> Result> { + async fn new() -> Result { debug!("Looking up remote version manifest."); - let text = reqwest::get(URL_VERSION_MANIFEST).await?.error_for_status()?.text().await?; + let text = reqwest::get(URL_VERSION_MANIFEST).await + .and_then(|r| r.error_for_status()) + .map_err(|e| VersionError::Request { what: "download version manifest".into(), error: e })? + .text().await.map_err(|e| VersionError::Request { what: "download version manifest (decode)".into(), error: e })?; + debug!("Parsing version manifest."); - let manifest: VersionManifest = serde_json::from_str(text.as_str())?; + let manifest: VersionManifest = serde_json::from_str(text.as_str()).map_err(|e| VersionError::MalformedObject { what: "version manifest".into(), error: e })?; let mut versions = HashMap::new(); for v in manifest.versions { @@ -37,32 +71,37 @@ impl RemoteVersionList { }) } - async fn download_version(&self, ver: &VersionManifestVersion, path: &Path) -> Result> { + async fn download_version(&self, ver: &VersionManifestVersion, path: &Path) -> Result { // ensure parent directory exists info!("Downloading version {}.", ver.id); - if let Err(e) = tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await { - warn!("failed to create {} parent dirs: {e}", path.display()); - return Err(e.into()); - } + tokio::fs::create_dir_all(path.parent().expect("version .json has no parent (impossible)")).await + .inspect_err(|e| warn!("failed to create {} parent dirs: {e}", path.display())) + .map_err(|e| VersionError::IO { what: format!("creating version directory for {}", path.display()), error: e })?; // download it - let ver_text = reqwest::get(ver.url.as_str()).await?.error_for_status()?.text().await?; - + let ver_text = reqwest::get(ver.url.as_str()).await + .and_then(|r| r.error_for_status()) + .map_err(|e| VersionError::Request { what: format!("download version {} from {}", ver.id, ver.url), error: e })? + .text().await.map_err(|e| VersionError::Request { what: format!("download version {} from {} (receive)", ver.id, ver.url), error: e })?; + debug!("Validating downloaded {}...", ver.id); // make sure it's valid util::verify_sha1(ver.sha1, ver_text.as_str()) - .map_err::, _>(|e| format!("downloaded version {} has wrong hash! (expect {}, got {})", ver.id.as_str(), &ver.sha1, e).as_str().into())?; + .map_err(|e| VersionError::VersionIntegrity { + id: ver.id.clone(), + expect: ver.sha1, + got: e + })?; // make sure it's well-formed - let cver: CompleteVersion = serde_json::from_str(ver_text.as_str())?; + let cver: CompleteVersion = serde_json::from_str(ver_text.as_str()).map_err(|e| VersionError::MalformedObject { what: format!("complete version {}", ver.id), error: e })?; debug!("Saving version {}...", ver.id); // write it out - tokio::fs::write(path, ver_text).await.map_err(|e| { - warn!("Failed to save version {}: {}", ver.id, e); - e - })?; + tokio::fs::write(path, ver_text).await + .inspect_err(|e| warn!("Failed to save version {}: {}", ver.id, e)) + .map_err(|e| VersionError::IO { what: format!("writing version file at {}", path.display()), error: e })?; info!("Done downloading and verifying {}!", ver.id); @@ -132,13 +171,13 @@ impl LocalVersionList { } } - async fn load_versions(home: &Path, skip: impl Fn(&str) -> bool) -> Result> { + async fn load_versions(home: &Path, skip: impl Fn(&str) -> bool) -> Result { info!("Loading local versions."); - let mut rd = tokio::fs::read_dir(home).await?; + let mut rd = tokio::fs::read_dir(home).await.map_err(|e| VersionError::IO { what: format!("open local versions directory {}", home.display()), error: e })?; let mut versions = BTreeMap::new(); - while let Some(ent) = rd.next_entry().await? { - if !ent.file_type().await?.is_dir() { continue; } + while let Some(ent) = rd.next_entry().await.map_err(|e| VersionError::IO { what: format!("read local versions directory {}", home.display()), error: e })? { + if !ent.file_type().await.map_err(|e| VersionError::IO { what: format!("version entry metadata {}", ent.path().display()), error: e} )?.is_dir() { continue; } // when the code is fugly let path = match ent.file_name().to_str() { @@ -215,7 +254,7 @@ impl<'a, T: Into>> From> for VersionResult<'a> { pub enum VersionResolveError { InheritanceLoop(String), MissingVersion(String), - Unknown(Box) + VersionLoad(VersionError) } impl Display for VersionResolveError { @@ -223,7 +262,7 @@ impl Display for VersionResolveError { match self { VersionResolveError::InheritanceLoop(s) => write!(f, "inheritance loop (saw {s} twice)"), VersionResolveError::MissingVersion(s) => write!(f, "unknown version {s}"), - VersionResolveError::Unknown(err) => write!(f, "unknown error: {err}") + VersionResolveError::VersionLoad(err) => write!(f, "version load error: {err}") } } } @@ -235,18 +274,16 @@ impl VersionList { debug!("Creating versions directory."); match fs::create_dir(home).await { Ok(_) => Ok(()), - Err(e) => match e.kind() { - ErrorKind::AlreadyExists => Ok(()), - _ => { - debug!("failed to create version home: {}", e); - Err(e) - } + Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()), + Err(e) => { + debug!("failed to create version home: {}", e); + Err(e) } } } - pub async fn online(home: &Path) -> Result> { - Self::create_dir_for(home).await?; + pub async fn online(home: &Path) -> Result { + Self::create_dir_for(home).await.map_err(|e| VersionError::IO { what: format!("create version directory {}", home.display()), error: e })?; let remote = RemoteVersionList::new().await?; let local = LocalVersionList::load_versions(home, |s| remote.versions.contains_key(s)).await?; @@ -258,8 +295,8 @@ impl VersionList { }) } - pub async fn offline(home: &Path) -> Result> { - Self::create_dir_for(home).await?; + pub async fn offline(home: &Path) -> Result { + Self::create_dir_for(home).await.map_err(|e| VersionError::IO { what: format!("create version directory {}", home.display()), error: e })?; let local = LocalVersionList::load_versions(home, |_| false).await?; @@ -294,7 +331,7 @@ impl VersionList { remote.versions.get(id) } - pub async fn load_remote_version(&self, ver: &VersionManifestVersion) -> Result> { + pub async fn load_remote_version(&self, ver: &VersionManifestVersion) -> Result { let remote = self.remote.as_ref().expect("load_remote_version called in offline mode!"); let id = ver.id.as_str(); @@ -340,7 +377,7 @@ impl VersionList { let inherited_ver = match self.get_version_lazy(inherit.as_str()) { VersionResult::Complete(v) => Cow::Borrowed(v), VersionResult::Remote(v) => - Cow::Owned(self.load_remote_version(v).await.map_err(VersionResolveError::Unknown)?), + Cow::Owned(self.load_remote_version(v).await.map_err(VersionResolveError::VersionLoad)?), VersionResult::None => { warn!("Cannot resolve version {}, it inherits an unknown version {inherit}", ver.id); return Err(VersionResolveError::MissingVersion(inherit)); diff --git a/src/util.rs b/src/util.rs index af960bd..7510a33 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +mod progress; + use std::error::Error; use std::fmt::{Display, Formatter}; use std::io::ErrorKind; @@ -188,12 +190,10 @@ pub async fn should_download(path: impl AsRef, expect_size: Option, pub async fn ensure_file(path: impl AsRef, url: Option<&str>, expect_size: Option, expect_sha1: Option, online: bool, force_download: bool) -> Result { let path = path.as_ref(); - if !force_download { - if !should_download(path, expect_size, expect_sha1).await + if !force_download && !should_download(path, expect_size, expect_sha1).await .map_err(|e| EnsureFileError::IO { what: "verifying file on disk", error: e })? { - - return Ok(false); - } + + return Ok(false); } if !online { diff --git a/src/util/progress.rs b/src/util/progress.rs new file mode 100644 index 0000000..e8bdde1 --- /dev/null +++ b/src/util/progress.rs @@ -0,0 +1,3 @@ +struct Progress { + +} \ No newline at end of file -- cgit v1.2.3-70-g09d2