diff --git a/package.json b/package.json
index 35db268..511a0e4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "iloader",
"private": true,
- "version": "2.2.6",
+ "version": "2.2.20",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index e5570c7..e575182 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -268,6 +268,25 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "async-dnssd"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d49ffe175ab45bbfd74b548313d9d7cdfff27161a94b007b52eeeb5f9aaa15e"
+dependencies = [
+ "bitflags 1.3.2",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-util",
+ "libc",
+ "log",
+ "pin-utils",
+ "pkg-config",
+ "tokio",
+ "winapi",
+]
+
[[package]]
name = "async-executor"
version = "1.14.0"
@@ -1213,34 +1232,12 @@ dependencies = [
"crossbeam-utils",
]
-[[package]]
-name = "crossbeam-queue"
-version = "0.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
-dependencies = [
- "crossbeam-utils",
-]
-
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
-[[package]]
-name = "crossfire"
-version = "2.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd901251b9b46c1752c85edfee0aee718c03a85a065f4126d32e5d6d419edf48"
-dependencies = [
- "crossbeam-queue",
- "crossbeam-utils",
- "enum_dispatch",
- "futures-core",
- "parking_lot",
-]
-
[[package]]
name = "crypto-bigint"
version = "0.5.5"
@@ -1878,18 +1875,6 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
-[[package]]
-name = "enum_dispatch"
-version = "0.3.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
-dependencies = [
- "once_cell",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
[[package]]
name = "enumflags2"
version = "0.7.12"
@@ -2071,6 +2056,17 @@ dependencies = [
"zlib-rs",
]
+[[package]]
+name = "flume"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin",
+]
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -2991,15 +2987,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idevice"
-version = "0.1.58"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "066aab61e525fdc6181ae32bf88d535a1eae8462e3e190516ec15a863f5926cd"
+version = "0.1.65"
dependencies = [
"aes 0.8.4",
"async-stream",
"async_zip",
"base64 0.22.1",
- "byteorder",
"cbc 0.1.2",
"chacha20poly1305",
"chrono",
@@ -3010,7 +3003,7 @@ dependencies = [
"idevice-srp",
"indexmap 2.13.1",
"jktcp",
- "json",
+ "libc",
"plist",
"plist-macro",
"rand 0.10.0",
@@ -3020,11 +3013,13 @@ dependencies = [
"serde_json",
"sha1 0.10.6",
"sha2 0.10.9",
+ "siphasher 1.0.2",
"thiserror 2.0.18",
"tokio",
"tokio-rustls",
"tracing",
"uuid",
+ "web-time",
"x25519-dalek",
"x509-cert 0.2.5",
]
@@ -3064,15 +3059,27 @@ dependencies = [
"icu_properties",
]
+[[package]]
+name = "if-addrs"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "iloader"
-version = "2.2.6"
+version = "2.2.20"
dependencies = [
+ "async-dnssd",
"chrono",
"futures",
"idevice",
"isideload",
"keyring",
+ "mdns-sd",
"once_cell",
"plist",
"plist-macro",
@@ -3208,8 +3215,6 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "isideload"
version = "0.2.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bdc9de61cd3845098801de35c642c41f59dd463f8f147c89042b85b803eba2"
dependencies = [
"aes 0.9.0",
"aes-gcm",
@@ -3486,15 +3491,17 @@ dependencies = [
[[package]]
name = "jktcp"
-version = "0.1.1"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a5e6f49d25ffd9617fde3b1f01b6bc349497affef78d5fc67f5b0f0b4b3924c"
+checksum = "54408d8a86952b9f1cc009782ad7bbb4007a7caaba0599db441e0eae8785288c"
dependencies = [
- "crossfire",
"futures",
+ "getrandom 0.3.4",
"rand 0.9.2",
"tokio",
"tracing",
+ "wasm-bindgen-futures",
+ "wasmtimer",
]
[[package]]
@@ -3563,12 +3570,6 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "json"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
-
[[package]]
name = "json-patch"
version = "3.0.1"
@@ -3844,6 +3845,21 @@ dependencies = [
"digest 0.10.7",
]
+[[package]]
+name = "mdns-sd"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18148fee27e99e76dbf6e137f27727113d31f766e578d1b93a93c3615fca7081"
+dependencies = [
+ "fastrand",
+ "flume",
+ "if-addrs",
+ "log",
+ "mio",
+ "socket-pktinfo",
+ "socket2",
+]
+
[[package]]
name = "memchr"
version = "2.8.0"
@@ -3894,6 +3910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
+ "log",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.61.2",
]
@@ -4745,6 +4762,12 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
[[package]]
name = "piper"
version = "0.2.5"
@@ -6358,6 +6381,17 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "socket-pktinfo"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f"
+dependencies = [
+ "libc",
+ "socket2",
+ "windows-sys 0.60.2",
+]
+
[[package]]
name = "socket2"
version = "0.6.3"
@@ -6421,6 +6455,9 @@ name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
[[package]]
name = "spki"
@@ -7990,6 +8027,19 @@ dependencies = [
"semver",
]
+[[package]]
+name = "wasmtimer"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b"
+dependencies = [
+ "futures",
+ "js-sys",
+ "parking_lot",
+ "pin-utils",
+ "wasm-bindgen",
+]
+
[[package]]
name = "web-sys"
version = "0.3.94"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index da28f91..c84706e 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iloader"
-version = "2.2.6"
+version = "2.2.20"
description = "User-friendly sideloader"
authors = ["nab138"]
edition = "2024"
@@ -22,7 +22,7 @@ tauri = { version = "2", features = ["devtools"] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
-idevice = { version = "0.1.57", features = ["usbmuxd", "house_arrest", "afc", "core_device_proxy", "remote_pairing", "tcp", "tunnel_tcp_stack", "xpc", "rsd", "pair"] }
+idevice = { version = "0.1.65", features = ["usbmuxd", "house_arrest", "afc", "installation_proxy", "core_device_proxy", "remote_pairing", "tcp", "tunnel_tcp_stack", "xpc", "rsd", "pair"] }
isideload = { version = "0.2.22", features = ["fs-storage"] }
keyring = { version = "3.6.3", features = ["apple-native", "windows-native", "linux-native-sync-persistent"] }
tauri-plugin-store = "2"
@@ -44,5 +44,29 @@ rootcause = "0.12.1"
thiserror = "2.0.18"
strum = {version = "0.28.0", features = ["derive"] }
+# Vision Pro discovery. On macOS we go through the system Bonjour daemon
+# (dns_sd / mDNSResponder) — raw-multicast mDNS is silently dropped by macOS 15+
+# Local Network privacy when the OS fails to attribute it (no prompt, no Settings
+# entry). Elsewhere there is no such gatekeeper and mdns-sd works fine.
+[target.'cfg(target_os = "macos")'.dependencies]
+async-dnssd = "0.5.1"
+
+[target.'cfg(not(target_os = "macos"))'.dependencies]
+mdns-sd = "0.19"
+
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"
+
+# Tolerate Apple "device already registered" (developer error 35) during sideload,
+# which fires for a Vision Pro registered under visionOS while isideload queries the
+# iOS device list. Upstream isideload already carries this fix on git; the published
+# 0.2.22 does not, so we vendor a patched copy in-repo (vendor/isideload) with the
+# same one-function change. Replace with the upstream release once published.
+[patch.crates-io]
+isideload = { path = "vendor/isideload" }
+# Vision Pro first-time pairing: published idevice hardcodes PIN "000000" when the
+# device answers `awaitingUserConsent` (a Trust-dialog assumption). A Vision Pro
+# answers that whenever its code screen isn't up yet, so every first pairing attempt
+# failed with "missing server proof in SRP response". The vendored copy waits for the
+# pairing data and asks the callback for the real on-screen code instead.
+idevice = { path = "vendor/idevice" }
diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist
new file mode 100644
index 0000000..cf0e20d
--- /dev/null
+++ b/src-tauri/Info.plist
@@ -0,0 +1,17 @@
+
+
+
+
+
+ NSLocalNetworkUsageDescription
+ iloader finds your Apple Vision Pro on your local network to pair with it and install apps over Wi-Fi.
+ NSBonjourServices
+
+ _remotepairing._tcp
+ _remotepairing-manual-pairing._tcp
+
+
+
diff --git a/src-tauri/src/device.rs b/src-tauri/src/device.rs
index c62e6ca..10f841b 100644
--- a/src-tauri/src/device.rs
+++ b/src-tauri/src/device.rs
@@ -10,7 +10,17 @@ use serde::{Deserialize, Serialize};
use tauri::{AppHandle, State};
use tokio_util::sync::CancellationToken;
-use crate::{error::AppError, pairing::pairing_file};
+use crate::{error::AppError, pairing::pairing_file, vision};
+
+/// How a device is reached. Usbmux (iPhone/iPad over USB or Wi-Fi via usbmuxd) is the
+/// default; Vision is an Apple Vision Pro reached over an RP tunnel (not usbmux).
+#[derive(Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Default, Debug)]
+#[serde(rename_all = "lowercase")]
+pub enum DeviceTransport {
+ #[default]
+ Usbmux,
+ Vision,
+}
#[derive(Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
@@ -20,6 +30,27 @@ pub struct DeviceInfo {
pub udid: String,
pub connection_type: String,
pub version: String,
+ /// Lockdown `DeviceClass` (e.g. "iPhone", "iPad", "RealityDevice"). Optional
+ /// because not every device reports it as a string; a missing value must not
+ /// drop the device from the list.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub device_class: Option,
+ /// Lockdown `ProductType` (e.g. "iPhone14,5", "RealityDevice17,1"). Optional
+ /// for the same reason as `device_class`.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub product_type: Option,
+ /// Transport used to reach the device. Defaults to usbmux for backwards
+ /// compatibility with any persisted iOS device.
+ #[serde(default)]
+ pub transport: DeviceTransport,
+ /// The Vision Pro's IP address (for the RP tunnel). Only set for `Vision`.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub ip: Option,
+ /// For a `Vision` device: whether a reusable pairing file is already stored (so
+ /// the frontend can select it directly instead of prompting for the headset
+ /// code). Ignored for usbmux devices.
+ #[serde(default)]
+ pub paired: bool,
}
#[derive(Deserialize, Serialize, Clone)]
@@ -33,7 +64,31 @@ pub type DeviceInfoMutex = Mutex