mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
Some Microsoft accounts get a delegation-compact (d=) ticket, which the
pinned Xbox backend (Nexus 0.2.8) rejected: the library stayed empty
(store-ticket-format) and downloads failed. This moves the backend to
Nexus 0.2.12, which has the upstream store-ticket fix, rebases the
DroidDeck credentials, launch-view and cloud-token patches, and strips
the d= prefix from compact JWE tickets so downloads work again.
Tested by seven users in the Discord Xbox Store Issues thread on the
pr-1729 build at d55e05a0.
* Update Xbox backend to 0.2.12
* Fix cloud offering fallback and add regression tests
* Fix Xbox authentication with compact JWE tickets
151 lines
7.6 KiB
Diff
151 lines
7.6 KiB
Diff
--- a/crates/xodus-cli/src/commands/run.rs
|
|
+++ b/crates/xodus-cli/src/commands/run.rs
|
|
@@ -146,6 +146,7 @@
|
|
launch_image: &Path,
|
|
materialize_chain: bool,
|
|
) -> (String, String) {
|
|
+ if materialize_chain { return (String::new(), String::new()); }
|
|
let source_nt = source.to_string_lossy().replace('/', "\\");
|
|
let source_nt = source_nt.trim_end_matches('\\');
|
|
let mirror_unix = mirror_nt_prefix.map(|prefix| prefix.replace('\\', "/"));
|
|
@@ -303,9 +304,7 @@
|
|
let entry_path = checked_relative_path(entry_suffix)?;
|
|
let mut decrypted = HashMap::new();
|
|
for (name, fd) in fds {
|
|
- if name.to_ascii_lowercase().ends_with(".exe") || checked_relative_path(name)? == entry_path {
|
|
- decrypted.insert(checked_relative_path(name)?, *fd);
|
|
- }
|
|
+ decrypted.insert(checked_relative_path(name)?, *fd);
|
|
}
|
|
if !decrypted.contains_key(&entry_path) {
|
|
return Err(std::io::Error::new(
|
|
@@ -325,7 +324,8 @@
|
|
}
|
|
}
|
|
let decrypted_files: HashSet<PathBuf> = decrypted.keys().cloned().collect();
|
|
- let mirror = launch_view_directory(source_root, ".xodus-chain-")?;
|
|
+ let mirror = Builder::new().prefix("droiddeck-xbox-")
|
|
+ .permissions(std::os::unix::fs::PermissionsExt::from_mode(0o700)).tempdir()?;
|
|
populate_launch_view(
|
|
source_root,
|
|
mirror.path(),
|
|
@@ -725,6 +725,22 @@
|
|
);
|
|
}
|
|
|
|
+ if wine == "droiddeck:prepare" {
|
|
+ use std::io::Write;
|
|
+ if !materialize_chain { return ExitCode::FAILURE; }
|
|
+ let ready = serde_json::json!({
|
|
+ "exe": stub_path,
|
|
+ "playfab": playfab_auth_header(client, tokens).await,
|
|
+ });
|
|
+ println!("DROIDDECK_LAUNCH_V1 {}", ready);
|
|
+ let _ = std::io::stdout().flush();
|
|
+ let mut release = String::new();
|
|
+ let _ = std::io::stdin().read_line(&mut release);
|
|
+ drop(launch_mirror);
|
|
+ cleanup().await;
|
|
+ return ExitCode::SUCCESS;
|
|
+ }
|
|
+
|
|
let mut command = Command::new(wine);
|
|
command
|
|
.arg(&stub_path)
|
|
@@ -831,21 +847,15 @@
|
|
}
|
|
|
|
#[test]
|
|
- fn full_launch_view_uses_real_executables_and_preserves_encrypted_dll_maps() {
|
|
+ fn full_launch_view_needs_no_inherited_descriptor_maps() {
|
|
let entry = String::from("\\Launcher.exe");
|
|
let child = String::from("\\Bin\\Child.ExE");
|
|
let dll = String::from("\\Bin\\Encrypted.dll");
|
|
let files = [(&entry, 10), (&child, 11), (&dll, 12)];
|
|
let (nt, unix) = launch_file_maps(&files, Path::new("/package"), Some("\\view"),
|
|
&entry, 10, Path::new("/view/Launcher.exe"), true);
|
|
- assert!(nt.contains("10:\\??\\Z:\\package\\Launcher.exe"));
|
|
- assert!(unix.contains("10:/package/Launcher.exe"));
|
|
- assert!(!nt.contains("\\view\\Launcher.exe"));
|
|
- assert!(!nt.contains("\\view\\Bin\\Child.ExE"));
|
|
- assert!(!unix.contains("/view/Launcher.exe"));
|
|
- assert!(!unix.contains("/view/Bin/Child.ExE"));
|
|
- assert!(nt.contains("12:\\??\\Z:\\view\\Bin\\Encrypted.dll"));
|
|
- assert!(unix.contains("12:/view/Bin/Encrypted.dll"));
|
|
+ assert!(nt.is_empty());
|
|
+ assert!(unix.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
@@ -945,6 +955,44 @@
|
|
}
|
|
|
|
#[test]
|
|
+ fn launch_view_materializes_encrypted_dlls_in_private_storage() {
|
|
+ let root = tempfile::tempdir().unwrap();
|
|
+ std::fs::write(root.path().join("Game.exe"), b"encrypted").unwrap();
|
|
+ std::fs::write(root.path().join("Secret.dll"), b"encrypted-dll").unwrap();
|
|
+ let mut image = tempfile::tempfile().unwrap();
|
|
+ image.write_all(&fake_pe()).unwrap();
|
|
+ let exe = String::from("Game.exe");
|
|
+ let dll = String::from("Secret.dll");
|
|
+ let files = [(&exe, image.as_raw_fd()), (&dll, image.as_raw_fd())];
|
|
+ let (view, _, count, _) = create_launch_chain_mirror(root.path(), &exe, &files).unwrap();
|
|
+ assert_eq!(count, 2);
|
|
+ assert_eq!(std::fs::read(view.path().join("Secret.dll")).unwrap(), fake_pe());
|
|
+ assert!(!view.path().join("Secret.dll").is_symlink());
|
|
+ assert_eq!(std::fs::read(root.path().join("Secret.dll")).unwrap(), b"encrypted-dll");
|
|
+ use std::os::unix::fs::PermissionsExt;
|
|
+ assert_eq!(std::fs::metadata(view.path()).unwrap().permissions().mode() & 0o077, 0);
|
|
+ }
|
|
+
|
|
+
|
|
+ #[test]
|
|
+ fn launch_view_rejects_invalid_decrypted_library_without_changing_sources() {
|
|
+ let root = tempfile::tempdir().unwrap();
|
|
+ std::fs::write(root.path().join("Game.exe"), b"encrypted executable").unwrap();
|
|
+ std::fs::write(root.path().join("Bad.dll"), b"encrypted library").unwrap();
|
|
+ let mut exe_image = tempfile::tempfile().unwrap();
|
|
+ exe_image.write_all(&fake_pe()).unwrap();
|
|
+ let mut dll_image = tempfile::tempfile().unwrap();
|
|
+ dll_image.write_all(b"not a PE library").unwrap();
|
|
+ let exe = String::from("Game.exe");
|
|
+ let dll = String::from("Bad.dll");
|
|
+ let images = [(&exe, exe_image.as_raw_fd()), (&dll, dll_image.as_raw_fd())];
|
|
+ let error = create_launch_chain_mirror(root.path(), &exe, &images).unwrap_err();
|
|
+ assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
|
|
+ assert_eq!(std::fs::read(root.path().join("Game.exe")).unwrap(), b"encrypted executable");
|
|
+ assert_eq!(std::fs::read(root.path().join("Bad.dll")).unwrap(), b"encrypted library");
|
|
+ }
|
|
+
|
|
+ #[test]
|
|
fn launch_chain_rejects_parent_path_components() {
|
|
assert!(checked_relative_path("..\\outside.exe").is_err());
|
|
}
|
|
@@ -961,16 +1009,20 @@
|
|
assert_eq!(name, "Bin\\MINECRAFT.WINDOWS.EXE");
|
|
assert!(!fcntl_getfd(file.as_fd()).unwrap().contains(FdFlags::CLOEXEC));
|
|
let dll_name = String::from("Encrypted.dll");
|
|
- let mut dll = tempfile::tempfile().unwrap(); dll.write_all(b"synthetic decrypted library").unwrap();
|
|
+ std::fs::write(root.path().join("Encrypted.dll"), b"encrypted library").unwrap();
|
|
+ let mut dll = tempfile::tempfile().unwrap(); dll.write_all(&fake_pe()).unwrap();
|
|
let images = [(&dll_name, dll.as_raw_fd()), (&name, file.as_raw_fd())];
|
|
assert!(select_launch_entry(&images[..1], Some("bin/Minecraft.Windows.exe")).is_err());
|
|
assert_eq!(select_launch_entry(&images, Some("bin/Minecraft.Windows.exe")).unwrap().1, file.as_raw_fd());
|
|
let (mirror, entry, count, bytes) = create_launch_chain_mirror(root.path(), &name, &images).unwrap();
|
|
- assert_eq!(count, 1); assert_eq!(bytes, original.len() as u64);
|
|
+ assert_eq!(count, 2); assert_eq!(bytes, 2 * original.len() as u64);
|
|
assert_eq!(std::fs::read(entry).unwrap(), original);
|
|
- let (nt, _) = launch_file_maps(&images, root.path(), Some("\\synthetic-view"), &name, file.as_raw_fd(), &mirror.path().join("Bin/MINECRAFT.WINDOWS.EXE"), true);
|
|
- assert!(nt.contains("synthetic-view\\Encrypted.dll"));
|
|
- assert!(!nt.contains("synthetic-view\\Bin\\MINECRAFT.WINDOWS.EXE"));
|
|
+ let (nt, unix) = launch_file_maps(&images, root.path(), Some("\\synthetic-view"), &name, file.as_raw_fd(), &mirror.path().join("Bin/MINECRAFT.WINDOWS.EXE"), true);
|
|
+ assert!(nt.is_empty());
|
|
+ assert!(unix.is_empty());
|
|
+ assert_eq!(std::fs::read(mirror.path().join("Encrypted.dll")).unwrap(), fake_pe());
|
|
+ assert!(!std::fs::symlink_metadata(mirror.path().join("Encrypted.dll")).unwrap().file_type().is_symlink());
|
|
+ assert_eq!(std::fs::read(root.path().join("Encrypted.dll")).unwrap(), b"encrypted library");
|
|
assert_eq!(std::fs::read(root.path().join("Bin/MINECRAFT.WINDOWS.EXE")).unwrap(), original);
|
|
assert_eq!(std::fs::read(root.path().join("MicrosoftGame.config")).unwrap(), b"metadata unchanged");
|
|
}
|