respect env var "USBMUXD_PAIRING_FILES_LOCATION", fix get_pairing_files on macos, fix device cleanup bugs

This commit is contained in:
uncor3
2026-04-02 14:59:18 -07:00
parent d38f1e7175
commit 491f1af88c
9 changed files with 224 additions and 196 deletions
+23 -14
View File
@@ -38,7 +38,27 @@ AppContext::AppContext(QObject *parent) : QObject{parent}
void AppContext::cachePairedDevices()
{
#ifndef __APPLE__
m_pairingFileCache = core->get_pairing_files();
#else
QMap<QString, QString> maybeStalePairingFiles =
SettingsManager::sharedInstance()->getAllIdeviceDefaultPairingFiles();
for (const QString &mac : maybeStalePairingFiles.keys()) {
const QString path = maybeStalePairingFiles.value(mac);
qDebug() << "Using pairing file" << path << "for MAC:" << mac
<< "cached from settings";
m_pairingFileCache[mac] = QVariant(path);
}
QMap<QString, QVariant> fresh = core->get_pairing_files();
for (const QString &mac : fresh.keys()) {
const QString path = fresh.value(mac).toString();
qDebug() << "Using fresh pairing file" << path << "for MAC:" << mac
<< "from backend";
m_pairingFileCache[mac] = QVariant(path);
}
#endif
}
void AppContext::addDevice(iDescriptor::Uniq uniq,
@@ -361,22 +381,11 @@ void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device)
qDebug() << "No pairing file cached for device with MAC:"
<< device.macAddress
<< "Emitting noPairingFileForWirelessDevice event";
emitNoPairingFileForWirelessDevice(device.macAddress);
emit noPairingFileForWirelessDevice(device.macAddress);
return;
}
core->init_wireless_device(device.address,
LOCKDOWN_PATH + QString("/") + pairing_file,
device.macAddress);
}
void AppContext::emitNoPairingFileForWirelessDevice(const QString &udid)
{
emit noPairingFileForWirelessDevice(udid);
}
void AppContext::emitInitStarted(const QString &macAddress)
{
emit initStarted(macAddress);
core->init_wireless_device(device.address, pairing_file, device.macAddress);
emit initStarted(device.macAddress);
}
void AppContext::handlePairing(iDescriptor::Uniq uniq, bool timeout)
-2
View File
@@ -57,8 +57,6 @@ public:
const DeviceSelection &getCurrentDeviceSelection() const;
const iDescriptorDevice *
getDeviceByMacAddress(const QString &macAddress) const;
void emitNoPairingFileForWirelessDevice(const QString &udid);
void emitInitStarted(const QString &macAddress);
private:
QMap<QString, std::shared_ptr<iDescriptorDevice>> m_devices;
+1 -23
View File
@@ -411,29 +411,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
return;
}
qDebug() << "Trying to add network device with MAC:"
<< device.macAddress;
QString pairing_file =
AppContext::sharedInstance()->getCachedPairingFile(
device.macAddress);
if (pairing_file.isEmpty()) {
qDebug() << "No pairing file cached for device with MAC:"
<< device.macAddress
<< "Emitting noPairingFileForWirelessDevice event";
AppContext::sharedInstance()
->emitNoPairingFileForWirelessDevice(device.macAddress);
return;
}
qDebug() << "Found cached pairing file for device with MAC:"
<< device.macAddress << "IP:" << device.address
<< "Initializing wireless connection";
AppContext::sharedInstance()->core->init_wireless_device(
device.address, LOCKDOWN_PATH + QString("/") + pairing_file,
device.macAddress);
AppContext::sharedInstance()->emitInitStarted(device.macAddress);
AppContext::sharedInstance()->tryToConnectToNetworkDevice(device);
});
}
+6
View File
@@ -165,9 +165,15 @@ NetworkDevicesToConnectWidget::NetworkDevicesToConnectWidget(QWidget *parent)
updateDeviceList();
// in case the backend fails to find pairing file
connect(AppContext::sharedInstance()->core, &CXX::Core::no_pairing_file,
this,
&NetworkDevicesToConnectWidget::onNoPairingFileForWirelessDevice);
connect(AppContext::sharedInstance(),
&AppContext::noPairingFileForWirelessDevice, this,
&NetworkDevicesToConnectWidget::onNoPairingFileForWirelessDevice);
connect(AppContext::sharedInstance()->core, &CXX::Core::init_failed, this,
&NetworkDevicesToConnectWidget::onDeviceInitFailed);
connect(AppContext::sharedInstance(), &AppContext::initStarted, this,
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int kind; // 1 = connected, 2 = disconnected, 3 = pairing pending, 4 =
// pairing failed
char *udid;
} IdeviceEvent;
typedef void (*IdeviceEventCallback)(const IdeviceEvent *event);
void idevice_event_subscribe(IdeviceEventCallback cb);
#ifdef __cplusplus
}
#endif
+7 -9
View File
@@ -1,14 +1,12 @@
use crate::{APP_DEVICE_STATE, RUNTIME, VIDEO_STREAMS, afc, run_sync, utils};
use cxx_qt::{CxxQtType, Threading};
use cxx_qt_lib::{QByteArray, QMap, QMapPair_QString_QVariant, QString};
use idevice::{
afc::{AfcClient, opcode::AfcFopenMode},
};
use idevice::afc::{AfcClient, opcode::AfcFopenMode};
use std::pin::Pin;
use std::sync::Arc;
use tokio::{
io::{AsyncReadExt},
io::AsyncReadExt,
net::TcpListener,
sync::{Mutex, oneshot},
};
@@ -239,11 +237,11 @@ impl qobject::HauseArrest {
fn start_video_stream(&self, file_path: &QString) -> QString {
let afc_opt = self.rust().afc_handle.clone();
let Some(afc) = afc_opt else {
eprintln!("HouseArrest: AfcClient not initialized");
return QString::default();
};
let Some(afc) = afc_opt else {
eprintln!("HouseArrest: AfcClient not initialized");
return QString::default();
};
let udid_str = self.udid.to_string();
let path_str = file_path.to_string();
+77 -62
View File
@@ -18,8 +18,8 @@ use std::future::Future;
use std::sync::mpsc;
use std::thread;
use tokio::runtime::{Builder, Runtime};
use tokio::task::JoinHandle;
use tokio::sync::oneshot;
use tokio::task::JoinHandle;
use core::pin::Pin;
use cxx_qt::Threading;
@@ -27,16 +27,14 @@ use cxx_qt_lib::{QMap, QMapPair_QString_QVariant, QString, QVariant};
use crate::qobject::Core;
use once_cell::sync::Lazy;
use plist::{Value};
mod afc_services;
mod image_loader;
mod service_manager;
mod screenshot;
mod utils;
mod hause_arrest;
use plist::Value;
mod afc;
mod afc_services;
mod hause_arrest;
mod io_manager;
mod screenshot;
mod service_manager;
mod utils;
const POSSIBLE_ROOT: &str = "../../../../";
const APP_LABEL: &str = "iDescriptor";
@@ -80,7 +78,6 @@ static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
static VIDEO_STREAMS: Lazy<std::sync::Mutex<HashMap<String, oneshot::Sender<()>>>> =
Lazy::new(|| std::sync::Mutex::new(HashMap::new()));
pub fn run_sync<F, R>(fut: F) -> R
where
F: Future<Output = R> + Send + 'static,
@@ -145,7 +142,7 @@ mod qobject {
pub struct RCore;
impl qobject::Core {
fn init(self: Pin<&mut Self>) {
fn init(self: Pin<&mut Self>) {
self.listen();
}
@@ -396,8 +393,11 @@ impl qobject::Core {
emit_connected(qt_thread.clone(), udid).await;
});
}
/* DISCONNECTED */
Ok(UsbmuxdListenEvent::Disconnected(device_id)) => {
if let Some(udid) = device_map.remove(&device_id) {
clean_device_from_app_state(&udid).await;
let qt_thread = qt_t.clone();
qt_thread
.queue(move |Core_qobj| {
@@ -506,8 +506,9 @@ impl qobject::Core {
fn get_pairing_files(self: Pin<&mut Self>) -> QMap<QMapPair_QString_QVariant> {
let mut map = QMap::<QMapPair_QString_QVariant>::default();
let paths =
match std::fs::read_dir(get_lockdown_path()) {
#[cfg(not(target_os = "macos"))]
{
let paths = match std::fs::read_dir(utils::get_lockdown_path()) {
Ok(iter) => iter
.filter_map(|entry| {
let entry = entry.ok()?;
@@ -518,49 +519,75 @@ impl qobject::Core {
Err(_) => Vec::new(),
};
paths.into_iter().for_each(|path| {
if let Ok(pf) = PairingFile::read_from_file(&path) {
if let Some(fname) = path.file_name().and_then(|s| s.to_str()) {
paths.into_iter().for_each(|path| {
if let Ok(pf) = PairingFile::read_from_file(&path) {
let abs = path.canonicalize().unwrap_or(path);
let abs_str = abs.to_string_lossy().to_string();
map.insert(
QString::from(pf.wifi_mac_address),
QVariant::from(&QString::from(fname)),
QVariant::from(&QString::from(abs_str)),
);
}
});
}
#[cfg(target_os = "macos")]
{
let entries: Vec<(String, String)> = run_sync(async {
let mut out = Vec::new();
if let Ok(mut uc) = UsbmuxdConnection::default().await {
if let Ok(devs) = uc.get_devices().await {
for dev in devs {
if let Ok(pair_rec) = uc.get_pair_record(&dev.udid).await {
out.push((
pair_rec.wifi_mac_address.clone(),
format!("{}.plist", dev.udid),
));
}
}
}
}
out
});
let base = utils::get_lockdown_path();
// turn $UDID.plist into /var/db/lockdown/UDID.plist
for (wifi_mac, file_name) in entries {
let full_path = base.join(&file_name);
let full_path_str = full_path.to_string_lossy().into_owned();
map.insert(
QString::from(wifi_mac),
QVariant::from(&QString::from(full_path_str)),
);
}
});
}
map
}
fn remove_device(self: Pin<&mut Self>, udid: &QString) {
let udid_str = udid.to_string();
RUNTIME.spawn(async move {
let mut state = APP_DEVICE_STATE.lock().await;
if let Some(svc) = state.remove(&udid_str) {
let streams = svc.video_streams.lock().await;
for (_path, flag) in streams.iter() {
flag.store(true, Ordering::Relaxed);
}
println!("Removed device with UDID {}", udid_str);
} else {
eprintln!(
"Attempted to remove non-existent device with UDID {}",
udid_str
);
}
clean_device_from_app_state(&udid_str).await;
});
}
}
fn get_lockdown_path() -> String {
#[cfg(target_os = "linux")]
return "/var/lib/lockdown".to_string();
#[cfg(target_os = "macos")]
return "/var/db/lockdown".to_string();
#[cfg(target_os = "windows")]
return std::env::var("PROGRAMDATA").unwrap_or_else(|_| "C:\\ProgramData".to_string())
+ "/Apple/Lockdown";
async fn clean_device_from_app_state(udid: &str) {
let mut state = APP_DEVICE_STATE.lock().await;
if let Some(svc) = state.remove(udid) {
let streams = svc.video_streams.lock().await;
for (_path, flag) in streams.iter() {
flag.store(true, Ordering::Relaxed);
}
println!("Removed device with UDID {}", udid);
} else {
eprintln!("Attempted to remove non-existent device with UDID {}", udid);
}
}
//FIXME: dont spawn hb if init fails
@@ -603,9 +630,7 @@ async fn init_idescriptor_device<
}
eprintln!("init_idescriptor_device: Lockdown session started.");
eprintln!(
"init_idescriptor_device: Attempting to get default values from Lockdown."
);
eprintln!("init_idescriptor_device: Attempting to get default values from Lockdown.");
let mut def_vals = match lc.get_value(None, None).await {
Ok(v) => v,
Err(e) => {
@@ -627,9 +652,7 @@ async fn init_idescriptor_device<
}
if is_wireless {
eprintln!(
"init_idescriptor_device: Attempting to connect to HeartbeatClient."
);
eprintln!("init_idescriptor_device: Attempting to connect to HeartbeatClient.");
hb = match heartbeat::HeartbeatClient::connect(&provider_for_hb).await {
Ok(h) => Some(h),
Err(e) => {
@@ -660,7 +683,7 @@ async fn init_idescriptor_device<
eprintln!("heartbeat: get_marco failed (fail count: {fails}): {e:?}");
if fails >= 3 {
eprintln!("heartbeat: too many failures for giving up");
APP_DEVICE_STATE.lock().await.remove(&udid_for_hb);
clean_device_from_app_state(&udid_for_hb).await;
let udid_for_event = udid_for_hb.clone();
let _ = qt_thread_for_hb.queue(move |Core_qobj| {
@@ -682,7 +705,7 @@ async fn init_idescriptor_device<
eprintln!("heartbeat: send_polo failed (fail count: {fails}): {e:?}");
if fails >= 3 {
eprintln!("heartbeat: too many failures for , giving up");
APP_DEVICE_STATE.lock().await.remove(&udid_for_hb);
clean_device_from_app_state(&udid_for_hb).await;
let udid_for_event = udid_for_hb.clone();
let _ = qt_thread_for_hb.queue(move |Core_qobj| {
@@ -699,11 +722,9 @@ async fn init_idescriptor_device<
}
eprintln!("heartbeat: Polo sent successfully.");
interval += 5;
// tokio::time::sleep(std::time::Duration::from_secs(interval + 5)).await;
}
eprintln!("heartbeat: heartbeat task for ended.");
// loop exits → task ends
eprintln!("heartbeat: heartbeat task ended.");
}));
}
@@ -729,9 +750,7 @@ async fn init_idescriptor_device<
}
};
eprintln!(
"init_idescriptor_device: Attempting to connect to AFC client."
);
eprintln!("init_idescriptor_device: Attempting to connect to AFC client.");
let mut afc_client = match AfcClient::connect(&provider).await {
Ok(c) => c,
Err(e) => {
@@ -741,9 +760,7 @@ async fn init_idescriptor_device<
};
eprintln!("init_idescriptor_device: Connected to AfcClient.");
eprintln!(
"init_idescriptor_device: Attempting to connect to DiagnosticsRelayClient."
);
eprintln!("init_idescriptor_device: Attempting to connect to DiagnosticsRelayClient.");
let mut diag_relay = match DiagnosticsRelayClient::connect(&provider).await {
Ok(c) => c,
Err(e) => {
@@ -754,7 +771,7 @@ async fn init_idescriptor_device<
eprintln!("init_idescriptor_device: Connected to DiagnosticsRelayClient.");
// afc_client.set_timeout(Some(5000)).await;
let afc2 = match AfcClient::new_afc2(&provider).await {
let afc2 = match AfcClient::new_afc2(&provider).await {
Ok(c) => Some(Arc::new(Mutex::new(c))),
Err(e) => {
eprintln!("AfcClient::new_afc2 failed: {e:?}");
@@ -829,9 +846,7 @@ async fn init_idescriptor_device<
let mut buf = Vec::new();
if def_vals.to_writer_xml(&mut buf).is_err() {
eprintln!(
"init_idescriptor_device: Failed to serialize default values to XML."
);
eprintln!("init_idescriptor_device: Failed to serialize default values to XML.");
return None;
}
let info = String::from_utf8(buf).ok()?;
+82 -67
View File
@@ -1,25 +1,23 @@
use crate::{APP_DEVICE_STATE, RUNTIME, utils};
use cxx_qt::Threading;
use cxx_qt_lib::{
QByteArray, QList, QMap, QMapPair_QString_QVariant, QString, QVariant,
};
use idevice::{
IdeviceService, RsdService, amfi, dvt::{
location_simulation::LocationSimulationClient, remote_server::RemoteServerClient
}, installation_proxy::InstallationProxyClient, mobile_image_mounter::ImageMounter, provider::IdeviceProvider, rsd::RsdHandshake, simulate_location::LocationSimulationService, springboardservices::SpringBoardServicesClient,
};
use cxx_qt_lib::{QByteArray, QList, QMap, QMapPair_QString_QVariant, QString, QVariant};
use idevice::afc::opcode::AfcFopenMode;
use idevice::services::core_device_proxy::CoreDeviceProxy;
use crate::{APP_DEVICE_STATE, RUNTIME, utils};
use plist::{ Value};
use serde_json;
use std::{
io::{Read},
pin::Pin,
time::Duration,
use idevice::{
IdeviceService, RsdService, amfi,
dvt::{location_simulation::LocationSimulationClient, remote_server::RemoteServerClient},
installation_proxy::InstallationProxyClient,
mobile_image_mounter::ImageMounter,
provider::IdeviceProvider,
rsd::RsdHandshake,
simulate_location::LocationSimulationService,
springboardservices::SpringBoardServicesClient,
};
use plist_macro::plist;
use plist::Value;
use plist_macro::plist;
use serde_json;
use std::{io::Read, pin::Pin, time::Duration};
#[cxx_qt::bridge(namespace = "CXX")]
mod qobject {
@@ -64,7 +62,6 @@ mod qobject {
#[qinvokable]
fn set_location(&self, latitude: &QString, longitude: &QString) -> i32;
#[qinvokable]
fn fetch_app_icon(&self, bundle_id: QString);
@@ -72,7 +69,10 @@ mod qobject {
fn cable_info_retrieved(self: Pin<&mut ServiceManager>, info: QString);
#[qsignal]
fn mobilegestalt_info_retrieved(self: Pin<&mut ServiceManager>, info: QMap_QString_QVariant);
fn mobilegestalt_info_retrieved(
self: Pin<&mut ServiceManager>,
info: QMap_QString_QVariant,
);
#[qsignal]
fn dev_image_mounted(self: Pin<&mut ServiceManager>, success: bool);
@@ -81,24 +81,35 @@ mod qobject {
fn developer_mode_option_revealed(self: Pin<&mut ServiceManager>, success: bool);
#[qsignal]
fn mounted_image_retrieved(self: Pin<&mut ServiceManager>, sig: QByteArray, sig_length: u64);
fn mounted_image_retrieved(
self: Pin<&mut ServiceManager>,
sig: QByteArray,
sig_length: u64,
);
#[qsignal]
fn installed_apps_retrieved(self: Pin<&mut ServiceManager>, apps: &QMap_QString_QVariant);
#[qsignal]
fn app_icon_loaded(self: Pin<&mut ServiceManager>, bundle_id: QString, icon_data: QByteArray);
fn app_icon_loaded(
self: Pin<&mut ServiceManager>,
bundle_id: QString,
icon_data: QByteArray,
);
#[qsignal]
fn battery_info_updated(self: Pin<&mut ServiceManager>, info: &QString);
#[qinvokable]
fn fetch_disk_usage(&self, skip_gallery_usage: bool);
#[qsignal]
fn disk_usage_retrieved(self: Pin<&mut ServiceManager>, success: bool, apps_usage: u64, gallery_usage: u64);
fn disk_usage_retrieved(
self: Pin<&mut ServiceManager>,
success: bool,
apps_usage: u64,
gallery_usage: u64,
);
}
impl cxx_qt::Threading for ServiceManager {}
@@ -130,28 +141,29 @@ impl cxx_qt::Constructor<(QString, u32)> for qobject::ServiceManager {
RServiceManager {
udid: args.0,
ios_version: args.1,
}
}
}
fn initialize(self: Pin<&mut Self>, _arguments: Self::InitializeArguments) {
let udid_for_log = self.get_udid().to_string();
println!("ServiceServiceManager::ServiceManager::initialize called for UDID: {udid_for_log}");
self.start_update_battery_info_interval();
}
fn initialize(self: Pin<&mut Self>, _arguments: Self::InitializeArguments) {
let udid_for_log = self.get_udid().to_string();
println!(
"ServiceServiceManager::ServiceManager::initialize called for UDID: {udid_for_log}"
);
self.start_update_battery_info_interval();
}
}
impl qobject::ServiceManager {
pub fn start_update_battery_info_interval(self: Pin<&mut Self>) {
let qt_thread = self.qt_thread();
pub fn start_update_battery_info_interval(self: Pin<&mut Self>) {
let qt_thread = self.qt_thread();
let udid = self.get_udid().to_string();
println!("Starting battery info update interval for device {udid}");
RUNTIME.spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
RUNTIME.spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
@@ -162,21 +174,27 @@ impl qobject::ServiceManager {
}
};
println!("start_update_battery_info_interval: Fetching battery info for device {udid}");
println!(
"start_update_battery_info_interval: Fetching battery info for device {udid}"
);
utils::get_battery_info(&mut *device.diag.lock().await).await.map(|info| {
let mut buf = Vec::new();
if Value::Dictionary(info).to_writer_xml(&mut buf).is_ok() {
if let Ok(s) = String::from_utf8(buf) {
qt_thread.queue(move |t| {
t.battery_info_updated(&QString::from(s));
}).ok();
utils::get_battery_info(&mut *device.diag.lock().await)
.await
.map(|info| {
let mut buf = Vec::new();
if Value::Dictionary(info).to_writer_xml(&mut buf).is_ok() {
if let Ok(s) = String::from_utf8(buf) {
qt_thread
.queue(move |t| {
t.battery_info_updated(&QString::from(s));
})
.ok();
}
}
}
});
}
});
}
});
}
});
}
fn get_udid(&self) -> &QString {
use cxx_qt::CxxQtType;
@@ -638,7 +656,6 @@ impl qobject::ServiceManager {
});
}
fn fetch_app_icon(&self, bundle_id: QString) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
@@ -684,11 +701,10 @@ impl qobject::ServiceManager {
});
}
fn set_location(&self, latitude: &QString, longitude: &QString) -> i32 {
let udid = self.get_udid().to_string();
let ios_version = self.get_ios_version();
/*
FIXME: use RUNTIME.spawn in the future
*/
@@ -866,16 +882,15 @@ async fn set_device_location_rsd(
longitude: f64,
) -> Result<(), idevice::IdeviceError> {
let rsd_port = proxy.handshake.server_rsd_port;
let adapter = proxy.create_software_tunnel()?;
let mut adapter = adapter.to_async_handle();
let stream = adapter.connect(rsd_port).await?;
let mut handshake = RsdHandshake::new(stream).await?;
let mut remote_server =
RemoteServerClient::connect_rsd(&mut adapter, &mut handshake).await?;
remote_server.read_message(0).await?;
let mut location_client = LocationSimulationClient::new(&mut remote_server).await?;
location_client.set(latitude, longitude).await
let adapter = proxy.create_software_tunnel()?;
let mut adapter = adapter.to_async_handle();
let stream = adapter.connect(rsd_port).await?;
let mut handshake = RsdHandshake::new(stream).await?;
let mut remote_server = RemoteServerClient::connect_rsd(&mut adapter, &mut handshake).await?;
remote_server.read_message(0).await?;
let mut location_client = LocationSimulationClient::new(&mut remote_server).await?;
location_client.set(latitude, longitude).await
}
+28
View File
@@ -8,6 +8,7 @@ use idevice::{
use plist::Dictionary as PlistDictionary;
use plist_macro::plist;
use rusqlite::Connection;
use std::path::PathBuf;
pub async fn get_battery_info(diag: &mut DiagnosticsRelayClient) -> Option<PlistDictionary> {
match diag.ioregistry(None, None, Some("IOPMPowerSource")).await {
@@ -118,3 +119,30 @@ pub fn query_gallery_usage(db_bytes: &mut Vec<u8>) -> Result<u64, rusqlite::Erro
println!("Gallery usage calculated: {} bytes", size);
Ok(size as u64)
}
pub fn get_lockdown_path() -> PathBuf {
if let Ok(val) = std::env::var("USBMUXD_PAIRING_FILES_LOCATION") {
if !val.is_empty() {
eprintln!("Pulling pairing files from USBMUXD_PAIRING_FILES_LOCATION: {val}");
return PathBuf::from(val);
}
}
#[cfg(target_os = "linux")]
{
PathBuf::from("/var/lib/lockdown")
}
#[cfg(target_os = "macos")]
{
PathBuf::from("/var/db/lockdown")
}
#[cfg(target_os = "windows")]
{
let base = std::env::var_os("PROGRAMDATA")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"));
base.join("Apple").join("Lockdown")
}
}