mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 14:36:16 +01:00
add bundle and application structs
This commit is contained in:
40
Cargo.lock
generated
40
Cargo.lock
generated
@@ -704,6 +704,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
|
"zlib-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1288,6 +1289,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
"x509-certificate",
|
"x509-certificate",
|
||||||
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2740,6 +2742,12 @@ dependencies = [
|
|||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typed-path"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3015e6ce46d5ad8751e4a772543a30c7511468070e98e64e20165f8f81155b64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
@@ -3605,8 +3613,40 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "7.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc12baa6db2b15a140161ce53d72209dacea594230798c24774139b54ecaa980"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"flate2",
|
||||||
|
"indexmap",
|
||||||
|
"memchr",
|
||||||
|
"typed-path",
|
||||||
|
"zopfli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zlib-rs"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.19"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
|
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zopfli"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"crc32fast",
|
||||||
|
"log",
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|||||||
@@ -49,4 +49,5 @@ keyring = { version = "3.6.3", features = ["apple-native", "linux-native-sync-pe
|
|||||||
# TODO: Fork to update dependencies (doubt it will ever be updated)
|
# TODO: Fork to update dependencies (doubt it will ever be updated)
|
||||||
x509-certificate = "0.25"
|
x509-certificate = "0.25"
|
||||||
rcgen = { version = "0.14.7", default-features = false, features = ["aws_lc_rs", "pem"] }
|
rcgen = { version = "0.14.7", default-features = false, features = ["aws_lc_rs", "pem"] }
|
||||||
p12-keystore = { optional = true, version = "0.2.0" }
|
p12-keystore = { optional = true, version = "0.2.0" }
|
||||||
|
zip = { version = "7.4", default-features = false, features = ["deflate"] }
|
||||||
@@ -22,6 +22,9 @@ pub enum SideloadError {
|
|||||||
|
|
||||||
#[error("Developer error {0}: {1}")]
|
#[error("Developer error {0}: {1}")]
|
||||||
DeveloperError(i64, String),
|
DeveloperError(i64, String),
|
||||||
|
|
||||||
|
#[error("Invalid bundle: {0}")]
|
||||||
|
InvalidBundle(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default reqwest error formatter sucks and provides no info
|
// The default reqwest error formatter sucks and provides no info
|
||||||
|
|||||||
86
isideload/src/sideload/application.rs
Normal file
86
isideload/src/sideload/application.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
|
||||||
|
// I'm planning on redoing this later to better handle entitlements, extensions, etc, but it will do for now
|
||||||
|
|
||||||
|
use crate::SideloadError;
|
||||||
|
use crate::sideload::bundle::Bundle;
|
||||||
|
use rootcause::prelude::*;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
pub struct Application {
|
||||||
|
pub bundle: Bundle,
|
||||||
|
//pub temp_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application {
|
||||||
|
pub fn new(path: PathBuf) -> Result<Self, Report> {
|
||||||
|
if !path.exists() {
|
||||||
|
bail!(SideloadError::InvalidBundle(
|
||||||
|
"Application path does not exist".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bundle_path = path.clone();
|
||||||
|
//let mut temp_path = PathBuf::new();
|
||||||
|
|
||||||
|
if path.is_file() {
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
let temp_path = temp_dir
|
||||||
|
.join(path.file_name().unwrap().to_string_lossy().to_string() + "_extracted");
|
||||||
|
if temp_path.exists() {
|
||||||
|
std::fs::remove_dir_all(&temp_path)
|
||||||
|
.context("Failed to remove existing temporary directory")?;
|
||||||
|
}
|
||||||
|
std::fs::create_dir_all(&temp_path).context("Failed to create temporary directory")?;
|
||||||
|
|
||||||
|
let file = File::open(&path).context("Failed to open application archive")?;
|
||||||
|
let mut archive =
|
||||||
|
ZipArchive::new(file).context("Failed to open application archive")?;
|
||||||
|
archive
|
||||||
|
.extract(&temp_path)
|
||||||
|
.context("Failed to extract application archive")?;
|
||||||
|
|
||||||
|
let payload_folder = temp_path.join("Payload");
|
||||||
|
if payload_folder.exists() && payload_folder.is_dir() {
|
||||||
|
let app_dirs: Vec<_> = std::fs::read_dir(&payload_folder)
|
||||||
|
.context("Failed to read Payload directory")?
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
|
||||||
|
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "app"))
|
||||||
|
.collect();
|
||||||
|
if app_dirs.len() == 1 {
|
||||||
|
bundle_path = app_dirs[0].path();
|
||||||
|
} else if app_dirs.is_empty() {
|
||||||
|
bail!(SideloadError::InvalidBundle(
|
||||||
|
"No .app directory found in Payload".to_string(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
bail!(SideloadError::InvalidBundle(
|
||||||
|
"Multiple .app directories found in Payload".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!(SideloadError::InvalidBundle(
|
||||||
|
"No Payload directory found in the application archive".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let bundle = Bundle::new(bundle_path)?;
|
||||||
|
|
||||||
|
Ok(Application {
|
||||||
|
bundle, /*temp_path*/
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_sidestore(&self) -> bool {
|
||||||
|
self.bundle.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_lc_and_sidestore(&self) -> bool {
|
||||||
|
self.bundle
|
||||||
|
.frameworks()
|
||||||
|
.iter()
|
||||||
|
.any(|f| f.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore")
|
||||||
|
}
|
||||||
|
}
|
||||||
190
isideload/src/sideload/bundle.rs
Normal file
190
isideload/src/sideload/bundle.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
// This file was made using https://github.com/Dadoum/Sideloader as a reference.
|
||||||
|
// I'm planning on redoing this later to better handle entitlements, extensions, etc, but it will do for now
|
||||||
|
|
||||||
|
use plist::{Dictionary, Value};
|
||||||
|
use rootcause::prelude::*;
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::SideloadError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Bundle {
|
||||||
|
pub app_info: Dictionary,
|
||||||
|
pub bundle_dir: PathBuf,
|
||||||
|
|
||||||
|
app_extensions: Vec<Bundle>,
|
||||||
|
frameworks: Vec<Bundle>,
|
||||||
|
_libraries: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bundle {
|
||||||
|
pub fn new(bundle_dir: PathBuf) -> Result<Self, Report> {
|
||||||
|
let mut bundle_path = bundle_dir;
|
||||||
|
// Remove trailing slash/backslash
|
||||||
|
if let Some(path_str) = bundle_path.to_str()
|
||||||
|
&& (path_str.ends_with('/') || path_str.ends_with('\\'))
|
||||||
|
{
|
||||||
|
bundle_path = PathBuf::from(&path_str[..path_str.len() - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let info_plist_path = bundle_path.join("Info.plist");
|
||||||
|
assert_bundle(
|
||||||
|
info_plist_path.exists(),
|
||||||
|
&format!("No Info.plist here: {}", info_plist_path.display()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let plist_data = fs::read(&info_plist_path).context(SideloadError::InvalidBundle(
|
||||||
|
"Failed to read Info.plist".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let app_info = plist::from_bytes(&plist_data).context(SideloadError::InvalidBundle(
|
||||||
|
"Failed to parse Info.plist".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// Load app extensions from PlugIns directory
|
||||||
|
let plug_ins_dir = bundle_path.join("PlugIns");
|
||||||
|
let app_extensions = if plug_ins_dir.exists() {
|
||||||
|
fs::read_dir(&plug_ins_dir)
|
||||||
|
.context(SideloadError::InvalidBundle(
|
||||||
|
"Failed to read PlugIns directory".to_string(),
|
||||||
|
))?
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| {
|
||||||
|
entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false)
|
||||||
|
&& entry.path().join("Info.plist").exists()
|
||||||
|
})
|
||||||
|
.filter_map(|entry| Bundle::new(entry.path()).ok())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load frameworks from Frameworks directory
|
||||||
|
let frameworks_dir = bundle_path.join("Frameworks");
|
||||||
|
let frameworks = if frameworks_dir.exists() {
|
||||||
|
fs::read_dir(&frameworks_dir)
|
||||||
|
.context(SideloadError::InvalidBundle(
|
||||||
|
"Failed to read Frameworks directory".to_string(),
|
||||||
|
))?
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| {
|
||||||
|
entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false)
|
||||||
|
&& entry.path().join("Info.plist").exists()
|
||||||
|
})
|
||||||
|
.filter_map(|entry| Bundle::new(entry.path()).ok())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find all .dylib files in the bundle directory (recursive)
|
||||||
|
let libraries = find_dylibs(&bundle_path, &bundle_path)?;
|
||||||
|
|
||||||
|
Ok(Bundle {
|
||||||
|
app_info,
|
||||||
|
bundle_dir: bundle_path,
|
||||||
|
app_extensions,
|
||||||
|
frameworks,
|
||||||
|
_libraries: libraries,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_bundle_identifier(&mut self, id: &str) {
|
||||||
|
self.app_info.insert(
|
||||||
|
"CFBundleIdentifier".to_string(),
|
||||||
|
Value::String(id.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bundle_identifier(&self) -> Option<&str> {
|
||||||
|
self.app_info
|
||||||
|
.get("CFBundleIdentifier")
|
||||||
|
.and_then(|v| v.as_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bundle_name(&self) -> Option<&str> {
|
||||||
|
self.app_info
|
||||||
|
.get("CFBundleName")
|
||||||
|
.and_then(|v| v.as_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn app_extensions(&self) -> &[Bundle] {
|
||||||
|
&self.app_extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn app_extensions_mut(&mut self) -> &mut [Bundle] {
|
||||||
|
&mut self.app_extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frameworks(&self) -> &[Bundle] {
|
||||||
|
&self.frameworks
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frameworks_mut(&mut self) -> &mut [Bundle] {
|
||||||
|
&mut self.frameworks
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_info(&self) -> Result<(), Report> {
|
||||||
|
let info_plist_path = self.bundle_dir.join("Info.plist");
|
||||||
|
plist::to_file_binary(&info_plist_path, &self.app_info).context(
|
||||||
|
SideloadError::InvalidBundle("Failed to write Info.plist".to_string()),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_bundle(condition: bool, msg: &str) -> Result<(), Report> {
|
||||||
|
if !condition {
|
||||||
|
bail!(SideloadError::InvalidBundle(msg.to_string()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_dylibs(dir: &Path, bundle_root: &Path) -> Result<Vec<String>, Report> {
|
||||||
|
let mut libraries = Vec::new();
|
||||||
|
|
||||||
|
fn collect_dylibs(
|
||||||
|
dir: &Path,
|
||||||
|
bundle_root: &Path,
|
||||||
|
libraries: &mut Vec<String>,
|
||||||
|
) -> Result<(), Report> {
|
||||||
|
let entries = fs::read_dir(dir).context(SideloadError::InvalidBundle(format!(
|
||||||
|
"Failed to read directory {}",
|
||||||
|
dir.display()
|
||||||
|
)))?;
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.context(SideloadError::InvalidBundle(
|
||||||
|
"Failed to read directory entry".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let path = entry.path();
|
||||||
|
let file_type = entry.file_type().context(SideloadError::InvalidBundle(
|
||||||
|
"Failed to get file type".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
if file_type.is_file() {
|
||||||
|
if let Some(name) = path.file_name().and_then(|n| n.to_str())
|
||||||
|
&& name.ends_with(".dylib")
|
||||||
|
{
|
||||||
|
// Get relative path from bundle root
|
||||||
|
if let Ok(relative_path) = path.strip_prefix(bundle_root)
|
||||||
|
&& let Some(relative_str) = relative_path.to_str()
|
||||||
|
{
|
||||||
|
libraries.push(relative_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if file_type.is_dir() {
|
||||||
|
collect_dylibs(&path, bundle_root, libraries)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_dylibs(dir, bundle_root, &mut libraries)?;
|
||||||
|
Ok(libraries)
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
pub mod application;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
|
pub mod bundle;
|
||||||
pub mod cert_identity;
|
pub mod cert_identity;
|
||||||
pub mod sideloader;
|
pub mod sideloader;
|
||||||
pub use builder::{SideloaderBuilder, TeamSelection};
|
pub use builder::{SideloaderBuilder, TeamSelection};
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ use crate::{
|
|||||||
devices::DevicesApi,
|
devices::DevicesApi,
|
||||||
teams::{DeveloperTeam, TeamsApi},
|
teams::{DeveloperTeam, TeamsApi},
|
||||||
},
|
},
|
||||||
sideload::{TeamSelection, builder::MaxCertsBehavior, cert_identity::CertificateIdentity},
|
sideload::{
|
||||||
|
TeamSelection, application::Application, builder::MaxCertsBehavior,
|
||||||
|
cert_identity::CertificateIdentity,
|
||||||
|
},
|
||||||
util::{device::IdeviceInfo, storage::SideloadingStorage},
|
util::{device::IdeviceInfo, storage::SideloadingStorage},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,10 +72,10 @@ impl Sideloader {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// info!(
|
let mut app = Application::new(app_path)?;
|
||||||
// "Using certificate for machine {} with ID {}",
|
|
||||||
// cert_identity.machine_name, cert_identity.machine_id
|
let is_sidestore = app.is_sidestore();
|
||||||
// );
|
let is_lc_and_sidestore = app.is_lc_and_sidestore();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user