Compare commits
5 Commits
583827f890
...
6bcc546b4b
Author | SHA1 | Date | |
---|---|---|---|
6bcc546b4b | |||
aeae89ab8e | |||
68e9fb938b | |||
3a9725e463 | |||
9eedc62e04 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1 +1,4 @@
|
||||
.vscode
|
||||
.vscode
|
||||
build64/*
|
||||
bin/*
|
||||
*.dll
|
113
aimeio/aimeio.c
113
aimeio/aimeio.c
@ -1,113 +0,0 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "aimeio/aimeio.h"
|
||||
#include "aimepcsc/aimepcsc.h"
|
||||
|
||||
static struct aimepcsc_context *ctx;
|
||||
static struct aime_data data;
|
||||
|
||||
uint16_t aime_io_get_api_version(void)
|
||||
{
|
||||
return 0x0100;
|
||||
}
|
||||
|
||||
HRESULT aime_io_init(void)
|
||||
{
|
||||
int ret;
|
||||
FILE* fp;
|
||||
|
||||
ret = AllocConsole();
|
||||
|
||||
// someone might already allocated a console - seeing this on fufubot's segatools
|
||||
if (ret != 0) {
|
||||
// only when we allocate a console, we need to redirect stdout
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
}
|
||||
|
||||
ctx = aimepcsc_create();
|
||||
if (!ctx) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
ret = aimepcsc_init(ctx);
|
||||
|
||||
if (ret != 0) {
|
||||
printf("aimeio-pcsc: failed to initialize: %s\n", aimepcsc_error(ctx));
|
||||
aimepcsc_destroy(ctx);
|
||||
ctx = NULL;
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
printf("aimeio-pcsc: initialized with reader: %s\n", aimepcsc_reader_name(ctx));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT aime_io_nfc_poll(uint8_t unit_no)
|
||||
{
|
||||
int ret;
|
||||
HRESULT hr;
|
||||
|
||||
if (unit_no != 0) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
memset(&data, 0, sizeof(data));
|
||||
|
||||
ret = aimepcsc_poll(ctx, &data);
|
||||
|
||||
if (ret < 0) {
|
||||
printf("aimeio-pcsc: poll failed: %s\n", aimepcsc_error(ctx));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT aime_io_nfc_get_aime_id(
|
||||
uint8_t unit_no,
|
||||
uint8_t *luid,
|
||||
size_t luid_size)
|
||||
{
|
||||
assert(luid != NULL);
|
||||
|
||||
if (unit_no != 0 || data.card_type != Mifare || luid_size != data.card_id_len) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
memcpy(luid, data.card_id, luid_size);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT aime_io_nfc_get_felica_id(uint8_t unit_no, uint64_t *IDm)
|
||||
{
|
||||
uint64_t val;
|
||||
size_t i;
|
||||
|
||||
assert(IDm != NULL);
|
||||
|
||||
if (unit_no != 0 || data.card_type != FeliCa || data.card_id_len != 8) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
val = 0;
|
||||
|
||||
for (i = 0 ; i < 8 ; i++) {
|
||||
val = (val << 8) | data.card_id[i];
|
||||
}
|
||||
|
||||
*IDm = val;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void aime_io_led_set_color(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b)
|
||||
{}
|
@ -1,11 +0,0 @@
|
||||
aimeio_lib = shared_library(
|
||||
'aimeio',
|
||||
name_prefix : '',
|
||||
include_directories: inc,
|
||||
implicit_include_directories : false,
|
||||
vs_module_defs : 'aimeio.def',
|
||||
link_with: [aimepcsc_lib],
|
||||
sources : [
|
||||
'aimeio.c',
|
||||
],
|
||||
)
|
@ -1,222 +0,0 @@
|
||||
#include "aimepcsc.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static const uint8_t atr_ios14443_common[] = {0x3B, 0x8F, 0x80, 0x01, 0x80, 0x4F, 0x0C, 0xA0, 0x00, 0x00, 0x03, 0x06};
|
||||
static const uint8_t cardtype_m1k[] = {0x03, 0x00, 0x01};
|
||||
static const uint8_t cardtype_felica[] = {0x11, 0x00, 0x3B};
|
||||
|
||||
static const uint8_t felica_cmd_readidm[] = {0xFF, 0xCA, 0x00, 0x00, 0x00};
|
||||
|
||||
static const uint8_t m1k_cmd_loadkey[] = {0xFF, 0x82, 0x00, 0x00, 0x06, 0x57, 0x43, 0x43, 0x46, 0x76, 0x32};
|
||||
static const uint8_t m1k_cmd_auth_block2[] = {0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, 0x02, 0x61, 0x00};
|
||||
static const uint8_t m1k_cmd_read_block2[] = {0xFF, 0xB0, 0x00, 0x02, 0x10};
|
||||
|
||||
struct aimepcsc_context {
|
||||
SCARDCONTEXT hContext;
|
||||
LPSTR mszReaders;
|
||||
DWORD pcchReaders;
|
||||
|
||||
CHAR last_error[256];
|
||||
};
|
||||
|
||||
#define APDU_SEND(card, cmd, expected_res_len) \
|
||||
do { \
|
||||
len = sizeof(buf); \
|
||||
ret = SCardTransmit(*card, SCARD_PCI_T1, cmd, sizeof(cmd), NULL, buf, &len); \
|
||||
if (ret != SCARD_S_SUCCESS) { \
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "SCardTransmit failed during " #cmd ": %08lX", (ULONG) ret); \
|
||||
return -1; \
|
||||
} \
|
||||
if (len != expected_res_len || buf[expected_res_len - 2] != 0x90 || buf[expected_res_len - 1] != 0x00) { \
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), #cmd " failed; res_len=%lu, res_code=%02x%02x", len, buf[2], buf[3]); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int read_felica_aime(struct aimepcsc_context *ctx, LPSCARDHANDLE card, struct aime_data *data) {
|
||||
uint8_t buf[32];
|
||||
DWORD len;
|
||||
LONG ret;
|
||||
|
||||
/* read card ID */
|
||||
APDU_SEND(card, felica_cmd_readidm, 10);
|
||||
|
||||
data->card_id_len = 8;
|
||||
memcpy(data->card_id, buf, 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_m1k_aime(struct aimepcsc_context *ctx, LPSCARDHANDLE card, struct aime_data *data) {
|
||||
uint8_t buf[32];
|
||||
DWORD len;
|
||||
LONG ret;
|
||||
|
||||
/* load key onto reader */
|
||||
APDU_SEND(card, m1k_cmd_loadkey, 2);
|
||||
|
||||
/* authenticate block 2 */
|
||||
APDU_SEND(card, m1k_cmd_auth_block2, 2);
|
||||
|
||||
/* read block 2 */
|
||||
APDU_SEND(card, m1k_cmd_read_block2, 18);
|
||||
|
||||
data->card_id_len = 10;
|
||||
memcpy(data->card_id, buf + 6, 10);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct aimepcsc_context* aimepcsc_create(void) {
|
||||
struct aimepcsc_context* ctx;
|
||||
|
||||
ctx = (struct aimepcsc_context*) malloc(sizeof(struct aimepcsc_context));
|
||||
if (!ctx) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(ctx, 0, sizeof(struct aimepcsc_context));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void aimepcsc_destroy(struct aimepcsc_context *ctx) {
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
int aimepcsc_init(struct aimepcsc_context *ctx) {
|
||||
LONG ret;
|
||||
|
||||
ret = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &ctx->hContext);
|
||||
|
||||
if (ret != SCARD_S_SUCCESS) {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "SCardEstablishContext failed: %08lX", (ULONG) ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx->pcchReaders = SCARD_AUTOALLOCATE;
|
||||
|
||||
ret = SCardListReaders(ctx->hContext, NULL, (LPSTR) &ctx->mszReaders, &ctx->pcchReaders);
|
||||
|
||||
if (ret != SCARD_S_SUCCESS) {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "SCardListReaders failed: %08lX", (ULONG) ret);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
errout:
|
||||
SCardReleaseContext(ctx->hContext);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void aimepcsc_shutdown(struct aimepcsc_context *ctx) {
|
||||
if (ctx->mszReaders != NULL) {
|
||||
SCardFreeMemory(ctx->hContext, ctx->mszReaders);
|
||||
}
|
||||
|
||||
SCardReleaseContext(ctx->hContext);
|
||||
}
|
||||
|
||||
int aimepcsc_poll(struct aimepcsc_context *ctx, struct aime_data *data) {
|
||||
SCARDHANDLE hCard;
|
||||
SCARD_READERSTATE rs;
|
||||
LONG ret;
|
||||
LPBYTE pbAttr = NULL;
|
||||
DWORD cByte = SCARD_AUTOALLOCATE;
|
||||
int retval;
|
||||
|
||||
retval = 1;
|
||||
|
||||
memset(&rs, 0, sizeof(SCARD_READERSTATE));
|
||||
|
||||
rs.szReader = ctx->mszReaders;
|
||||
rs.dwCurrentState = SCARD_STATE_UNAWARE;
|
||||
|
||||
/* check if a card is present */
|
||||
ret = SCardGetStatusChange(ctx->hContext, 0, &rs, 1);
|
||||
|
||||
if (ret == SCARD_E_TIMEOUT) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ret != SCARD_S_SUCCESS) {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "SCardGetStatusChange failed: %08lX", (ULONG) ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rs.dwEventState & SCARD_STATE_EMPTY) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!(rs.dwEventState & SCARD_STATE_PRESENT)) {
|
||||
sprintf(ctx->last_error, "unknown dwCurrentState: %08lX", rs.dwCurrentState);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* connect to card */
|
||||
ret = SCardConnect(ctx->hContext, rs.szReader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, NULL);
|
||||
|
||||
if (ret != SCARD_S_SUCCESS) {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "SCardConnect failed: %08lX", (ULONG) ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* get ATR string */
|
||||
ret = SCardGetAttrib(hCard, SCARD_ATTR_ATR_STRING, (LPBYTE) &pbAttr, &cByte);
|
||||
|
||||
if (ret != SCARD_S_SUCCESS) {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "SCardGetAttrib failed: %08lX", (ULONG) ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (cByte != 20) {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "invalid ATR length: %lu", cByte);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* check ATR */
|
||||
if (memcmp(pbAttr, atr_ios14443_common, sizeof(atr_ios14443_common)) != 0) {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "invalid card type.");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* check card type */
|
||||
if (memcmp(pbAttr + sizeof(atr_ios14443_common), cardtype_m1k, sizeof(cardtype_m1k)) == 0) {
|
||||
data->card_type = Mifare;
|
||||
ret = read_m1k_aime(ctx, &hCard, data);
|
||||
if (ret < 0) {
|
||||
retval = -1;
|
||||
goto out;
|
||||
} else if (ret > 0) {
|
||||
goto out;
|
||||
}
|
||||
} else if (memcmp(pbAttr + sizeof(atr_ios14443_common), cardtype_felica, sizeof(cardtype_felica)) == 0) {
|
||||
data->card_type = FeliCa;
|
||||
ret = read_felica_aime(ctx, &hCard, data);
|
||||
if (ret < 0) {
|
||||
retval = -1;
|
||||
goto out;
|
||||
} else if (ret > 0) {
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
snprintf(ctx->last_error, sizeof(ctx->last_error), "invalid card type.");
|
||||
goto out;
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
|
||||
out:
|
||||
SCardFreeMemory(ctx->hContext, pbAttr);
|
||||
SCardDisconnect(hCard, SCARD_LEAVE_CARD);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
const char* aimepcsc_error(struct aimepcsc_context *ctx) {
|
||||
return ctx->last_error;
|
||||
}
|
||||
|
||||
const char* aimepcsc_reader_name(struct aimepcsc_context *ctx) {
|
||||
return ctx->mszReaders;
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <winioctl.h>
|
||||
#include <winscard.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* opaque context */
|
||||
struct aimepcsc_context;
|
||||
|
||||
/* aime card types */
|
||||
enum AIME_CARDTYPE {
|
||||
Mifare = 0x01,
|
||||
FeliCa = 0x02,
|
||||
};
|
||||
|
||||
/* aime card data */
|
||||
struct aime_data {
|
||||
/* AIME_CARDTYPE */
|
||||
uint8_t card_type;
|
||||
|
||||
/* Card ID */
|
||||
uint8_t card_id[32];
|
||||
|
||||
/* Card ID length */
|
||||
uint8_t card_id_len;
|
||||
};
|
||||
|
||||
/* create new context for aimepcsc
|
||||
* @return context on success, NULL on failure
|
||||
*/
|
||||
struct aimepcsc_context* aimepcsc_create(void);
|
||||
|
||||
/* destroy context for aimepcsc */
|
||||
void aimepcsc_destroy(struct aimepcsc_context *ctx);
|
||||
|
||||
/* setup reader (first detected reader will be used)
|
||||
* @param ctx context
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int aimepcsc_init(struct aimepcsc_context *ctx);
|
||||
|
||||
/* shutdown reader */
|
||||
void aimepcsc_shutdown(struct aimepcsc_context *ctx);
|
||||
|
||||
/* poll for card
|
||||
* @param ctx context
|
||||
* @param data data to be filled
|
||||
* @return 0 on success, -1 on failure, 1 on no card
|
||||
*/
|
||||
int aimepcsc_poll(struct aimepcsc_context *ctx, struct aime_data *data);
|
||||
|
||||
/* get last error
|
||||
* @param ctx context
|
||||
* @return error string
|
||||
*/
|
||||
const char* aimepcsc_error(struct aimepcsc_context *ctx);
|
||||
|
||||
/* get reader name
|
||||
* @param ctx context
|
||||
* @return reader name
|
||||
*/
|
||||
const char* aimepcsc_reader_name(struct aimepcsc_context *ctx);
|
@ -1,44 +0,0 @@
|
||||
#include "aimepcsc.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
struct aimepcsc_context* ctx;
|
||||
int ret;
|
||||
|
||||
ctx = aimepcsc_create();
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = aimepcsc_init(ctx);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "aimepcsc_init failed: %s\n", aimepcsc_error(ctx));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fprintf(stderr, "connected to reader: %s; waiting for cards...\n", aimepcsc_reader_name(ctx));
|
||||
|
||||
while (1) {
|
||||
struct aime_data data;
|
||||
|
||||
ret = aimepcsc_poll(ctx, &data);
|
||||
if (ret == 0) {
|
||||
printf("card detected: ");
|
||||
printf("type=%s, id=", data.card_type == Mifare ? "Mifare (old Aime)" : "FeliCa (AIC-Aime)");
|
||||
for (int i = 0; i < data.card_id_len; i++) {
|
||||
printf("%02X", data.card_id[i]);
|
||||
}
|
||||
printf("\n");
|
||||
} else if (ret == -1) {
|
||||
fprintf(stderr, "aimepcsc_poll failed: %s\n", aimepcsc_error(ctx));
|
||||
break;
|
||||
}
|
||||
|
||||
Sleep(500);
|
||||
}
|
||||
|
||||
aimepcsc_shutdown(ctx);
|
||||
aimepcsc_destroy(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
winscard_lib = cc.find_library('winscard')
|
||||
|
||||
aimepcsc_lib = static_library(
|
||||
'aimepcsc',
|
||||
name_prefix : '',
|
||||
include_directories: inc,
|
||||
implicit_include_directories : false,
|
||||
dependencies: [winscard_lib],
|
||||
sources : [
|
||||
'aimepcsc.c',
|
||||
],
|
||||
)
|
||||
|
||||
aimereader = executable(
|
||||
'aimereader',
|
||||
name_prefix : '',
|
||||
include_directories : [inc],
|
||||
implicit_include_directories : false,
|
||||
link_with: [aimepcsc_lib],
|
||||
sources : [
|
||||
'aimereader.c'
|
||||
]
|
||||
)
|
13
build64.bat
Normal file
13
build64.bat
Normal file
@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
cd /d %~dp0
|
||||
|
||||
REM Edit this with the right path to vcvarsall.bat.
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
meson setup build64 --buildtype=release
|
||||
meson configure build64
|
||||
ninja -C build64
|
||||
|
||||
mkdir bin
|
||||
copy build64\src\aimeio.dll bin\aimeio.dll
|
||||
copy build64\src\aimetest.exe bin\aimetest.exe
|
@ -1,10 +0,0 @@
|
||||
[binaries]
|
||||
c = 'i686-w64-mingw32-gcc'
|
||||
ar = 'i686-w64-mingw32-ar'
|
||||
strip = 'i686-w64-mingw32-strip'
|
||||
|
||||
[host_machine]
|
||||
system = 'windows'
|
||||
cpu_family = 'x86'
|
||||
cpu = 'i686'
|
||||
endian = 'little'
|
@ -1,10 +0,0 @@
|
||||
[binaries]
|
||||
c = 'x86_64-w64-mingw32-gcc'
|
||||
ar = 'x86_64-w64-mingw32-ar'
|
||||
strip = 'x86_64-w64-mingw32-strip'
|
||||
|
||||
[host_machine]
|
||||
system = 'windows'
|
||||
cpu_family = 'x86_64'
|
||||
cpu = 'x86_64'
|
||||
endian = 'little'
|
41
meson.build
41
meson.build
@ -1,44 +1,9 @@
|
||||
project(
|
||||
'aimeio-pcsc',
|
||||
'aimeio_scard',
|
||||
'c',
|
||||
version: '0.0.1',
|
||||
default_options: [
|
||||
'warning_level=3',
|
||||
],
|
||||
version : '0.0.2'
|
||||
)
|
||||
|
||||
add_project_arguments(
|
||||
'-DCOBJMACROS',
|
||||
'-DDIRECTINPUT_VERSION=0x0800',
|
||||
'-DWIN32_LEAN_AND_MEAN',
|
||||
'-D_WIN32_WINNT=_WIN32_WINNT_WIN7',
|
||||
'-DMINGW_HAS_SECURE_API=1',
|
||||
'-Wno-unused',
|
||||
language: 'c',
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
if cc.get_id() != 'msvc'
|
||||
add_project_arguments(
|
||||
'-ffunction-sections',
|
||||
'-fdata-sections',
|
||||
'-flto', # Enable Link-Time Optimization
|
||||
language: 'c',
|
||||
)
|
||||
|
||||
add_project_link_arguments(
|
||||
'-Wl,--enable-stdcall-fixup',
|
||||
'-Wl,--exclude-all-symbols',
|
||||
'-Wl,--gc-sections',
|
||||
'-static-libgcc',
|
||||
'-flto', # Enable Link-Time Optimization
|
||||
'-Wl,-s', # Strip debug symbols
|
||||
language: 'c',
|
||||
)
|
||||
endif
|
||||
|
||||
inc = include_directories('.')
|
||||
|
||||
subdir('aimepcsc')
|
||||
subdir('aimeio')
|
||||
subdir('src/')
|
65
readme.md
65
readme.md
@ -1,28 +1,67 @@
|
||||
aimeio-pcsc
|
||||
---
|
||||
# AimeIO CardReader
|
||||
|
||||
PC/SC-based Aime card reader for `segatools`. `aimeio-pcsc` allows you to use PC/SC compliant smart card readers as your Aime card reader in `segatools`. `aimeio-pcsc` only properly supports the old Mifare Classic 1K-based Aime cards.
|
||||
This allows you to a smartcard reader (specifically the acr122u) with segatools
|
||||
|
||||
If you scan a newer AIC-based Aime, its FeliCa IDm will be provided to the game. The game will not see the correct "access code," but the IDm should be unique to each card so that particular card can still track your plays.
|
||||
# Acknowledgments
|
||||
|
||||
Tested on SONY's PaSoRi RC-S300. Other readers should, in theory, also work.
|
||||
This is a plugin destined to be used with [Dniel97](https://gitea.tendokyu.moe/Dniel97)'s [segatools fork](https://gitea.tendokyu.moe/Dniel97/segatools) (but should work on others too).
|
||||
|
||||
### Usage
|
||||
SmartCard implementation taken from [spicetools](https://github.com/spicetools/spicetools)
|
||||
|
||||
To test if your card reader is supported, run `aimereader.exe` and try read your Aime card.
|
||||
Initial project made by [nat](https://gitea.tendokyu.moe/nat/aimeio-pcsc)
|
||||
|
||||
All the logic for reading cards from a file has been taken from [CrazyRedMachine](https://github.com/CrazyRedMachine)'s [RedBoard](https://github.com/CrazyRedMachine/RedBoard) aimeio.
|
||||
|
||||
# Usage
|
||||
|
||||
To use it with a game, copy `aimeio.dll` to your `segatools` folder and add the following to your `segatools.ini`:
|
||||
|
||||
```ini
|
||||
[aimeio]
|
||||
path=aimeio.dll
|
||||
scan=0x0D ;Sets the key which will be used to insert a card in game. The default is 'Return'
|
||||
|
||||
;Everything below this line is optional.
|
||||
|
||||
;aimePath= ;Manually specify an aime.txt file
|
||||
;felicaPath= ;Manually specify a felica.txt file
|
||||
;felicaGen=0 ;Generate a new random card if it's missing from the file
|
||||
;debug=0 ;Display function calls
|
||||
```
|
||||
|
||||
### Build
|
||||
## Scanning cards
|
||||
|
||||
On Linux:
|
||||
If you scan a newer AIC-based Aime, its FeliCa IDm will be provided to the game. The game will not see the correct "access code," but the IDm should be unique to each card so that particular card can still track your plays.
|
||||
|
||||
```sh
|
||||
meson setup --cross cross-mingw-64.txt b64
|
||||
ninja -C b64
|
||||
```
|
||||
As for Mifare cards, their access code won't be 1:1 with a real reader (i still need to fix this). You can still use them and they will work though !
|
||||
|
||||
## Inserting cards
|
||||
|
||||
By pressing the key you have set in segatools.ini and holding it for a few moments, you will insert a card set in either aime.txt or felica.txt
|
||||
|
||||
You can have multiple cards in those files!
|
||||
By holding a number from 0 to 9 on your keypad, and then holding enter, you can choose what card to insert.
|
||||
|
||||
For example...
|
||||
|
||||
```text
|
||||
aime.txt
|
||||
---
|
||||
|
||||
1a7f3e925cb866d45a3b
|
||||
a5d04d668bc529e35aaa
|
||||
```
|
||||
|
||||
By only pressing the insert key, you will insert card0, "1a7f3e925cb866d45a3b".
|
||||
If you hold 1 on your numpad and press the insert key, card1, "a5d04d668bc529e35aaa" will be inserted !
|
||||
|
||||
This works for up to 10 cards.
|
||||
|
||||
# Build
|
||||
|
||||
To build this, you'll need two things :
|
||||
|
||||
- [Meson 1.1.0](https://mesonbuild.com)
|
||||
- [Build Tools pour Visual Studio 2022](https://visualstudio.microsoft.com/fr/downloads/)
|
||||
|
||||
Once you've edited your build64.bat file to point to your local installation of the VS2022 build tools, run build64.bat and the output will be located in `bin/aime.dll`.
|
420
src/aimeio.c
Normal file
420
src/aimeio.c
Normal file
@ -0,0 +1,420 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "aimeio.h"
|
||||
#include "scard/scard.h"
|
||||
|
||||
char module[] = "CardReader";
|
||||
|
||||
// Reader Thread
|
||||
static bool READER_RUNNER_INITIALIZED = false;
|
||||
static HANDLE READER_POLL_THREAD;
|
||||
static bool READER_POLL_STOP_FLAG;
|
||||
|
||||
static bool polling = false;
|
||||
static bool HasCard = false;
|
||||
uint8_t UID[8] = {0};
|
||||
|
||||
struct aime_io_config
|
||||
{
|
||||
bool debug;
|
||||
wchar_t aime_path[MAX_PATH];
|
||||
wchar_t felica_path[MAX_PATH];
|
||||
bool felica_gen;
|
||||
uint8_t vk_scan;
|
||||
};
|
||||
|
||||
static struct aime_io_config aime_io_cfg;
|
||||
static uint8_t aime_io_aime_id[10];
|
||||
static uint8_t aime_io_felica_id[8];
|
||||
static bool aime_io_aime_id_present;
|
||||
static bool aime_io_felica_id_present;
|
||||
|
||||
#pragma region CONFIG
|
||||
static void aime_io_config_read(struct aime_io_config *cfg, const wchar_t *filename)
|
||||
{
|
||||
assert(cfg != NULL);
|
||||
assert(filename != NULL);
|
||||
|
||||
cfg->debug = GetPrivateProfileIntW(
|
||||
L"aimeio",
|
||||
L"debug",
|
||||
0,
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"aimeio",
|
||||
L"aimePath",
|
||||
L"DEVICE\\aime.txt",
|
||||
cfg->aime_path,
|
||||
_countof(cfg->aime_path),
|
||||
filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"aimeio",
|
||||
L"felicaPath",
|
||||
L"DEVICE\\felica.txt",
|
||||
cfg->felica_path,
|
||||
_countof(cfg->felica_path),
|
||||
filename);
|
||||
|
||||
cfg->felica_gen = GetPrivateProfileIntW(
|
||||
L"aimeio",
|
||||
L"felicaGen",
|
||||
1,
|
||||
filename);
|
||||
|
||||
cfg->vk_scan = GetPrivateProfileIntW(
|
||||
L"aimeio",
|
||||
L"scan",
|
||||
VK_RETURN,
|
||||
filename);
|
||||
|
||||
if (aime_io_cfg.debug)
|
||||
printf("DEBUG: aime_io_config_read(filename : %ls). \r\n", filename);
|
||||
}
|
||||
|
||||
static HRESULT aime_io_read_id_file(const wchar_t *path, uint8_t *bytes, size_t nbytes, int LineToRead)
|
||||
{
|
||||
HRESULT hr;
|
||||
FILE *f;
|
||||
size_t i;
|
||||
int byte;
|
||||
long offset;
|
||||
int currentLine = 0;
|
||||
|
||||
f = _wfopen(path, L"r");
|
||||
if (f == NULL) // If the file doesn't exist, we bail.
|
||||
return S_FALSE;
|
||||
|
||||
memset(bytes, 0, nbytes);
|
||||
|
||||
// Calculate the offset to the start of the desired line
|
||||
offset = 0;
|
||||
while (currentLine < LineToRead)
|
||||
{
|
||||
int c = fgetc(f);
|
||||
if (c == EOF)
|
||||
{
|
||||
// If the end of the file is reached before the desired line, print an error
|
||||
printf("%s: %S: Error: Line %d does not exist\n", module, path, LineToRead);
|
||||
hr = E_FAIL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
currentLine++;
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
// Seek to the calculated offset
|
||||
fseek(f, offset, SEEK_SET);
|
||||
|
||||
// Read the desired line and extract hexadecimal values
|
||||
for (i = 0; i < nbytes; i++)
|
||||
{
|
||||
int r = fscanf(f, "%02x", &byte);
|
||||
|
||||
if (r != 1)
|
||||
{
|
||||
printf("%s: %S: Error parsing line %d\n", module, path, LineToRead);
|
||||
hr = E_FAIL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
bytes[i] = byte;
|
||||
}
|
||||
|
||||
// Check if the line is not nbytes long
|
||||
if (fgetc(f) != '\n' && !feof(f))
|
||||
{
|
||||
printf("%s: %S: Error: Line %d is not %zu bytes long\n", module, path, LineToRead, nbytes);
|
||||
hr = E_FAIL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
hr = S_OK;
|
||||
|
||||
end:
|
||||
if (f != NULL)
|
||||
{
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT aime_io_generate_felica(const wchar_t *path, uint8_t *bytes, size_t nbytes)
|
||||
{
|
||||
size_t i;
|
||||
FILE *f;
|
||||
|
||||
assert(path != NULL);
|
||||
assert(bytes != NULL);
|
||||
assert(nbytes > 0);
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
for (i = 0; i < nbytes; i++)
|
||||
bytes[i] = rand();
|
||||
|
||||
/* FeliCa IDm values should have a 0 in their high nibble. I think. */
|
||||
bytes[0] &= 0x0F;
|
||||
|
||||
f = _wfopen(path, L"w");
|
||||
|
||||
if (f == NULL) // If we somehow can't create the file, we error out.
|
||||
{
|
||||
printf("%s: %S: fopen failed: %i\n", module, path, (int)errno);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
for (i = 0; i < nbytes; i++)
|
||||
fprintf(f, "%02X", bytes[i]);
|
||||
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
|
||||
printf("%s: Generated random FeliCa ID\n", module);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region READER SPECIFIC
|
||||
static unsigned int __stdcall reader_poll_thread_proc(void *ctx)
|
||||
{
|
||||
if (aime_io_cfg.debug)
|
||||
printf("DEBUG: reader_poll_thread_proc(). \r\n");
|
||||
while (!READER_POLL_STOP_FLAG)
|
||||
{
|
||||
if (!HasCard && polling)
|
||||
{
|
||||
uint8_t _UID[8] = {0};
|
||||
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.
|
||||
{
|
||||
printf("%s: Read card %02X%02X%02X%02X%02X%02X%02X%02X\n", module, _UID[0], _UID[1], _UID[2], _UID[3], _UID[4], _UID[5], _UID[6], _UID[7]);
|
||||
for (int i = 0; i < 8; i++)
|
||||
UID[i] = _UID[i];
|
||||
|
||||
HasCard = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region AIME
|
||||
uint16_t aime_io_get_api_version(void)
|
||||
{
|
||||
return 0x0100;
|
||||
}
|
||||
|
||||
HRESULT aime_io_init(void)
|
||||
{
|
||||
|
||||
// At init we want to open a console...
|
||||
int ret = AllocConsole();
|
||||
FILE *fp;
|
||||
// someone might already allocated a console - seeing this on fufubot's segatools
|
||||
if (ret != 0)
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout); // only when we allocate a console, we need to redirect stdout
|
||||
|
||||
// We then read the segatools config file to get settings.
|
||||
aime_io_config_read(&aime_io_cfg, L".\\segatools.ini");
|
||||
|
||||
if (aime_io_cfg.debug)
|
||||
printf("DEBUG: aime_io_init(). \r\n");
|
||||
|
||||
// Find and initialize reader(s)
|
||||
if (!READER_RUNNER_INITIALIZED)
|
||||
{
|
||||
READER_RUNNER_INITIALIZED = true;
|
||||
printf("%s: Initializing SmartCard\n", module);
|
||||
|
||||
if (!scard_init())
|
||||
{
|
||||
printf("%s: Couldn't init SmartCard\n", module);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s: Starting reader thread.\n", module);
|
||||
|
||||
// Start reader thread
|
||||
READER_POLL_STOP_FLAG = false;
|
||||
READER_POLL_THREAD = (HANDLE)_beginthreadex(
|
||||
NULL,
|
||||
0,
|
||||
reader_poll_thread_proc,
|
||||
NULL,
|
||||
0,
|
||||
NULL);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT aime_io_nfc_poll(uint8_t unit_no)
|
||||
{
|
||||
if (aime_io_cfg.debug)
|
||||
printf("\n\nDEBUG: aime_io_nfc_poll(unit_no %d). \r\n", unit_no);
|
||||
|
||||
if (unit_no != 0)
|
||||
return S_OK;
|
||||
|
||||
polling = true;
|
||||
|
||||
bool sense;
|
||||
HRESULT hr;
|
||||
|
||||
// Don't do anything more if the scan key is not held
|
||||
sense = GetAsyncKeyState(aime_io_cfg.vk_scan) & 0x8000;
|
||||
if (!sense)
|
||||
{
|
||||
aime_io_aime_id_present = false;
|
||||
aime_io_felica_id_present = false;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Set which card we want to read (we will read the x'th line in the card's file, x being determined by which key is pressed on the keypad).
|
||||
int card = 0;
|
||||
for (int key = VK_NUMPAD0; key <= VK_NUMPAD9; key++)
|
||||
{
|
||||
short keyState = GetAsyncKeyState(key);
|
||||
if (keyState & 0x8000) // Check if the most significant bit is set, indicating that the key is down
|
||||
{
|
||||
card = key - VK_NUMPAD0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s: Attempting to read card %d from file. \r\n", module, card);
|
||||
|
||||
// Try AiMe IC
|
||||
hr = aime_io_read_id_file(
|
||||
aime_io_cfg.aime_path,
|
||||
aime_io_aime_id,
|
||||
sizeof(aime_io_aime_id),
|
||||
card);
|
||||
|
||||
if (SUCCEEDED(hr) && hr != S_FALSE)
|
||||
{
|
||||
aime_io_aime_id_present = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Try FeliCa IC
|
||||
hr = aime_io_read_id_file(
|
||||
aime_io_cfg.felica_path,
|
||||
aime_io_felica_id,
|
||||
sizeof(aime_io_felica_id),
|
||||
card);
|
||||
|
||||
if (SUCCEEDED(hr) && hr != S_FALSE)
|
||||
{
|
||||
aime_io_felica_id_present = true;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Try generating FeliCa IC (if enabled)
|
||||
if (aime_io_cfg.felica_gen)
|
||||
{
|
||||
hr = aime_io_generate_felica(
|
||||
aime_io_cfg.felica_path,
|
||||
aime_io_felica_id,
|
||||
sizeof(aime_io_felica_id));
|
||||
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
aime_io_felica_id_present = true;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT aime_io_nfc_get_aime_id(uint8_t unit_no, uint8_t *luid, size_t luid_size)
|
||||
{
|
||||
if (aime_io_cfg.debug)
|
||||
printf("DEBUG: aime_io_nfc_get_aime_id(unit_no : %d). \r\n", unit_no);
|
||||
|
||||
assert(luid != NULL);
|
||||
assert(luid_size == sizeof(aime_io_aime_id));
|
||||
|
||||
if (unit_no != 0)
|
||||
return S_FALSE;
|
||||
|
||||
if (aime_io_aime_id_present)
|
||||
{
|
||||
memcpy(luid, aime_io_aime_id, luid_size);
|
||||
printf("%s: Read Aime card from file with uid ", module);
|
||||
for (int i = 0; i < 10; i++)
|
||||
printf("%02x ", aime_io_aime_id[i]);
|
||||
printf("\r\n");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT aime_io_nfc_get_felica_id(uint8_t unit_no, uint64_t *IDm)
|
||||
{
|
||||
if (aime_io_cfg.debug)
|
||||
printf("DEBUG: aime_io_nfc_get_felica_id(unit_no : %d). \r\n", unit_no);
|
||||
|
||||
uint64_t val;
|
||||
size_t i;
|
||||
|
||||
assert(IDm != NULL);
|
||||
if (unit_no != 0)
|
||||
return S_FALSE;
|
||||
|
||||
if (aime_io_felica_id_present)
|
||||
{
|
||||
val = 0;
|
||||
for (i = 0; i < 8; i++)
|
||||
val = (val << 8) | aime_io_felica_id[i];
|
||||
|
||||
*IDm = val;
|
||||
printf("%s: Read FeliCa card from file with uid %llx\r\n", module, val);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (HasCard)
|
||||
{
|
||||
polling = false;
|
||||
HasCard = false;
|
||||
|
||||
uint64_t val;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
val = (val << 8) | UID[i];
|
||||
}
|
||||
*IDm = val;
|
||||
printf("%s: FeliCa card has been scanned ! %llx\r\n", module, val);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
void aime_io_led_set_color(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
if (aime_io_cfg.debug)
|
||||
printf("DEBUG: aime_io_led_set_color(unit_no : %d, r : %d, g : %d, b : %d). \r\n", unit_no, r, g, b);
|
||||
}
|
||||
#pragma endregion
|
@ -4,6 +4,6 @@ EXPORTS
|
||||
aime_io_get_api_version
|
||||
aime_io_init
|
||||
aime_io_led_set_color
|
||||
aime_io_nfc_get_aime_id
|
||||
aime_io_nfc_get_felica_id
|
||||
aime_io_nfc_poll
|
||||
aime_io_nfc_get_aime_id
|
||||
aime_io_nfc_get_felica_id
|
@ -48,10 +48,7 @@ HRESULT aime_io_nfc_poll(uint8_t unit_no);
|
||||
|
||||
Minimum API version: 0x0100
|
||||
*/
|
||||
HRESULT aime_io_nfc_get_aime_id(
|
||||
uint8_t unit_no,
|
||||
uint8_t *luid,
|
||||
size_t luid_size);
|
||||
HRESULT aime_io_nfc_get_aime_id(uint8_t unit_no, uint8_t *luid, size_t luid_size);
|
||||
|
||||
/*
|
||||
Attempt to read out a FeliCa card ID ("IDm"). The following are examples
|
74
src/aimetest.c
Normal file
74
src/aimetest.c
Normal file
@ -0,0 +1,74 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "aimeio.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("AIMETEST\r\n---------\r\n");
|
||||
// printf("api version = %04x\r\n", chuni_io_get_api_version()); /* not compatible with older dlls */
|
||||
printf("aime_io_init() : \n");
|
||||
switch (aime_io_init())
|
||||
{
|
||||
case E_FAIL:
|
||||
printf("aime_io_init() returned E_FAIL. Reader is either missing or incompatible !\r\n");
|
||||
break;
|
||||
|
||||
case S_OK:
|
||||
printf("aime_io_init() returned S_OK !\r\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("aime_io_init() returned an unknown state !\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// printf("aime_io_led_set_color(red) : ");
|
||||
// aime_io_led_set_color(0, 255, 0, 0);
|
||||
// printf("OK\r\n");
|
||||
// Sleep(2000);
|
||||
// printf("aime_io_led_set_color(green) : ");
|
||||
// aime_io_led_set_color(0, 0, 255, 0);
|
||||
// printf("OK\r\n");
|
||||
// Sleep(2000);
|
||||
// printf("aime_io_led_set_color(blue) : ");
|
||||
// aime_io_led_set_color(0, 0, 0, 255);
|
||||
// printf("OK\r\n");
|
||||
// Sleep(2000);
|
||||
// aime_io_led_set_color(0, 0, 0, 0);
|
||||
|
||||
printf("Running input loop. Press Ctrl+C to exit.\r\n");
|
||||
|
||||
uint8_t luid[10] = {0};
|
||||
uint64_t IDm = 0;
|
||||
while (1)
|
||||
{
|
||||
if (aime_io_nfc_poll(0) == S_OK)
|
||||
{
|
||||
if (aime_io_nfc_get_felica_id(0, &IDm) == S_OK)
|
||||
{
|
||||
// aime_io_led_set_color(0, 0, 255, 0);
|
||||
printf("Found FeliCa card with uid %llx\r\n\n", IDm);
|
||||
}
|
||||
if (aime_io_nfc_get_aime_id(0, luid, 10) == S_OK)
|
||||
{
|
||||
// aime_io_led_set_color(0, 0, 0, 255);
|
||||
printf("Found old card with uid ");
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
printf("%02x ", luid[i]);
|
||||
}
|
||||
printf("\r\n\n");
|
||||
}
|
||||
Sleep(500);
|
||||
// printf("poll ok but no card?!\r\n");
|
||||
}
|
||||
// Sleep(300);
|
||||
// aime_io_led_set_color(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
26
src/meson.build
Normal file
26
src/meson.build
Normal file
@ -0,0 +1,26 @@
|
||||
compiler = meson.get_compiler('c')
|
||||
hid_lib = compiler.find_library('hid')
|
||||
winscard_lib = compiler.find_library('winscard')
|
||||
setupapi_lib = compiler.find_library('setupapi')
|
||||
|
||||
deps = [
|
||||
hid_lib,
|
||||
winscard_lib,
|
||||
setupapi_lib,
|
||||
]
|
||||
|
||||
aimeio_dll = shared_library(
|
||||
'aimeio',
|
||||
implicit_include_directories : false,
|
||||
vs_module_defs : 'aimeio.def',
|
||||
sources: [
|
||||
'aimeio.c',
|
||||
'scard/scard.c',
|
||||
],
|
||||
dependencies: deps,
|
||||
include_directories: [
|
||||
inc
|
||||
]
|
||||
)
|
||||
|
||||
executable('aimetest', 'aimetest.c', link_with : aimeio_dll)
|
356
src/scard/scard.c
Normal file
356
src/scard/scard.c
Normal file
@ -0,0 +1,356 @@
|
||||
/**
|
||||
* 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 <tchar.h>
|
||||
// #include <thread>
|
||||
|
||||
extern char module[];
|
||||
|
||||
#define MAX_APDU_SIZE 255
|
||||
int readCooldown = 500;
|
||||
// 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)
|
||||
{
|
||||
printf("%s: Update on reader : %s\n", module, reader_states[unit_no].szReader);
|
||||
// Connect to the smart card.
|
||||
LONG lRet = 0;
|
||||
SCARDHANDLE hCard;
|
||||
DWORD dwActiveProtocol;
|
||||
for (int retry = 0; retry < 100; retry++) // retry times has to be increased since poll rate is set to 500ms
|
||||
{
|
||||
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)
|
||||
{
|
||||
printf("%s: Error connecting to the card: 0x%08X\n", 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];
|
||||
|
||||
// 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)
|
||||
{
|
||||
printf("%s: Error getting card status: 0x%08X\n", module, lRet);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only care about 20-byte ATRs returned by arcade-type smart cards
|
||||
if (cByteAtr != 20)
|
||||
{
|
||||
printf("%s: Ignoring card with len(%d) = %02X (%08X)\n", module, cByteAtr, atr, cByteAtr);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s: atr Return: len(%d) = %02X (%08X)\n", 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)
|
||||
{
|
||||
printf("%s: Card protocol: ISO15693_PART3\n", module);
|
||||
shouldReverseUid = true;
|
||||
}
|
||||
|
||||
else if (cardProtocol == SCARD_ATR_PROTOCOL_ISO14443_PART3)
|
||||
printf("%s: Card protocol: ISO14443_PART3\n", module);
|
||||
|
||||
else if (cardProtocol == SCARD_ATR_PROTOCOL_FELICA_212K)
|
||||
printf("%s: Card protocol: FELICA_212K\n", module);
|
||||
|
||||
else if (cardProtocol == SCARD_ATR_PROTOCOL_FELICA_424K)
|
||||
printf("%s: Card protocol: FELICA_424K\n", module);
|
||||
|
||||
else
|
||||
{
|
||||
printf("%s: Unknown NFC Protocol: 0x%02X\n", 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)
|
||||
{
|
||||
printf("%s: Error querying card UID: 0x%08X\n", module, lRet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cbRecv > 1 && pbRecv[0] == PICC_ERROR)
|
||||
{
|
||||
printf("%s: UID query failed\n", module);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((lRet = SCardDisconnect(hCard, SCARD_LEAVE_CARD)) != SCARD_S_SUCCESS)
|
||||
printf("%s: Failed SCardDisconnect: 0x%08X\n", module, lRet);
|
||||
|
||||
if (cbRecv < 8)
|
||||
{
|
||||
printf("%s: Padding card uid to 8 bytes\n", module);
|
||||
memset(&pbRecv[cbRecv], 0, 8 - cbRecv);
|
||||
}
|
||||
else if (cbRecv > 8)
|
||||
printf("%s: taking first 8 bytes of len(uid) = %02X\n", 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)
|
||||
{
|
||||
printf("%s: Failed SCardGetStatusChange: 0x%08X\n", 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)
|
||||
{
|
||||
printf("%s: New card state: unavailable\n", module);
|
||||
Sleep(readCooldown);
|
||||
}
|
||||
else if (newState & SCARD_STATE_EMPTY)
|
||||
{
|
||||
printf("%s: New card state: empty\n", module);
|
||||
// scard_clear(unit_no);
|
||||
}
|
||||
else if (newState & SCARD_STATE_PRESENT && !wasCardPresent)
|
||||
{
|
||||
printf("%s: New card state: present\n", 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:
|
||||
printf("%s: No readers available\n", 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)
|
||||
{
|
||||
printf("%s: Found reader: %s\n", module, reader);
|
||||
readerCount++;
|
||||
|
||||
// Connect to reader and send PICC operating params command
|
||||
LONG lRet = 0;
|
||||
SCARDHANDLE hCard;
|
||||
DWORD dwActiveProtocol;
|
||||
lRet = SCardConnect(hContext, reader, SCARD_SHARE_DIRECT, 0, &hCard, &dwActiveProtocol);
|
||||
if (lRet != SCARD_S_SUCCESS)
|
||||
{
|
||||
printf("%s: Error connecting to the reader: 0x%08X\n", module, lRet);
|
||||
continue;
|
||||
}
|
||||
printf("%s: Connected to reader: %s, sending PICC operating params command\n", module, reader);
|
||||
|
||||
// set the reader params
|
||||
lRet = 0;
|
||||
DWORD cbRecv = MAX_APDU_SIZE;
|
||||
BYTE pbRecv[MAX_APDU_SIZE];
|
||||
lRet = SCardControl(hCard, SCARD_CTL_CODE(3500), PICC_OPERATING_PARAM_CMD, sizeof(PICC_OPERATING_PARAM_CMD), pbRecv, cbRecv, &cbRecv);
|
||||
Sleep(100);
|
||||
if (lRet != SCARD_S_SUCCESS)
|
||||
{
|
||||
printf("%s: Error setting PICC params: 0x%08X\n", module, lRet);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (cbRecv > 2 && pbRecv[0] != PICC_SUCCESS && pbRecv[1] != PICC_OPERATING_PARAMS)
|
||||
{
|
||||
printf("%s: PICC params not valid 0x%02X != 0x%02X\n", module, pbRecv[1], PICC_OPERATING_PARAMS);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Disconnect from reader
|
||||
if ((lRet = SCardDisconnect(hCard, SCARD_LEAVE_CARD)) != SCARD_S_SUCCESS)
|
||||
{
|
||||
printf("%s: Failed SCardDisconnect: 0x%08X\n", module, lRet);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%s: Disconnected from reader: %s, this is expected behavior\n", module, reader);
|
||||
}
|
||||
}
|
||||
|
||||
// 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])
|
||||
printf("%s: Using reader slot 0: %s\n", module, reader_name_slots[0]);
|
||||
|
||||
if (reader_name_slots[1])
|
||||
printf("%s: Using reader slot 1: %s\n", 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:
|
||||
printf("%s: Failed SCardListReaders: 0x%08X\n", module, lRet);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
46
src/scard/scard.h
Normal file
46
src/scard/scard.h
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <winscard.h>
|
||||
#include <windows.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();
|
Loading…
Reference in New Issue
Block a user