From 9427fae170102cd3b2e2470b6a5158b9c62abe08 Mon Sep 17 00:00:00 2001 From: Bartolo <162994065+BartolomeoRusso9@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:13:30 +0100 Subject: [PATCH 1/2] #22 --- Cargo.toml | 2 +- src/install_archive.rs | 26 +++++++++++++++++++++++--- src/phone.rs | 15 +++++++++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e575650..a42b768 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] default-run = "altkeeper" name = "altkeeper" -version = "0.5.0" +version = "0.5.1" edition = "2021" description = "AltServer for iOS 17+ that runs on your home server (Docker): renews and installs sideloaded iPhone apps over Wi-Fi, no computer or VPN" license = "MIT" diff --git a/src/install_archive.rs b/src/install_archive.rs index ffad643..dcc2a0e 100644 --- a/src/install_archive.rs +++ b/src/install_archive.rs @@ -22,9 +22,14 @@ fn err(e: impl std::fmt::Display) -> String { /// On Linux, one IPA avoids thousands of remote AFC create/open/close operations. /// Windows uses the previous directory path until ZIP file permissions are verified there. pub(crate) fn use_archive_on_wifi() -> bool { - cfg!(unix) - && !std::env::var("ALTKEEPER_INSTALL_MODE") - .is_ok_and(|mode| mode.eq_ignore_ascii_case("directory")) + let mode = std::env::var("ALTKEEPER_INSTALL_MODE").unwrap_or_default(); + if mode.eq_ignore_ascii_case("directory") { + false + } else if mode.eq_ignore_ascii_case("archive") { + true // explicit opt-in, also on Windows + } else { + cfg!(unix) + } } fn permissions(metadata: &Metadata, directory: bool) -> u32 { @@ -41,6 +46,19 @@ fn permissions(metadata: &Metadata, directory: bool) -> u32 { } } +/// On Windows there are no Unix modes, so executables would be archived as 0o644 and +/// installd would reject them. Mach-O binaries (thin or fat) get 0o755. +#[cfg(not(unix))] +fn is_macho(path: &Path) -> bool { + use std::io::Read; + let mut magic = [0u8; 4]; + File::open(path).and_then(|mut f| f.read_exact(&mut magic)).is_ok() + && matches!( + u32::from_be_bytes(magic), + 0xfeedface | 0xfeedfacf | 0xcefaedfe | 0xcffaedfe | 0xcafebabe | 0xbebafeca + ) +} + fn write_tree( zip: &mut ZipWriter, local: &Path, @@ -77,6 +95,8 @@ fn write_tree( write_tree(zip, &entry.path(), &format!("{archived}/{name}"))?; } } else if metadata.is_file() { + #[cfg(not(unix))] + let mode = if is_macho(local) { 0o755 } else { mode }; zip.start_file( archived, SimpleFileOptions::default() diff --git a/src/phone.rs b/src/phone.rs index 61efce3..5685f22 100644 --- a/src/phone.rs +++ b/src/phone.rs @@ -33,6 +33,17 @@ use tokio::net::TcpStream; pub const HOST_NAME: &str = "casa2"; +/// `Display` of an isideload `Report` can be empty (the user saw "Installation failed:" with +/// nothing after it). Fall back to the Debug chain so the real reason reaches the log. +fn describe_report(e: &E) -> String { + let short = e.to_string(); + if short.trim().is_empty() { + format!("{e:?}") + } else { + format!("{short} | {e:?}") + } +} + pub fn dbg(e: E) -> String { format!("{e:?}") } @@ -405,7 +416,7 @@ impl PhoneLink { .. } => isideload::sideload::install::install_app(provider, app_path, progress) .await - .map_err(|e| format!("Installation failed: {e}")), + .map_err(|e| format!("Installation failed: {}", describe_report(&e))), Self::Rsd { adapter, hs, @@ -419,7 +430,7 @@ impl PhoneLink { } else { isideload::sideload::install::install_app_rsd(adapter, hs, app_path, progress) .await - .map_err(|e| format!("Installation failed: {e}")) + .map_err(|e| format!("Installation failed: {}", describe_report(&e))) } } } From fc74ba2a942438f595258c763a63e01b3c581e45 Mon Sep 17 00:00:00 2001 From: Bartolo <162994065+BartolomeoRusso9@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:15:21 +0100 Subject: [PATCH 2/2] Update Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 095a55f..bd6582c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ dependencies = [ [[package]] name = "altkeeper" -version = "0.5.0" +version = "0.5.1" dependencies = [ "axum", "base64 0.21.7",