From c9ea34a0a197a983c92efcc74d4b926f3c0decf0 Mon Sep 17 00:00:00 2001 From: nab138 Date: Sat, 14 Feb 2026 18:13:42 -0500 Subject: [PATCH] Cleanup merge leftovers --- isideload/src/application.rs | 75 --- isideload/src/bundle.rs | 189 ------- isideload/src/certificate.rs | 273 ---------- isideload/src/developer_session.rs | 805 ----------------------------- isideload/src/device.rs | 91 ---- isideload/src/sideload.rs | 473 ----------------- 6 files changed, 1906 deletions(-) delete mode 100644 isideload/src/application.rs delete mode 100644 isideload/src/bundle.rs delete mode 100644 isideload/src/certificate.rs delete mode 100644 isideload/src/developer_session.rs delete mode 100644 isideload/src/device.rs delete mode 100644 isideload/src/sideload.rs diff --git a/isideload/src/application.rs b/isideload/src/application.rs deleted file mode 100644 index 50df046..0000000 --- a/isideload/src/application.rs +++ /dev/null @@ -1,75 +0,0 @@ -// This file was made using https://github.com/Dadoum/Sideloader as a reference. - -use crate::Error; -use crate::bundle::Bundle; -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 { - if !path.exists() { - return Err(Error::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).map_err(Error::Filesystem)?; - } - std::fs::create_dir_all(&temp_path).map_err(Error::Filesystem)?; - - let file = File::open(&path).map_err(Error::Filesystem)?; - let mut archive = ZipArchive::new(file).map_err(|e| { - Error::Generic(format!("Failed to open application archive: {}", e)) - })?; - archive.extract(&temp_path).map_err(|e| { - Error::Generic(format!("Failed to extract application archive: {}", e)) - })?; - - 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) - .map_err(|e| { - Error::Generic(format!("Failed to read Payload directory: {}", e)) - })? - .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() { - return Err(Error::InvalidBundle( - "No .app directory found in Payload".to_string(), - )); - } else { - return Err(Error::InvalidBundle( - "Multiple .app directories found in Payload".to_string(), - )); - } - } else { - return Err(Error::InvalidBundle( - "No Payload directory found in the application archive".to_string(), - )); - } - } - let bundle = Bundle::new(bundle_path)?; - - Ok(Application { - bundle, /*temp_path*/ - }) - } -} diff --git a/isideload/src/bundle.rs b/isideload/src/bundle.rs deleted file mode 100644 index c8ba1b2..0000000 --- a/isideload/src/bundle.rs +++ /dev/null @@ -1,189 +0,0 @@ -// This file was made using https://github.com/Dadoum/Sideloader as a reference. - -use crate::Error; -use plist::{Dictionary, Value}; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -#[derive(Debug)] -pub struct Bundle { - pub app_info: Dictionary, - pub bundle_dir: PathBuf, - - app_extensions: Vec, - frameworks: Vec, - _libraries: Vec, -} - -impl Bundle { - pub fn new(bundle_dir: PathBuf) -> Result { - 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) - .map_err(|e| Error::InvalidBundle(format!("Failed to read Info.plist: {}", e)))?; - - let app_info = plist::from_bytes(&plist_data) - .map_err(|e| Error::InvalidBundle(format!("Failed to parse Info.plist: {}", e)))?; - - // 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) - .map_err(|e| { - Error::InvalidBundle(format!("Failed to read PlugIns directory: {}", e)) - })? - .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) - .map_err(|e| { - Error::InvalidBundle(format!("Failed to read Frameworks directory: {}", e)) - })? - .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<(), Error> { - let info_plist_path = self.bundle_dir.join("Info.plist"); - let result = plist::to_file_binary(&info_plist_path, &self.app_info); - - if let Err(e) = result { - return Err(Error::InvalidBundle(format!( - "Failed to write Info.plist: {}", - e - ))); - } - Ok(()) - } -} - -fn assert_bundle(condition: bool, msg: &str) -> Result<(), Error> { - if !condition { - Err(Error::InvalidBundle(msg.to_string())) - } else { - Ok(()) - } -} - -fn find_dylibs(dir: &Path, bundle_root: &Path) -> Result, Error> { - let mut libraries = Vec::new(); - - fn collect_dylibs( - dir: &Path, - bundle_root: &Path, - libraries: &mut Vec, - ) -> Result<(), Error> { - let entries = fs::read_dir(dir).map_err(|e| { - Error::InvalidBundle(format!("Failed to read directory {}: {}", dir.display(), e)) - })?; - - for entry in entries { - let entry = entry.map_err(|e| { - Error::InvalidBundle(format!("Failed to read directory entry: {}", e)) - })?; - - let path = entry.path(); - let file_type = entry - .file_type() - .map_err(|e| Error::InvalidBundle(format!("Failed to get file type: {}", e)))?; - - 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) -} diff --git a/isideload/src/certificate.rs b/isideload/src/certificate.rs deleted file mode 100644 index 59f8f44..0000000 --- a/isideload/src/certificate.rs +++ /dev/null @@ -1,273 +0,0 @@ -// This file was made using https://github.com/Dadoum/Sideloader as a reference. - -use hex; -use openssl::{ - hash::MessageDigest, - pkcs12::Pkcs12, - pkey::{PKey, Private}, - rsa::Rsa, - x509::{X509, X509Name, X509ReqBuilder}, -}; -use sha1::{Digest, Sha1}; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use crate::Error; -use crate::developer_session::{DeveloperDeviceType, DeveloperSession, DeveloperTeam}; - -#[derive(Debug, Clone)] -pub struct CertificateIdentity { - pub certificate: Option, - pub private_key: PKey, - pub key_file: PathBuf, - pub cert_file: PathBuf, - pub machine_name: String, - pub machine_id: String, -} - -impl CertificateIdentity { - pub async fn new( - configuration_path: &Path, - dev_session: &DeveloperSession, - apple_id: String, - machine_name: String, - ) -> Result { - let mut hasher = Sha1::new(); - hasher.update(apple_id.as_bytes()); - let hash_string = hex::encode(hasher.finalize()).to_lowercase(); - let key_path = configuration_path.join("keys").join(hash_string); - fs::create_dir_all(&key_path).map_err(Error::Filesystem)?; - - let key_file = key_path.join("key.pem"); - let cert_file = key_path.join("cert.pem"); - let teams = dev_session.list_teams().await?; - let team = teams - .first() - .ok_or(Error::Certificate("No teams found".to_string()))?; - let private_key = if key_file.exists() { - let key_data = fs::read_to_string(&key_file) - .map_err(|e| Error::Certificate(format!("Failed to read key file: {}", e)))?; - PKey::private_key_from_pem(key_data.as_bytes()) - .map_err(|e| Error::Certificate(format!("Failed to load private key: {}", e)))? - } else { - let rsa = Rsa::generate(2048) - .map_err(|e| Error::Certificate(format!("Failed to generate RSA key: {}", e)))?; - let key = PKey::from_rsa(rsa) - .map_err(|e| Error::Certificate(format!("Failed to create private key: {}", e)))?; - let pem_data = key - .private_key_to_pem_pkcs8() - .map_err(|e| Error::Certificate(format!("Failed to encode private key: {}", e)))?; - fs::write(&key_file, pem_data).map_err(Error::Filesystem)?; - key - }; - - let mut cert_identity = CertificateIdentity { - certificate: None, - private_key, - key_file, - cert_file, - machine_name, - machine_id: "".to_owned(), - }; - - if let Ok((cert, machine_id)) = cert_identity - .find_matching_certificate(dev_session, team) - .await - { - cert_identity.certificate = Some(cert.clone()); - cert_identity.machine_id = machine_id; - let cert_pem = cert.to_pem().map_err(|e| { - Error::Certificate(format!("Failed to encode certificate to PEM: {}", e)) - })?; - fs::write(&cert_identity.cert_file, cert_pem).map_err(Error::Filesystem)?; - - return Ok(cert_identity); - } - - cert_identity - .request_new_certificate(dev_session, team) - .await?; - Ok(cert_identity) - } - - async fn find_matching_certificate( - &self, - dev_session: &DeveloperSession, - team: &DeveloperTeam, - ) -> Result<(X509, String), Error> { - let certificates = dev_session - .list_all_development_certs(DeveloperDeviceType::Ios, team) - .await - .map_err(|e| Error::Certificate(format!("Failed to list certificates: {:?}", e)))?; - - let our_public_key = self - .private_key - .public_key_to_der() - .map_err(|e| Error::Certificate(format!("Failed to get public key: {}", e)))?; - - for cert in certificates - .iter() - .filter(|c| c.machine_name == self.machine_name) - { - if let Ok(x509_cert) = X509::from_der(&cert.cert_content) - && let Ok(cert_public_key) = x509_cert.public_key() - && let Ok(cert_public_key_der) = cert_public_key.public_key_to_der() - && cert_public_key_der == our_public_key - { - return Ok((x509_cert, cert.machine_id.clone())); - } - } - Err(Error::Certificate( - "No matching certificate found".to_string(), - )) - } - - async fn request_new_certificate( - &mut self, - dev_session: &DeveloperSession, - team: &DeveloperTeam, - ) -> Result<(), Error> { - let mut req_builder = X509ReqBuilder::new() - .map_err(|e| Error::Certificate(format!("Failed to create request builder: {}", e)))?; - let mut name_builder = X509Name::builder() - .map_err(|e| Error::Certificate(format!("Failed to create name builder: {}", e)))?; - - name_builder - .append_entry_by_text("C", "US") - .map_err(|e| Error::Certificate(format!("Failed to set country: {}", e)))?; - name_builder - .append_entry_by_text("ST", "STATE") - .map_err(|e| Error::Certificate(format!("Failed to set state: {}", e)))?; - name_builder - .append_entry_by_text("L", "LOCAL") - .map_err(|e| Error::Certificate(format!("Failed to set locality: {}", e)))?; - name_builder - .append_entry_by_text("O", "ORGNIZATION") - .map_err(|e| Error::Certificate(format!("Failed to set organization: {}", e)))?; - name_builder - .append_entry_by_text("CN", "CN") - .map_err(|e| Error::Certificate(format!("Failed to set common name: {}", e)))?; - - req_builder - .set_subject_name(&name_builder.build()) - .map_err(|e| Error::Certificate(format!("Failed to set subject name: {}", e)))?; - req_builder - .set_pubkey(&self.private_key) - .map_err(|e| Error::Certificate(format!("Failed to set public key: {}", e)))?; - req_builder - .sign(&self.private_key, MessageDigest::sha256()) - .map_err(|e| Error::Certificate(format!("Failed to sign request: {}", e)))?; - - let csr_pem = req_builder - .build() - .to_pem() - .map_err(|e| Error::Certificate(format!("Failed to encode CSR: {}", e)))?; - - let certificate_id = dev_session - .submit_development_csr( - DeveloperDeviceType::Ios, - team, - String::from_utf8_lossy(&csr_pem).to_string(), - self.machine_name.clone(), - ) - .await - .map_err(|e| { - let is_7460 = match &e { - Error::DeveloperSession(code, _) => *code == 7460, - _ => false, - }; - if is_7460 { - Error::Certificate("You have too many certificates!".to_string()) - } else { - Error::Certificate(format!("Failed to submit CSR: {:?}", e)) - } - })?; - - let certificates = dev_session - .list_all_development_certs(DeveloperDeviceType::Ios, team) - .await?; - - let apple_cert = certificates - .iter() - .find(|cert| cert.certificate_id == certificate_id) - .ok_or(Error::Certificate( - "Certificate not found after submission".to_string(), - ))?; - - let certificate = X509::from_der(&apple_cert.cert_content) - .map_err(|e| Error::Certificate(format!("Failed to parse certificate: {}", e)))?; - - // Write certificate to disk - let cert_pem = certificate.to_pem().map_err(|e| { - Error::Certificate(format!("Failed to encode certificate to PEM: {}", e)) - })?; - fs::write(&self.cert_file, cert_pem).map_err(Error::Filesystem)?; - - self.certificate = Some(certificate); - self.machine_id = apple_cert.machine_id.clone(); - - Ok(()) - } - - pub fn get_certificate_file_path(&self) -> &Path { - &self.cert_file - } - - pub fn get_private_key_file_path(&self) -> &Path { - &self.key_file - } - - pub fn get_serial_number(&self) -> Result { - let cert = match &self.certificate { - Some(c) => c, - None => { - return Err(Error::Certificate( - "No certificate available to get serial number".to_string(), - )); - } - }; - let num = cert - .serial_number() - .to_bn() - .map_err(|e| { - Error::Certificate(format!("Failed to convert serial number to bn: {}", e)) - })? - .to_hex_str() - .map_err(|e| { - Error::Certificate(format!( - "Failed to convert serial number to hex string: {}", - e - )) - })? - .to_string(); - - Ok(num.trim_start_matches("0").to_string()) - } - - pub fn to_pkcs12(&self, password: &str) -> Result, Error> { - let cert = match &self.certificate { - Some(c) => c, - None => { - return Err(Error::Certificate( - "No certificate available to create PKCS#12".to_string(), - )); - } - }; - - let mut pkcs12_builder = Pkcs12::builder(); - pkcs12_builder.pkey(&self.private_key); - pkcs12_builder.cert(cert); - pkcs12_builder.name("certificate"); - let pkcs12 = pkcs12_builder - .build2(password) - .map_err(|e| Error::Certificate(format!("Failed to create PKCS#12 bundle: {}", e)))?; - - let der_bytes = pkcs12 - .to_der() - .map_err(|e| Error::Certificate(format!("Failed to encode PKCS#12 to DER: {}", e)))?; - - Ok(der_bytes) - } -} diff --git a/isideload/src/developer_session.rs b/isideload/src/developer_session.rs deleted file mode 100644 index 2d2296e..0000000 --- a/isideload/src/developer_session.rs +++ /dev/null @@ -1,805 +0,0 @@ -// This file was made using https://github.com/Dadoum/Sideloader as a reference for the apple private endpoints - -use crate::{Error, obf}; -use icloud_auth::AppleAccount; -use idevice::pretty_print_dictionary; -use plist::{Date, Dictionary, Value}; -use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use uuid::Uuid; - -use std::str::FromStr; - -pub struct DeveloperSession { - pub account: Arc, - team: Option, -} - -impl DeveloperSession { - pub fn new(account: Arc) -> Self { - DeveloperSession { - account, - team: None, - } - } - - pub async fn send_developer_request( - &self, - url: &str, - body: Option, - ) -> Result { - let mut request = Dictionary::new(); - request.insert( - "clientId".to_string(), - Value::String(obf!("XABBG36SBA").to_string()), - ); - request.insert( - "protocolVersion".to_string(), - Value::String(obf!("QH65B2").to_string()), - ); - request.insert( - "requestId".to_string(), - Value::String(Uuid::new_v4().to_string().to_uppercase()), - ); - request.insert( - "userLocale".to_string(), - Value::Array(vec![Value::String("en_US".to_string())]), - ); - if let Some(body) = body { - for (key, value) in body { - request.insert(key, value); - } - } - - let response = self - .account - .send_request(url, Some(request)) - .await - .map_err(Error::ICloudError)?; - - let status_code = response - .get("resultCode") - .and_then(|v| v.as_unsigned_integer()) - .unwrap_or(0); - if status_code != 0 { - let description = response - .get("userString") - .and_then(|v| v.as_string()) - .or_else(|| response.get("resultString").and_then(|v| v.as_string())) - .unwrap_or("(null)"); - return Err(Error::DeveloperSession( - status_code as i64, - description.to_string(), - )); - } - Ok(response) - } - - pub async fn list_teams(&self) -> Result, Error> { - let url = obf!( - "https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA" - ); - let response = self - .send_developer_request(url.to_string().as_str(), None) - .await?; - - let teams = response - .get("teams") - .and_then(|v| v.as_array()) - .ok_or(Error::Parse("teams".to_string()))?; - - let mut result = Vec::new(); - for team in teams { - let dict = team - .as_dictionary() - .ok_or(Error::Parse("team".to_string()))?; - let name = dict - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let team_id = dict - .get("teamId") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("teamId".to_string()))? - .to_string(); - result.push(DeveloperTeam { - _name: name, - team_id, - }); - } - Ok(result) - } - - pub async fn get_team(&self) -> Result { - if let Some(team) = &self.team { - return Ok(team.clone()); - } - let teams = self.list_teams().await?; - if teams.is_empty() { - return Err(Error::DeveloperSession( - -1, - "No developer teams found".to_string(), - )); - } - // TODO: Handle multiple teams - Ok(teams[0].clone()) - } - - pub fn set_team(&mut self, team: DeveloperTeam) { - self.team = Some(team); - } - - pub async fn list_devices( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - ) -> Result, Error> { - let url = dev_url(device_type, obf!("listDevices")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - let response = self.send_developer_request(&url, Some(body)).await?; - - let devices = response - .get("devices") - .and_then(|v| v.as_array()) - .ok_or(Error::Parse("devices".to_string()))?; - - let mut result = Vec::new(); - for device in devices { - let dict = device - .as_dictionary() - .ok_or(Error::Parse("device".to_string()))?; - let device_id = dict - .get("deviceId") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("deviceId".to_string()))? - .to_string(); - let name = dict - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let device_number = dict - .get("deviceNumber") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("deviceNumber".to_string()))? - .to_string(); - result.push(DeveloperDevice { - _device_id: device_id, - _name: name, - device_number, - }); - } - Ok(result) - } - - pub async fn add_device( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - device_name: &str, - udid: &str, - ) -> Result { - let url = dev_url(device_type, obf!("addDevice")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert("name".to_string(), Value::String(device_name.to_string())); - body.insert("deviceNumber".to_string(), Value::String(udid.to_string())); - - let response = self.send_developer_request(&url, Some(body)).await?; - - let device_dict = response - .get("device") - .and_then(|v| v.as_dictionary()) - .ok_or(Error::Parse("device".to_string()))?; - - let device_id = device_dict - .get("deviceId") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("deviceId".to_string()))? - .to_string(); - let name = device_dict - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let device_number = device_dict - .get("deviceNumber") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("deviceNumber".to_string()))? - .to_string(); - - Ok(DeveloperDevice { - _device_id: device_id, - _name: name, - device_number, - }) - } - - pub async fn list_all_development_certs( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - ) -> Result, Error> { - let url = dev_url(device_type, obf!("listAllDevelopmentCerts")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - - let response = self.send_developer_request(&url, Some(body)).await?; - - let certs = response - .get("certificates") - .and_then(|v| v.as_array()) - .ok_or(Error::Parse("certificates".to_string()))?; - - let mut result = Vec::new(); - for cert in certs { - let dict = cert - .as_dictionary() - .ok_or(Error::Parse("certificate".to_string()))?; - let name = dict - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let certificate_id = dict - .get("certificateId") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("certificateId".to_string()))? - .to_string(); - let serial_number = dict - .get("serialNumber") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("serialNumber".to_string()))? - .to_string(); - let machine_name = match dict.get("machineName").and_then(|v| v.as_string()) { - Some(name) => name.to_string(), - None => "".to_string(), - }; - - let machine_id = match dict.get("machineId").and_then(|v| v.as_string()) { - Some(id) => Ok(id.to_string()), - None => Err(Error::Parse(format!( - "machineId {:?}", - pretty_print_dictionary(dict) - ))), - }?; - - let cert_content = dict - .get("certContent") - .and_then(|v| v.as_data()) - .ok_or(Error::Parse("certContent".to_string()))? - .to_vec(); - - result.push(DevelopmentCertificate { - name, - certificate_id, - serial_number, - machine_name, - machine_id, - cert_content, - }); - } - Ok(result) - } - - pub async fn revoke_development_cert( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - serial_number: &str, - ) -> Result<(), Error> { - let url = dev_url(device_type, obf!("revokeDevelopmentCert")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert( - "serialNumber".to_string(), - Value::String(serial_number.to_string()), - ); - - self.send_developer_request(&url, Some(body)).await?; - Ok(()) - } - - pub async fn submit_development_csr( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - csr_content: String, - machine_name: String, - ) -> Result { - let url = dev_url(device_type, obf!("submitDevelopmentCSR")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert("csrContent".to_string(), Value::String(csr_content)); - body.insert( - "machineId".to_string(), - Value::String(uuid::Uuid::new_v4().to_string().to_uppercase()), - ); - body.insert("machineName".to_string(), Value::String(machine_name)); - - let response = self.send_developer_request(&url, Some(body)).await?; - let cert_dict = response - .get("certRequest") - .and_then(|v| v.as_dictionary()) - .ok_or(Error::Parse("certRequest".to_string()))?; - let id = cert_dict - .get("certRequestId") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("certRequestId".to_string()))? - .to_string(); - - Ok(id) - } - - pub async fn list_app_ids( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - ) -> Result { - let url = dev_url(device_type, obf!("listAppIds")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - - let response = self.send_developer_request(&url, Some(body)).await?; - - let app_ids = response - .get("appIds") - .and_then(|v| v.as_array()) - .ok_or(Error::Parse("appIds".to_string()))?; - - let mut result = Vec::new(); - for app_id in app_ids { - let dict = app_id - .as_dictionary() - .ok_or(Error::Parse("appId".to_string()))?; - let name = dict - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let app_id_id = dict - .get("appIdId") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("appIdId".to_string()))? - .to_string(); - let identifier = dict - .get("identifier") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("identifier".to_string()))? - .to_string(); - let features = dict - .get("features") - .and_then(|v| v.as_dictionary()) - .ok_or(Error::Parse("features".to_string()))?; - let expiration_date = if dict.contains_key("expirationDate") { - Some( - dict.get("expirationDate") - .and_then(|v| v.as_date()) - .ok_or(Error::Parse("expirationDate".to_string()))?, - ) - } else { - None - }; - - result.push(AppId { - name, - app_id_id, - identifier, - features: features.clone(), - expiration_date, - }); - } - - let max_quantity = if response.contains_key("maxQuantity") { - Some( - response - .get("maxQuantity") - .and_then(|v| v.as_unsigned_integer()) - .ok_or(Error::Parse("maxQuantity".to_string()))?, - ) - } else { - None - }; - - let available_quantity = if response.contains_key("availableQuantity") { - Some( - response - .get("availableQuantity") - .and_then(|v| v.as_unsigned_integer()) - .ok_or(Error::Parse("availableQuantity".to_string()))?, - ) - } else { - None - }; - - Ok(ListAppIdsResponse { - app_ids: result, - max_quantity, - available_quantity, - }) - } - - pub async fn add_app_id( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - name: &str, - identifier: &str, - ) -> Result<(), Error> { - let url = dev_url(device_type, obf!("addAppId")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert("name".to_string(), Value::String(name.to_string())); - body.insert( - "identifier".to_string(), - Value::String(identifier.to_string()), - ); - - self.send_developer_request(&url, Some(body)).await?; - - Ok(()) - } - - pub async fn update_app_id( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - app_id: &AppId, - features: &Dictionary, - ) -> Result { - let url = dev_url(device_type, obf!("updateAppId")); - let mut body = Dictionary::new(); - body.insert( - "appIdId".to_string(), - Value::String(app_id.app_id_id.clone()), - ); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - - for (key, value) in features { - body.insert(key.clone(), value.clone()); - } - - let response = self.send_developer_request(&url, Some(body)).await?; - let cert_dict = response - .get("appId") - .and_then(|v| v.as_dictionary()) - .ok_or(Error::Parse("appId".to_string()))?; - let feats = cert_dict - .get("features") - .and_then(|v| v.as_dictionary()) - .ok_or(Error::Parse("features".to_string()))?; - - Ok(feats.clone()) - } - - pub async fn delete_app_id( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - app_id_id: String, - ) -> Result<(), Error> { - let url = dev_url(device_type, obf!("deleteAppId")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert("appIdId".to_string(), Value::String(app_id_id.clone())); - - self.send_developer_request(&url, Some(body)).await?; - - Ok(()) - } - - pub async fn list_application_groups( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - ) -> Result, Error> { - let url = dev_url(device_type, obf!("listApplicationGroups")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - - let response = self.send_developer_request(&url, Some(body)).await?; - - let app_groups = response - .get("applicationGroupList") - .and_then(|v| v.as_array()) - .ok_or(Error::Parse("applicationGroupList".to_string()))?; - - let mut result = Vec::new(); - for app_group in app_groups { - let dict = app_group - .as_dictionary() - .ok_or(Error::Parse("applicationGroup".to_string()))?; - let application_group = dict - .get("applicationGroup") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("applicationGroup".to_string()))? - .to_string(); - let name = dict - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let identifier = dict - .get("identifier") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("identifier".to_string()))? - .to_string(); - - result.push(ApplicationGroup { - application_group, - _name: name, - identifier, - }); - } - - Ok(result) - } - - pub async fn add_application_group( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - group_identifier: &str, - name: &str, - ) -> Result { - let url = dev_url(device_type, obf!("addApplicationGroup")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert("name".to_string(), Value::String(name.to_string())); - body.insert( - "identifier".to_string(), - Value::String(group_identifier.to_string()), - ); - - let response = self.send_developer_request(&url, Some(body)).await?; - let app_group_dict = response - .get("applicationGroup") - .and_then(|v| v.as_dictionary()) - .ok_or(Error::Parse("applicationGroup".to_string()))?; - let application_group = app_group_dict - .get("applicationGroup") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("applicationGroup".to_string()))? - .to_string(); - let name = app_group_dict - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let identifier = app_group_dict - .get("identifier") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("identifier".to_string()))? - .to_string(); - - Ok(ApplicationGroup { - application_group, - _name: name, - identifier, - }) - } - - pub async fn assign_application_group_to_app_id( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - app_id: &AppId, - app_group: &ApplicationGroup, - ) -> Result<(), Error> { - let url = dev_url(device_type, obf!("assignApplicationGroupToAppId")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert( - "appIdId".to_string(), - Value::String(app_id.app_id_id.clone()), - ); - body.insert( - "applicationGroups".to_string(), - Value::String(app_group.application_group.clone()), - ); - - self.send_developer_request(&url, Some(body)).await?; - - Ok(()) - } - - pub async fn download_team_provisioning_profile( - &self, - device_type: DeveloperDeviceType, - team: &DeveloperTeam, - app_id: &AppId, - ) -> Result { - let url = dev_url(device_type, obf!("downloadTeamProvisioningProfile")); - let mut body = Dictionary::new(); - body.insert("teamId".to_string(), Value::String(team.team_id.clone())); - body.insert( - "appIdId".to_string(), - Value::String(app_id.app_id_id.clone()), - ); - - let response = self.send_developer_request(&url, Some(body)).await?; - - let profile = response - .get("provisioningProfile") - .and_then(|v| v.as_dictionary()) - .ok_or(Error::Parse("provisioningProfile".to_string()))?; - let name = profile - .get("name") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("name".to_string()))? - .to_string(); - let provisioning_profile_id = profile - .get("provisioningProfileId") - .and_then(|v| v.as_string()) - .ok_or(Error::Parse("provisioningProfileId".to_string()))? - .to_string(); - let encoded_profile = profile - .get("encodedProfile") - .and_then(|v| v.as_data()) - .ok_or(Error::Parse("encodedProfile".to_string()))? - .to_vec(); - - Ok(ProvisioningProfile { - _name: name, - _provisioning_profile_id: provisioning_profile_id, - encoded_profile, - }) - } - - // This is jank and I will do it better in the rewrite - pub async fn add_increased_memory_limit( - &self, - team: &DeveloperTeam, - app_id: &AppId, - ) -> Result<(), Error> { - let spd = self.account.spd.as_ref().unwrap(); - let app_token = self - .account - .get_app_token("com.apple.gs.xcode.auth") - .await?; - let valid_anisette = self.account.get_anisette().await; - - let mut headers = HeaderMap::new(); - headers.insert( - "Content-Type", - HeaderValue::from_static("application/vnd.api+json"), - ); - headers.insert( - "Accept", - HeaderValue::from_static("application/vnd.api+json"), - ); - headers.insert("Accept-Language", HeaderValue::from_static("en-us")); - headers.insert("User-Agent", HeaderValue::from_str("Xcode").unwrap()); - headers.insert( - HeaderName::from_str("X-Apple-I-Identity-Id").unwrap(), - HeaderValue::from_str(spd.get("adsid").unwrap().as_string().unwrap()).unwrap(), - ); - headers.insert( - HeaderName::from_str("X-Apple-GS-Token").unwrap(), - HeaderValue::from_str(&app_token.auth_token).unwrap(), - ); - - for (k, v) in valid_anisette.generate_headers(false, true, true) { - headers.insert( - HeaderName::from_bytes(k.as_bytes()).unwrap(), - HeaderValue::from_str(&v).unwrap(), - ); - } - - if let Ok(locale) = valid_anisette.get_header("x-apple-locale") { - headers.insert( - HeaderName::from_str("X-Apple-Locale").unwrap(), - HeaderValue::from_str(&locale).unwrap(), - ); - } - - let response = self - .account - .client - .patch(format!( - "https://developerservices2.apple.com/services/v1/bundleIds/{}", - app_id.app_id_id - )) - .headers(headers) - .body(format!( - "{{\"data\":{{\"relationships\":{{\"bundleIdCapabilities\":{{\"data\":[{{\"relationships\":{{\"capability\":{{\"data\":{{\"id\":\"INCREASED_MEMORY_LIMIT\",\"type\":\"capabilities\"}}}}}},\"type\":\"bundleIdCapabilities\",\"attributes\":{{\"settings\":[],\"enabled\":true}}}}]}}}},\"id\":\"{}\",\"attributes\":{{\"hasExclusiveManagedCapabilities\":false,\"teamId\":\"{}\",\"bundleType\":\"bundle\",\"identifier\":\"{}\",\"seedId\":\"{}\",\"name\":\"{}\"}},\"type\":\"bundleIds\"}}}}", - app_id.app_id_id, team.team_id, app_id.identifier, team.team_id, app_id.name - )) - .send() - .await - .map_err(|e| Error::ICloudError(icloud_auth::Error::ReqwestError(e)))?; - - let _response = response - .text() - .await - .map_err(|e| Error::ICloudError(icloud_auth::Error::ReqwestError(e)))?; - - //println!("Response: {:?}", response); - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub enum DeveloperDeviceType { - Any, - Ios, - Tvos, - Watchos, -} - -impl DeveloperDeviceType { - pub fn url_segment(&self) -> &'static str { - match self { - DeveloperDeviceType::Any => "", - DeveloperDeviceType::Ios => "ios/", - DeveloperDeviceType::Tvos => "tvos/", - DeveloperDeviceType::Watchos => "watchos/", - } - } -} - -fn dev_url(device_type: DeveloperDeviceType, endpoint: &str) -> String { - format!( - "{}{}{}{}", - obf!("https://developerservices2.apple.com/services/QH65B2/"), - device_type.url_segment(), - endpoint, - obf!(".action?clientId=XABBG36SBA") - ) -} - -#[derive(Debug, Clone)] -pub struct DeveloperDevice { - pub _device_id: String, - pub _name: String, - pub device_number: String, -} - -#[derive(Debug, Clone)] -pub struct DeveloperTeam { - pub _name: String, - pub team_id: String, -} - -#[derive(Debug, Clone)] -pub struct DevelopmentCertificate { - pub name: String, - pub certificate_id: String, - pub serial_number: String, - pub machine_name: String, - pub machine_id: String, - pub cert_content: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AppId { - pub app_id_id: String, - pub identifier: String, - pub name: String, - pub features: Dictionary, - pub expiration_date: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ListAppIdsResponse { - pub app_ids: Vec, - pub max_quantity: Option, - pub available_quantity: Option, -} - -#[derive(Debug, Clone)] -pub struct ApplicationGroup { - pub application_group: String, - pub _name: String, - pub identifier: String, -} - -#[derive(Debug, Clone)] -pub struct ProvisioningProfile { - pub _provisioning_profile_id: String, - pub _name: String, - pub encoded_profile: Vec, -} diff --git a/isideload/src/device.rs b/isideload/src/device.rs deleted file mode 100644 index 81a2570..0000000 --- a/isideload/src/device.rs +++ /dev/null @@ -1,91 +0,0 @@ -use idevice::{ - IdeviceService, afc::AfcClient, installation_proxy::InstallationProxyClient, - provider::IdeviceProvider, -}; -use std::pin::Pin; -use std::{future::Future, path::Path}; - -use crate::Error; - -/// Installs an ***already signed*** app onto your device. -/// To sign and install an app, see [`crate::sideload::sideload_app`] -pub async fn install_app( - provider: &impl IdeviceProvider, - app_path: &Path, - progress_callback: impl Fn(u64), -) -> Result<(), Error> { - let mut afc_client = AfcClient::connect(provider) - .await - .map_err(Error::IdeviceError)?; - - let dir = format!( - "PublicStaging/{}", - app_path.file_name().unwrap().to_string_lossy() - ); - afc_upload_dir(&mut afc_client, app_path, &dir).await?; - - let mut instproxy_client = InstallationProxyClient::connect(provider) - .await - .map_err(Error::IdeviceError)?; - - let mut options = plist::Dictionary::new(); - options.insert("PackageType".to_string(), "Developer".into()); - instproxy_client - .install_with_callback( - dir, - Some(plist::Value::Dictionary(options)), - async |(percentage, _)| { - progress_callback(percentage); - }, - (), - ) - .await - .map_err(Error::IdeviceError)?; - - Ok(()) -} - -fn afc_upload_dir<'a>( - afc_client: &'a mut AfcClient, - path: &'a Path, - afc_path: &'a str, -) -> Pin> + Send + 'a>> { - Box::pin(async move { - let entries = std::fs::read_dir(path).map_err(Error::Filesystem)?; - afc_client - .mk_dir(afc_path) - .await - .map_err(Error::IdeviceError)?; - for entry in entries { - let entry = entry.map_err(Error::Filesystem)?; - let path = entry.path(); - if path.is_dir() { - let new_afc_path = format!( - "{}/{}", - afc_path, - path.file_name().unwrap().to_string_lossy() - ); - afc_upload_dir(afc_client, &path, &new_afc_path).await?; - } else { - let mut file_handle = afc_client - .open( - format!( - "{}/{}", - afc_path, - path.file_name().unwrap().to_string_lossy() - ), - idevice::afc::opcode::AfcFopenMode::WrOnly, - ) - .await - .map_err(Error::IdeviceError)?; - let bytes = std::fs::read(&path).map_err(Error::Filesystem)?; - file_handle - .write_entire(&bytes) - .await - .map_err(Error::IdeviceError)?; - file_handle.close().await.map_err(Error::IdeviceError)?; - } - } - Ok(()) - }) -} diff --git a/isideload/src/sideload.rs b/isideload/src/sideload.rs deleted file mode 100644 index 5b4e93c..0000000 --- a/isideload/src/sideload.rs +++ /dev/null @@ -1,473 +0,0 @@ -// This file was made using https://github.com/Dadoum/Sideloader as a reference. - -use idevice::IdeviceService; -use idevice::lockdown::LockdownClient; -use idevice::provider::IdeviceProvider; -use zsign_rust::ZSignOptions; - -use crate::application::Application; -use crate::device::install_app; -use crate::{DeveloperTeam, Error, SideloadConfiguration, SideloadLogger}; -use crate::{ - certificate::CertificateIdentity, - developer_session::{DeveloperDeviceType, DeveloperSession}, -}; -use std::{io::Write, path::PathBuf}; - -fn error_and_return(logger: &dyn SideloadLogger, error: Error) -> Result<(), Error> { - logger.error(&error); - Err(error) -} - -/// Signs and installs an `.ipa` or `.app` onto a device. -/// -/// # Arguments -/// - `device_provider` - [`idevice::provider::IdeviceProvider`] for the device -/// - `dev_session` - Authenticated Apple developer session ([`crate::developer_session::DeveloperSession`]). -/// - `app_path` - Path to the `.ipa` file or `.app` bundle to sign and install -/// - `config` - Sideload configuration options ([`crate::SideloadConfiguration`]) -pub async fn sideload_app( - device_provider: &impl IdeviceProvider, - dev_session: &DeveloperSession, - app_path: PathBuf, - config: SideloadConfiguration<'_>, -) -> Result<(), Error> { - let logger = config.logger; - let mut lockdown_client = match LockdownClient::connect(device_provider).await { - Ok(l) => l, - Err(e) => { - return error_and_return(logger, Error::IdeviceError(e)); - } - }; - - if let Ok(pairing_file) = device_provider.get_pairing_file().await { - lockdown_client - .start_session(&pairing_file) - .await - .map_err(Error::IdeviceError)?; - } - - let device_name = lockdown_client - .get_value(Some("DeviceName"), None) - .await - .map_err(Error::IdeviceError)? - .as_string() - .ok_or(Error::Generic( - "Failed to convert DeviceName to string".to_string(), - ))? - .to_string(); - - let device_uuid = lockdown_client - .get_value(Some("UniqueDeviceID"), None) - .await - .map_err(Error::IdeviceError)? - .as_string() - .ok_or(Error::Generic( - "Failed to convert UniqueDeviceID to string".to_string(), - ))? - .to_string(); - - let team = match dev_session.get_team().await { - Ok(t) => t, - Err(e) => { - return error_and_return(logger, e); - } - }; - - logger.log("Successfully retrieved team"); - - ensure_device_registered(logger, dev_session, &team, &device_uuid, &device_name).await?; - - let cert = match CertificateIdentity::new( - &config.store_dir, - dev_session, - dev_session.account.apple_id.clone(), - config.machine_name, - ) - .await - { - Ok(c) => c, - Err(e) => { - return error_and_return(logger, e); - } - }; - - logger.log("Successfully acquired certificate"); - - let mut list_app_id_response = match dev_session - .list_app_ids(DeveloperDeviceType::Ios, &team) - .await - { - Ok(ids) => ids, - Err(e) => { - return error_and_return(logger, e); - } - }; - - let mut app = Application::new(app_path)?; - - let is_sidestore = app.bundle.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore"; - let is_lc_and_sidestore = app - .bundle - .frameworks() - .iter() - .any(|f| f.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore"); - - let main_app_bundle_id = match app.bundle.bundle_identifier() { - Some(id) => id.to_string(), - None => { - return error_and_return( - logger, - Error::InvalidBundle("No bundle identifier found in IPA".to_string()), - ); - } - }; - let main_app_id_str = format!("{}.{}", main_app_bundle_id, team.team_id); - let main_app_name = match app.bundle.bundle_name() { - Some(name) => name.to_string(), - None => { - return error_and_return( - logger, - Error::InvalidBundle("No bundle name found in IPA".to_string()), - ); - } - }; - - let extensions = app.bundle.app_extensions_mut(); - // for each extension, ensure it has a unique bundle identifier that starts with the main app's bundle identifier - for ext in extensions.iter_mut() { - if let Some(id) = ext.bundle_identifier() { - if !(id.starts_with(&main_app_bundle_id) && id.len() > main_app_bundle_id.len()) { - return error_and_return( - logger, - Error::InvalidBundle(format!( - "Extension {} is not part of the main app bundle identifier: {}", - ext.bundle_name().unwrap_or("Unknown"), - id - )), - ); - } else { - ext.set_bundle_identifier(&format!( - "{}{}", - main_app_id_str, - &id[main_app_bundle_id.len()..] - )); - } - } - } - app.bundle.set_bundle_identifier(&main_app_id_str); - - let extension_refs: Vec<_> = app.bundle.app_extensions().iter().collect(); - let mut bundles_with_app_id = vec![&app.bundle]; - bundles_with_app_id.extend(extension_refs); - - let app_ids_to_register = bundles_with_app_id - .iter() - .filter(|bundle| { - let bundle_id = bundle.bundle_identifier().unwrap_or(""); - !list_app_id_response - .app_ids - .iter() - .any(|app_id| app_id.identifier == bundle_id) - }) - .collect::>(); - - if let Some(available) = list_app_id_response.available_quantity - && app_ids_to_register.len() > available.try_into().unwrap() - { - return error_and_return( - logger, - Error::InvalidBundle(format!( - "This app requires {} app ids, but you only have {} available", - app_ids_to_register.len(), - available - )), - ); - } - - for bundle in app_ids_to_register { - let id = bundle.bundle_identifier().unwrap_or(""); - let name = bundle.bundle_name().unwrap_or(""); - if let Err(e) = dev_session - .add_app_id(DeveloperDeviceType::Ios, &team, name, id) - .await - { - return error_and_return(logger, e); - } - } - list_app_id_response = match dev_session - .list_app_ids(DeveloperDeviceType::Ios, &team) - .await - { - Ok(ids) => ids, - Err(e) => { - return error_and_return(logger, e); - } - }; - - let mut app_ids: Vec<_> = list_app_id_response - .app_ids - .into_iter() - .filter(|app_id| { - bundles_with_app_id - .iter() - .any(|bundle| app_id.identifier == bundle.bundle_identifier().unwrap_or("")) - }) - .collect(); - let main_app_id = match app_ids - .iter() - .find(|app_id| app_id.identifier == main_app_id_str) - .cloned() - { - Some(id) => id, - None => { - return error_and_return( - logger, - Error::Generic(format!( - "Main app ID {} not found in registered app IDs", - main_app_id_str - )), - ); - } - }; - - logger.log("Successfully registered app IDs"); - - for app_id in app_ids.iter_mut() { - let app_group_feature_enabled = app_id - .features - .get( - "APG3427HIY", /* Gotta love apple and their magic strings! */ - ) - .and_then(|v| v.as_boolean()) - .ok_or(Error::Generic( - "App group feature not found in app id".to_string(), - ))?; - if !app_group_feature_enabled { - let mut body = plist::Dictionary::new(); - body.insert("APG3427HIY".to_string(), plist::Value::Boolean(true)); - let new_features = match dev_session - .update_app_id(DeveloperDeviceType::Ios, &team, app_id, &body) - .await - { - Ok(new_feats) => new_feats, - Err(e) => { - return error_and_return(logger, e); - } - }; - app_id.features = new_features; - } - - if config.add_increased_memory_limit { - dev_session - .add_increased_memory_limit(&team, app_id) - .await?; - } - } - - let group_identifier = format!( - "group.{}", - if is_lc_and_sidestore { - format!("com.SideStore.SideStore.{}", team.team_id) - } else { - main_app_id_str.clone() - } - ); - - if is_sidestore || is_lc_and_sidestore { - app.bundle.app_info.insert( - "ALTAppGroups".to_string(), - plist::Value::Array(vec![plist::Value::String(group_identifier.clone())]), - ); - - let target_bundle = if is_lc_and_sidestore { - app.bundle - .frameworks_mut() - .iter_mut() - .find(|fw| fw.bundle_identifier().unwrap_or("") == "com.SideStore.SideStore") - } else { - Some(&mut app.bundle) - }; - - if let Some(target_bundle) = target_bundle { - target_bundle.app_info.insert( - "ALTCertificateID".to_string(), - plist::Value::String(cert.get_serial_number().unwrap()), - ); - - match cert.to_pkcs12(&cert.machine_id) { - Ok(p12_bytes) => { - let alt_cert_path = target_bundle.bundle_dir.join("ALTCertificate.p12"); - - let mut file = - std::fs::File::create(&alt_cert_path).map_err(Error::Filesystem)?; - file.write_all(&p12_bytes).map_err(Error::Filesystem)?; - } - Err(e) => return error_and_return(logger, e), - } - } - } - - let app_groups = match dev_session - .list_application_groups(DeveloperDeviceType::Ios, &team) - .await - { - Ok(groups) => groups, - Err(e) => { - return error_and_return(logger, e); - } - }; - - let matching_app_groups = app_groups - .iter() - .filter(|group| group.identifier == group_identifier.clone()) - .collect::>(); - - let app_group = if matching_app_groups.is_empty() { - match dev_session - .add_application_group( - DeveloperDeviceType::Ios, - &team, - &group_identifier, - &main_app_name, - ) - .await - { - Ok(group) => group, - Err(e) => { - return error_and_return(logger, e); - } - } - } else { - matching_app_groups[0].clone() - }; - - //let mut provisioning_profiles: HashMap = HashMap::new(); - for app_id in app_ids { - let assign_res = dev_session - .assign_application_group_to_app_id( - DeveloperDeviceType::Ios, - &team, - &app_id, - &app_group, - ) - .await; - if assign_res.is_err() { - return error_and_return(logger, assign_res.err().unwrap()); - } - // let provisioning_profile = match account - // // This doesn't seem right to me, but it's what Sideloader does... Shouldn't it be downloading the provisioning profile for this app ID, not the main? - // .download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id) - // .await - // { - // Ok(pp /* tee hee */) => pp, - // Err(e) => { - // return emit_error_and_return( - // &window, - // &format!("Failed to download provisioning profile: {:?}", e), - // ); - // } - // }; - // provisioning_profiles.insert(app_id.identifier.clone(), provisioning_profile); - } - - logger.log("Successfully registered app groups"); - - let provisioning_profile = match dev_session - .download_team_provisioning_profile(DeveloperDeviceType::Ios, &team, &main_app_id) - .await - { - Ok(pp /* tee hee */) => pp, - Err(e) => { - return error_and_return(logger, e); - } - }; - - let profile_path = config - .store_dir - .join(format!("{}.mobileprovision", main_app_id_str)); - - if profile_path.exists() { - std::fs::remove_file(&profile_path).map_err(Error::Filesystem)?; - } - - let mut file = std::fs::File::create(&profile_path).map_err(Error::Filesystem)?; - file.write_all(&provisioning_profile.encoded_profile) - .map_err(Error::Filesystem)?; - - // Without this, zsign complains it can't find the provision file - #[cfg(target_os = "windows")] - { - file.sync_all().map_err(|e| Error::Filesystem(e))?; - drop(file); - } - - app.bundle.write_info()?; - for ext in app.bundle.app_extensions_mut() { - ext.write_info()?; - } - for ext in app.bundle.frameworks_mut() { - ext.write_info()?; - } - - match ZSignOptions::new(app.bundle.bundle_dir.to_str().unwrap()) - .with_cert_file(cert.get_certificate_file_path().to_str().unwrap()) - .with_pkey_file(cert.get_private_key_file_path().to_str().unwrap()) - .with_prov_file(profile_path.to_str().unwrap()) - .with_force() - .with_disable_cache() - .sign() - { - Ok(_) => {} - Err(e) => { - return error_and_return(logger, Error::ZSignError(e)); - } - }; - - logger.log("App signed!"); - - logger.log("Installing app (Transfer)... 0%"); - - let res = install_app(device_provider, &app.bundle.bundle_dir, |percentage| { - logger.log(&format!("Installing app... {}%", percentage)); - }) - .await; - if let Err(e) = res { - return error_and_return(logger, e); - } - - if config.revoke_cert { - dev_session - .revoke_development_cert(DeveloperDeviceType::Ios, &team, &cert.get_serial_number()?) - .await?; - logger.log("Certificate revoked"); - } - - Ok(()) -} - -pub async fn ensure_device_registered( - logger: &dyn SideloadLogger, - dev_session: &DeveloperSession, - team: &DeveloperTeam, - uuid: &str, - name: &str, -) -> Result<(), Error> { - let devices = dev_session - .list_devices(DeveloperDeviceType::Ios, team) - .await; - if let Err(e) = devices { - return error_and_return(logger, e); - } - let devices = devices.unwrap(); - if !devices.iter().any(|d| d.device_number == uuid) { - logger.log("Device not found in your account"); - // TODO: Actually test! - dev_session - .add_device(DeveloperDeviceType::Ios, team, name, uuid) - .await?; - logger.log("Successfully added device to your account"); - } - logger.log("Device is a development device"); - Ok(()) -}