summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/auth.rs9
-rw-r--r--src/auth/msa.rs4
-rw-r--r--src/auth/types.rs4
-rw-r--r--src/auth/types/property_map.rs27
-rw-r--r--src/launcher.rs22
-rw-r--r--src/launcher/version.rs105
-rw-r--r--src/util.rs10
-rw-r--r--src/util/progress.rs3
8 files changed, 122 insertions, 62 deletions
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("<none>"));
@@ -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<Uuid> {
- 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<String>
}
@@ -99,7 +101,7 @@ pub struct MinecraftPlayerInfo {
pub struct MsaUser {
#[serde(skip_serializing_if = "Option::is_none")]
pub player_profile: Option<PlayerProfile>,
- pub xuid: Option<Uuid>,
+ pub xuid: Option<String>,
pub gamertag: Option<String>,
#[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<String, Property>;
+pub mod legacy {
+ use serde::Serializer;
+ use super::PropertyMap;
+
+ pub fn serialize<S>(value: &PropertyMap, serializer: S) -> Result<S::Ok, S::Error>
+ 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::<Vec<_>>()))
+ }
+ }))
+ }
+}
+
pub fn serialize<S>(value: &PropertyMap, serializer: S) -> Result<S::Ok, S::Error>
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<PropertyMap, D::Error>
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<dyn Error>),
+ 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<Path>, online: bool) -> Result<Launcher, Box<dyn Error>> {
+ pub async fn new(home: impl AsRef<Path>, online: bool) -> Result<Launcher, LaunchError> {
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<String, VersionManifestVersion>,
latest: LatestVersions
}
impl RemoteVersionList {
- async fn new() -> Result<RemoteVersionList, Box<dyn Error>> {
+ async fn new() -> Result<RemoteVersionList, VersionError> {
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<CompleteVersion, Box<dyn Error>> {
+ async fn download_version(&self, ver: &VersionManifestVersion, path: &Path) -> Result<CompleteVersion, VersionError> {
// 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::<Box<dyn Error>, _>(|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<LocalVersionList, Box<dyn Error>> {
+ async fn load_versions(home: &Path, skip: impl Fn(&str) -> bool) -> Result<LocalVersionList, VersionError> {
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<VersionResult<'a>>> From<Option<T>> for VersionResult<'a> {
pub enum VersionResolveError {
InheritanceLoop(String),
MissingVersion(String),
- Unknown(Box<dyn Error>)
+ 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<VersionList, Box<dyn Error>> {
- Self::create_dir_for(home).await?;
+ pub async fn online(home: &Path) -> Result<VersionList, VersionError> {
+ 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<VersionList, Box<dyn Error>> {
- Self::create_dir_for(home).await?;
+ pub async fn offline(home: &Path) -> Result<VersionList, VersionError> {
+ 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<CompleteVersion, Box<dyn Error>> {
+ pub async fn load_remote_version(&self, ver: &VersionManifestVersion) -> Result<CompleteVersion, VersionError> {
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<Path>, expect_size: Option<usize>,
pub async fn ensure_file(path: impl AsRef<Path>, url: Option<&str>, expect_size: Option<usize>, expect_sha1: Option<Digest>, online: bool, force_download: bool) -> Result<bool, EnsureFileError> {
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