mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 06:26: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 = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1288,6 +1289,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"uuid",
|
||||
"x509-certificate",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2740,6 +2742,12 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3015e6ce46d5ad8751e4a772543a30c7511468070e98e64e20165f8f81155b64"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
@@ -3605,8 +3613,40 @@ dependencies = [
|
||||
"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]]
|
||||
name = "zmij"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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)
|
||||
x509-certificate = "0.25"
|
||||
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}")]
|
||||
DeveloperError(i64, String),
|
||||
|
||||
#[error("Invalid bundle: {0}")]
|
||||
InvalidBundle(String),
|
||||
}
|
||||
|
||||
// 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 bundle;
|
||||
pub mod cert_identity;
|
||||
pub mod sideloader;
|
||||
pub use builder::{SideloaderBuilder, TeamSelection};
|
||||
|
||||
@@ -4,7 +4,10 @@ use crate::{
|
||||
devices::DevicesApi,
|
||||
teams::{DeveloperTeam, TeamsApi},
|
||||
},
|
||||
sideload::{TeamSelection, builder::MaxCertsBehavior, cert_identity::CertificateIdentity},
|
||||
sideload::{
|
||||
TeamSelection, application::Application, builder::MaxCertsBehavior,
|
||||
cert_identity::CertificateIdentity,
|
||||
},
|
||||
util::{device::IdeviceInfo, storage::SideloadingStorage},
|
||||
};
|
||||
|
||||
@@ -69,10 +72,10 @@ impl Sideloader {
|
||||
)
|
||||
.await?;
|
||||
|
||||
// info!(
|
||||
// "Using certificate for machine {} with ID {}",
|
||||
// cert_identity.machine_name, cert_identity.machine_id
|
||||
// );
|
||||
let mut app = Application::new(app_path)?;
|
||||
|
||||
let is_sidestore = app.is_sidestore();
|
||||
let is_lc_and_sidestore = app.is_lc_and_sidestore();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user