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!("=== _ANECVAIRCompiler + kANEFNetPlistFilenameKey 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 xpc_path = CString::new("/System/Library/PrivateFrameworks/AppleNeuralEngine.framework/XPCServices/ANECompilerService.xpc/Contents/MacOS/ANECompilerService").unwrap();
let xpc_handle = unsafe { dlopen(xpc_path.as_ptr(), RTLD_NOW | 0x8) }; if xpc_handle.is_null() {
println!("dlopen XPC service failed");
} else {
println!("dlopen XPC service: OK @ {xpc_handle:p}");
}
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 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 SetKVFn = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId, ObjcId);
type SetDictFn = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId);
let dictf: DictFn = std::mem::transmute(objc_msgSend as *const c_void);
let strf2: StrFn2 = std::mem::transmute(objc_msgSend as *const c_void);
let dataf: DataFn = 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 modelf2: ModelFn2 = std::mem::transmute(objc_msgSend as *const c_void);
let compilef: CompileFn = std::mem::transmute(objc_msgSend as *const c_void);
let strf: StrFn = std::mem::transmute(objc_msgSend as *const c_void);
let setkv: SetKVFn = std::mem::transmute(objc_msgSend as *const c_void);
let setd: SetDictFn = std::mem::transmute(objc_msgSend as *const c_void);
let cls_str = cls("NSString");
let cls_dict = cls("NSDictionary");
let cls_mdict = cls("NSMutableDictionary");
let cls_data = cls("NSData");
let cls_desc = cls("_ANEInMemoryModelDescriptor");
let cls_model = cls("_ANEInMemoryModel");
let make_nsstr = |s: &str| -> ObjcId {
let c = CString::new(s).unwrap();
strf2(
cls_str as ObjcId,
sel("stringWithUTF8String:"),
c.as_ptr() as ObjcId,
)
};
println!("=== 1. kANEF* constants via dlsym(xpc_handle) ===");
let const_names_c = [
"kANEFModelANECIRValue",
"kANEFModelCoreMLValue",
"kANEFModelMILValue",
"kANEFModelMLIRValue",
"kANEFModelLLIRBundleValue",
"kANEFModelPreCompiledValue",
"kANEFIsInMemoryModelTypeKey",
"kANEFInMemoryModelIsCachedKey",
"kANEFEspressoFileResourcesKey",
"kANEFNetPlistFilenameKey",
"kANEFCompilerOptionsFilenameKey",
"kANEFModelType",
"kANEFModelIdentityStrKey",
"kANEFBaseModelIdentifierKey",
"kANEFModelDescriptionKey",
"kANEFModelIsEncryptedKey",
"kANEFModelHasCacheURLIdentifierKey",
"kANEFModelCacheIdentifierUsingSourceURLKey",
"kANEFRetainModelsWithoutSourceURLKey",
"kANEFCompilationInitiatedByE5MLKey",
];
let mut kanef: std::collections::HashMap<String, ObjcId> = Default::default();
for name in &const_names_c {
let sym_name = CString::new(*name).unwrap();
let mut p = dlsym(std::ptr::null_mut(), sym_name.as_ptr()) as *const ObjcId;
if p.is_null() && !xpc_handle.is_null() {
p = dlsym(xpc_handle, sym_name.as_ptr()) as *const ObjcId;
}
if !p.is_null() && !(*p).is_null() {
let val = *p;
kanef.insert(name.to_string(), val);
println!(" {name} = \"{}\"", nsstring_to_str(val));
} else {
println!(" {name} = NOT FOUND");
}
}
println!("\n=== 2. _ANECVAIRCompiler full method walk ===");
let cvair_cls = cls("_ANECVAIRCompiler");
if cvair_cls.is_null() {
println!(" NOT FOUND");
} else {
let mut c = cvair_cls;
while !c.is_null() {
let cname = {
let p = class_getName(c);
if p.is_null() {
"?".to_string()
} else {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
let mut count: u32 = 0;
let methods = class_copyMethodList(c, &mut count);
if !methods.is_null() && count > 0 {
println!(" [{cname}] ({count} methods):");
for i in 0..count as usize {
let m = *methods.add(i);
let s = method_getName(m);
let enc = method_getTypeEncoding(m);
let name = if s.is_null() {
"?".to_string()
} else {
let n = sel_getName(s);
if n.is_null() {
"?".to_string()
} else {
CStr::from_ptr(n).to_string_lossy().into_owned()
}
};
let enc_s = if enc.is_null() {
"?"
} else {
CStr::from_ptr(enc).to_str().unwrap_or("?")
};
println!(" {} [{}]", name, enc_s);
}
libc_free(methods as *mut c_void);
} else {
println!(" [{cname}] 0 own methods");
}
c = class_getSuperclass(c);
}
}
println!("\n=== 3. _ANEEspressoIRTranslator full method walk ===");
let esp_cls = cls("_ANEEspressoIRTranslator");
if esp_cls.is_null() {
println!(" NOT FOUND");
} else {
let mut c = esp_cls;
let mut depth = 0;
while !c.is_null() && depth < 3 {
let cname = {
let p = class_getName(c);
if p.is_null() {
"?".to_string()
} else {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
let mut count: u32 = 0;
let methods = class_copyMethodList(c, &mut count);
if !methods.is_null() && count > 0 {
println!(" [{cname}] ({count} methods):");
for i in 0..count as usize {
let m = *methods.add(i);
let s = method_getName(m);
let enc = method_getTypeEncoding(m);
let name = if s.is_null() {
"?".to_string()
} else {
let n = sel_getName(s);
if n.is_null() {
"?".to_string()
} else {
CStr::from_ptr(n).to_string_lossy().into_owned()
}
};
let enc_s = if enc.is_null() {
"?"
} else {
CStr::from_ptr(enc).to_str().unwrap_or("?")
};
println!(" {} [{}]", name, enc_s);
}
libc_free(methods as *mut c_void);
}
c = class_getSuperclass(c);
depth += 1;
}
}
println!("\n=== 4. Compile with kANEFNetPlistFilenameKey ===");
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";
let ns_text = dataf(
cls_data as ObjcId,
sel("dataWithBytes:length:"),
mil_text.as_bytes().as_ptr(),
mil_text.len() as u64,
);
let empty = dictf(cls_dict as ObjcId, sel("dictionary"));
let desc = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
ns_text,
empty,
std::ptr::null_mut(),
);
let p = (desc as *mut u8).add(8);
*p = 0;
let model = modelf(
cls_model as ObjcId,
sel("inMemoryModelWithDescriptor:"),
desc,
);
if model.is_null() {
println!(" model=null");
return Ok(());
}
let lp = nsstring_to_str(strf(model, sel("localModelPath")));
println!(" localModelPath: {}", &lp[..lp.len().min(100)]);
std::fs::create_dir_all(&lp)?;
for fname in &[
"model.espresso.net",
"model.espresso.shape",
"model.espresso.weights",
] {
if let Ok(content) = std::fs::read(format!("{ESPRESSO_BUNDLE}/{fname}")) {
std::fs::write(format!("{lp}/{fname}"), &content)?;
println!(" wrote {fname}: {}B", content.len());
}
}
let base_opts = modelf2(
model,
sel("compilerOptionsWithOptions:isCompiledModelCached:"),
empty,
0,
);
let mut_opts = dictf(cls_mdict as ObjcId, sel("new"));
setd(mut_opts, sel("setDictionary:"), base_opts);
let net_plist_key = kanef.get("kANEFNetPlistFilenameKey").copied().unwrap_or_else(|| {
println!(" kANEFNetPlistFilenameKey not found via dlsym, using raw string \"kANEFNetPlistFilenameKey\"");
make_nsstr("kANEFNetPlistFilenameKey")
});
let net_plist_key_str = nsstring_to_str(net_plist_key);
println!(" using key: \"{net_plist_key_str}\"");
setkv(
mut_opts,
sel("setObject:forKey:"),
make_nsstr("model.espresso.net"),
net_plist_key,
);
if let Some(&co_key) = kanef.get("kANEFCompilerOptionsFilenameKey") {
println!(
" kANEFCompilerOptionsFilenameKey = \"{}\"",
nsstring_to_str(co_key)
);
}
let desc2 = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
ns_text,
empty,
std::ptr::null_mut(),
);
let pp2 = (desc2 as *mut u8).add(8);
*pp2 = 0;
let model2 = modelf(
cls_model as ObjcId,
sel("inMemoryModelWithDescriptor:"),
desc2,
);
if !model2.is_null() {
let mut err: ObjcId = std::ptr::null_mut();
let ok = compilef(
model2,
sel("compileWithQoS:options:error:"),
21,
mut_opts,
&mut err,
);
if ok {
println!(" *** COMPILE SUCCESS! ***");
list_dir(&lp);
} else {
println!(
" err: {}",
&nserror_string(err).unwrap_or_default()
[..300.min(nserror_string(err).unwrap_or_default().len())]
);
}
}
println!("\n=== 5. Try known string key values ===");
let candidate_keys = [
"NetPlistFilename",
"NetworkPlistFilename",
"ModelPlistFilename",
"NetworkSourcePlistFilename",
"NetPlistFileURL",
"kNetPlistFilename",
"NetPlistPath",
"EspressoFilePath",
];
for key_val in &candidate_keys {
let d = descf(
cls_desc as ObjcId,
sel("modelWithMILText:weights:optionsPlist:"),
ns_text,
empty,
std::ptr::null_mut(),
);
let pp = (d as *mut u8).add(8);
*pp = 0;
let m = modelf(cls_model as ObjcId, sel("inMemoryModelWithDescriptor:"), d);
if m.is_null() {
continue;
}
let opts_t = dictf(cls_mdict as ObjcId, sel("new"));
setd(opts_t, sel("setDictionary:"), base_opts);
let ns_k = make_nsstr(key_val);
let ns_v = make_nsstr("model.espresso.net");
setkv(opts_t, sel("setObject:forKey:"), ns_v, ns_k);
let mut e: ObjcId = std::ptr::null_mut();
let ok = compilef(m, sel("compileWithQoS:options:error:"), 21, opts_t, &mut e);
let result = if ok {
"SUCCESS".to_string()
} else {
inner_error(&nserror_string(e).unwrap_or_default())
};
println!(" [{key_val}]: {result}");
}
std::fs::remove_dir_all(&lp).ok();
}
Ok(())
}
fn inner_error(e: &str) -> String {
if let Some(idx) = e.rfind("err=(\n ") {
let rest = &e[idx + 9..];
if let Some(end) = rest.find('\n') {
return rest[..end].trim().to_string();
}
}
e[..e.len().min(200)].to_string()
}
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 libc_free(p: *mut c_void) {
extern "C" {
fn free(p: *mut c_void);
}
free(p);
}
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());
}
}
}