Files
anisette-js/src/exports.rs

464 lines
12 KiB
Rust

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<Adi>,
last_error: String,
cpim: Vec<u8>,
session: u32,
otp: Vec<u8>,
mid: Vec<u8>,
read_buf: Vec<u8>,
}
thread_local! {
static STATE: RefCell<ExportState> = RefCell::new(ExportState::default());
}
fn set_last_error(message: impl Into<String>) {
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<String, String> {
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<Option<String>, String> {
if ptr.is_null() {
return Ok(None);
}
unsafe { c_string(ptr).map(Some) }
}
unsafe fn input_bytes(ptr: *const u8, len: usize) -> Result<Vec<u8>, 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<T, F>(f: F) -> Result<T, String>
where
F: FnOnce(&mut Adi) -> Result<T, String>,
{
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<u8>,
coreadi: Vec<u8>,
library_path: String,
provisioning_path: Option<String>,
identifier: Option<String>,
) -> 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<i32, String> {
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<Vec<u8>, 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())
}