diff --git a/aimeio/aimeio.c b/aimeio/aimeio.c new file mode 100644 index 0000000..f505aac --- /dev/null +++ b/aimeio/aimeio.c @@ -0,0 +1,284 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "aimeio/aimeio.h" + +#include "util/crc.h" +#include "util/dprintf.h" + +struct aime_io_config { + 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; + +static void aime_io_config_read( + struct aime_io_config *cfg, + const wchar_t *filename); + +static HRESULT aime_io_read_id_file( + const wchar_t *path, + uint8_t *bytes, + size_t nbytes); + +static HRESULT aime_io_generate_felica( + const wchar_t *path, + uint8_t *bytes, + size_t nbytes); + +static void aime_io_config_read( + struct aime_io_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"aime", + L"aimePath", + L"DEVICE\\aime.txt", + cfg->aime_path, + _countof(cfg->aime_path), + filename); + + GetPrivateProfileStringW( + L"aime", + L"felicaPath", + L"DEVICE\\felica.txt", + cfg->felica_path, + _countof(cfg->felica_path), + filename); + dprintf("NFC: felicaPath GetLastError %lx\n", GetLastError()); + + cfg->felica_gen = GetPrivateProfileIntW( + L"aime", + L"felicaGen", + 1, + filename); + + cfg->vk_scan = GetPrivateProfileIntW( + L"aime", + L"scan", + VK_RETURN, + filename); +} + +static HRESULT aime_io_read_id_file( + const wchar_t *path, + uint8_t *bytes, + size_t nbytes) +{ + HRESULT hr; + FILE *f; + size_t i; + int byte; + int r; + + f = _wfopen(path, L"r"); + + if (f == NULL) { + return S_FALSE; + } + + memset(bytes, 0, nbytes); + + for (i = 0 ; i < nbytes ; i++) { + r = fscanf(f, "%02x ", &byte); + + if (r != 1) { + hr = E_FAIL; + dprintf("AimeIO DLL: %S: fscanf[%i] failed: %i\n", + path, + (int) i, + r); + + goto end; + } + + bytes[i] = byte; + } + + 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) { + dprintf("AimeIO DLL: %S: fopen failed: %i\n", path, (int) errno); + + return E_FAIL; + } + + for (i = 0 ; i < nbytes ; i++) { + fprintf(f, "%02X", bytes[i]); + } + + fprintf(f, "\n"); + fclose(f); + + dprintf("AimeIO DLL: Generated random FeliCa ID\n"); + + return S_OK; +} + +uint16_t aime_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT aime_io_init(void) +{ + aime_io_config_read(&aime_io_cfg, L".\\segatools.ini"); + + return S_OK; +} + +HRESULT aime_io_nfc_poll(uint8_t unit_no) +{ + bool sense; + HRESULT hr; + + if (unit_no != 0) { + return S_OK; + } + + /* Reset presence flags */ + + aime_io_aime_id_present = false; + aime_io_felica_id_present = false; + + /* Don't do anything more if the scan key is not held */ + + sense = GetAsyncKeyState(aime_io_cfg.vk_scan) & 0x8000; + + if (!sense) { + return S_OK; + } + + /* Try AiMe IC */ + + hr = aime_io_read_id_file( + aime_io_cfg.aime_path, + aime_io_aime_id, + sizeof(aime_io_aime_id)); + + 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)); + + 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) +{ + assert(luid != NULL); + assert(luid_size == sizeof(aime_io_aime_id)); + + if (unit_no != 0 || !aime_io_aime_id_present) { + return S_FALSE; + } + + memcpy(luid, aime_io_aime_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 || !aime_io_felica_id_present) { + return S_FALSE; + } + + val = 0; + + for (i = 0 ; i < 8 ; i++) { + val = (val << 8) | aime_io_felica_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) +{} diff --git a/aimeio/aimeio.h b/aimeio/aimeio.h new file mode 100644 index 0000000..221f8e0 --- /dev/null +++ b/aimeio/aimeio.h @@ -0,0 +1,87 @@ +#pragma once + +#include + +#include +#include + +/* + Get the version of the Aime IO API that this DLL supports. This function + should return a positive 16-bit integer, where the high byte is the major + version and the low byte is the minor version (as defined by the Semantic + Versioning standard). + + The latest API version as of this writing is 0x0100. + */ +uint16_t aime_io_get_api_version(void); + +/* + Initialize Aime IO provider DLL. Only called once, before any other + functions exported from this DLL are called (except for + aime_io_get_api_version). + + Minimum API version: 0x0100 + */ +HRESULT aime_io_init(void); + +/* + Poll for IC cards in the vicinity. + + - unit_no: Always 0 as of the current API version + + Minimum API version: 0x0100 + */ +HRESULT aime_io_nfc_poll(uint8_t unit_no); + +/* + Attempt to read out a classic Aime card ID + + - unit_no: Always 0 as of the current API version + - luid: Pointer to a ten-byte buffer that will receive the ID + - luid_size: Size of the buffer at *luid. Always 10. + + Returns: + + - S_OK if a classic Aime is present and was read successfully + - S_FALSE if no classic Aime card is present (*luid will be ignored) + - Any HRESULT error if an error occured. + + Minimum API version: 0x0100 +*/ +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 + of FeliCa cards: + + - Amuse IC (which includes new-style Aime-branded cards, among others) + - Smartphones with FeliCa NFC capability (uncommon outside Japan) + - Various Japanese e-cash cards and train passes + + Parameters: + + - unit_no: Always 0 as of the current API version + - IDm: Output parameter that will receive the card ID + + Returns: + + - S_OK if a FeliCa device is present and was read successfully + - S_FALSE if no FeliCa device is present (*IDm will be ignored) + - Any HRESULT error if an error occured. + + Minimum API version: 0x0100 +*/ +HRESULT aime_io_nfc_get_felica_id(uint8_t unit_no, uint64_t *IDm); + +/* + Change the color and brightness of the card reader's RGB lighting + + - unit_no: Always 0 as of the current API version + - r, g, b: Primary color intensity, from 0 to 255 inclusive. + + Minimum API version: 0x0100 +*/ +void aime_io_led_set_color(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b); diff --git a/aimeio/meson.build b/aimeio/meson.build new file mode 100644 index 0000000..ddbb66a --- /dev/null +++ b/aimeio/meson.build @@ -0,0 +1,13 @@ +aimeio_lib = static_library( + 'aimeio', + name_prefix : '', + include_directories: inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + link_with : [ + util_lib, + ], + sources : [ + 'aimeio.c', + ], +) diff --git a/board/aime-dll.c b/board/aime-dll.c new file mode 100644 index 0000000..465fcfd --- /dev/null +++ b/board/aime-dll.c @@ -0,0 +1,112 @@ +#include + +#include +#include + +#include "board/aime-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym aime_dll_syms[] = { + { + .sym = "aime_io_init", + .off = offsetof(struct aime_dll, init), + }, { + .sym = "aime_io_nfc_poll", + .off = offsetof(struct aime_dll, nfc_poll), + }, { + .sym = "aime_io_nfc_get_aime_id", + .off = offsetof(struct aime_dll, nfc_get_aime_id), + }, { + .sym = "aime_io_nfc_get_felica_id", + .off = offsetof(struct aime_dll, nfc_get_felica_id), + }, { + .sym = "aime_io_led_set_color", + .off = offsetof(struct aime_dll, led_set_color), + } +}; + +struct aime_dll aime_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT aime_dll_init(const struct aime_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("NFC Assembly: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("NFC Assembly: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "aime_io_get_api_version"); + + if (get_api_version != NULL) { + aime_dll.api_version = get_api_version(); + } else { + aime_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose aime_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (aime_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("NFC Assembly: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Segatools.\n", + aime_dll.api_version); + + goto end; + } + + sym = aime_dll_syms; + hr = dll_bind(&aime_dll, src, &sym, _countof(aime_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("NFC Assembly: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/board/aime-dll.h b/board/aime-dll.h new file mode 100644 index 0000000..354516b --- /dev/null +++ b/board/aime-dll.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "aimeio/aimeio.h" + +struct aime_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*nfc_poll)(uint8_t unit_no); + HRESULT (*nfc_get_aime_id)( + uint8_t unit_no, + uint8_t *luid, + size_t luid_size); + HRESULT (*nfc_get_felica_id)(uint8_t unit_no, uint64_t *IDm); + void (*led_set_color)(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b); +}; + +struct aime_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct aime_dll aime_dll; + +HRESULT aime_dll_init(const struct aime_dll_config *cfg, HINSTANCE self); diff --git a/board/config.c b/board/config.c index 61b5f71..0fad281 100644 --- a/board/config.c +++ b/board/config.c @@ -58,4 +58,27 @@ void qr_config_load(struct qr_config *cfg, const wchar_t *filename) cfg->enable = GetPrivateProfileIntW(L"qr", L"enable", 1, filename); cfg->port = GetPrivateProfileIntW(L"qr", L"port", 0, filename); -} \ No newline at end of file +} + +static void aime_dll_config_load(struct aime_dll_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"aimeio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void aime_config_load(struct aime_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + aime_dll_config_load(&cfg->dll, filename); + cfg->enable = GetPrivateProfileIntW(L"aime", L"enable", 1, filename); +} diff --git a/board/config.h b/board/config.h index 47380e6..5815f0b 100644 --- a/board/config.h +++ b/board/config.h @@ -6,7 +6,9 @@ #include "board/usio.h" #include "board/bpreader.h" #include "board/qr.h" +#include "board/sg-reader.h" void bpreader_config_load(struct bpreader_config *cfg, const wchar_t *filename); void usio_config_load(struct usio_config *cfg, const wchar_t *filename); -void qr_config_load(struct qr_config *cfg, const wchar_t *filename); \ No newline at end of file +void qr_config_load(struct qr_config *cfg, const wchar_t *filename); +void aime_config_load(struct aime_config *cfg, const wchar_t *filename); diff --git a/board/meson.build b/board/meson.build index b35c761..5bb6452 100644 --- a/board/meson.build +++ b/board/meson.build @@ -10,8 +10,11 @@ board_lib = static_library( hooklib_lib, util_lib, jvs_lib, + iccard_lib, ], sources : [ + 'aime-dll.c', + 'aime-dll.h', 'bpreader.c', 'bpreader.h', 'najv4.c', @@ -24,5 +27,19 @@ board_lib = static_library( 'usio.h', 'qr.c', 'qr.h', + 'sg-cmd.c', + 'sg-cmd.h', + 'sg-frame.c', + 'sg-frame.h', + 'sg-led.c', + 'sg-led.h', + 'sg-led-cmd.h', + 'sg-nfc.c', + 'sg-nfc.h', + 'sg-nfc-cmd.h', + 'sg-reader.c', + 'sg-reader.h', + 'vfd.c', + 'vfd.h', ], ) diff --git a/board/sg-cmd.c b/board/sg-cmd.c new file mode 100644 index 0000000..0ed6f25 --- /dev/null +++ b/board/sg-cmd.c @@ -0,0 +1,134 @@ +#include + +#include "board/sg-cmd.h" +#include "board/sg-frame.h" + +#include "hook/iobuf.h" + +#include "util/dprintf.h" + +union sg_req_any { + struct sg_req_header req; + uint8_t bytes[256]; +}; + +union sg_res_any { + struct sg_res_header res; + uint8_t bytes[256]; +}; + +static HRESULT sg_req_validate(const void *ptr, size_t nbytes); + +static void sg_res_error( + struct sg_res_header *res, + const struct sg_req_header *req); + +static HRESULT sg_req_validate(const void *ptr, size_t nbytes) +{ + const struct sg_req_header *req; + size_t payload_len; + + assert(ptr != NULL); + + if (nbytes < sizeof(*req)) { + dprintf("SG Cmd: Request header truncated\n"); + + return E_FAIL; + } + + req = ptr; + + if (req->hdr.frame_len != nbytes) { + dprintf("SG Cmd: Frame length mismatch: got %i exp %i\n", + req->hdr.frame_len, + (int) nbytes); + + return E_FAIL; + } + + payload_len = req->hdr.frame_len - sizeof(*req); + + if (req->payload_len != payload_len) { + dprintf("SG Cmd: Payload length mismatch: got %i exp %i\n", + req->payload_len, + (int) payload_len); + + return E_FAIL; + } + + return S_OK; +} + +void sg_req_transact( + struct iobuf *res_frame, + const uint8_t *req_bytes, + size_t req_nbytes, + sg_dispatch_fn_t dispatch, + void *ctx) +{ + struct iobuf req_span; + union sg_req_any req; + union sg_res_any res; + HRESULT hr; + + assert(res_frame != NULL); + assert(req_bytes != NULL); + assert(dispatch != NULL); + + req_span.bytes = req.bytes; + req_span.nbytes = sizeof(req.bytes); + req_span.pos = 0; + + hr = sg_frame_decode(&req_span, req_bytes, req_nbytes); + + if (FAILED(hr)) { + return; + } + + hr = sg_req_validate(req.bytes, req_span.pos); + + if (FAILED(hr)) { + return; + } + + hr = dispatch(ctx, &req, &res); + + if (hr != S_FALSE) { + if (FAILED(hr)) { + sg_res_error(&res.res, &req.req); + } + + sg_frame_encode(res_frame, res.bytes, res.res.hdr.frame_len); + } +} + +void sg_res_init( + struct sg_res_header *res, + const struct sg_req_header *req, + size_t payload_len) +{ + assert(res != NULL); + assert(req != NULL); + + res->hdr.frame_len = sizeof(*res) + payload_len; + res->hdr.addr = req->hdr.addr; + res->hdr.seq_no = req->hdr.seq_no; + res->hdr.cmd = req->hdr.cmd; + res->status = 0; + res->payload_len = payload_len; +} + +static void sg_res_error( + struct sg_res_header *res, + const struct sg_req_header *req) +{ + assert(res != NULL); + assert(req != NULL); + + res->hdr.frame_len = sizeof(*res); + res->hdr.addr = req->hdr.addr; + res->hdr.seq_no = req->hdr.seq_no; + res->hdr.cmd = req->hdr.cmd; + res->status = 1; + res->payload_len = 0; +} diff --git a/board/sg-cmd.h b/board/sg-cmd.h new file mode 100644 index 0000000..685377f --- /dev/null +++ b/board/sg-cmd.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +struct sg_header { + uint8_t frame_len; + uint8_t addr; + uint8_t seq_no; + uint8_t cmd; +}; + +struct sg_req_header { + struct sg_header hdr; + uint8_t payload_len; +}; + +struct sg_res_header { + struct sg_header hdr; + uint8_t status; + uint8_t payload_len; +}; + +typedef HRESULT (*sg_dispatch_fn_t)( + void *ctx, + const void *req, + void *res); + +void sg_req_transact( + struct iobuf *res_frame, + const uint8_t *req_bytes, + size_t req_nbytes, + sg_dispatch_fn_t dispatch, + void *ctx); + +void sg_res_init( + struct sg_res_header *res, + const struct sg_req_header *req, + size_t payload_len); diff --git a/board/sg-frame.c b/board/sg-frame.c new file mode 100644 index 0000000..d65dbb1 --- /dev/null +++ b/board/sg-frame.c @@ -0,0 +1,165 @@ +#include + +#include +#include +#include +#include + +#include "board/sg-frame.h" + +#include "hook/iobuf.h" + +#include "util/dprintf.h" + +static HRESULT sg_frame_accept(struct iobuf *dest); +static HRESULT sg_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Frame structure: + + [0] Sync byte (0xE0) + [1] Frame size (including self) + [2] Address + [3] Sequence no + ... Body + [n] Checksum: Sum of all non-framing bytes + + Byte stuffing: + + 0xD0 is an escape byte. Un-escape the subsequent byte by adding 1. */ + +static HRESULT sg_frame_accept(struct iobuf *dest) +{ + uint8_t checksum; + size_t i; + + if (dest->pos < 1 || dest->pos != dest->bytes[0] + 1) { + dprintf("SG Frame: Size mismatch\n"); + + return S_FALSE; + } + + checksum = 0; + + for (i = 0 ; i < dest->pos - 1 ; i++) { + checksum += dest->bytes[i]; + } + + if (checksum != dest->bytes[dest->pos - 1]) { + dprintf("SG Frame: Checksum mismatch\n"); + + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + /* Discard checksum */ + dest->pos--; + + return S_OK; +} + +HRESULT sg_frame_decode(struct iobuf *dest, const uint8_t *bytes, size_t nbytes) +{ + uint8_t byte; + size_t i; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(bytes != NULL); + + if (nbytes < 1 || bytes[0] != 0xE0) { + dprintf("SG Frame: Bad sync\n"); + + return E_FAIL; + } + + dest->pos = 0; + i = 1; + + while (i < nbytes) { + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + byte = bytes[i++]; + + if (byte == 0xE0) { + dprintf("SG Frame: Unescaped sync\n"); + + return E_FAIL; + } else if (byte == 0xD0) { + if (i >= nbytes) { + dprintf("SG Frame: Trailing escape\n"); + + return E_FAIL; + } + + byte = bytes[i++]; + dest->bytes[dest->pos++] = byte + 1; + } else { + dest->bytes[dest->pos++] = byte; + } + } + + return sg_frame_accept(dest); +} + +HRESULT sg_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *src; + uint8_t checksum; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + assert(nbytes != 0 && src[0] == nbytes); + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + checksum = 0; + + for (i = 0 ; i < nbytes ; i++) { + byte = src[i]; + checksum += byte; + + hr = sg_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } + + return sg_frame_encode_byte(dest, checksum); +} + +static HRESULT sg_frame_encode_byte(struct iobuf *dest, uint8_t byte) +{ + if (byte == 0xD0 || byte == 0xE0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/board/sg-frame.h b/board/sg-frame.h new file mode 100644 index 0000000..d74d9b3 --- /dev/null +++ b/board/sg-frame.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +HRESULT sg_frame_decode( + struct iobuf *dest, + const uint8_t *bytes, + size_t nbytes); + +HRESULT sg_frame_encode(struct iobuf *dest, const void *ptr, size_t nbytes); diff --git a/board/sg-led-cmd.h b/board/sg-led-cmd.h new file mode 100644 index 0000000..f74505b --- /dev/null +++ b/board/sg-led-cmd.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "board/sg-cmd.h" + +enum { + SG_RGB_CMD_SET_COLOR = 0x81, + SG_RGB_CMD_RESET = 0xF5, + SG_RGB_CMD_GET_INFO = 0xF0, +}; + +struct sg_led_res_reset { + struct sg_res_header res; + uint8_t payload; +}; + +struct sg_led_res_get_info { + struct sg_res_header res; + uint8_t payload[9]; +}; + +struct sg_led_req_set_color { + struct sg_req_header req; + uint8_t payload[3]; +}; + +union sg_led_req_any { + uint8_t bytes[256]; + struct sg_req_header simple; + struct sg_led_req_set_color set_color; +}; + +union sg_led_res_any { + uint8_t bytes[256]; + struct sg_res_header simple; + struct sg_led_res_reset reset; + struct sg_led_res_get_info get_info; +}; diff --git a/board/sg-led.c b/board/sg-led.c new file mode 100644 index 0000000..855e36b --- /dev/null +++ b/board/sg-led.c @@ -0,0 +1,178 @@ +#include + +#include + +#include "board/sg-cmd.h" +#include "board/sg-led.h" +#include "board/sg-led-cmd.h" + +#include "util/dprintf.h" + +static HRESULT sg_led_dispatch( + void *ctx, + const void *v_req, + void *v_res); + +static HRESULT sg_led_cmd_reset( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_reset *res); + +static HRESULT sg_led_cmd_get_info( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_get_info *res); + +static HRESULT sg_led_cmd_set_color( + const struct sg_led *led, + const struct sg_led_req_set_color *req); + +static const uint8_t sg_led_info[] = { + '1', '5', '0', '8', '4', 0xFF, 0x10, 0x00, 0x12, +}; + +void sg_led_init( + struct sg_led *led, + uint8_t addr, + const struct sg_led_ops *ops, + void *ctx) +{ + assert(led != NULL); + assert(ops != NULL); + + led->ops = ops; + led->ops_ctx = ctx; + led->addr = addr; +} + +void sg_led_transact( + struct sg_led *led, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes) +{ + assert(led != NULL); + assert(res_frame != NULL); + assert(req_bytes != NULL); + + sg_req_transact(res_frame, req_bytes, req_nbytes, sg_led_dispatch, led); +} + +#ifdef NDEBUG +#define sg_led_dprintfv(led, fmt, ap) +#define sg_led_dprintf(led, fmt, ...) +#else +static void sg_led_dprintfv( + const struct sg_led *led, + const char *fmt, + va_list ap) +{ + dprintf("RGB LED %02x: ", led->addr); + dprintfv(fmt, ap); +} + +static void sg_led_dprintf( + const struct sg_led *led, + const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + sg_led_dprintfv(led, fmt, ap); + va_end(ap); +} +#endif + +static HRESULT sg_led_dispatch( + void *ctx, + const void *v_req, + void *v_res) +{ + const struct sg_led *led; + const union sg_led_req_any *req; + union sg_led_res_any *res; + + led = ctx; + req = v_req; + res = v_res; + + if (req->simple.hdr.addr != led->addr) { + /* Not addressed to us, don't send a response */ + return S_FALSE; + } + + switch (req->simple.hdr.cmd) { + case SG_RGB_CMD_RESET: + return sg_led_cmd_reset(led, &req->simple, &res->reset); + + case SG_RGB_CMD_GET_INFO: + return sg_led_cmd_get_info(led, &req->simple, &res->get_info); + + case SG_RGB_CMD_SET_COLOR: + return sg_led_cmd_set_color(led, &req->set_color); + + default: + sg_led_dprintf(led, "Unimpl command %02x\n", req->simple.hdr.cmd); + + return E_NOTIMPL; + } +} + +static HRESULT sg_led_cmd_reset( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_reset *res) +{ + HRESULT hr; + + sg_led_dprintf(led, "Reset\n"); + sg_res_init(&res->res, req, sizeof(res->payload)); + res->payload = 0; + + if (led->ops->reset != NULL) { + hr = led->ops->reset(led->ops_ctx); + } else { + hr = S_OK; + } + + if (FAILED(hr)) { + sg_led_dprintf(led, "led->ops->reset: Error %x\n", hr); + return hr; + } + + return S_OK; +} + +static HRESULT sg_led_cmd_get_info( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_get_info *res) +{ + sg_led_dprintf(led, "Get info\n"); + sg_res_init(&res->res, req, sizeof(res->payload)); + memcpy(res->payload, sg_led_info, sizeof(sg_led_info)); + + return S_OK; +} + +static HRESULT sg_led_cmd_set_color( + const struct sg_led *led, + const struct sg_led_req_set_color *req) +{ + if (req->req.payload_len != sizeof(req->payload)) { + sg_led_dprintf(led, "%s: Payload size is incorrect\n", __func__); + + goto fail; + } + + led->ops->set_color( + led->ops_ctx, + req->payload[0], + req->payload[1], + req->payload[2]); + +fail: + /* No response */ + return S_FALSE; +} diff --git a/board/sg-led.h b/board/sg-led.h new file mode 100644 index 0000000..de3caa6 --- /dev/null +++ b/board/sg-led.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "hook/iobuf.h" + +struct sg_led_ops { + HRESULT (*reset)(void *ctx); + void (*set_color)(void *ctx, uint8_t r, uint8_t g, uint8_t b); +}; + +struct sg_led { + const struct sg_led_ops *ops; + void *ops_ctx; + uint8_t addr; +}; + +void sg_led_init( + struct sg_led *led, + uint8_t addr, + const struct sg_led_ops *ops, + void *ctx); + +void sg_led_transact( + struct sg_led *led, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes); diff --git a/board/sg-nfc-cmd.h b/board/sg-nfc-cmd.h new file mode 100644 index 0000000..b6754c2 --- /dev/null +++ b/board/sg-nfc-cmd.h @@ -0,0 +1,115 @@ +#pragma once + +#include + +#pragma pack(push, 1) + +enum { + SG_NFC_CMD_GET_FW_VERSION = 0x30, + SG_NFC_CMD_GET_HW_VERSION = 0x32, + SG_NFC_CMD_RADIO_ON = 0x40, + SG_NFC_CMD_RADIO_OFF = 0x41, + SG_NFC_CMD_POLL = 0x42, + SG_NFC_CMD_MIFARE_SELECT_TAG = 0x43, + SG_NFC_CMD_MIFARE_SET_KEY_BANA = 0x50, + SG_NFC_CMD_MIFARE_READ_BLOCK = 0x52, + SG_NFC_CMD_MIFARE_SET_KEY_AIME = 0x54, + SG_NFC_CMD_MIFARE_AUTHENTICATE = 0x55, /* guess based on time sent */ + SG_NFC_CMD_RESET = 0x62, + SG_NFC_CMD_FELICA_ENCAP = 0x71, +}; + +struct sg_nfc_res_get_fw_version { + struct sg_res_header res; + char version[23]; +}; + +struct sg_nfc_res_get_hw_version { + struct sg_res_header res; + char version[23]; +}; + +struct sg_nfc_req_mifare_set_key { + struct sg_req_header req; + uint8_t key_a[6]; +}; + +struct sg_nfc_req_mifare_50 { + struct sg_req_header req; + uint8_t payload[6]; +}; + +struct sg_nfc_req_poll_40 { + struct sg_req_header req; + uint8_t payload; +}; + +struct sg_nfc_poll_mifare { + uint8_t type; + uint8_t id_len; + uint32_t uid; +}; + +struct sg_nfc_poll_felica { + uint8_t type; + uint8_t id_len; + uint64_t IDm; + uint64_t PMm; +}; + +struct sg_nfc_res_poll { + struct sg_res_header res; + uint8_t count; + uint8_t payload[250]; +}; + +struct sg_nfc_req_mifare_select_tag { + struct sg_res_header res; + uint32_t uid; +}; + +struct sg_nfc_req_mifare_read_block { + struct sg_req_header req; + struct { + uint32_t uid; + uint8_t block_no; + } payload; +}; + +struct sg_nfc_res_mifare_read_block { + struct sg_res_header res; + uint8_t block[16]; +}; + +struct sg_nfc_req_felica_encap { + struct sg_req_header req; + uint64_t IDm; + uint8_t payload[243]; +}; + +struct sg_nfc_res_felica_encap { + struct sg_res_header res; + uint8_t payload[250]; +}; + +union sg_nfc_req_any { + uint8_t bytes[256]; + struct sg_req_header simple; + struct sg_nfc_req_mifare_set_key mifare_set_key; + struct sg_nfc_req_mifare_read_block mifare_read_block; + struct sg_nfc_req_mifare_50 mifare_50; + struct sg_nfc_req_poll_40 poll_40; + struct sg_nfc_req_felica_encap felica_encap; +}; + +union sg_nfc_res_any { + uint8_t bytes[256]; + struct sg_res_header simple; + struct sg_nfc_res_get_fw_version get_fw_version; + struct sg_nfc_res_get_hw_version get_hw_version; + struct sg_nfc_res_poll poll; + struct sg_nfc_res_mifare_read_block mifare_read_block; + struct sg_nfc_res_felica_encap felica_encap; +}; + +#pragma pack(pop) diff --git a/board/sg-nfc.c b/board/sg-nfc.c new file mode 100644 index 0000000..2db684d --- /dev/null +++ b/board/sg-nfc.c @@ -0,0 +1,434 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "board/sg-cmd.h" +#include "board/sg-nfc.h" +#include "board/sg-nfc-cmd.h" + +#include "iccard/aime.h" +#include "iccard/felica.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT sg_nfc_dispatch( + void *ctx, + const void *v_req, + void *v_res); + +static HRESULT sg_nfc_cmd_reset( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res); + +static HRESULT sg_nfc_cmd_get_fw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_fw_version *res); + +static HRESULT sg_nfc_cmd_get_hw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_hw_version *res); + +static HRESULT sg_nfc_cmd_poll( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_poll *res); + +static HRESULT sg_nfc_poll_aime( + struct sg_nfc *nfc, + struct sg_nfc_poll_mifare *mifare); + +static HRESULT sg_nfc_poll_felica( + struct sg_nfc *nfc, + struct sg_nfc_poll_felica *felica); + +static HRESULT sg_nfc_cmd_mifare_read_block( + struct sg_nfc *nfc, + const struct sg_nfc_req_mifare_read_block *req, + struct sg_nfc_res_mifare_read_block *res); + +static HRESULT sg_nfc_cmd_felica_encap( + struct sg_nfc *nfc, + const struct sg_nfc_req_felica_encap *req, + struct sg_nfc_res_felica_encap *res); + +static HRESULT sg_nfc_cmd_dummy( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res); + +void sg_nfc_init( + struct sg_nfc *nfc, + uint8_t addr, + const struct sg_nfc_ops *ops, + void *ops_ctx) +{ + assert(nfc != NULL); + assert(ops != NULL); + + nfc->ops = ops; + nfc->ops_ctx = ops_ctx; + nfc->addr = addr; +} + +#ifdef NDEBUG +#define sg_nfc_dprintfv(nfc, fmt, ap) +#define sg_nfc_dprintf(nfc, fmt, ...) +#else +static void sg_nfc_dprintfv( + const struct sg_nfc *nfc, + const char *fmt, + va_list ap) +{ + dprintf("NFC %02x: ", nfc->addr); + dprintfv(fmt, ap); +} + +static void sg_nfc_dprintf( + const struct sg_nfc *nfc, + const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + sg_nfc_dprintfv(nfc, fmt, ap); + va_end(ap); +} +#endif + +void sg_nfc_transact( + struct sg_nfc *nfc, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes) +{ + assert(nfc != NULL); + assert(res_frame != NULL); + assert(req_bytes != NULL); + + sg_req_transact(res_frame, req_bytes, req_nbytes, sg_nfc_dispatch, nfc); +} + +static HRESULT sg_nfc_dispatch( + void *ctx, + const void *v_req, + void *v_res) +{ + struct sg_nfc *nfc; + const union sg_nfc_req_any *req; + union sg_nfc_res_any *res; + + nfc = ctx; + req = v_req; + res = v_res; + + if (req->simple.hdr.addr != nfc->addr) { + /* Not addressed to us, don't send a response */ + return S_FALSE; + } + + switch (req->simple.hdr.cmd) { + case SG_NFC_CMD_RESET: + return sg_nfc_cmd_reset(nfc, &req->simple, &res->simple); + + case SG_NFC_CMD_GET_FW_VERSION: + return sg_nfc_cmd_get_fw_version( + nfc, + &req->simple, + &res->get_fw_version); + + case SG_NFC_CMD_GET_HW_VERSION: + return sg_nfc_cmd_get_hw_version( + nfc, + &req->simple, + &res->get_hw_version); + + case SG_NFC_CMD_POLL: + return sg_nfc_cmd_poll( + nfc, + &req->simple, + &res->poll); + + case SG_NFC_CMD_MIFARE_READ_BLOCK: + return sg_nfc_cmd_mifare_read_block( + nfc, + &req->mifare_read_block, + &res->mifare_read_block); + + case SG_NFC_CMD_FELICA_ENCAP: + return sg_nfc_cmd_felica_encap( + nfc, + &req->felica_encap, + &res->felica_encap); + + case SG_NFC_CMD_MIFARE_AUTHENTICATE: + case SG_NFC_CMD_MIFARE_SELECT_TAG: + case SG_NFC_CMD_MIFARE_SET_KEY_AIME: + case SG_NFC_CMD_MIFARE_SET_KEY_BANA: + case SG_NFC_CMD_RADIO_ON: + case SG_NFC_CMD_RADIO_OFF: + return sg_nfc_cmd_dummy(nfc, &req->simple, &res->simple); + + default: + sg_nfc_dprintf(nfc, "Unimpl command %02x\n", req->simple.hdr.cmd); + + return E_NOTIMPL; + } +} + +static HRESULT sg_nfc_cmd_reset( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res) +{ + sg_nfc_dprintf(nfc, "Reset\n"); + sg_res_init(res, req, 0); + res->status = 3; + + return S_OK; +} + +static HRESULT sg_nfc_cmd_get_fw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_fw_version *res) +{ + /* Dest version is not NUL terminated, this is intentional */ + sg_res_init(&res->res, req, sizeof(res->version)); + memcpy(res->version, "TN32MSEC003S F/W Ver1.2E", sizeof(res->version)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_get_hw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_hw_version *res) +{ + /* Dest version is not NUL terminated, this is intentional */ + sg_res_init(&res->res, req, sizeof(res->version)); + memcpy(res->version, "TN32MSEC003S H/W Ver3.0J", sizeof(res->version)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_poll( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_poll *res) +{ + struct sg_nfc_poll_mifare mifare; + struct sg_nfc_poll_felica felica; + HRESULT hr; + + hr = nfc->ops->poll(nfc->ops_ctx); + + if (FAILED(hr)) { + return hr; + } + + hr = sg_nfc_poll_felica(nfc, &felica); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + sg_res_init(&res->res, req, 1 + sizeof(felica)); + memcpy(res->payload, &felica, sizeof(felica)); + res->count = 1; + + return S_OK; + } + + hr = sg_nfc_poll_aime(nfc, &mifare); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + sg_res_init(&res->res, req, 1 + sizeof(mifare)); + memcpy(res->payload, &mifare, sizeof(mifare)); + res->count = 1; + + return S_OK; + } + + sg_res_init(&res->res, req, 1); + res->count = 0; + + return S_OK; +} + +static HRESULT sg_nfc_poll_aime( + struct sg_nfc *nfc, + struct sg_nfc_poll_mifare *mifare) +{ + uint8_t luid[10]; + HRESULT hr; + + /* Call backend */ + + if (nfc->ops->get_aime_id != NULL) { + hr = nfc->ops->get_aime_id(nfc->ops_ctx, luid, sizeof(luid)); + } else { + hr = S_FALSE; + } + + if (FAILED(hr) || hr == S_FALSE) { + return hr; + } + + sg_nfc_dprintf(nfc, "AiMe card is present\n"); + + /* Construct response (use an arbitrary UID) */ + + mifare->type = 0x10; + mifare->id_len = sizeof(mifare->uid); + mifare->uid = _byteswap_ulong(0x01020304); + + /* Initialize MIFARE IC emulator */ + + hr = aime_card_populate(&nfc->mifare, luid, sizeof(luid)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT sg_nfc_poll_felica( + struct sg_nfc *nfc, + struct sg_nfc_poll_felica *felica) +{ + uint64_t IDm; + HRESULT hr; + + /* Call backend */ + + if (nfc->ops->get_felica_id != NULL) { + hr = nfc->ops->get_felica_id(nfc->ops_ctx, &IDm); + } else { + hr = S_FALSE; + } + + if (FAILED(hr) || hr == S_FALSE) { + return hr; + } + + sg_nfc_dprintf(nfc, "FeliCa card is present\n"); + + /* Construct poll response */ + + felica->type = 0x20; + felica->id_len = sizeof(felica->IDm) + sizeof(felica->PMm); + felica->IDm = _byteswap_uint64(IDm); + felica->PMm = _byteswap_uint64(felica_get_generic_PMm()); + + /* Initialize FeliCa IC emulator */ + + nfc->felica.IDm = IDm; + nfc->felica.PMm = felica_get_generic_PMm(); + nfc->felica.system_code = 0x0000; + + return S_OK; +} + +static HRESULT sg_nfc_cmd_mifare_read_block( + struct sg_nfc *nfc, + const struct sg_nfc_req_mifare_read_block *req, + struct sg_nfc_res_mifare_read_block *res) +{ + uint32_t uid; + + if (req->req.payload_len != sizeof(req->payload)) { + sg_nfc_dprintf(nfc, "%s: Payload size is incorrect\n", __func__); + + return E_FAIL; + } + + uid = _byteswap_ulong(req->payload.uid); + + sg_nfc_dprintf(nfc, "Read uid %08x block %i\n", uid, req->payload.block_no); + + if (req->payload.block_no > 3) { + sg_nfc_dprintf(nfc, "MIFARE block number out of range\n"); + + return E_FAIL; + } + + sg_res_init(&res->res, &req->req, sizeof(res->block)); + + memcpy( res->block, + nfc->mifare.sectors[0].blocks[req->payload.block_no].bytes, + sizeof(res->block)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_felica_encap( + struct sg_nfc *nfc, + const struct sg_nfc_req_felica_encap *req, + struct sg_nfc_res_felica_encap *res) +{ + struct const_iobuf f_req; + struct iobuf f_res; + HRESULT hr; + + /* First byte of encapsulated request and response is a length byte + (inclusive of itself). The FeliCa emulator expects its caller to handle + that length byte on its behalf (we adopt the convention that the length + byte is part of the FeliCa protocol's framing layer). */ + + if (req->req.payload_len != 8 + req->payload[0]) { + sg_nfc_dprintf( + nfc, + "FeliCa encap payload length mismatch: sg %i != felica %i + 8", + req->req.payload_len, + req->payload[0]); + + return E_FAIL; + } + + f_req.bytes = req->payload; + f_req.nbytes = req->payload[0]; + f_req.pos = 1; + + f_res.bytes = res->payload; + f_res.nbytes = sizeof(res->payload); + f_res.pos = 1; + +#if 0 + dprintf("FELICA OUTBOUND:\n"); + dump_const_iobuf(&f_req); +#endif + + hr = felica_transact(&nfc->felica, &f_req, &f_res); + + if (FAILED(hr)) { + return hr; + } + + sg_res_init(&res->res, &req->req, f_res.pos); + res->payload[0] = f_res.pos; + +#if 0 + dprintf("FELICA INBOUND:\n"); + dump_iobuf(&f_res); +#endif + + return S_OK; +} + +static HRESULT sg_nfc_cmd_dummy( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res) +{ + sg_res_init(res, req, 0); + + return S_OK; +} diff --git a/board/sg-nfc.h b/board/sg-nfc.h new file mode 100644 index 0000000..5562b2b --- /dev/null +++ b/board/sg-nfc.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +#include "iccard/felica.h" +#include "iccard/mifare.h" + +struct sg_nfc_ops { + HRESULT (*poll)(void *ctx); + HRESULT (*get_aime_id)(void *ctx, uint8_t *luid, size_t nbytes); + HRESULT (*get_felica_id)(void *ctx, uint64_t *IDm); + + // TODO Banapass, AmuseIC +}; + +struct sg_nfc { + const struct sg_nfc_ops *ops; + void *ops_ctx; + uint8_t addr; + struct felica felica; + struct mifare mifare; +}; + +void sg_nfc_init( + struct sg_nfc *nfc, + uint8_t addr, + const struct sg_nfc_ops *ops, + void *ops_ctx); + +void sg_nfc_transact( + struct sg_nfc *nfc, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes); diff --git a/board/sg-reader.c b/board/sg-reader.c new file mode 100644 index 0000000..dbf0392 --- /dev/null +++ b/board/sg-reader.c @@ -0,0 +1,186 @@ +#include + +#include +#include +#include + +#include "board/aime-dll.h" +#include "board/sg-led.h" +#include "board/sg-nfc.h" +#include "board/sg-reader.h" + +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT sg_reader_handle_irp(struct irp *irp); +static HRESULT sg_reader_handle_irp_locked(struct irp *irp); +static HRESULT sg_reader_nfc_poll(void *ctx); +static HRESULT sg_reader_nfc_get_aime_id( + void *ctx, + uint8_t *luid, + size_t luid_size); +static HRESULT sg_reader_nfc_get_felica_id(void *ctx, uint64_t *IDm); +static void sg_reader_led_set_color(void *ctx, uint8_t r, uint8_t g, uint8_t b); + +static const struct sg_nfc_ops sg_reader_nfc_ops = { + .poll = sg_reader_nfc_poll, + .get_aime_id = sg_reader_nfc_get_aime_id, + .get_felica_id = sg_reader_nfc_get_felica_id, +}; + +static const struct sg_led_ops sg_reader_led_ops = { + .set_color = sg_reader_led_set_color, +}; + +static CRITICAL_SECTION sg_reader_lock; +static bool sg_reader_started; +static HRESULT sg_reader_start_hr; +static struct uart sg_reader_uart; +static uint8_t sg_reader_written_bytes[520]; +static uint8_t sg_reader_readable_bytes[520]; +static struct sg_nfc sg_reader_nfc; +static struct sg_led sg_reader_led; + +HRESULT sg_reader_hook_init( + const struct aime_config *cfg, + unsigned int port_no, + HINSTANCE self) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = aime_dll_init(&cfg->dll, self); + + if (FAILED(hr)) { + return hr; + } + + sg_nfc_init(&sg_reader_nfc, 0x00, &sg_reader_nfc_ops, NULL); + sg_led_init(&sg_reader_led, 0x08, &sg_reader_led_ops, NULL); + + InitializeCriticalSection(&sg_reader_lock); + + uart_init(&sg_reader_uart, port_no); + sg_reader_uart.written.bytes = sg_reader_written_bytes; + sg_reader_uart.written.nbytes = sizeof(sg_reader_written_bytes); + sg_reader_uart.readable.bytes = sg_reader_readable_bytes; + sg_reader_uart.readable.nbytes = sizeof(sg_reader_readable_bytes); + + return iohook_push_handler(sg_reader_handle_irp); +} + +static HRESULT sg_reader_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&sg_reader_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&sg_reader_lock); + hr = sg_reader_handle_irp_locked(irp); + LeaveCriticalSection(&sg_reader_lock); + + return hr; +} + +static HRESULT sg_reader_handle_irp_locked(struct irp *irp) +{ + HRESULT hr; + +#if 0 + if (irp->op == IRP_OP_WRITE) { + dprintf("WRITE:\n"); + dump_const_iobuf(&irp->write); + } +#endif + +#if 0 + if (irp->op == IRP_OP_READ) { + dprintf("READ:\n"); + dump_iobuf(&sg_reader_uart.readable); + } +#endif + + if (irp->op == IRP_OP_OPEN) { + /* Unfortunately the card reader UART gets opened and closed + repeatedly */ + + if (!sg_reader_started) { + dprintf("NFC Assembly: Starting backend DLL\n"); + hr = aime_dll.init(); + + sg_reader_started = true; + sg_reader_start_hr = hr; + + if (FAILED(hr)) { + dprintf("NFC Assembly: Backend error: %x\n", (int) hr); + + return hr; + } + } else { + hr = sg_reader_start_hr; + + if (FAILED(hr)) { + return hr; + } + } + } + + hr = uart_handle_irp(&sg_reader_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + sg_nfc_transact( + &sg_reader_nfc, + &sg_reader_uart.readable, + sg_reader_uart.written.bytes, + sg_reader_uart.written.pos); + + sg_led_transact( + &sg_reader_led, + &sg_reader_uart.readable, + sg_reader_uart.written.bytes, + sg_reader_uart.written.pos); + + sg_reader_uart.written.pos = 0; + + return hr; +} + +static HRESULT sg_reader_nfc_poll(void *ctx) +{ + return aime_dll.nfc_poll(0); +} + +static HRESULT sg_reader_nfc_get_aime_id( + void *ctx, + uint8_t *luid, + size_t luid_size) +{ + return aime_dll.nfc_get_aime_id(0, luid, luid_size); +} + +static HRESULT sg_reader_nfc_get_felica_id(void *ctx, uint64_t *IDm) +{ + return aime_dll.nfc_get_felica_id(0, IDm); +} + +static void sg_reader_led_set_color(void *ctx, uint8_t r, uint8_t g, uint8_t b) +{ + aime_dll.led_set_color(0, r, g, b); +} diff --git a/board/sg-reader.h b/board/sg-reader.h new file mode 100644 index 0000000..673a8bd --- /dev/null +++ b/board/sg-reader.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +#include "board/aime-dll.h" + +struct aime_config { + struct aime_dll_config dll; + bool enable; +}; + +HRESULT sg_reader_hook_init( + const struct aime_config *cfg, + unsigned int port_no, + HINSTANCE self); diff --git a/board/usio.c b/board/usio.c index d590e4f..f479a39 100644 --- a/board/usio.c +++ b/board/usio.c @@ -19,12 +19,12 @@ #include "hook/iohook.h" #include "hooklib/setupapi.h" +#include "hooklib/procaddr.h" #include "util/dprintf.h" #include "util/dump.h" static const wchar_t usio_path[] = L"$usio"; -//static const wchar_t usio_path[] = L"USBIO_Device0"; static int my_bnusio_Open(); static int my_bnusio_Close(); @@ -44,7 +44,7 @@ static int my_bnusio_GetCoinError(uint8_t id); static int my_bnusio_GetService(uint8_t id); static int my_bnusio_GetServiceError(uint8_t id); -static const struct hook_symbol winusb_syms[] = { +static struct hook_symbol usio_syms[] = { { .name = "bnusio_Open", .patch = my_bnusio_Open @@ -112,6 +112,74 @@ static const struct hook_symbol winusb_syms[] = { { .name = "bnusio_GetServiceError", .patch = my_bnusio_GetServiceError + }, + { + .name = "Open", + .patch = my_bnusio_Open + }, + { + .name = "GetFirmwareVersion", + .patch = my_bnusio_GetFirmwareVersion + }, + { + .name = "Close", + .patch = my_bnusio_Close + }, + { + .name = "SetSystemError", + .patch = my_bnusio_SetSystemError + }, + { + .name = "ClearSram", + .patch = my_bnusio_ClearSram + }, + { + .name = "ResetIoBoard", + .patch = my_bnusio_ResetIoBoard + }, + { + .name = "Communication", + .patch = my_bnusio_Communication + }, + { + .name = "GetSystemError", + .patch = my_bnusio_GetSystemError + }, + { + .name = "SetPLCounter", + .patch = my_bnusio_SetPLCounter + }, + { + .name = "SetGout", + .patch = my_bnusio_SetGout + }, + { + .name = "GetAnalogIn", + .patch = my_bnusio_GetAnalogIn + }, + { + .name = "GetSwIn", + .patch = my_bnusio_GetSwIn + }, + { + .name = "SetCoinLock", + .patch = my_bnusio_SetCoinLock + }, + { + .name = "GetCoin", + .patch = my_bnusio_GetCoin + }, + { + .name = "GetCoinError", + .patch = my_bnusio_GetCoinError + }, + { + .name = "GetService", + .patch = my_bnusio_GetService + }, + { + .name = "GetServiceError", + .patch = my_bnusio_GetServiceError } }; @@ -138,7 +206,7 @@ HRESULT usio_hook_init( usio_ops = ops; usio_ops_ctx = ctx; - hook_table_apply(target, "bnusio.dll", winusb_syms, _countof(winusb_syms)); + hook_table_apply(target, "bnusio.dll", usio_syms, _countof(usio_syms)); memset(&state, 0, sizeof(state)); dprintf("USIO: Init\n"); @@ -146,6 +214,14 @@ HRESULT usio_hook_init( return S_OK; } +HRESULT usio_hook_proc_addr(HMODULE target) +{ + if (usio_ops != NULL) + return proc_addr_table_push(target, "bnusio.dll", usio_syms, _countof(usio_syms)); + + return S_OK; +} + static int my_bnusio_Open() { dprintf("USIO: Open\n"); diff --git a/board/usio.h b/board/usio.h index e0505a2..5f709ec 100644 --- a/board/usio.h +++ b/board/usio.h @@ -47,3 +47,5 @@ HRESULT usio_hook_init( const struct usio_ops *ops, void *ctx, HMODULE target); + +HRESULT usio_hook_proc_addr(HMODULE target); diff --git a/board/vfd.c b/board/vfd.c new file mode 100644 index 0000000..081a8d8 --- /dev/null +++ b/board/vfd.c @@ -0,0 +1,62 @@ +/* This is some sort of LCD display found on various cabinets. It is driven + directly by amdaemon, and it has something to do with displaying the status + of electronic payments. + + Part number in schematics is "VFD GP1232A02A FUTABA". + + Little else about this board is known. Black-holing the RS232 comms that it + receives seems to be sufficient for the time being. */ + +#include + +#include +#include + +#include "board/vfd.h" + +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT vfd_handle_irp(struct irp *irp); + +static struct uart vfd_uart; +static uint8_t vfd_written[512]; +static uint8_t vfd_readable[512]; + +HRESULT vfd_hook_init(unsigned int port_no) +{ + uart_init(&vfd_uart, port_no); + vfd_uart.written.bytes = vfd_written; + vfd_uart.written.nbytes = sizeof(vfd_written); + vfd_uart.readable.bytes = vfd_readable; + vfd_uart.readable.nbytes = sizeof(vfd_readable); + + return iohook_push_handler(vfd_handle_irp); +} + +static HRESULT vfd_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&vfd_uart, irp)) { + return iohook_invoke_next(irp); + } + + hr = uart_handle_irp(&vfd_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + dprintf("VFD TX:\n"); + dump_iobuf(&vfd_uart.written); + vfd_uart.written.pos = 0; + + return hr; +} diff --git a/board/vfd.h b/board/vfd.h new file mode 100644 index 0000000..01cd82e --- /dev/null +++ b/board/vfd.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +HRESULT vfd_hook_init(unsigned int port_no); diff --git a/iccard/aime.c b/iccard/aime.c new file mode 100644 index 0000000..199413a --- /dev/null +++ b/iccard/aime.c @@ -0,0 +1,64 @@ +#include +#include +#include + +#include "iccard/aime.h" +#include "iccard/mifare.h" +#include "iccard/solitaire.h" + +#include "util/dprintf.h" + +HRESULT aime_card_populate( + struct mifare *mifare, + const uint8_t *luid, + size_t nbytes) +{ + uint8_t b; + size_t i; + char accessCode[21]; + char hashed_id_wrk[9]; + char id_wrk[9]; + + assert(mifare != NULL); + assert(luid != NULL); + + memset(mifare, 0, sizeof(*mifare)); + + if (nbytes != 10) { + dprintf("AiMe IC: LUID must be 10 bytes\n"); + + return E_INVALIDARG; + } + + for (i = 0 ; i < 10 ; i++) { + b = luid[i]; + + if ((b & 0xF0) > 0x90 || (b & 0x0F) > 0x09) { + dprintf("AiMe IC: LUID must be binary-coded decimal\n"); + return E_INVALIDARG; + } + + mifare->sectors[0].blocks[2].bytes[6 + i] = b; + } + + + sprintf_s(accessCode, sizeof accessCode, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + luid[0], luid[1], luid[2], luid[3], luid[4], luid[5], luid[6], luid[7], luid[8], luid[9]); + + memcpy_s(hashed_id_wrk, sizeof(hashed_id_wrk), &accessCode[5], + 8); + + hashed_id_wrk[8] = '\0'; + + SolitaireCipherDecode(&accessCode[13], hashed_id_wrk, id_wrk); + + + DWORD nSerial = atoi(id_wrk); + + mifare->sectors[0].blocks[1].bytes[12] = (nSerial >> 24) & 0xff; + mifare->sectors[0].blocks[1].bytes[13] = (nSerial >> 16) & 0xff; + mifare->sectors[0].blocks[1].bytes[14] = (nSerial >> 8) & 0xff; + mifare->sectors[0].blocks[1].bytes[15] = nSerial & 0xff; + + return S_OK; +} diff --git a/iccard/aime.h b/iccard/aime.h new file mode 100644 index 0000000..34857ef --- /dev/null +++ b/iccard/aime.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include + +#include "iccard/mifare.h" + +HRESULT aime_card_populate( + struct mifare *mifare, + const uint8_t *luid, + size_t nbytes); diff --git a/iccard/felica.c b/iccard/felica.c new file mode 100644 index 0000000..09e3a77 --- /dev/null +++ b/iccard/felica.c @@ -0,0 +1,196 @@ +#include + +#include +#include +#include + +#include "hook/iobuf.h" + +#include "iccard/felica.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_nda_a4( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +uint64_t felica_get_generic_PMm(void) +{ + /* A FeliCa PMm contains low-level protocol timing information for + communicating with a particular IC card. The exact values are not + particularly important for our purposes, so we'll just return a hard- + coded PMm. This current value has been taken from an iPhone, emulating + a Suica pass via Apple Wallet, which seems to be one of the few + universally accepted FeliCa types for these games. Certain older + Suica passes and other payment and transportation cards + do not seem to be supported anymore. */ + + return 0x01168B868FBECBFFULL; +} + +HRESULT felica_transact( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint64_t IDm; + uint8_t code; + HRESULT hr; + + assert(f != NULL); + assert(req != NULL); + assert(res != NULL); + + hr = iobuf_read_8(req, &code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_8(res, code + 1); + + if (FAILED(hr)) { + return hr; + } + + if (code != FELICA_CMD_POLL) { + hr = iobuf_read_be64(req, &IDm); + + if (FAILED(hr)) { + return hr; + } + + if (IDm != f->IDm) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = iobuf_write_be64(res, IDm); + + if (FAILED(hr)) { + return hr; + } + } + + switch (code) { + case FELICA_CMD_POLL: + return felica_cmd_poll(f, req, res); + + case FELICA_CMD_GET_SYSTEM_CODE: + return felica_cmd_get_system_code(f, req, res); + + case FELICA_CMD_NDA_A4: + return felica_cmd_nda_a4(f, req, res); + + default: + dprintf("FeliCa: Unimplemented command %02x, payload:\n", code); + dump_const_iobuf(req); + + return E_NOTIMPL; + } +} + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; + HRESULT hr; + + /* Request: */ + + hr = iobuf_read_be16(req, &system_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &request_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &time_slot); + + if (FAILED(hr)) { + return hr; + } + + if (system_code != 0xFFFF && system_code != f->system_code) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + // TODO handle other params correctly... + + /* Response: */ + + hr = iobuf_write_be64(res, f->IDm); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be64(res, f->PMm); + + if (FAILED(hr)) { + return hr; + } + + if (request_code == 0x01) { + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + } + + return S_OK; +} + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + HRESULT hr; + + hr = iobuf_write_8(res, 1); /* Number of system codes */ + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT felica_cmd_nda_a4( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + /* The specification for this command is probably only available under NDA. + Returning what the driver seems to want. */ + + return iobuf_write_8(res, 0); +} diff --git a/iccard/felica.h b/iccard/felica.h new file mode 100644 index 0000000..33be201 --- /dev/null +++ b/iccard/felica.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +enum { + FELICA_CMD_POLL = 0x00, + FELICA_CMD_GET_SYSTEM_CODE = 0x0c, + FELICA_CMD_NDA_A4 = 0xa4, +}; + +struct felica { + uint64_t IDm; + uint64_t PMm; + uint16_t system_code; +}; + +HRESULT felica_transact( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +uint64_t felica_get_generic_PMm(void); diff --git a/iccard/meson.build b/iccard/meson.build new file mode 100644 index 0000000..6cad0a0 --- /dev/null +++ b/iccard/meson.build @@ -0,0 +1,18 @@ +iccard_lib = static_library( + 'iccard', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'aime.c', + 'aime.h', + 'felica.c', + 'felica.h', + 'mifare.h', + 'solitaire.c', + 'solitaire.h', + ], +) diff --git a/iccard/mifare.h b/iccard/mifare.h new file mode 100644 index 0000000..10dbc34 --- /dev/null +++ b/iccard/mifare.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct mifare_block { + uint8_t bytes[16]; +}; + +struct mifare_sector { + struct mifare_block blocks[4]; +}; + +struct mifare { + struct mifare_sector sectors[16]; +}; diff --git a/iccard/solitaire.c b/iccard/solitaire.c new file mode 100644 index 0000000..cfe3da3 --- /dev/null +++ b/iccard/solitaire.c @@ -0,0 +1,143 @@ +#include "solitaire.h" + +#include + +#define DECK_SIZE 22 +#define JOKER_A 21 +#define JOKER_B 22 + +typedef struct { + char m_Deck[DECK_SIZE]; +} DECK, *PDECK; + +static DECK SOL_INIT_DECK = { + .m_Deck = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 }, +}; + +#define char2num(c) ((c) - '0' + 1) +static inline char num2char(char num) { + while (num < 1) num = num + 10; + return (num - 1) % 10 + '0'; +} + +static void SolMoveCard(PDECK lpDeck, char card) { + int p = 0; + + for (int i = 0; i < DECK_SIZE; i++) { + if (lpDeck->m_Deck[i] == card) { + p = i; + break; + } + } + + if (p < DECK_SIZE - 1) { + lpDeck->m_Deck[p] = lpDeck->m_Deck[p + 1]; + lpDeck->m_Deck[p + 1] = card; + } else { + for (int i = DECK_SIZE - 1; i > 1; i--) lpDeck->m_Deck[i] = lpDeck->m_Deck[i - 1]; + lpDeck->m_Deck[1] = card; + } +} + +static void SolCutDeck(PDECK lpDeck, char point) { + DECK tmp; + + memcpy(tmp.m_Deck, &lpDeck->m_Deck[(size_t)point], DECK_SIZE - point - 1); + memcpy(&tmp.m_Deck[DECK_SIZE - point - 1], lpDeck->m_Deck, point); + memcpy(lpDeck->m_Deck, tmp.m_Deck, DECK_SIZE - 1); +} + +static void SolSwapOutsideJoker(PDECK lpDeck) { + int j1 = -1; + int j2 = -1; + DECK tmp; + + for (int i = 0; i < DECK_SIZE; i++) { + if (lpDeck->m_Deck[i] == JOKER_A || lpDeck->m_Deck[i] == JOKER_B) { + if (j1 == -1) { + j1 = i; + } else { + j2 = i; + } + } + } + + if (0 < DECK_SIZE - j2 - 1) memcpy(tmp.m_Deck, &lpDeck->m_Deck[j2 + 1], DECK_SIZE - j2 - 1); + tmp.m_Deck[DECK_SIZE - j2 - 1] = lpDeck->m_Deck[j1]; + if (0 < j2 - j1 - 1) memcpy(&tmp.m_Deck[DECK_SIZE - j2], &lpDeck->m_Deck[j1 + 1], j2 - j1 - 1); + tmp.m_Deck[DECK_SIZE - j1 - 1] = lpDeck->m_Deck[j2]; + if (0 < j1) memcpy(&tmp.m_Deck[DECK_SIZE - j1], lpDeck->m_Deck, j1); + memcpy(lpDeck->m_Deck, tmp.m_Deck, DECK_SIZE); +} + +static void SolCutByBottomCard(PDECK lpDeck) { + char p = lpDeck->m_Deck[DECK_SIZE - 1]; + if (p == JOKER_B) p = JOKER_A; + SolCutDeck(lpDeck, p); +} + +static char SolGetTopCardNum(PDECK lpDeck) { + char p = lpDeck->m_Deck[0]; + if (p == JOKER_B) p = JOKER_A; + return lpDeck->m_Deck[(size_t)p]; +} + +static void SolDeckHash(PDECK lpDeck) { + char p; + + do { + SolMoveCard(lpDeck, JOKER_A); + SolMoveCard(lpDeck, JOKER_B); + SolMoveCard(lpDeck, JOKER_B); + SolSwapOutsideJoker(lpDeck); + SolCutByBottomCard(lpDeck); + + p = SolGetTopCardNum(lpDeck); + } while (p == JOKER_A || p == JOKER_B); +} + +static void SolCreateDeck(PDECK lpDeck, const char *key) { + memcpy_s(lpDeck, sizeof *lpDeck, &SOL_INIT_DECK, sizeof SOL_INIT_DECK); + int p = 0; + while (key[p] != '\0') { + SolDeckHash(lpDeck); + char c = char2num(key[p]); + SolCutDeck(lpDeck, c); + p++; + } +} + +void SolitaireCipherEncode(const char *szKey, const char *szSrc, char *szDst) { + DECK deck; + SolCreateDeck(&deck, szKey); + + int i = 0; + while (szSrc[i] != '\0') { + SolDeckHash(&deck); + char p = SolGetTopCardNum(&deck); + szDst[i] = num2char(char2num(szSrc[i]) + p); + i++; + } + szDst[i] = '\0'; +} + +void SolitaireCipherDecode(const char *szKey, const char *szSrc, char *szDst) { + DECK deck; + SolCreateDeck(&deck, szKey); + + int i = 0; + while (szSrc[i] != '\0') { + SolDeckHash(&deck); + char p = SolGetTopCardNum(&deck); + szDst[i] = num2char(char2num(szSrc[i]) - p); + i++; + } + szDst[i] = '\0'; +} + +void SolitaireCipher(int nMode, const char *szKey, const char *szSrc, char *szDst) { + if (nMode == 0) + SolitaireCipherEncode(szKey, szSrc, szDst); + else if (nMode == 1) + SolitaireCipherDecode(szKey, szSrc, szDst); +} diff --git a/iccard/solitaire.h b/iccard/solitaire.h new file mode 100644 index 0000000..34ea802 --- /dev/null +++ b/iccard/solitaire.h @@ -0,0 +1,3 @@ +void SolitaireCipherDecode(const char *szKey, const char *szSrc, char *szDst); +void SolitaireCipherEncode(const char *szKey, const char *szSrc, char *szDst); +void SolitaireCipher(int mode, const char *key, const char *src_str, char *dest_str);