forked from beerpsi/chuniio-yubideck
407 lines
11 KiB
Rust
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;
|
||
|
}
|