chuniio-yubideck/aimeio/src/lib.rs

407 lines
11 KiB
Rust

#![allow(clippy::missing_safety_doc)]
mod configuration;
use std::{
ffi::{c_char, CStr, CString},
path::PathBuf,
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
Mutex,
},
};
use configuration::Configuration;
use log::error;
use shared_memory::Shmem;
use winapi::{
shared::{
minwindef::{BOOL, DWORD, HINSTANCE, LPVOID, MAX_PATH, TRUE},
winerror::{E_FAIL, S_FALSE, S_OK},
},
um::{
winbase::{GetPrivateProfileIntA, GetPrivateProfileStringA},
winnt::{DLL_PROCESS_ATTACH, HRESULT},
winuser::{GetAsyncKeyState, VK_RETURN},
},
};
use yubideck_common::{create_shared_memory, init_logger, INPUT_SHMEM_SIZE, OUTPUT_SHMEM_SIZE};
static mut INPUT_SHMEM: Option<Rc<Shmem>> = None;
static mut OUTPUT_SHMEM: Option<Rc<Mutex<Shmem>>> = None;
static AIME_ID_PRESENT: AtomicBool = AtomicBool::new(false);
static FELICA_ID_PRESENT: AtomicBool = AtomicBool::new(false);
static mut REMOTE_CARD_ID: [u8; 10] = [0u8; 10];
lazy_static::lazy_static! {
static ref CONFIGURATION: Configuration = {
let aime = CString::new("aime").unwrap();
let aime_path = CString::new("aimePath").unwrap();
let aime_path_default = CString::new("DEVICE\\aime.txt").unwrap();
let felica_path = CString::new("felicaPath").unwrap();
let felica_path_default = CString::new("DEVICE\\felica.txt").unwrap();
let felica_gen = CString::new("felicaGen").unwrap();
let aime_gen = CString::new("aimeGen").unwrap();
let scan = CString::new("scan").unwrap();
let cfg_file = CString::new(".\\segatools.ini").unwrap();
let mut aime_path_buf: [c_char; MAX_PATH] = [0; MAX_PATH];
let mut felica_path_buf: [c_char; MAX_PATH] = [0; MAX_PATH];
unsafe {
GetPrivateProfileStringA(
aime.as_ptr(),
aime_path.as_ptr(),
aime_path_default.as_ptr(),
aime_path_buf.as_mut_ptr(),
MAX_PATH as u32,
cfg_file.as_ptr()
);
GetPrivateProfileStringA(
aime.as_ptr(),
felica_path.as_ptr(),
felica_path_default.as_ptr(),
felica_path_buf.as_mut_ptr(),
MAX_PATH as u32,
cfg_file.as_ptr()
);
Configuration {
aime_path: CStr::from_ptr(aime_path_buf.as_ptr()).to_str().unwrap().into(),
felica_path: CStr::from_ptr(felica_path_buf.as_ptr()).to_str().unwrap().into(),
aime_gen: GetPrivateProfileIntA(
aime.as_ptr(),
aime_gen.as_ptr(),
1,
cfg_file.as_ptr()
) == 1,
felica_gen: GetPrivateProfileIntA(
aime.as_ptr(),
felica_gen.as_ptr(),
1,
cfg_file.as_ptr()
) == 1,
vk_scan: GetPrivateProfileIntA(
aime.as_ptr(),
scan.as_ptr(),
VK_RETURN,
cfg_file.as_ptr()
),
}
}
};
}
#[no_mangle]
extern "system" fn DllMain(_dll_module: HINSTANCE, call_reason: DWORD, _reserved: LPVOID) -> BOOL {
if call_reason == DLL_PROCESS_ATTACH {
init_logger()
}
TRUE
}
/// Get the version of the Aime IO API that this DLL supports. This function
/// should return a positive 16-bit integer, where the high byte is the major
/// version and the low byte is the minor version (as defined by the Semantic
/// Versioning standard).
/// The latest API version as of this writing is 0x0100.
#[no_mangle]
pub extern "C" fn aime_io_get_api_version() -> u16 {
0x0100
}
/// Initialize Aime IO provider DLL. Only called once, before any other
/// functions exported from this DLL are called (except for
/// aime_io_get_api_version).
///
/// Minimum API version: 0x0100
#[no_mangle]
pub extern "C" fn aime_io_init() -> HRESULT {
match create_shared_memory("YubideckInput", INPUT_SHMEM_SIZE, false) {
Ok(s) => unsafe { INPUT_SHMEM = Some(Rc::new(s)) },
Err(e) => {
error!("Could not acquire shared input memory: {e:#?}");
return E_FAIL;
}
}
match create_shared_memory("YubideckOutput", OUTPUT_SHMEM_SIZE, false) {
Ok(s) => {
unsafe { OUTPUT_SHMEM = Some(Rc::new(Mutex::new(s))) };
S_OK
}
Err(e) => {
error!("Could not obtain shared output memory: {e:#?}");
E_FAIL
}
}
}
fn aime_io_read_id_file<const N: usize>(path: &PathBuf) -> Option<[u8; N]> {
let min_length = N * 2;
let id_string = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(_) => return None,
};
if id_string.len() < min_length {
return None;
}
let mut out = [0u8; N];
if faster_hex::hex_decode(id_string[0..min_length].as_bytes(), &mut out).is_err() {
return None;
}
Some(out)
}
/// # IMPORTANT
/// This is not how Aime access codes are created! This is literally
/// just 10 random bytes! Do not use this for anything serious!
fn aime_io_generate_aime(path: &PathBuf) -> Option<[u8; 10]> {
let mut access_code = [0u8; 10];
loop {
for nib in access_code.iter_mut() {
*nib = ((rand::random::<u8>() % 10) << 4) | (rand::random::<u8>() % 10);
}
if access_code[0] >> 4 != 3 {
break;
}
}
let mut encoded_ac = [0u8; 20];
if let Err(e) = faster_hex::hex_encode(&access_code, &mut encoded_ac) {
error!("Encoding access code failed: {e:#?}");
return None;
}
if let Err(e) = std::fs::write(path, encoded_ac) {
error!("Could not write generated access code to file: {e:#?}");
return None;
}
Some(access_code)
}
/// # IMPORTANT
/// This is not how FeliCa IDms are created! This is literally
/// just 8 random bytes! Do not use this for anything serious!
fn aime_io_generate_felica(path: &PathBuf) -> Option<[u8; 8]> {
let mut idm = [0u8; 8];
for nib in idm.iter_mut() {
*nib = rand::random::<u8>();
}
// FeliCa IDm values should have a 0 in their high nibble. I think.
idm[0] &= 0x0F;
let mut encoded_idm = [0u8; 16];
if let Err(e) = faster_hex::hex_encode(&idm, &mut encoded_idm) {
error!("Encoding Felica IDm failed: {e:#?}");
return None;
}
if let Err(e) = std::fs::write(path, encoded_idm) {
error!("Could not write generated FeliCa IDm to file: {e:#?}");
return None;
}
Some(idm)
}
/// Poll for IC cards in the vicinity.
///
/// - unit_no: Always 0 as of the current API version
///
/// Minimum API version: 0x0100
#[no_mangle]
pub extern "C" fn aime_io_nfc_poll(unit_no: u8) -> HRESULT {
if unit_no != 0 {
return S_OK;
}
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
return S_OK;
};
let input = unsafe { input_shmem.as_slice() };
AIME_ID_PRESENT.store(false, Ordering::Relaxed);
FELICA_ID_PRESENT.store(false, Ordering::Relaxed);
if input[34] != 0 {
match input[34] {
1 => AIME_ID_PRESENT.store(true, Ordering::Relaxed),
2 => FELICA_ID_PRESENT.store(true, Ordering::Relaxed),
_ => {}
}
unsafe {
REMOTE_CARD_ID.copy_from_slice(&input[35..45]);
}
return S_OK;
}
if unsafe { GetAsyncKeyState(CONFIGURATION.vk_scan as i32) } == 0 {
return S_OK;
}
if let Some(aime_id) = aime_io_read_id_file::<10>(&CONFIGURATION.aime_path) {
AIME_ID_PRESENT.store(true, Ordering::Relaxed);
unsafe { REMOTE_CARD_ID.copy_from_slice(&aime_id) }
return S_OK;
}
if CONFIGURATION.aime_gen {
if let Some(aime_id) = aime_io_generate_aime(&CONFIGURATION.aime_path) {
AIME_ID_PRESENT.store(true, Ordering::Relaxed);
unsafe { REMOTE_CARD_ID.copy_from_slice(&aime_id) }
return S_OK;
}
return E_FAIL;
}
if let Some(felica_id) = aime_io_read_id_file::<8>(&CONFIGURATION.felica_path) {
FELICA_ID_PRESENT.store(true, Ordering::Relaxed);
unsafe {
REMOTE_CARD_ID[..8].copy_from_slice(&felica_id);
}
return S_OK;
}
if CONFIGURATION.felica_gen {
if let Some(felica_id) = aime_io_generate_felica(&CONFIGURATION.felica_path) {
FELICA_ID_PRESENT.store(true, Ordering::Relaxed);
unsafe { REMOTE_CARD_ID[..8].copy_from_slice(&felica_id) }
return S_OK;
}
return E_FAIL;
}
S_OK
}
/// Attempt to read out a classic Aime card ID
///
/// - unit_no: Always 0 as of the current API version
/// - luid: Pointer to a ten-byte buffer that will receive the ID
/// - luid_size: Size of the buffer at *luid. Always 10.
///
/// Returns:
///
/// - S_OK if a classic Aime is present and was read successfully
/// - S_FALSE if no classic Aime card is present (*luid will be ignored)
/// - Any HRESULT error if an error occured.
///
/// Minimum API version: 0x0100
#[no_mangle]
pub unsafe extern "C" fn aime_io_nfc_get_aime_id(
unit_no: u8,
access_code: *mut u8,
access_code_size: usize,
) -> HRESULT {
if access_code.is_null() || access_code_size != 10 {
return S_FALSE;
}
if unit_no != 0 || !AIME_ID_PRESENT.load(Ordering::Relaxed) {
return S_FALSE;
}
let ac_slice = std::slice::from_raw_parts_mut(access_code, access_code_size);
ac_slice.copy_from_slice(&REMOTE_CARD_ID);
S_OK
}
/// Attempt to read out a FeliCa card ID ("IDm"). The following are examples
/// of FeliCa cards:
///
/// - Amuse IC (which includes new-style Aime-branded cards, among others)
/// - Smartphones with FeliCa NFC capability (uncommon outside Japan)
/// - Various Japanese e-cash cards and train passes
///
/// Parameters:
///
/// - unit_no: Always 0 as of the current API version
/// - IDm: Output parameter that will receive the card ID
///
/// Returns:
///
/// - S_OK if a FeliCa device is present and was read successfully
/// - S_FALSE if no FeliCa device is present (*IDm will be ignored)
/// - Any HRESULT error if an error occured.
///
/// Minimum API version: 0x0100
#[no_mangle]
pub unsafe extern "C" fn aime_io_nfc_get_felica_id(unit_no: u8, idm: *mut u64) -> HRESULT {
if idm.is_null() {
return S_FALSE;
}
if unit_no != 0 || !FELICA_ID_PRESENT.load(Ordering::Relaxed) {
return S_FALSE;
}
*idm = 0;
for nib in REMOTE_CARD_ID.iter().take(8) {
*idm = (*idm << 8) | (*nib as u64);
}
S_OK
}
/// Change the color and brightness of the card reader's RGB lighting
///
/// - unit_no: Always 0 as of the current API version
/// - r, g, b: Primary color intensity, from 0 to 255 inclusive.
///
/// Minimum API version: 0x0100
#[no_mangle]
pub extern "C" fn aime_io_led_set_color(unit_no: u8, r: u8, g: u8, b: u8) {
if unit_no != 0 {
return;
}
let Some(output_shmem) = (unsafe { &OUTPUT_SHMEM }) else {
return;
};
let Ok(mut output_shmem) = output_shmem.lock() else {
error!("Could not acquire mutex of output shared memory");
return;
};
let buf = unsafe { output_shmem.as_slice_mut() };
buf[101] = r;
buf[102] = g;
buf[103] = b;
buf[122] = 1;
}