Skip to content

Commit

Permalink
Merge pull request #8 from OpenXbox/fix/auth_titlehub
Browse files Browse the repository at this point in the history
fix: Recursively download every atom of data from titlestorage
  • Loading branch information
tuxuser authored Jan 14, 2025
2 parents 109961d + 2e31c96 commit 67e8970
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 43 deletions.
4 changes: 2 additions & 2 deletions examples/src/bin/auth_azure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use xal::{
use xal_examples::auth_main;

// Replace with your own Azure Client parameters
const CLIENT_ID: &'static str = "388ea51c-0b25-4029-aae2-17df49d23905";
const REDIRECT_URL: &'static str = "http://localhost:8080/auth/callback";
const CLIENT_ID: &str = "388ea51c-0b25-4029-aae2-17df49d23905";
const REDIRECT_URL: &str = "http://localhost:8080/auth/callback";
const CLIENT_SECRET: Option<&'static str> = None;

pub struct HttpCallbackHandler {
Expand Down
86 changes: 63 additions & 23 deletions examples/src/bin/auth_titlehub.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Download savegames for a specific title
//!
use std::collections::HashMap;
use std::{collections::HashMap, path::Path};
use std::io::Write;
use std::path::PathBuf;

use log::{info, warn, debug, trace};
use log::{info, debug, trace};
use async_trait::async_trait;
use reqwest::Url;
use serde::Deserialize;
Expand Down Expand Up @@ -52,8 +52,8 @@ pub struct SavegameAtoms {
}

// Replace with your own Azure Client parameters
const CLIENT_ID: &'static str = "388ea51c-0b25-4029-aae2-17df49d23905";
const REDIRECT_URL: &'static str = "http://localhost:8080/auth/callback";
const CLIENT_ID: &str = "388ea51c-0b25-4029-aae2-17df49d23905";
const REDIRECT_URL: &str = "http://localhost:8080/auth/callback";
const CLIENT_SECRET: Option<&'static str> = None;

pub struct HttpCallbackHandler {
Expand Down Expand Up @@ -97,16 +97,30 @@ impl AuthPromptCallback for HttpCallbackHandler {
}
}

pub fn assemble_filepath(root_path: &PathBuf, path: &str) -> PathBuf {
let modified_path = path
// Replace separator with platform-specific separator
.replace("/", std::path::MAIN_SEPARATOR_STR)
// Strip ,savedgame suffix
.replace(",savedgame", "")
.replace("X", ".")
.replace("E", "-");
pub fn assemble_filepath(root_path: &Path, atom_type: &str, path: &str) -> PathBuf {
let modified_path = {
let tmp = path
// Replace separator with platform-specific separator
.replace("/", std::path::MAIN_SEPARATOR_STR)
// Strip ,savedgame suffix
.replace(",savedgame", "")
.replace("X", ".")
.replace("E", "-");

if let Some(stripped) = tmp.strip_prefix(std::path::MAIN_SEPARATOR_STR) {
// Remove leading path seperator
stripped.to_string()
}
else {
tmp
}
};

let mut new_path = root_path.to_path_buf();
new_path.push(atom_type);
new_path.push(modified_path);

root_path.join(modified_path)
new_path
}

#[tokio::main]
Expand Down Expand Up @@ -155,11 +169,11 @@ async fn main() -> Result<(), Error> {

let client = reqwest::Client::new();

let pfn = "Microsoft.ProjectSpark-Dakota_8wekyb3d8bbwe";
let scid = "d3d00100-7976-472f-a3f7-bc1760d19e14";
let pfn = "Microsoft.ArthurProduct_8wekyb3d8bbwe";
let scid = "05c20100-6e60-45d5-878a-4903149e11ae";

let mut target_dir = PathBuf::new();
target_dir.push(&pfn);
target_dir.push(pfn);
target_dir.push(&xuid);

if !target_dir.exists() {
Expand Down Expand Up @@ -190,8 +204,6 @@ async fn main() -> Result<(), Error> {
for blob in metadata.blobs {
info!("- Fetching {} ({} bytes)", &blob.file_name, blob.size);

let filepath = assemble_filepath(&target_dir, &blob.file_name);

let atoms = client
.get(format!("https://titlestorage.xboxlive.com/connectedstorage/users/xuid({xuid})/scids/{scid}/{}", blob.file_name))
.header("x-xbl-contract-version", "107")
Expand All @@ -213,8 +225,9 @@ async fn main() -> Result<(), Error> {
trace!("{atoms:?}");

debug!("* Found {} atoms", atoms.atoms.len());
if let Some(atom_guid) = atoms.atoms.get("Data") {
debug!("Fetching atom {atom_guid}");
for (atom_type, atom_guid) in atoms.atoms.iter() {
let filepath = assemble_filepath(&target_dir, atom_type, &blob.file_name);
debug!("Fetching atom {atom_guid} (Type: {atom_type})");
let filedata = client
.get(format!("https://titlestorage.xboxlive.com/connectedstorage/users/xuid({xuid})/scids/{scid}/{atom_guid}"))
.header("x-xbl-contract-version", "107")
Expand All @@ -235,14 +248,12 @@ async fn main() -> Result<(), Error> {

if let Some(parent) = filepath.parent() {
if !parent.exists() {
std::fs::create_dir_all(&parent)?;
std::fs::create_dir_all(parent)?;
}
}

let mut filehandle = std::fs::File::create(filepath)?;
filehandle.write_all(&filedata)?;
} else {
warn!("No atom with 'Data' found for blob {}", blob.file_name);
}
}
Ok(())
Expand All @@ -252,8 +263,37 @@ async fn main() -> Result<(), Error> {
#[cfg(test)]
mod tests
{
use std::str::FromStr;

use super::*;

//#[cfg(target_os="windows")]
#[cfg(not(target_os = "windows"))]
#[test]
fn test_assemble_unix() {
assert_eq!(
"/root/filesystem/Data/save-container.bin",
assemble_filepath(&PathBuf::from_str("/root/filesystem").unwrap(), "Data", "/saveEcontainerXbin,savedgame").as_os_str()
);
assert_eq!(
"/root/filesystem/Data/save-container.bin",
assemble_filepath(&PathBuf::from_str("/root/filesystem").unwrap(), "Data", "saveEcontainerXbin,savedgame").as_os_str()
);
}

#[cfg(target_os = "windows")]
#[test]
fn test_assemble_filepath_windows() {
assert_eq!(
"C:\\some_dir\\Data\\save-container.bin",
assemble_filepath(&PathBuf::from_str("C:\\some_dir\\").unwrap(), "Data", "/saveEcontainerXbin,savedgame").as_os_str()
);
assert_eq!(
"C:\\some_dir\\Data\\save-container.bin",
assemble_filepath(&PathBuf::from_str("C:\\some_dir\\").unwrap(), "Data", "saveEcontainerXbin,savedgame").as_os_str()
);
}

#[test]
fn deserialize_blob_response() {
let data = r#"{"blobs":[{"fileName":"save_container,savedgame","displayName":"Save Data","etag":"\"0x8DCA185D3F40E2A\"","clientFileTime":"2024-07-11T08:45:22.5700000Z","size":6745}],"pagingInfo":{"totalItems":1,"continuationToken":null}}"#;
Expand Down
20 changes: 5 additions & 15 deletions src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,9 +943,7 @@ impl XalAuthenticator {
.send()
.await?
.json_ex::<response::SisuAuthorizationResponse>()
.await
.map_err(std::convert::Into::into)
}
.await}

/// Requests a Xbox Live Device Token from the Xbox Live authentication service.
///
Expand Down Expand Up @@ -1013,9 +1011,7 @@ impl XalAuthenticator {
.send()
.await?
.json_ex::<response::DeviceToken>()
.await
.map_err(std::convert::Into::into)
}
.await}

/// Retrieves a Xbox User Token for a specified Access Token.
///
Expand Down Expand Up @@ -1090,9 +1086,7 @@ impl XalAuthenticator {
.log()
.await?
.json_ex::<response::UserToken>()
.await
.map_err(std::convert::Into::into)
}
.await}

/// Retrieves a Title Token for a specified Access Token and Device Token.
///
Expand Down Expand Up @@ -1170,9 +1164,7 @@ impl XalAuthenticator {
.log()
.await?
.json_ex::<response::TitleToken>()
.await
.map_err(std::convert::Into::into)
}
.await}

/// Authenticates with the Xbox Live service and retrieves an XSTS token.
///
Expand Down Expand Up @@ -1275,9 +1267,7 @@ impl XalAuthenticator {
.log()
.await?
.json_ex::<response::XSTSToken>()
.await
.map_err(std::convert::Into::into)
}
.await}
}

#[cfg(test)]
Expand Down
4 changes: 1 addition & 3 deletions src/request_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,7 @@ impl RequestSigner {
&request.authorization,
&request.body,
max_body_bytes,
)
.map_err(std::convert::Into::into)
}
)}

/// Create signature from low-level parts
#[allow(clippy::too_many_arguments)]
Expand Down

0 comments on commit 67e8970

Please sign in to comment.