Skip to content
Merged

#22 #25

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
26 changes: 23 additions & 3 deletions src/install_archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<W: Write + Seek>(
zip: &mut ZipWriter<W>,
local: &Path,
Expand Down Expand Up @@ -77,6 +95,8 @@ fn write_tree<W: Write + Seek>(
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()
Expand Down
15 changes: 13 additions & 2 deletions src/phone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: std::fmt::Display + std::fmt::Debug>(e: &E) -> String {
let short = e.to_string();
if short.trim().is_empty() {
format!("{e:?}")
} else {
format!("{short} | {e:?}")
}
}

pub fn dbg<E: std::fmt::Debug>(e: E) -> String {
format!("{e:?}")
}
Expand Down Expand Up @@ -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,
Expand All @@ -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)))
}
}
}
Expand Down
Loading