use rane::ffi::*;
use std::ffi::{c_void, CStr, CString};
const ESPRESSO_BUNDLE: &str = "/System/Library/DuetExpertCenter/Assets/Assets.bundle/AssetData/ATXActionValuationMLModel.mlmodelc";
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== _networkText swap probe ===\n");
for name in &["AppleNeuralEngine", "ANECompiler", "ANEServices"] {
let path = format!("/System/Library/PrivateFrameworks/{name}.framework/{name}");
let c = CString::new(path).unwrap();
unsafe {
dlopen(c.as_ptr(), RTLD_NOW);
}
}
let mil_text = "program(1, 0)\nfunc main<ios16>(tensor<fp16, [1,16,1,1]> x) -> (tensor<fp16, [1,16,1,1]>) {\n block0() {\n tensor<fp16, [1,16,1,1]> y = relu()[x = x];\n } -> (y)\n}\n";
unsafe {
type DataFn = unsafe extern "C" fn(ObjcId, ObjcSel, *const u8, u64) -> ObjcId;
type DictFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
type DescFn = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId, ObjcId, ObjcId) -> ObjcId;
type ModelFn = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId) -> ObjcId;
type StrFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
type Utf8Fn = unsafe extern "C" fn(ObjcId, ObjcSel) -> *const std::ffi::c_char;
type ModelFn2 = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId, u8) -> ObjcId;
type CompileFn = unsafe extern "C" fn(ObjcId, ObjcSel, u32, ObjcId, *mut ObjcId) -> bool;
type StrFn2 = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId) -> ObjcId;
type RetainFn = unsafe extern "C" fn(ObjcId) -> ObjcId;
let cls_data = cls("NSData");
let cls_dict = cls("NSDictionary");
let cls_desc = cls("_ANEInMemoryModelDescriptor");
let cls_model = cls("_ANEInMemoryModel");
let cls_str = cls("NSString");
let dataf: DataFn = std::mem::transmute(objc_msgSend as *const c_void);
let dictf: DictFn = std::mem::transmute(objc_msgSend as *const c_void);
let descf: DescFn = std::mem::transmute(objc_msgSend as *const c_void);
let modelf: ModelFn = std::mem::transmute(objc_msgSend as *const c_void);
let strf: StrFn = std::mem::transmute(objc_msgSend as *const c_void);
let utf8f: Utf8Fn = std::mem::transmute(objc_msgSend as *const c_void);
let modelf2: ModelFn2 = std::mem::transmute(objc_msgSend as *const c_void);
let compilef: CompileFn = std::mem::transmute(objc_msgSend as *const c_void);
let strf2: StrFn2 = std::mem::transmute(objc_msgSend as *const c_void);
let make_nsstr = |s: &str| -> ObjcId {
let c = CString::new(s).unwrap();
strf2(
cls_str as ObjcId,
sel("stringWithUTF8String:"),
c.as_ptr() as ObjcId,
)
};
let bytes = mil_text.as_bytes();
let ns_text = dataf(
cls_data as ObjcId,
sel("dataWithBytes:length:"),
bytes.as_ptr(),
bytes.len() as u64,
);
let empty = dictf(cls_dict as ObjcId, sel("dictionary"));
println!("=== Test A: _networkText swap at offset 0x28 ===");
let desc_a = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
ns_text,
empty,
std::ptr::null_mut(),
);
let p_mil = (desc_a as *mut u8).add(8);
*p_mil = 0;
let ns_fname = make_nsstr("model.espresso.net");
let p_text = (desc_a as *mut ObjcId).add(5); *p_text = ns_fname;
let model_a = modelf(
cls_model as ObjcId,
sel("inMemoryModelWithDescriptor:"),
desc_a,
);
if model_a.is_null() {
println!(" model_a=null");
} else {
let lp = strf(model_a, sel("localModelPath"));
let lp_s = nsstring_to_str(lp);
println!(" localModelPath: {lp_s}");
let opts = modelf2(
model_a,
sel("compilerOptionsWithOptions:isCompiledModelCached:"),
empty,
0,
);
println!(" opts: {}", objc_desc(opts));
std::fs::create_dir_all(&lp_s)?;
for fname in &[
"model.espresso.net",
"model.espresso.shape",
"model.espresso.weights",
] {
let src = format!("{ESPRESSO_BUNDLE}/{fname}");
let dst = format!("{lp_s}/{fname}");
if let Ok(content) = std::fs::read(&src) {
std::fs::write(&dst, &content)?;
println!(" wrote {fname}: {}B", content.len());
} else {
println!(" FAILED read {fname}");
}
}
let mut err: ObjcId = std::ptr::null_mut();
let ok = compilef(
model_a,
sel("compileWithQoS:options:error:"),
21,
empty,
&mut err,
);
if ok {
println!(" *** COMPILE SUCCESS! ***");
list_dir(&lp_s);
} else {
println!(" err: {:?}", nserror_string(err));
}
std::fs::remove_dir_all(&lp_s).ok();
}
println!("\n=== Test B: _networkText swap to NSData(espresso.net content) ===");
let espresso_content = std::fs::read(format!("{ESPRESSO_BUNDLE}/model.espresso.net"))?;
println!(" espresso.net content: {}B", espresso_content.len());
let desc_b = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
ns_text,
empty,
std::ptr::null_mut(),
);
let p_mil_b = (desc_b as *mut u8).add(8);
*p_mil_b = 0;
let ns_esp_data = dataf(
cls_data as ObjcId,
sel("dataWithBytes:length:"),
espresso_content.as_ptr(),
espresso_content.len() as u64,
);
let p_text_b = (desc_b as *mut ObjcId).add(5); *p_text_b = ns_esp_data;
let model_b = modelf(
cls_model as ObjcId,
sel("inMemoryModelWithDescriptor:"),
desc_b,
);
if model_b.is_null() {
println!(" model_b=null");
} else {
let lp_b = nsstring_to_str(strf(model_b, sel("localModelPath")));
println!(" localModelPath: {lp_b}");
let opts_b = modelf2(
model_b,
sel("compilerOptionsWithOptions:isCompiledModelCached:"),
empty,
0,
);
println!(" opts: {}", objc_desc(opts_b));
std::fs::create_dir_all(&lp_b)?;
for fname in &[
"model.espresso.net",
"model.espresso.shape",
"model.espresso.weights",
] {
let src = format!("{ESPRESSO_BUNDLE}/{fname}");
if let Ok(content) = std::fs::read(&src) {
std::fs::write(format!("{lp_b}/{fname}"), &content)?;
println!(" wrote {fname}: {}B", content.len());
}
}
let mut err: ObjcId = std::ptr::null_mut();
let ok = compilef(
model_b,
sel("compileWithQoS:options:error:"),
21,
empty,
&mut err,
);
if ok {
println!(" *** COMPILE SUCCESS! ***");
list_dir(&lp_b);
} else {
println!(" err: {:?}", nserror_string(err));
}
std::fs::remove_dir_all(&lp_b).ok();
}
println!("\n=== Test C: _networkText = full path NSString ===");
let tmp_esp = format!("/tmp/espresso_test_{}", std::process::id());
std::fs::create_dir_all(&tmp_esp)?;
for fname in &[
"model.espresso.net",
"model.espresso.shape",
"model.espresso.weights",
] {
let src = format!("{ESPRESSO_BUNDLE}/{fname}");
if let Ok(content) = std::fs::read(&src) {
std::fs::write(format!("{tmp_esp}/{fname}"), &content)?;
}
}
let desc_c = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
ns_text,
empty,
std::ptr::null_mut(),
);
let p_mil_c = (desc_c as *mut u8).add(8);
*p_mil_c = 0;
let full_path_str = make_nsstr(&format!("{tmp_esp}/model.espresso.net"));
let p_text_c = (desc_c as *mut ObjcId).add(5);
*p_text_c = full_path_str;
let model_c = modelf(
cls_model as ObjcId,
sel("inMemoryModelWithDescriptor:"),
desc_c,
);
if model_c.is_null() {
println!(" model_c=null");
} else {
let lp_c = nsstring_to_str(strf(model_c, sel("localModelPath")));
println!(" localModelPath: {lp_c}");
let opts_c = modelf2(
model_c,
sel("compilerOptionsWithOptions:isCompiledModelCached:"),
empty,
0,
);
println!(" opts: {}", objc_desc(opts_c));
std::fs::create_dir_all(&lp_c)?;
for fname in &[
"model.espresso.net",
"model.espresso.shape",
"model.espresso.weights",
] {
let src = format!("{tmp_esp}/{fname}");
if let Ok(content) = std::fs::read(&src) {
std::fs::write(format!("{lp_c}/{fname}"), &content)?;
}
}
let mut err: ObjcId = std::ptr::null_mut();
let ok = compilef(
model_c,
sel("compileWithQoS:options:error:"),
21,
empty,
&mut err,
);
if ok {
println!(" *** COMPILE SUCCESS! ***");
list_dir(&lp_c);
} else {
println!(" err: {:?}", nserror_string(err));
}
std::fs::remove_dir_all(&lp_c).ok();
}
std::fs::remove_dir_all(&tmp_esp).ok();
println!("\n=== Test D: ivar dump of fresh descriptor ===");
let desc_d = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
ns_text,
empty,
std::ptr::null_mut(),
);
for i in 0..8usize {
let ptr = (desc_d as *const ObjcId).add(i);
let val = *ptr;
println!(" +{:02x}:{:03} = {:p}", i * 8, i * 8, val);
if !val.is_null() && i >= 2 {
let isa = *(val as *const ObjcId);
if !isa.is_null() {
let cn = class_getName(isa as ObjcClass);
if !cn.is_null() {
println!(" [{}]", CStr::from_ptr(cn).to_str().unwrap_or("?"));
}
}
}
}
}
Ok(())
}
unsafe fn nsstring_to_str(obj: ObjcId) -> String {
if obj.is_null() {
return "(null)".to_string();
}
type Utf8Fn = unsafe extern "C" fn(ObjcId, ObjcSel) -> *const std::ffi::c_char;
let uf: Utf8Fn = std::mem::transmute(objc_msgSend as *const c_void);
let p = uf(obj, sel("UTF8String"));
if p.is_null() {
return "(null utf8)".to_string();
}
CStr::from_ptr(p).to_string_lossy().into_owned()
}
unsafe fn objc_desc(obj: ObjcId) -> String {
if obj.is_null() {
return "(null)".into();
}
type StrFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
type Utf8Fn = unsafe extern "C" fn(ObjcId, ObjcSel) -> *const std::ffi::c_char;
let sf: StrFn = std::mem::transmute(objc_msgSend as *const c_void);
let uf: Utf8Fn = std::mem::transmute(objc_msgSend as *const c_void);
let d = sf(obj, sel("description"));
if d.is_null() {
return "(no desc)".into();
}
let c = uf(d, sel("UTF8String"));
if c.is_null() {
return "(null utf8)".into();
}
CStr::from_ptr(c).to_string_lossy().into_owned()
}
fn list_dir(dir: &str) {
if let Ok(entries) = std::fs::read_dir(dir) {
println!(" dir:");
for entry in entries.flatten() {
let meta = std::fs::metadata(entry.path()).ok();
let size = meta.map(|m| m.len()).unwrap_or(0);
println!(" {}: {size}B", entry.file_name().to_string_lossy());
}
}
}