summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock21
-rw-r--r--Cargo.toml4
-rw-r--r--src/auth.rs54
-rw-r--r--src/auth/device_code.rs103
-rw-r--r--src/auth/types.rs49
-rw-r--r--src/launcher/constants.rs7
-rw-r--r--src/launcher/download.rs3
-rw-r--r--src/lib.rs2
-rw-r--r--src/util.rs7
9 files changed, 239 insertions, 11 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4cf2006..f9091a2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3342,6 +3342,15 @@ dependencies = [
]
[[package]]
+name = "multimap"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "native-tls"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3490,6 +3499,7 @@ dependencies = [
"lazy_static",
"log",
"lzma-rs",
+ "multimap",
"regex",
"reqwest",
"serde",
@@ -3498,6 +3508,7 @@ dependencies = [
"sysinfo",
"tokio",
"tokio-stream",
+ "uuid",
"walkdir",
"zip",
]
@@ -5897,6 +5908,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
+name = "uuid"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
+dependencies = [
+ "getrandom 0.2.15",
+ "serde",
+]
+
+[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 0b15061..d4a405c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,14 +12,16 @@ indexmap = { version = "2.7.1", features = ["serde"] }
lazy_static = "1.5.0"
log = "0.4.22"
lzma-rs = { version = "0.3.0", features = ["stream"] }
+multimap = { version = "0.10.0", features = ["serde"] }
regex = "1.11.1"
reqwest = { version = "0.12.12", features = ["json", "stream"] }
serde = { version = "1.0.216", features = ["derive"] }
serde_json = "1.0.133"
sha1_smol = { version = "1.0.1", features = ["alloc", "std", "serde"] }
sysinfo = { version = "0.33.1", features = ["system", "multithread"] }
-tokio = { version = "1.42.0", features = ["fs", "io-util", "sync", "rt"] }
+tokio = { version = "1.42.0", features = ["fs", "io-util", "sync", "rt", "macros"] }
tokio-stream = { version = "0.1.17", features = ["fs"] }
+uuid = { version = "1.12.1", features = ["v4", "serde"] }
walkdir = "2.5.0"
zip = { version = "2.2.2", default-features = false, features = ["bzip2", "deflate", "deflate64", "lzma", "xz"] }
diff --git a/src/auth.rs b/src/auth.rs
index a11c0de..f4522ed 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,3 +1,55 @@
-struct AuthenticationDatabase {
+mod types;
+pub mod device_code;
+use std::error::Error;
+use std::fmt::{Display, Formatter};
+use chrono::{DateTime, Utc};
+pub use types::*;
+
+#[derive(Debug)]
+pub enum AuthError {
+ Request { what: &'static str, error: reqwest::Error },
+}
+
+impl Display for AuthError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ AuthError::Request { what, error } => write!(f, "auth request error ({}): {}", what, error)
+ }
+ }
+}
+
+impl Error for AuthError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ AuthError::Request { error, .. } => Some(error)
+ }
+ }
+}
+
+impl Token {
+ fn is_expired(&self, now: DateTime<Utc>) -> bool {
+ self.expire.is_some_and(|exp| now < exp)
+ }
+}
+
+impl MsaUser {
+ async fn log_in(&mut self) -> Result<(), AuthError> {
+ todo!()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use reqwest::Client;
+ use super::*;
+
+ #[tokio::test]
+ async fn abc() {
+ device_code::DeviceCodeAuthBuilder::new()
+ .client_id("00000000402b5328")
+ .scope("service::user.auth.xboxlive.com::MBI_SSL")
+ .url("https://login.live.com/oauth20_connect.srf")
+ .begin(Client::new()).await.unwrap();
+ }
}
diff --git a/src/auth/device_code.rs b/src/auth/device_code.rs
new file mode 100644
index 0000000..087ff27
--- /dev/null
+++ b/src/auth/device_code.rs
@@ -0,0 +1,103 @@
+use std::ops::Add;
+use std::time::Duration;
+use futures::TryFutureExt;
+use reqwest::Client;
+use serde::{Deserialize, Serialize};
+use tokio::time::{Instant, MissedTickBehavior};
+use super::AuthError;
+use crate::util::USER_AGENT;
+
+pub struct DeviceCodeAuthBuilder {
+ client_id: Option<String>,
+ scope: Option<String>,
+ url: Option<String>
+}
+
+#[derive(Serialize, Debug)]
+struct DeviceCodeRequest {
+ client_id: String,
+ scope: String,
+ response_type: String
+}
+
+#[derive(Deserialize, Debug)]
+struct DeviceCodeResponse {
+ device_code: String,
+ user_code: String,
+ verification_uri: String,
+ expires_in: u64,
+ interval: u64,
+ message: Option<String>
+}
+
+impl DeviceCodeAuthBuilder {
+ pub fn new() -> DeviceCodeAuthBuilder {
+ DeviceCodeAuthBuilder {
+ client_id: None,
+ scope: None,
+ url: None
+ }
+ }
+
+ pub fn client_id(mut self, client_id: &str) -> Self {
+ self.client_id = Some(client_id.to_owned());
+ self
+ }
+
+ pub fn scope(mut self, scope: &str) -> Self {
+ self.scope = Some(scope.to_owned());
+ self
+ }
+
+ pub fn url(mut self, url: &str) -> Self {
+ self.url = Some(url.to_owned());
+ self
+ }
+
+ pub async fn begin(self, client: Client) -> Result<DeviceCodeAuth, AuthError> {
+ let scope = self.scope.expect("scope is not optional");
+ let client_id = self.client_id.expect("client_id is not optional");
+ let url = self.url.expect("url is not optional");
+
+ let device_code: DeviceCodeResponse = client.post(&url)
+ .header(reqwest::header::USER_AGENT, USER_AGENT)
+ .header(reqwest::header::ACCEPT, "application/json")
+ .form(&DeviceCodeRequest {
+ client_id,
+ scope,
+ response_type: "device_code".into()
+ })
+ .send().await
+ .and_then(|r| r.error_for_status())
+ .map_err(|e| AuthError::Request { what: "requesting device code auth", error: e })?
+ .json().await.map_err(|e| AuthError::Request { what: "receiving device code auth", error: e })?;
+
+ let now = Instant::now();
+ Ok(DeviceCodeAuth {
+ client,
+ start: now,
+ interval: Duration::from_secs(device_code.interval + 1),
+ expire_time: now.add(Duration::from_secs(device_code.expires_in)),
+ info: dbg!(device_code)
+ })
+ }
+}
+
+pub struct DeviceCodeAuth {
+ client: Client,
+ start: Instant,
+ interval: Duration,
+ expire_time: Instant,
+ info: DeviceCodeResponse
+}
+
+impl DeviceCodeAuth {
+ async fn drive(&self) {
+ let mut i = tokio::time::interval_at(self.start, self.interval);
+ i.set_missed_tick_behavior(MissedTickBehavior::Skip);
+
+ while self.expire_time.elapsed().is_zero() {
+
+ }
+ }
+}
diff --git a/src/auth/types.rs b/src/auth/types.rs
new file mode 100644
index 0000000..8889b63
--- /dev/null
+++ b/src/auth/types.rs
@@ -0,0 +1,49 @@
+use chrono::{DateTime, Utc};
+use multimap::MultiMap;
+use serde::{Deserialize, Serialize};
+use uuid::Uuid;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Property {
+ pub value: String,
+ pub signature: Option<String>
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct UserProfile {
+ pub uuid: Option<Uuid>,
+ pub name: Option<String>,
+
+ #[serde(default, skip_serializing_if = "MultiMap::is_empty")]
+ pub properties: MultiMap<String, Property>
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub(super) struct Token {
+ pub value: String,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub expire: Option<DateTime<Utc>>
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct MsaUser {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub profile: Option<UserProfile>,
+ pub xuid: Uuid,
+ pub(super) auth_token: Option<Token>,
+ pub(super) xbl_token: Option<Token>,
+ pub(super) refresh_token: Option<Token>
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum User {
+ Dummy(UserProfile),
+ MSA(MsaUser)
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct AuthenticationDatabase {
+ pub users: Vec<User>
+}
diff --git a/src/launcher/constants.rs b/src/launcher/constants.rs
index aba0650..db90d2f 100644
--- a/src/launcher/constants.rs
+++ b/src/launcher/constants.rs
@@ -1,12 +1,6 @@
-use const_format::formatcp;
use lazy_static::lazy_static;
use regex::Regex;
-const PKG_NAME: &str = env!("CARGO_PKG_NAME");
-const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
-const CRATE_NAME: &str = env!("CARGO_CRATE_NAME");
-
-pub const USER_AGENT: &str = formatcp!("{PKG_NAME}/{PKG_VERSION} (in {CRATE_NAME})");
pub const URL_VERSION_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
pub const URL_RESOURCE_BASE: &str = "https://resources.download.minecraft.net/";
pub const URL_JRE_MANIFEST: &str = "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json";
@@ -16,6 +10,7 @@ pub const NATIVES_PREFIX: &str = "natives-";
pub const DEF_INSTANCE_NAME: &'static str = "default";
pub const DEF_PROFILE_NAME: &'static str = "default";
+// https://github.com/unmojang/FjordLauncher/pull/14/files
// https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&redirect_uri=ms-xal-00000000402b5328://auth&response_type=token&display=touch&scope=service::user.auth.xboxlive.com::MBI_SSL%20offline_access&prompt=select_account
lazy_static! {
diff --git a/src/launcher/download.rs b/src/launcher/download.rs
index ec4a59c..3a89d79 100644
--- a/src/launcher/download.rs
+++ b/src/launcher/download.rs
@@ -8,9 +8,8 @@ use sha1_smol::{Digest, Sha1};
use tokio::fs;
use tokio::fs::File;
use tokio::io::{self, AsyncWriteExt};
-use crate::launcher::constants::USER_AGENT;
use crate::util;
-use crate::util::{FileVerifyError, IntegrityError};
+use crate::util::{FileVerifyError, IntegrityError, USER_AGENT};
pub trait Download: Debug + Display {
// return Ok(None) to skip downloading this file
diff --git a/src/lib.rs b/src/lib.rs
index 05c0c7e..0d2233b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,4 +2,4 @@ mod util;
pub mod version;
pub mod assets;
pub mod launcher;
-mod auth;
+pub mod auth; // temporarily public
diff --git a/src/util.rs b/src/util.rs
index 0685848..8d35fb9 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -2,6 +2,7 @@ use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::path::{Component, Path, PathBuf};
+use const_format::formatcp;
use log::{debug, info, warn};
use sha1_smol::{Digest, Sha1};
use tokio::fs::File;
@@ -9,6 +10,12 @@ use tokio::{fs, io};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::util;
+const PKG_NAME: &str = env!("CARGO_PKG_NAME");
+const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
+const CRATE_NAME: &str = env!("CARGO_CRATE_NAME");
+
+pub const USER_AGENT: &str = formatcp!("{PKG_NAME}/{PKG_VERSION} (in {CRATE_NAME})");
+
#[derive(Debug)]
pub enum IntegrityError {
SizeMismatch{ expect: usize, actual: usize },