1
0
mirror of https://github.com/AkaiiKitsune/TAL_CardReader synced 2024-11-27 18:30:48 +01:00

Added support for SmartCard readers

Added configuration file;
Fixed a possible race condition;
Cleaned up the mess that used to be my console logs;
This commit is contained in:
Farewell_ 2023-05-30 16:54:35 +02:00
parent 1cb7165bdf
commit 0f8ce56d96
14 changed files with 867 additions and 341 deletions

2
.gitignore vendored
View File

@ -56,3 +56,5 @@ dkms.conf
subprojects/minhook subprojects/minhook
subprojects/tomlc99 subprojects/tomlc99
bin/*

View File

@ -1,12 +1,12 @@
# TAL CardIO # TAL CardReader
This is a plugin that allows you to use USB HID card readers (cardio) with [TaikoArcadeLoader](https://github.com/BroGamer4256/TaikoArcadeLoader) This is a plugin that allows you to use USB HID card (cardio) and SmartCard readers with [TaikoArcadeLoader](https://github.com/BroGamer4256/TaikoArcadeLoader)
# Acknowledgments # Acknowledgments
This is a plugin destined to be used with [BroGamer4256](https://github.com/BroGamer4256)'s TaikoArcadeLoader. This is a plugin destined to be used with [BroGamer4256](https://github.com/BroGamer4256)'s TaikoArcadeLoader.
CardIO implementation taken from [Spicetools](https://github.com/spicetools/spicetools) and adapted with the help of [CrazyRedMachine](https://github.com/CrazyRedMachine) (Huge thanks!) CardIO and SmartCard implementation taken from [Spicetools](https://github.com/spicetools/spicetools) and adapted with the help of [CrazyRedMachine](https://github.com/CrazyRedMachine) (Huge thanks!)
Initial project setup done with the help of [Stepland](https://github.com/Stepland) Initial project setup done with the help of [Stepland](https://github.com/Stepland)
@ -19,4 +19,12 @@ To build this, you'll need two things :
Once you've edited your build64.bat file to point to your local installation of the VS2022 build tools, you should be good to go. Once you've edited your build64.bat file to point to your local installation of the VS2022 build tools, you should be good to go.
Copy the cardio.dll to your TAL's plugin folder. You should be able to see the plugin initializing in the game's console. Copy cardreader.dll to your TAL's plugin folder. You should be able to see the plugin initializing in the game's console.
# Settings
- using_smartcard (default : false)
_Reader type defaults to CardIO, if you want to use a SmartCard reader, set this to true_.
- read_cooldown (default : 20)
_Cooldown between each read attempt, a low value (below 15) can lead to game crashes, a high value (above 500) will break SmartCard readers_.

View File

@ -7,5 +7,6 @@ meson configure build64
ninja -C build64 ninja -C build64
mkdir bin mkdir bin
copy build64\src\cardio.dll bin\cardio.dll copy build64\src\cardreader.dll bin\cardreader.dll
copy dist\cardreader.toml bin\cardreader.toml
pause pause

8
dist/cardreader.toml vendored Normal file
View File

@ -0,0 +1,8 @@
# Smart Card Reader ==================
using_smartcard = false
# Card Read Cooldown =================
read_cooldown = 20
# Default is 20. Set to a higher value if you notice any weird behaviour.
# Too small of a value can lead to weird behaviour and game crashes.
# Too high of a value will result in an unresponsive card reader.

View File

@ -1,7 +1,7 @@
project( project(
'TAL_CardIO', 'TAL_CardReader',
'c', 'c',
version : '0.1.0' version : '0.2.0'
) )
inc = include_directories('.') inc = include_directories('.')

View File

@ -1,162 +0,0 @@
#include "cardio.h"
char module[] = "TAL HID";
static bool CARDIO_RUNNER_INITIALIZED = false;
static HANDLE CARDIO_POLL_THREAD;
static bool CARDIO_POLL_STOP_FLAG;
static bool waitingForTouch = false;
static bool HasCard = false;
typedef void (*callbackTouch)(i32, i32, u8[168], u64);
callbackTouch touchCallback;
u64 touchData;
char AccessID[21] = "00000000000000000001";
static u8 cardData[168] = {0x01, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x2E, 0x58, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x5C, 0x97, 0x44, 0xF0, 0x88, 0x04, 0x00, 0x43, 0x26, 0x2C, 0x33, 0x00, 0x04,
0x06, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x42, 0x47, 0x49, 0x43, 0x36,
0x00, 0x00, 0xFA, 0xE9, 0x69, 0x00, 0xF6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
void PollReader()
{
if (HasCard) // A card scan is already in progress
{
Sleep(1000);
return;
}
// update HID devices
EnterCriticalSection(&CARDIO_HID_CRIT_SECTION);
for (size_t device_no = 0; device_no < CARDIO_HID_CONTEXTS_LENGTH; device_no++)
{
struct cardio_hid_device *device = &CARDIO_HID_CONTEXTS[device_no];
// get status
cardio_hid_poll_value_t status = cardio_hid_device_poll(device);
if (status == HID_POLL_CARD_READY)
{
// read card
if (cardio_hid_device_read(device) == HID_CARD_NONE)
continue;
// if card not empty
if (*((uint64_t *)&device->u.usage_value[0]) > 0)
{
printWarning("%s (%s): Read card %02X%02X%02X%02X%02X%02X%02X%02X\n", __func__, module, device->u.usage_value[0], device->u.usage_value[1], device->u.usage_value[2], device->u.usage_value[3], device->u.usage_value[4], device->u.usage_value[5], device->u.usage_value[6], device->u.usage_value[7]);
if (waitingForTouch)
{
// Properly format the AccessID
u64 ReversedAccessID;
for (int i = 0; i < 8; i++)
ReversedAccessID = (ReversedAccessID << 8) | device->u.usage_value[i];
sprintf(AccessID, "%020llu", ReversedAccessID);
HasCard = true;
}
}
}
}
LeaveCriticalSection(&CARDIO_HID_CRIT_SECTION);
Sleep(15);
}
static unsigned int __stdcall cardio_poll_thread_proc(void *ctx)
{
while (!CARDIO_POLL_STOP_FLAG)
{
PollReader();
}
return 0;
}
void cardio_runner_start()
{
// initialize
if (!CARDIO_RUNNER_INITIALIZED)
{
CARDIO_RUNNER_INITIALIZED = true;
printWarning("%s (%s): Initializing CARDIO\n", __func__, module);
// initialize
if (!cardio_hid_init())
{
printWarning("%s (%s): Couldn't init CARDIO HID\n", __func__, module);
return;
}
// scan HID devices
if (!cardio_hid_scan())
{
printWarning("%s (%s): Couldn't scan for CARDIO devices\n", __func__, module);
return;
}
}
}
void cardio_runner_stop()
{
// shutdown HID
cardio_hid_close();
// set initialized to false
CARDIO_RUNNER_INITIALIZED = false;
}
void Init()
{
printWarning("%s (%s): Starting HID Service...\n", __func__, module);
// Find and initialize reader(s)
cardio_runner_start();
// Start reader thread
CARDIO_POLL_STOP_FLAG = false;
CARDIO_POLL_THREAD = (HANDLE)_beginthreadex(
NULL,
0,
cardio_poll_thread_proc,
NULL,
0,
NULL);
}
void Update()
{
if (HasCard)
{
HasCard = false;
// Generating a ChipID Derived from the AccessID
char ChipID[33] = "000000000000";
strcat(ChipID, AccessID);
// Insert card in game
printWarning("%s (%s): Inserting Card %s, with ChipID %s\n", __func__, module, AccessID, ChipID);
memcpy(cardData + 0x2C, ChipID, 33);
memcpy(cardData + 0x50, AccessID, 21);
touchCallback(0, 0, cardData, touchData);
}
}
void WaitTouch(i32 (*callback)(i32, i32, u8[168], u64), u64 data)
{
printWarning("%s (%s): Waiting for touch\n", __func__, module);
waitingForTouch = true;
touchCallback = callback;
touchData = data;
}
void Exit()
{
cardio_runner_stop();
CARDIO_POLL_STOP_FLAG = true;
WaitForSingleObject(CARDIO_POLL_THREAD, INFINITE);
CloseHandle(CARDIO_POLL_THREAD);
CARDIO_POLL_THREAD = NULL;
CARDIO_POLL_STOP_FLAG = false;
}

View File

@ -1,4 +1,4 @@
LIBRARY cardio LIBRARY cardreader
EXPORTS EXPORTS
Init Init

View File

@ -38,7 +38,8 @@ CRITICAL_SECTION CARDIO_HID_CRIT_SECTION;
struct cardio_hid_device *CARDIO_HID_CONTEXTS = NULL; struct cardio_hid_device *CARDIO_HID_CONTEXTS = NULL;
size_t CARDIO_HID_CONTEXTS_LENGTH = 0; size_t CARDIO_HID_CONTEXTS_LENGTH = 0;
void hid_ctx_init(struct cardio_hid_device *ctx) { void hid_ctx_init(struct cardio_hid_device *ctx)
{
ctx->dev_path = NULL; ctx->dev_path = NULL;
ctx->dev_handle = INVALID_HANDLE_VALUE; ctx->dev_handle = INVALID_HANDLE_VALUE;
ctx->initialized = FALSE; ctx->initialized = FALSE;
@ -54,30 +55,36 @@ void hid_ctx_init(struct cardio_hid_device *ctx) {
memset(&ctx->caps, 0, sizeof(HIDP_CAPS)); memset(&ctx->caps, 0, sizeof(HIDP_CAPS));
} }
void hid_ctx_free(struct cardio_hid_device *ctx) { void hid_ctx_free(struct cardio_hid_device *ctx)
if (ctx->dev_path != NULL) { {
if (ctx->dev_path != NULL)
{
HeapFree(GetProcessHeap(), 0, ctx->dev_path); HeapFree(GetProcessHeap(), 0, ctx->dev_path);
ctx->dev_path = NULL; ctx->dev_path = NULL;
} }
if (ctx->dev_handle != INVALID_HANDLE_VALUE) { if (ctx->dev_handle != INVALID_HANDLE_VALUE)
{
CancelIo(ctx->dev_handle); CancelIo(ctx->dev_handle);
CloseHandle(ctx->dev_handle); CloseHandle(ctx->dev_handle);
ctx->dev_handle = INVALID_HANDLE_VALUE; ctx->dev_handle = INVALID_HANDLE_VALUE;
} }
if (ctx->pp_data != NULL) { if (ctx->pp_data != NULL)
{
HidD_FreePreparsedData(ctx->pp_data); HidD_FreePreparsedData(ctx->pp_data);
ctx->pp_data = NULL; ctx->pp_data = NULL;
} }
if (ctx->collection != NULL) { if (ctx->collection != NULL)
{
HeapFree(GetProcessHeap(), 0, ctx->collection); HeapFree(GetProcessHeap(), 0, ctx->collection);
ctx->collection = NULL; ctx->collection = NULL;
} }
} }
void hid_ctx_reset(struct cardio_hid_device *ctx) { void hid_ctx_reset(struct cardio_hid_device *ctx)
{
ctx->initialized = FALSE; ctx->initialized = FALSE;
ctx->io_pending = FALSE; ctx->io_pending = FALSE;
ctx->read_size = 0; ctx->read_size = 0;
@ -91,31 +98,37 @@ void hid_ctx_reset(struct cardio_hid_device *ctx) {
memset(&ctx->caps, 0, sizeof(HIDP_CAPS)); memset(&ctx->caps, 0, sizeof(HIDP_CAPS));
} }
BOOL cardio_hid_init() { BOOL cardio_hid_init()
{
size_t i, contexts_size; size_t i, contexts_size;
InitializeCriticalSectionAndSpinCount(&CARDIO_HID_CRIT_SECTION, 0x00000400); InitializeCriticalSectionAndSpinCount(&CARDIO_HID_CRIT_SECTION, 0x00000400);
contexts_size = DEFAULT_ALLOCATED_CONTEXTS * sizeof(struct cardio_hid_device); contexts_size = DEFAULT_ALLOCATED_CONTEXTS * sizeof(struct cardio_hid_device);
CARDIO_HID_CONTEXTS = (struct cardio_hid_device *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, contexts_size); CARDIO_HID_CONTEXTS = (struct cardio_hid_device *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, contexts_size);
if (CARDIO_HID_CONTEXTS == NULL) { if (CARDIO_HID_CONTEXTS == NULL)
{
return FALSE; return FALSE;
} }
CARDIO_HID_CONTEXTS_LENGTH = DEFAULT_ALLOCATED_CONTEXTS; CARDIO_HID_CONTEXTS_LENGTH = DEFAULT_ALLOCATED_CONTEXTS;
for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++) { for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++)
{
hid_ctx_init(&CARDIO_HID_CONTEXTS[i]); hid_ctx_init(&CARDIO_HID_CONTEXTS[i]);
} }
return TRUE; return TRUE;
} }
void cardio_hid_close() { void cardio_hid_close()
{
size_t i; size_t i;
if (CARDIO_HID_CONTEXTS_LENGTH > 0) { if (CARDIO_HID_CONTEXTS_LENGTH > 0)
for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++) { {
for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++)
{
hid_ctx_free(&CARDIO_HID_CONTEXTS[i]); hid_ctx_free(&CARDIO_HID_CONTEXTS[i]);
} }
@ -127,14 +140,17 @@ void cardio_hid_close() {
} }
} }
BOOL cardio_hid_add_device(LPCWSTR device_path) { BOOL cardio_hid_add_device(LPCWSTR device_path)
{
BOOL res = FALSE; BOOL res = FALSE;
size_t i; size_t i;
EnterCriticalSection(&CARDIO_HID_CRIT_SECTION); EnterCriticalSection(&CARDIO_HID_CRIT_SECTION);
for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++) { for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++)
if (!CARDIO_HID_CONTEXTS[i].initialized) { {
if (!CARDIO_HID_CONTEXTS[i].initialized)
{
res = cardio_hid_scan_device(&CARDIO_HID_CONTEXTS[i], device_path); res = cardio_hid_scan_device(&CARDIO_HID_CONTEXTS[i], device_path);
break; break;
} }
@ -145,16 +161,19 @@ BOOL cardio_hid_add_device(LPCWSTR device_path) {
return res; return res;
} }
BOOL cardio_hid_remove_device(LPCWSTR device_path) { BOOL cardio_hid_remove_device(LPCWSTR device_path)
{
BOOL res = FALSE; BOOL res = FALSE;
size_t i; size_t i;
EnterCriticalSection(&CARDIO_HID_CRIT_SECTION); EnterCriticalSection(&CARDIO_HID_CRIT_SECTION);
for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++) { for (i = 0; i < CARDIO_HID_CONTEXTS_LENGTH; i++)
{
// The device paths in `hid_scan` are partially lower-case, so perform a // The device paths in `hid_scan` are partially lower-case, so perform a
// case-insensitive comparison here // case-insensitive comparison here
if (CARDIO_HID_CONTEXTS[i].initialized && (_wcsicmp(device_path, CARDIO_HID_CONTEXTS[i].dev_path) == 0)) { if (CARDIO_HID_CONTEXTS[i].initialized && (_wcsicmp(device_path, CARDIO_HID_CONTEXTS[i].dev_path) == 0))
{
hid_ctx_reset(&CARDIO_HID_CONTEXTS[i]); hid_ctx_reset(&CARDIO_HID_CONTEXTS[i]);
res = TRUE; res = TRUE;
break; break;
@ -169,12 +188,14 @@ BOOL cardio_hid_remove_device(LPCWSTR device_path) {
/* /*
* Scan HID device to see if it is a HID reader * Scan HID device to see if it is a HID reader
*/ */
BOOL cardio_hid_scan_device(struct cardio_hid_device *ctx, LPCWSTR device_path) { BOOL cardio_hid_scan_device(struct cardio_hid_device *ctx, LPCWSTR device_path)
{
NTSTATUS res; NTSTATUS res;
size_t dev_path_size = (wcslen(device_path) + 1) * sizeof(WCHAR); size_t dev_path_size = (wcslen(device_path) + 1) * sizeof(WCHAR);
ctx->dev_path = (LPWSTR) HeapAlloc(GetProcessHeap(), 0, dev_path_size); ctx->dev_path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dev_path_size);
if (ctx->dev_path == NULL) { if (ctx->dev_path == NULL)
{
return FALSE; return FALSE;
} }
@ -188,33 +209,40 @@ BOOL cardio_hid_scan_device(struct cardio_hid_device *ctx, LPCWSTR device_path)
OPEN_EXISTING, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, FILE_FLAG_OVERLAPPED,
NULL); NULL);
if (ctx->dev_handle == INVALID_HANDLE_VALUE) { if (ctx->dev_handle == INVALID_HANDLE_VALUE)
printWarning ("%s (%s): could not open device: %ls\n", __func__, module, ctx->dev_path); {
printError("%s (%s): Could not open device: %ls\n", __func__, module, ctx->dev_path);
HeapFree(GetProcessHeap(), 0, ctx->dev_path); HeapFree(GetProcessHeap(), 0, ctx->dev_path);
ctx->dev_path = NULL; ctx->dev_path = NULL;
ctx->dev_handle = INVALID_HANDLE_VALUE; ctx->dev_handle = INVALID_HANDLE_VALUE;
return FALSE; return FALSE;
} }
if (!HidD_GetPreparsedData(ctx->dev_handle, &ctx->pp_data)) { if (!HidD_GetPreparsedData(ctx->dev_handle, &ctx->pp_data))
{
goto end; goto end;
} }
res = HidP_GetCaps(ctx->pp_data, &ctx->caps); res = HidP_GetCaps(ctx->pp_data, &ctx->caps);
if (res != HIDP_STATUS_SUCCESS) { if (res != HIDP_STATUS_SUCCESS)
{
goto end; goto end;
} }
// 0xffca is the card reader usage page ID // 0xffca is the card reader usage page ID
if (ctx->caps.UsagePage != CARD_READER_USAGE_PAGE) { if (ctx->caps.UsagePage != CARD_READER_USAGE_PAGE)
{
goto end; goto end;
} else if (ctx->caps.NumberInputValueCaps == 0) { }
else if (ctx->caps.NumberInputValueCaps == 0)
{
goto end; goto end;
} }
ctx->collection_length = ctx->caps.NumberInputValueCaps; ctx->collection_length = ctx->caps.NumberInputValueCaps;
ctx->collection = (HIDP_VALUE_CAPS *) HeapAlloc(GetProcessHeap(), 0, ctx->collection = (HIDP_VALUE_CAPS *)HeapAlloc(GetProcessHeap(), 0,
ctx->collection_length * sizeof(HIDP_VALUE_CAPS)); ctx->collection_length * sizeof(HIDP_VALUE_CAPS));
if (ctx->collection == NULL) { if (ctx->collection == NULL)
{
goto end; goto end;
} }
res = HidP_GetValueCaps( res = HidP_GetValueCaps(
@ -222,15 +250,16 @@ BOOL cardio_hid_scan_device(struct cardio_hid_device *ctx, LPCWSTR device_path)
ctx->collection, ctx->collection,
&ctx->collection_length, &ctx->collection_length,
ctx->pp_data); ctx->pp_data);
if (res != HIDP_STATUS_SUCCESS) { if (res != HIDP_STATUS_SUCCESS)
{
goto end; goto end;
} }
printWarning ("%s (%s): detected reader: %ls\n", __func__, module, ctx->dev_path); printInfo("%s (%s): Detected reader: %ls\n", __func__, module, ctx->dev_path);
ctx->initialized = TRUE; ctx->initialized = TRUE;
return TRUE; return TRUE;
end: end:
hid_ctx_reset(ctx); hid_ctx_reset(ctx);
return FALSE; return FALSE;
} }
@ -242,7 +271,8 @@ BOOL cardio_hid_scan_device(struct cardio_hid_device *ctx, LPCWSTR device_path)
* Usage 0x41 => ISO_15693 * Usage 0x41 => ISO_15693
* Usage 0x42 => ISO_18092 (FeliCa) * Usage 0x42 => ISO_18092 (FeliCa)
*/ */
BOOL cardio_hid_scan() { BOOL cardio_hid_scan()
{
BOOL res = TRUE; BOOL res = TRUE;
SP_DEVINFO_DATA devinfo_data; SP_DEVINFO_DATA devinfo_data;
SP_DEVICE_INTERFACE_DATA device_interface_data; SP_DEVICE_INTERFACE_DATA device_interface_data;
@ -259,7 +289,8 @@ BOOL cardio_hid_scan() {
// HID collection opening needs `DIGCF_DEVICEINTERFACE` and ignore // HID collection opening needs `DIGCF_DEVICEINTERFACE` and ignore
// disconnected devices // disconnected devices
device_info_set = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); device_info_set = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (device_info_set == INVALID_HANDLE_VALUE) { if (device_info_set == INVALID_HANDLE_VALUE)
{
res = FALSE; res = FALSE;
goto end; goto end;
} }
@ -270,44 +301,51 @@ BOOL cardio_hid_scan() {
// `SetupDiEnumDeviceInterfaces` must come before `SetupDiEnumDeviceInfo` // `SetupDiEnumDeviceInterfaces` must come before `SetupDiEnumDeviceInfo`
// else `SetupDiEnumDeviceInterfaces` will fail with error 259 // else `SetupDiEnumDeviceInterfaces` will fail with error 259
while (SetupDiEnumDeviceInterfaces(device_info_set, NULL, &hid_guid, device_index, &device_interface_data)) { while (SetupDiEnumDeviceInterfaces(device_info_set, NULL, &hid_guid, device_index, &device_interface_data))
{
// Get the required size // Get the required size
if (SetupDiGetDeviceInterfaceDetailW(device_info_set, &device_interface_data, NULL, 0, &dwSize, NULL)) { if (SetupDiGetDeviceInterfaceDetailW(device_info_set, &device_interface_data, NULL, 0, &dwSize, NULL))
{
goto cont; goto cont;
} }
device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_W *) HeapAlloc(GetProcessHeap(), 0, dwSize); device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_W *)HeapAlloc(GetProcessHeap(), 0, dwSize);
if (device_interface_detail_data == NULL) { if (device_interface_detail_data == NULL)
{
goto cont; goto cont;
} }
device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W); device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
if (!SetupDiGetDeviceInterfaceDetailW(device_info_set, &device_interface_data, device_interface_detail_data, dwSize, NULL, NULL)) { if (!SetupDiGetDeviceInterfaceDetailW(device_info_set, &device_interface_data, device_interface_detail_data, dwSize, NULL, NULL))
{
goto cont; goto cont;
} }
if (!SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data)) { if (!SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data))
{
goto cont; goto cont;
} }
if (!IsEqualGUID((const void *)&hidclass_guid, (const void *)&devinfo_data.ClassGuid)) { if (!IsEqualGUID((const void *)&hidclass_guid, (const void *)&devinfo_data.ClassGuid))
{
goto cont; goto cont;
} }
EnterCriticalSection(&CARDIO_HID_CRIT_SECTION); EnterCriticalSection(&CARDIO_HID_CRIT_SECTION);
if (hid_devices == CARDIO_HID_CONTEXTS_LENGTH) { if (hid_devices == CARDIO_HID_CONTEXTS_LENGTH)
{
CARDIO_HID_CONTEXTS_LENGTH++; CARDIO_HID_CONTEXTS_LENGTH++;
CARDIO_HID_CONTEXTS = (struct cardio_hid_device *) HeapReAlloc( CARDIO_HID_CONTEXTS = (struct cardio_hid_device *)HeapReAlloc(
GetProcessHeap(), GetProcessHeap(),
HEAP_ZERO_MEMORY, HEAP_ZERO_MEMORY,
CARDIO_HID_CONTEXTS, CARDIO_HID_CONTEXTS,
CARDIO_HID_CONTEXTS_LENGTH * sizeof(struct cardio_hid_device) CARDIO_HID_CONTEXTS_LENGTH * sizeof(struct cardio_hid_device));
); if (CARDIO_HID_CONTEXTS == NULL)
if (CARDIO_HID_CONTEXTS == NULL) { {
LeaveCriticalSection(&CARDIO_HID_CRIT_SECTION); LeaveCriticalSection(&CARDIO_HID_CRIT_SECTION);
HeapFree(GetProcessHeap(), 0, device_interface_detail_data); HeapFree(GetProcessHeap(), 0, device_interface_detail_data);
res = FALSE; res = FALSE;
@ -317,14 +355,16 @@ BOOL cardio_hid_scan() {
hid_ctx_init(&CARDIO_HID_CONTEXTS[hid_devices]); hid_ctx_init(&CARDIO_HID_CONTEXTS[hid_devices]);
} }
if (cardio_hid_scan_device(&CARDIO_HID_CONTEXTS[hid_devices], device_interface_detail_data->DevicePath)) { if (cardio_hid_scan_device(&CARDIO_HID_CONTEXTS[hid_devices], device_interface_detail_data->DevicePath))
{
hid_devices++; hid_devices++;
} }
LeaveCriticalSection(&CARDIO_HID_CRIT_SECTION); LeaveCriticalSection(&CARDIO_HID_CRIT_SECTION);
cont: cont:
if (device_interface_detail_data) { if (device_interface_detail_data)
{
HeapFree(GetProcessHeap(), 0, device_interface_detail_data); HeapFree(GetProcessHeap(), 0, device_interface_detail_data);
device_interface_detail_data = NULL; device_interface_detail_data = NULL;
} }
@ -332,30 +372,36 @@ BOOL cardio_hid_scan() {
device_index++; device_index++;
} }
end: end:
if (device_info_set != INVALID_HANDLE_VALUE) { if (device_info_set != INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(device_info_set); SetupDiDestroyDeviceInfoList(device_info_set);
} }
return res; return res;
} }
cardio_hid_poll_value_t cardio_hid_device_poll(struct cardio_hid_device *ctx) { cardio_hid_poll_value_t cardio_hid_device_poll(struct cardio_hid_device *ctx)
{
DWORD error = 0; DWORD error = 0;
if (!ctx->initialized) { if (!ctx->initialized)
{
return HID_POLL_ERROR; return HID_POLL_ERROR;
} }
if (ctx->io_pending) { if (ctx->io_pending)
{
// Do this if inside to not have more `ReadFile` overlapped I/O requests. // Do this if inside to not have more `ReadFile` overlapped I/O requests.
// If there are more calls to `ReadFile` than `GetOverlappedResult` then // If there are more calls to `ReadFile` than `GetOverlappedResult` then
// eventually the working set quota will run out triggering error 1426 // eventually the working set quota will run out triggering error 1426
// (ERROR_WORKING_SET_QUOTA). // (ERROR_WORKING_SET_QUOTA).
if (HasOverlappedIoCompleted(&ctx->read_state)) { if (HasOverlappedIoCompleted(&ctx->read_state))
{
ctx->io_pending = FALSE; ctx->io_pending = FALSE;
if (!GetOverlappedResult(ctx->dev_handle, &ctx->read_state, &ctx->read_size, FALSE)) { if (!GetOverlappedResult(ctx->dev_handle, &ctx->read_state, &ctx->read_size, FALSE))
{
return HID_POLL_ERROR; return HID_POLL_ERROR;
} }
@ -363,21 +409,29 @@ cardio_hid_poll_value_t cardio_hid_device_poll(struct cardio_hid_device *ctx) {
return HID_POLL_CARD_READY; return HID_POLL_CARD_READY;
} }
} else { }
else
{
if (!ReadFile( if (!ReadFile(
ctx->dev_handle, ctx->dev_handle,
&ctx->report_buffer, &ctx->report_buffer,
sizeof(ctx->report_buffer), sizeof(ctx->report_buffer),
&ctx->read_size, &ctx->read_size,
&ctx->read_state)) { &ctx->read_state))
{
error = GetLastError(); error = GetLastError();
if (error == ERROR_IO_PENDING) { if (error == ERROR_IO_PENDING)
{
ctx->io_pending = TRUE; ctx->io_pending = TRUE;
} else { }
else
{
return HID_POLL_ERROR; return HID_POLL_ERROR;
} }
} else { }
else
{
// The read completed right away // The read completed right away
return HID_POLL_CARD_READY; return HID_POLL_CARD_READY;
} }
@ -386,25 +440,30 @@ cardio_hid_poll_value_t cardio_hid_device_poll(struct cardio_hid_device *ctx) {
return HID_POLL_CARD_NOT_READY; return HID_POLL_CARD_NOT_READY;
} }
cardio_hid_card_type_t cardio_hid_device_read(struct cardio_hid_device *hid_ctx) { cardio_hid_card_type_t cardio_hid_device_read(struct cardio_hid_device *hid_ctx)
{
// check if not initialized // check if not initialized
if (!hid_ctx->initialized) { if (!hid_ctx->initialized)
{
return HID_CARD_NONE; return HID_CARD_NONE;
} }
// check if IO is pending // check if IO is pending
if (hid_ctx->io_pending) { if (hid_ctx->io_pending)
{
return HID_CARD_NONE; return HID_CARD_NONE;
} }
// check if nothing was read // check if nothing was read
if (hid_ctx->read_size == 0) { if (hid_ctx->read_size == 0)
{
return HID_CARD_NONE; return HID_CARD_NONE;
} }
// iterate collections // iterate collections
for (int i = 0; i < hid_ctx->collection_length; i++) { for (int i = 0; i < hid_ctx->collection_length; i++)
{
HIDP_VALUE_CAPS *item = &hid_ctx->collection[i]; HIDP_VALUE_CAPS *item = &hid_ctx->collection[i];
// get usages // get usages
@ -413,24 +472,26 @@ cardio_hid_card_type_t cardio_hid_device_read(struct cardio_hid_device *hid_ctx)
CARD_READER_USAGE_PAGE, CARD_READER_USAGE_PAGE,
0, // LinkCollection 0, // LinkCollection
item->NotRange.Usage, item->NotRange.Usage,
(PCHAR) &hid_ctx->u.usage_value, (PCHAR)&hid_ctx->u.usage_value,
sizeof(hid_ctx->u.usage_value), sizeof(hid_ctx->u.usage_value),
hid_ctx->pp_data, hid_ctx->pp_data,
(PCHAR) &hid_ctx->report_buffer, (PCHAR)&hid_ctx->report_buffer,
hid_ctx->read_size); hid_ctx->read_size);
// loop through the collection to find the entry that handles this report ID // loop through the collection to find the entry that handles this report ID
if (res == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) { if (res == HIDP_STATUS_INCOMPATIBLE_REPORT_ID)
{
continue; continue;
} }
// check if failed // check if failed
if (res != HIDP_STATUS_SUCCESS) { if (res != HIDP_STATUS_SUCCESS)
{
return HID_CARD_NONE; return HID_CARD_NONE;
} }
// return card type // return card type
return (cardio_hid_card_type_t) item->NotRange.Usage; return (cardio_hid_card_type_t)item->NotRange.Usage;
} }
return HID_CARD_NONE; return HID_CARD_NONE;

View File

@ -26,7 +26,7 @@
#ifndef SPICETOOLS_CARDIO_HID_H #ifndef SPICETOOLS_CARDIO_HID_H
#define SPICETOOLS_CARDIO_HID_H #define SPICETOOLS_CARDIO_HID_H
#include "../helpers.h" #include "../../helpers.h"
#include <hidsdi.h> #include <hidsdi.h>
#include <setupapi.h> #include <setupapi.h>

View File

@ -0,0 +1,334 @@
/**
* MIT-License
* Copyright (c) 2018 by nolm <nolan@nolm.name>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* Modified version.
*/
#include "scard.h"
#include <windows.h>
#include <winscard.h>
// #include <tchar.h>
// #include <thread>
extern char module[];
#define MAX_APDU_SIZE 255
extern int readCooldown;
// set to detect all cards, reduce polling rate to 500ms.
// based off acr122u reader, see page 26 in api document.
// https://www.acs.com.hk/en/download-manual/419/API-ACR122U-2.04.pdf
#define PICC_OPERATING_PARAMS 0xDFu
BYTE PICC_OPERATING_PARAM_CMD[5] = {0xFFu, 0x00u, 0x51u, PICC_OPERATING_PARAMS, 0x00u};
// return bytes from device
#define PICC_SUCCESS 0x90u
#define PICC_ERROR 0x63u
static const BYTE UID_CMD[5] = {0xFFu, 0xCAu, 0x00u, 0x00u, 0x00u};
enum scard_atr_protocol
{
SCARD_ATR_PROTOCOL_ISO14443_PART3 = 0x03,
SCARD_ATR_PROTOCOL_ISO15693_PART3 = 0x0B,
SCARD_ATR_PROTOCOL_FELICA_212K = 0x11,
SCARD_ATR_PROTOCOL_FELICA_424K = 0x12,
};
// winscard_config_t WINSCARD_CONFIG;
SCARDCONTEXT hContext = 0;
SCARD_READERSTATE reader_states[2];
LPTSTR reader_name_slots[2] = {NULL, NULL};
int reader_count = 0;
LONG lRet = 0;
void scard_poll(uint8_t *buf, SCARDCONTEXT _hContext, LPCTSTR _readerName, uint8_t unit_no)
{
printInfo("%s (%s): Update on reader : %s\n", __func__, module, reader_states[unit_no].szReader);
// Connect to the smart card.
LONG lRet = 0;
SCARDHANDLE hCard;
DWORD dwActiveProtocol;
for (int retry = 0; retry < 10; retry++)
{
if ((lRet = SCardConnect(_hContext, _readerName, SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol)) == SCARD_S_SUCCESS)
break;
Sleep(20);
}
if (lRet != SCARD_S_SUCCESS)
{
printError("%s (%s): Error connecting to the card: 0x%08X\n", __func__, module, lRet);
return;
}
// set the reader params
lRet = 0;
LPCSCARD_IO_REQUEST pci = dwActiveProtocol == SCARD_PROTOCOL_T1 ? SCARD_PCI_T1 : SCARD_PCI_T0;
DWORD cbRecv = MAX_APDU_SIZE;
BYTE pbRecv[MAX_APDU_SIZE];
if ((lRet = SCardTransmit(hCard, pci, PICC_OPERATING_PARAM_CMD, sizeof(PICC_OPERATING_PARAM_CMD), NULL, pbRecv, &cbRecv)) != SCARD_S_SUCCESS)
{
printError("%s (%s): Error setting PICC params: 0x%08X\n", __func__, module, lRet);
return;
}
if (cbRecv > 2 && pbRecv[0] != PICC_SUCCESS && pbRecv[1] != PICC_OPERATING_PARAMS)
{
printError("%s (%s): PICC params not valid 0x%02X != 0x%02X\n", __func__, module, pbRecv[1], PICC_OPERATING_PARAMS);
return;
}
// Read ATR to determine card type.
TCHAR szReader[200];
DWORD cchReader = 200;
BYTE atr[32];
DWORD cByteAtr = 32;
lRet = SCardStatus(hCard, szReader, &cchReader, NULL, NULL, atr, &cByteAtr);
if (lRet != SCARD_S_SUCCESS)
{
printError("%s (%s): Error getting card status: 0x%08X\n", __func__, module, lRet);
return;
}
// Only care about 20-byte ATRs returned by arcade-type smart cards
if (cByteAtr != 20)
{
printError("%s (%s): Ignoring card with len(%d) = %02X (%08X)\n", __func__, module, cByteAtr, atr, cByteAtr);
return;
}
printInfo("%s (%s): atr Return: len(%d) = %02X (%08X)\n", __func__, module, cByteAtr, atr, cByteAtr);
// Figure out if we should reverse the UID returned by the card based on the ATR protocol
BYTE cardProtocol = atr[12];
BOOL shouldReverseUid = false;
if (cardProtocol == SCARD_ATR_PROTOCOL_ISO15693_PART3)
{
printWarning("%s (%s): Card protocol: ISO15693_PART3\n", __func__, module);
shouldReverseUid = true;
}
else if (cardProtocol == SCARD_ATR_PROTOCOL_ISO14443_PART3)
printWarning("%s (%s): Card protocol: ISO14443_PART3\n", __func__, module);
else if (cardProtocol == SCARD_ATR_PROTOCOL_FELICA_212K)
printWarning("%s (%s): Card protocol: FELICA_212K\n", __func__, module);
else if (cardProtocol == SCARD_ATR_PROTOCOL_FELICA_424K)
printWarning("%s (%s): Card protocol: FELICA_424K\n", __func__, module);
else
{
printError("%s (%s): Unknown NFC Protocol: 0x%02X\n", __func__, module, cardProtocol);
return;
}
// Read UID
cbRecv = MAX_APDU_SIZE;
if ((lRet = SCardTransmit(hCard, pci, UID_CMD, sizeof(UID_CMD), NULL, pbRecv, &cbRecv)) != SCARD_S_SUCCESS)
{
printError("%s (%s): Error querying card UID: 0x%08X\n", __func__, module, lRet);
return;
}
if (cbRecv > 1 && pbRecv[0] == PICC_ERROR)
{
printError("%s (%s): UID query failed\n", __func__, module);
return;
}
if ((lRet = SCardDisconnect(hCard, SCARD_LEAVE_CARD)) != SCARD_S_SUCCESS)
printError("%s (%s): Failed SCardDisconnect: 0x%08X\n", __func__, module, lRet);
if (cbRecv < 8)
{
printWarning("%s (%s): Padding card uid to 8 bytes\n", __func__, module);
memset(&pbRecv[cbRecv], 0, 8 - cbRecv);
}
else if (cbRecv > 8)
printWarning("%s (%s): taking first 8 bytes of len(uid) = %02X\n", __func__, module, cbRecv);
// Copy UID to struct, reversing if necessary
card_info_t card_info;
if (shouldReverseUid)
for (DWORD i = 0; i < 8; i++)
card_info.uid[i] = pbRecv[7 - i];
else
memcpy(card_info.uid, pbRecv, 8);
for (int i = 0; i < 8; ++i)
buf[i] = card_info.uid[i];
}
void scard_clear(uint8_t unitNo)
{
card_info_t empty_cardinfo;
}
void scard_update(uint8_t *buf)
{
if (reader_count < 1)
{
return;
}
lRet = SCardGetStatusChange(hContext, readCooldown, reader_states, reader_count);
if (lRet == SCARD_E_TIMEOUT)
{
return;
}
else if (lRet != SCARD_S_SUCCESS)
{
printError("%s (%s): Failed SCardGetStatusChange: 0x%08X\n", __func__, module, lRet);
return;
}
for (uint8_t unit_no = 0; unit_no < reader_count; unit_no++)
{
if (!(reader_states[unit_no].dwEventState & SCARD_STATE_CHANGED))
continue;
DWORD newState = reader_states[unit_no].dwEventState ^ SCARD_STATE_CHANGED;
bool wasCardPresent = (reader_states[unit_no].dwCurrentState & SCARD_STATE_PRESENT) > 0;
if (newState & SCARD_STATE_UNAVAILABLE)
{
printWarning("%s (%s): New card state: unavailable\n", __func__, module);
Sleep(readCooldown);
}
else if (newState & SCARD_STATE_EMPTY)
{
printWarning("%s (%s): New card state: empty\n", __func__, module);
scard_clear(unit_no);
}
else if (newState & SCARD_STATE_PRESENT && !wasCardPresent)
{
printInfo("%s (%s): New card state: present\n", __func__, module);
scard_poll(buf, hContext, reader_states[unit_no].szReader, unit_no);
}
reader_states[unit_no].dwCurrentState = reader_states[unit_no].dwEventState;
}
return;
}
bool scard_init()
{
if ((lRet = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext)) != SCARD_S_SUCCESS)
{
// log_warning("scard", "failed to establish SCard context: {}", bin2hex(&lRet, sizeof(LONG)));
return lRet;
}
LPCTSTR reader = NULL;
int readerNameLen = 0;
// get list of readers
LPTSTR reader_list = NULL;
auto pcchReaders = SCARD_AUTOALLOCATE;
lRet = SCardListReaders(hContext, NULL, (LPTSTR)&reader_list, &pcchReaders);
int slot0_idx = -1;
int slot1_idx = -1;
int readerCount = 0;
switch (lRet)
{
case SCARD_E_NO_READERS_AVAILABLE:
printError("%s (%s): No readers available\n", __func__, module);
return FALSE;
case SCARD_S_SUCCESS:
// So WinAPI has this terrible "multi-string" concept wherein you have a list
// of null-terminated strings, terminated by a double-null.
for (reader = reader_list; *reader; reader = reader + lstrlen(reader) + 1)
{
printInfo("%s (%s): Found reader: %s\n", __func__, module, reader);
readerCount++;
}
// If we have at least two readers, assign readers to slots as necessary.
if (readerCount >= 2)
{
if (slot1_idx != 0)
slot0_idx = 0;
if (slot0_idx != 1)
slot1_idx = 1;
}
// if the reader count is 1 and no reader was set, set first reader
if (readerCount == 1 && slot0_idx < 0 && slot1_idx < 0)
slot0_idx = 0;
// If we somehow only found slot 1, promote slot 1 to slot 0.
if (slot0_idx < 0 && slot1_idx >= 0)
{
slot0_idx = slot1_idx;
slot1_idx = -1;
}
// Extract the relevant names from the multi-string.
int i;
for (i = 0, reader = reader_list; *reader; reader = reader + lstrlen(reader) + 1, i++)
{
if (slot0_idx == i)
{
readerNameLen = lstrlen(reader);
reader_name_slots[0] = (LPTSTR)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sizeof(TCHAR) * (readerNameLen + 1));
memcpy(reader_name_slots[0], &reader[0], (size_t)(readerNameLen + 1));
}
if (slot1_idx == i)
{
readerNameLen = lstrlen(reader);
reader_name_slots[1] = (LPTSTR)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sizeof(TCHAR) * (readerNameLen + 1));
memcpy(reader_name_slots[1], &reader[0], (size_t)(readerNameLen + 1));
}
}
if (reader_name_slots[0])
printInfo("%s (%s): Using reader slot 0: %s\n", __func__, module, reader_name_slots[0]);
if (reader_name_slots[1])
printInfo("%s (%s): Using reader slot 1: %s\n", __func__, module, reader_name_slots[1]);
reader_count = reader_name_slots[1] ? 2 : 1;
memset(&reader_states[0], 0, sizeof(SCARD_READERSTATE));
reader_states[0].szReader = reader_name_slots[0];
memset(&reader_states[1], 0, sizeof(SCARD_READERSTATE));
reader_states[1].szReader = reader_name_slots[1];
return TRUE;
default:
printWarning("%s (%s): Failed SCardListReaders: 0x%08X\n", __func__, module, lRet);
return FALSE;
}
if (reader_list)
{
SCardFreeMemory(hContext, reader_list);
}
}

View File

@ -0,0 +1,44 @@
/**
* MIT-License
* Copyright (c) 2018 by nolm <nolan@nolm.name>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* Modified version.
*/
#pragma once
// #include <cstdint>
#include "../../helpers.h"
// cardinfo_t is a description of a card that was presented to a reader
typedef struct card_info
{
int card_type;
uint8_t uid[8];
} card_info_t;
void scard_update(uint8_t *buf);
void scard_poll(uint8_t *buf, SCARDCONTEXT _hContext, LPCTSTR _readerName, uint8_t unit_no);
void scard_clear(uint8_t unitNo);
bool scard_init();

View File

@ -0,0 +1,226 @@
#include "CardIO/cardio.h"
#include "SmartCard/scard.h"
char module[] = "CardReader";
// Reader Thread
static bool READER_RUNNER_INITIALIZED = false;
static HANDLE READER_POLL_THREAD;
static bool READER_POLL_STOP_FLAG;
// SmartCard Specific
// Game specific
static bool waitingForTouch = false;
static bool HasCard = false;
// Misc
bool usingSmartCard = false;
int readCooldown = 200;
typedef void (*callbackTouch)(i32, i32, u8[168], u64);
callbackTouch touchCallback;
u64 touchData;
char AccessID[21] = "00000000000000000001";
static u8 cardData[168] = {0x01, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x2E, 0x58, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x5C, 0x97, 0x44, 0xF0, 0x88, 0x04, 0x00, 0x43, 0x26, 0x2C, 0x33, 0x00, 0x04,
0x06, 0x10, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x42, 0x47, 0x49, 0x43, 0x36,
0x00, 0x00, 0xFA, 0xE9, 0x69, 0x00, 0xF6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static unsigned int __stdcall reader_poll_thread_proc(void *ctx)
{
while (!READER_POLL_STOP_FLAG)
{
if (HasCard && !usingSmartCard) // A Cardio scan is already in progress, SmartCard doesn't need any extra cooldown.
{
Sleep(500);
return 0;
}
uint8_t UID[8] = {0};
// update devices
if (!usingSmartCard) // CardIO
{
EnterCriticalSection(&CARDIO_HID_CRIT_SECTION);
for (size_t device_no = 0; device_no < CARDIO_HID_CONTEXTS_LENGTH; device_no++)
{
struct cardio_hid_device *device = &CARDIO_HID_CONTEXTS[device_no];
// get status
cardio_hid_poll_value_t status = cardio_hid_device_poll(device);
if (status == HID_POLL_CARD_READY)
{
// read card
if (cardio_hid_device_read(device) == HID_CARD_NONE)
continue;
// if card not empty
if (*((uint64_t *)&device->u.usage_value[0]) > 0)
for (int i = 0; i < 8; ++i)
UID[i] = device->u.usage_value[i];
}
}
LeaveCriticalSection(&CARDIO_HID_CRIT_SECTION);
Sleep(readCooldown);
}
else // SmartCard
{
scard_update(UID);
}
if (UID[0] > 0) // If a card was read, format it properly and set HasCard to true so the game can insert it on next frame.
{
printWarning("%s (%s): Read card %02X%02X%02X%02X%02X%02X%02X%02X\n", __func__, module, UID[0], UID[1], UID[2], UID[3], UID[4], UID[5], UID[6], UID[7]);
if (waitingForTouch) // Check if game is waiting for a card.
{
// Properly format the AccessID
u64 ReversedAccessID;
for (int i = 0; i < 8; i++)
ReversedAccessID = (ReversedAccessID << 8) | UID[i];
sprintf(AccessID, "%020llu", ReversedAccessID);
waitingForTouch = false; // We don't want to read more cards unless the game asks us to, this is a failsafe to avoid weird behaviour.
HasCard = true;
}
else // Game wasn't checking for cards yet.
printError("%s (%s): Card %02X%02X%02X%02X%02X%02X%02X%02X was rejected, still waiting for WaitTouch()\n", __func__, module, UID[0], UID[1], UID[2], UID[3], UID[4], UID[5], UID[6], UID[7]);
}
}
return 0;
}
bool reader_runner_start()
{
// initialize
if (!READER_RUNNER_INITIALIZED)
{
READER_RUNNER_INITIALIZED = true;
if (!usingSmartCard)
{ // CardIO INIT
printWarning("%s (%s): Initializing CardIO\n", __func__, module);
// Initialize
if (!cardio_hid_init())
{
printError("%s (%s): Couldn't init CardIO\n", __func__, module);
return FALSE;
}
// Scan HID devices
if (!cardio_hid_scan())
{
printError("%s (%s): Couldn't scan for CardIO devices\n", __func__, module);
return FALSE;
}
}
else
{ // SmartCard INIT
printWarning("%s (%s): Initializing SmartCard\n", __func__, module);
if (!scard_init())
{
// Initialize
printError("%s (%s): Couldn't init SmartCard\n", __func__, module);
return FALSE;
}
}
return TRUE; // Init success
}
return TRUE;
}
void reader_runner_stop()
{
// shutdown HID
if (!usingSmartCard)
{
cardio_hid_close();
}
// set initialized to false
READER_RUNNER_INITIALIZED = false;
}
void Init()
{
printWarning("%s (%s): Starting HID Service...\n", __func__, module);
// Read config
toml_table_t *config = openConfig(configPath("plugins/cardreader.toml"));
if (config)
{
usingSmartCard = readConfigBool(config, "using_smartcard", false);
readCooldown = readConfigInt(config, "read_cooldown", usingSmartCard ? 500 : 50);
toml_free(config);
}
else
{
printError("%s (%s): Config file not found\n", __func__, module);
return;
}
printInfo("%s (%s): Using %s as reader type\n", __func__, module, usingSmartCard ? "Smart Card" : "cardIO");
printInfo("%s (%s): Card read cooldown set to %d ms\n", __func__, module, readCooldown);
// Find and initialize reader(s)
if (!reader_runner_start())
return;
printWarning("%s (%s): Starting reader thread.\n", __func__, module);
// Start reader thread
READER_POLL_STOP_FLAG = false;
READER_POLL_THREAD = (HANDLE)_beginthreadex(
NULL,
0,
reader_poll_thread_proc,
NULL,
0,
NULL);
}
void Update()
{
if (HasCard)
{
// Generating a ChipID Derived from the AccessID
char ChipID[33] = "000000000000";
strcat(ChipID, AccessID);
// Insert card in game
printInfo("%s (%s): Inserting Card %s, with ChipID %s\n", __func__, module, AccessID, ChipID);
memcpy(cardData + 0x2C, ChipID, 33);
memcpy(cardData + 0x50, AccessID, 21);
touchCallback(0, 0, cardData, touchData);
HasCard = false;
}
}
void WaitTouch(i32 (*callback)(i32, i32, u8[168], u64), u64 data) // This is called everytime the game expects a card to be read
{
printInfo("%s (%s): Waiting for touch\n", __func__, module);
waitingForTouch = true;
touchCallback = callback;
touchData = data;
}
void Exit()
{
reader_runner_stop();
READER_POLL_STOP_FLAG = true;
WaitForSingleObject(READER_POLL_THREAD, INFINITE);
CloseHandle(READER_POLL_THREAD);
READER_POLL_THREAD = NULL;
READER_POLL_STOP_FLAG = false;
}

View File

@ -19,100 +19,101 @@ typedef uint64_t u64;
#undef DEFINE_GUID #undef DEFINE_GUID
#endif #endif
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
#ifdef BASE_ADDRESS #ifdef BASE_ADDRESS
#define ASLR(address, handle) ((u64)handle + (u64)address - (u64)BASE_ADDRESS) #define ASLR(address, handle) ((u64)handle + (u64)address - (u64)BASE_ADDRESS)
#endif #endif
#define FUNCTION_PTR(returnType, callingConvention, function, location, ...) \ #define FUNCTION_PTR(returnType, callingConvention, function, location, ...) \
returnType (callingConvention *function) (__VA_ARGS__) = (returnType (callingConvention *) (__VA_ARGS__)) (location) returnType(callingConvention *function)(__VA_ARGS__) = (returnType(callingConvention *)(__VA_ARGS__))(location)
#define PROC_ADDRESS(libraryName, procName) GetProcAddress (LoadLibrary (TEXT (libraryName)), procName) #define PROC_ADDRESS(libraryName, procName) GetProcAddress(LoadLibrary(TEXT(libraryName)), procName)
#define HOOK(returnType, callingConvention, functionName, location, ...) \ #define HOOK(returnType, callingConvention, functionName, location, ...) \
typedef returnType callingConvention (*functionName) (__VA_ARGS__); \ typedef returnType callingConvention (*functionName)(__VA_ARGS__); \
functionName original##functionName = NULL; \ functionName original##functionName = NULL; \
void *where##functionName = (void *)location; \ void *where##functionName = (void *)location; \
returnType callingConvention implOf##functionName (__VA_ARGS__) returnType callingConvention implOf##functionName(__VA_ARGS__)
#define HOOK_DYNAMIC(returnType, callingConvention, functionName, ...) \ #define HOOK_DYNAMIC(returnType, callingConvention, functionName, ...) \
typedef returnType callingConvention (*functionName) (__VA_ARGS__); \ typedef returnType callingConvention (*functionName)(__VA_ARGS__); \
functionName original##functionName = NULL; \ functionName original##functionName = NULL; \
void *where##functionName = NULL; \ void *where##functionName = NULL; \
returnType callingConvention implOf##functionName (__VA_ARGS__) returnType callingConvention implOf##functionName(__VA_ARGS__)
#define INSTALL_HOOK(functionName) \ #define INSTALL_HOOK(functionName) \
{ \ { \
MH_Initialize (); \ MH_Initialize(); \
MH_CreateHook ((void *)where##functionName, (void *)implOf##functionName, (void **)(&original##functionName)); \ MH_CreateHook((void *)where##functionName, (void *)implOf##functionName, (void **)(&original##functionName)); \
MH_EnableHook ((void *)where##functionName); \ MH_EnableHook((void *)where##functionName); \
} }
#define INSTALL_HOOK_DYNAMIC(functionName, location) \ #define INSTALL_HOOK_DYNAMIC(functionName, location) \
{ \ { \
where##functionName = (void *)location; \ where##functionName = (void *)location; \
INSTALL_HOOK (functionName); \ INSTALL_HOOK(functionName); \
} }
#define WRITE_MEMORY(location, type, ...) \ #define WRITE_MEMORY(location, type, ...) \
{ \ { \
const type data[] = { __VA_ARGS__ }; \ const type data[] = {__VA_ARGS__}; \
DWORD oldProtect; \ DWORD oldProtect; \
VirtualProtect ((void *)(location), sizeof (data), PAGE_EXECUTE_READWRITE, &oldProtect); \ VirtualProtect((void *)(location), sizeof(data), PAGE_EXECUTE_READWRITE, &oldProtect); \
memcpy ((void *)(location), data, sizeof (data)); \ memcpy((void *)(location), data, sizeof(data)); \
VirtualProtect ((void *)(location), sizeof (data), oldProtect, &oldProtect); \ VirtualProtect((void *)(location), sizeof(data), oldProtect, &oldProtect); \
} }
#define WRITE_MEMORY_STRING(location, data, length) \ #define WRITE_MEMORY_STRING(location, data, length) \
{ \ { \
DWORD oldProtect; \ DWORD oldProtect; \
VirtualProtect ((void *)(location), length, PAGE_EXECUTE_READWRITE, &oldProtect); \ VirtualProtect((void *)(location), length, PAGE_EXECUTE_READWRITE, &oldProtect); \
memcpy ((void *)(location), data, length); \ memcpy((void *)(location), data, length); \
VirtualProtect ((void *)(location), length, oldProtect, &oldProtect); \ VirtualProtect((void *)(location), length, oldProtect, &oldProtect); \
} }
#define WRITE_NOP(location, count) \ #define WRITE_NOP(location, count) \
{ \ { \
DWORD oldProtect; \ DWORD oldProtect; \
VirtualProtect ((void *)(location), (size_t)(count), PAGE_EXECUTE_READWRITE, &oldProtect); \ VirtualProtect((void *)(location), (size_t)(count), PAGE_EXECUTE_READWRITE, &oldProtect); \
for (size_t i = 0; i < (size_t)(count); i++) \ for (size_t i = 0; i < (size_t)(count); i++) \
*((uint8_t *)(location) + i) = 0x90; \ *((uint8_t *)(location) + i) = 0x90; \
VirtualProtect ((void *)(location), (size_t)(count), oldProtect, &oldProtect); \ VirtualProtect((void *)(location), (size_t)(count), oldProtect, &oldProtect); \
} }
#define WRITE_NULL(location, count) \ #define WRITE_NULL(location, count) \
{ \ { \
DWORD oldProtect; \ DWORD oldProtect; \
VirtualProtect ((void *)(location), (size_t)(count), PAGE_EXECUTE_READWRITE, &oldProtect); \ VirtualProtect((void *)(location), (size_t)(count), PAGE_EXECUTE_READWRITE, &oldProtect); \
for (size_t i = 0; i < (size_t)(count); i++) \ for (size_t i = 0; i < (size_t)(count); i++) \
*((uint8_t *)(location) + i) = 0x00; \ *((uint8_t *)(location) + i) = 0x00; \
VirtualProtect ((void *)(location), (size_t)(count), oldProtect, &oldProtect); \ VirtualProtect((void *)(location), (size_t)(count), oldProtect, &oldProtect); \
} }
#define COUNTOFARR(arr) sizeof (arr) / sizeof (arr[0]) #define COUNTOFARR(arr) sizeof(arr) / sizeof(arr[0])
#define RETURN_FALSE(returnType, callingConvention, functionName, ...) \ #define RETURN_FALSE(returnType, callingConvention, functionName, ...) \
returnType callingConvention functionName (__VA_ARGS__) { return 0; } returnType callingConvention functionName(__VA_ARGS__) { return 0; }
#define RETURN_TRUE(returnType, callingConvention, functionName, ...) \ #define RETURN_TRUE(returnType, callingConvention, functionName, ...) \
returnType callingConvention functionName (__VA_ARGS__) { return 1; } returnType callingConvention functionName(__VA_ARGS__) { return 1; }
#define INFO_COLOUR FOREGROUND_GREEN #define INFO_COLOUR FOREGROUND_GREEN
#define WARNING_COLOUR (FOREGROUND_RED | FOREGROUND_GREEN) #define WARNING_COLOUR (FOREGROUND_RED | FOREGROUND_GREEN)
#define ERROR_COLOUR FOREGROUND_RED #define ERROR_COLOUR FOREGROUND_RED
#define printInfo(format, ...) printColour (INFO_COLOUR, format, __VA_ARGS__) #define printInfo(format, ...) printColour(INFO_COLOUR, format, __VA_ARGS__)
#define printWarning(format, ...) printColour (WARNING_COLOUR, format, __VA_ARGS__) #define printWarning(format, ...) printColour(WARNING_COLOUR, format, __VA_ARGS__)
#define printError(format, ...) printColour (ERROR_COLOUR, format, __VA_ARGS__) #define printError(format, ...) printColour(ERROR_COLOUR, format, __VA_ARGS__)
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C"
{
#endif #endif
char *configPath (char *name); char *configPath(char *name);
toml_table_t *openConfig (char *configFilePath); toml_table_t *openConfig(char *configFilePath);
toml_table_t *openConfigSection (toml_table_t *config, char *sectionName); toml_table_t *openConfigSection(toml_table_t *config, char *sectionName);
bool readConfigBool (toml_table_t *table, char *key, bool notFoundValue); bool readConfigBool(toml_table_t *table, char *key, bool notFoundValue);
int64_t readConfigInt (toml_table_t *table, char *key, int64_t notFoundValue); int64_t readConfigInt(toml_table_t *table, char *key, int64_t notFoundValue);
char *readConfigString (toml_table_t *table, char *key, char *notFoundValue); char *readConfigString(toml_table_t *table, char *key, char *notFoundValue);
void printColour (int colour, const char *format, ...); void printColour(int colour, const char *format, ...);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -1,22 +1,25 @@
compiler = meson.get_compiler('c') compiler = meson.get_compiler('c')
hid_lib = compiler.find_library('hid') hid_lib = compiler.find_library('hid')
winscard_lib = compiler.find_library('winscard')
setupapi_lib = compiler.find_library('setupapi') setupapi_lib = compiler.find_library('setupapi')
deps = [ deps = [
dependency('tomlc99'), dependency('tomlc99'),
dependency('minhook'), dependency('minhook'),
hid_lib, hid_lib,
setupapi_lib winscard_lib,
setupapi_lib,
] ]
shared_library( shared_library(
'cardio', 'cardreader',
implicit_include_directories : false, implicit_include_directories : false,
vs_module_defs : 'cardio.def', vs_module_defs : 'cardreader.def',
sources: [ sources: [
'helpers.c', 'helpers.c',
'cardio_plugin/dllmain.c', 'cardreader_plugin/dllmain.c',
'cardio_plugin/cardio.c', 'cardreader_plugin/CardIO/cardio.c',
'cardreader_plugin/SmartCard/scard.c',
], ],
dependencies: deps, dependencies: deps,
include_directories: [ include_directories: [