use std::cell::RefCell; use std::ffi::{CStr, c_char}; use std::fs; use std::path::Path; use crate::{Adi, AdiInit, sync_idbfs}; #[derive(Default)] struct ExportState { adi: Option, last_error: String, cpim: Vec, session: u32, otp: Vec, mid: Vec, read_buf: Vec, } thread_local! { static STATE: RefCell = RefCell::new(ExportState::default()); } fn set_last_error(message: impl Into) { STATE.with(|state| { state.borrow_mut().last_error = message.into(); }); } fn clear_last_error() { STATE.with(|state| { state.borrow_mut().last_error.clear(); }); } unsafe fn c_string(ptr: *const c_char) -> Result { if ptr.is_null() { return Err("null C string pointer".to_string()); } unsafe { CStr::from_ptr(ptr) } .to_str() .map(|s| s.to_string()) .map_err(|e| format!("invalid utf-8 string: {e}")) } unsafe fn optional_c_string(ptr: *const c_char) -> Result, String> { if ptr.is_null() { return Ok(None); } unsafe { c_string(ptr).map(Some) } } unsafe fn input_bytes(ptr: *const u8, len: usize) -> Result, String> { if len == 0 { return Ok(Vec::new()); } if ptr.is_null() { return Err("null bytes pointer with non-zero length".to_string()); } Ok(unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec()) } fn with_adi_mut(f: F) -> Result where F: FnOnce(&mut Adi) -> Result, { STATE.with(|state| { let mut state = state.borrow_mut(); let adi = state .adi .as_mut() .ok_or_else(|| "ADI is not initialized".to_string())?; f(adi) }) } fn install_adi(adi: Adi) { STATE.with(|state| { let mut state = state.borrow_mut(); state.adi = Some(adi); state.cpim.clear(); state.otp.clear(); state.mid.clear(); state.session = 0; }); } fn init_adi_from_parts( storeservicescore: Vec, coreadi: Vec, library_path: String, provisioning_path: Option, identifier: Option, ) -> Result<(), String> { let adi = Adi::new(AdiInit { storeservicescore, coreadi, library_path, provisioning_path, identifier, }) .map_err(|e| format!("ADI init failed: {e}"))?; install_adi(adi); Ok(()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_init_from_files( storeservices_path: *const c_char, coreadi_path: *const c_char, library_path: *const c_char, provisioning_path: *const c_char, identifier: *const c_char, ) -> i32 { let result = (|| -> Result<(), String> { let storeservices_path = unsafe { c_string(storeservices_path)? }; let coreadi_path = unsafe { c_string(coreadi_path)? }; let library_path = unsafe { c_string(library_path)? }; let provisioning_path = unsafe { optional_c_string(provisioning_path)? }; let identifier = unsafe { optional_c_string(identifier)? }; let storeservicescore = fs::read(&storeservices_path).map_err(|e| { format!( "failed to read storeservices core '{}': {e}", storeservices_path ) })?; let coreadi = fs::read(&coreadi_path) .map_err(|e| format!("failed to read coreadi '{}': {e}", coreadi_path))?; init_adi_from_parts( storeservicescore, coreadi, library_path, provisioning_path, identifier, ) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_init_from_blobs( storeservices_ptr: *const u8, storeservices_len: usize, coreadi_ptr: *const u8, coreadi_len: usize, library_path: *const c_char, provisioning_path: *const c_char, identifier: *const c_char, ) -> i32 { let result = (|| -> Result<(), String> { let storeservicescore = unsafe { input_bytes(storeservices_ptr, storeservices_len)? }; let coreadi = unsafe { input_bytes(coreadi_ptr, coreadi_len)? }; let library_path = unsafe { c_string(library_path)? }; let provisioning_path = unsafe { optional_c_string(provisioning_path)? }; let identifier = unsafe { optional_c_string(identifier)? }; init_adi_from_parts( storeservicescore, coreadi, library_path, provisioning_path, identifier, ) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_set_identifier(identifier: *const c_char) -> i32 { let result = (|| -> Result<(), String> { let identifier = unsafe { c_string(identifier)? }; with_adi_mut(|adi| adi.set_identifier(&identifier).map_err(|e| e.to_string())) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_set_provisioning_path(path: *const c_char) -> i32 { let result = (|| -> Result<(), String> { let path = unsafe { c_string(path)? }; with_adi_mut(|adi| adi.set_provisioning_path(&path).map_err(|e| e.to_string())) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_is_machine_provisioned(dsid: u64) -> i32 { let result = (|| -> Result { let mut out = -1; with_adi_mut(|adi| { let provisioned = adi .is_machine_provisioned(dsid) .map_err(|e| e.to_string())?; out = if provisioned { 1 } else { 0 }; Ok(()) })?; Ok(out) })(); match result { Ok(value) => { clear_last_error(); value } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_start_provisioning( dsid: u64, spim_ptr: *const u8, spim_len: usize, ) -> i32 { let result = (|| -> Result<(), String> { let spim = unsafe { input_bytes(spim_ptr, spim_len)? }; let out = with_adi_mut(|adi| { adi.start_provisioning(dsid, &spim) .map_err(|e| format!("start_provisioning failed: {e}")) })?; STATE.with(|state| { let mut state = state.borrow_mut(); state.cpim = out.cpim; state.session = out.session; }); Ok(()) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_get_cpim_ptr() -> *const u8 { STATE.with(|state| state.borrow().cpim.as_ptr()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_get_cpim_len() -> usize { STATE.with(|state| state.borrow().cpim.len()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_get_session() -> u32 { STATE.with(|state| state.borrow().session) } #[unsafe(no_mangle)] pub extern "C" fn anisette_end_provisioning( session: u32, ptm_ptr: *const u8, ptm_len: usize, tk_ptr: *const u8, tk_len: usize, ) -> i32 { let result = (|| -> Result<(), String> { let ptm = unsafe { input_bytes(ptm_ptr, ptm_len)? }; let tk = unsafe { input_bytes(tk_ptr, tk_len)? }; with_adi_mut(|adi| { adi.end_provisioning(session, &ptm, &tk) .map_err(|e| format!("end_provisioning failed: {e}")) }) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_request_otp(dsid: u64) -> i32 { let result = (|| -> Result<(), String> { let out = with_adi_mut(|adi| { adi.request_otp(dsid) .map_err(|e| format!("request_otp failed: {e:#}")) })?; STATE.with(|state| { let mut state = state.borrow_mut(); state.otp = out.otp; state.mid = out.machine_id; }); Ok(()) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_get_otp_ptr() -> *const u8 { STATE.with(|state| state.borrow().otp.as_ptr()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_get_otp_len() -> usize { STATE.with(|state| state.borrow().otp.len()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_get_mid_ptr() -> *const u8 { STATE.with(|state| state.borrow().mid.as_ptr()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_get_mid_len() -> usize { STATE.with(|state| state.borrow().mid.len()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_fs_write_file( path: *const c_char, data_ptr: *const u8, data_len: usize, ) -> i32 { let result = (|| -> Result<(), String> { let path = unsafe { c_string(path)? }; let data = unsafe { input_bytes(data_ptr, data_len)? }; let path_ref = Path::new(&path); if let Some(parent) = path_ref.parent() && !parent.as_os_str().is_empty() { fs::create_dir_all(parent) .map_err(|e| format!("failed to create dir '{}': {e}", parent.display()))?; } fs::write(&path, data).map_err(|e| format!("failed to write '{path}': {e}"))?; Ok(()) })(); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_fs_read_file(path: *const c_char) -> i32 { let result = (|| -> Result, String> { let path = unsafe { c_string(path)? }; fs::read(&path).map_err(|e| format!("failed to read '{path}': {e}")) })(); match result { Ok(data) => { STATE.with(|state| state.borrow_mut().read_buf = data); clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_fs_read_ptr() -> *const u8 { STATE.with(|state| state.borrow().read_buf.as_ptr()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_fs_read_len() -> usize { STATE.with(|state| state.borrow().read_buf.len()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_idbfs_sync(populate_from_storage: i32) -> i32 { let result = sync_idbfs(populate_from_storage != 0); match result { Ok(()) => { clear_last_error(); 0 } Err(err) => { set_last_error(err); -1 } } } #[unsafe(no_mangle)] pub extern "C" fn anisette_last_error_ptr() -> *const u8 { STATE.with(|state| state.borrow().last_error.as_ptr()) } #[unsafe(no_mangle)] pub extern "C" fn anisette_last_error_len() -> usize { STATE.with(|state| state.borrow().last_error.len()) }