diff --git a/examples/src/bin/auth_titlehub.rs b/examples/src/bin/auth_titlehub.rs index b5b6d75..132b922 100644 --- a/examples/src/bin/auth_titlehub.rs +++ b/examples/src/bin/auth_titlehub.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; 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; @@ -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: &PathBuf, 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 tmp.starts_with(std::path::MAIN_SEPARATOR_STR) { + // Remove leading path seperator + tmp[1..].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] @@ -155,8 +169,8 @@ 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); @@ -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") @@ -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") @@ -241,8 +254,6 @@ async fn main() -> Result<(), Error> { 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(()) @@ -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}}"#;