diff --git a/Arduino-Chunithm-Reader.ino b/Arduino-Chunithm-Reader.ino new file mode 100644 index 0000000..647f96f --- /dev/null +++ b/Arduino-Chunithm-Reader.ino @@ -0,0 +1,119 @@ +#include "cmd.h" + +void SerialCheck() { + switch (packet_read()) { + case SG_NFC_CMD_RESET: + sg_nfc_cmd_reset(); + break; + case SG_NFC_CMD_GET_FW_VERSION: + sg_nfc_cmd_get_fw_version(); + break; + case SG_NFC_CMD_GET_HW_VERSION: + sg_nfc_cmd_get_hw_version(); + break; + case SG_NFC_CMD_POLL: + sg_nfc_cmd_poll(); + break; + case SG_NFC_CMD_MIFARE_READ_BLOCK: + sg_nfc_cmd_mifare_read_block(); + break; + case SG_NFC_CMD_FELICA_ENCAP: + sg_nfc_cmd_felica_encap(); + break; + case SG_NFC_CMD_MIFARE_AUTHENTICATE: + sg_res_init(); + break; + case SG_NFC_CMD_MIFARE_SELECT_TAG: + sg_res_init(); + break; + case SG_NFC_CMD_MIFARE_SET_KEY_AIME: + sg_nfc_cmd_mifare_set_key_aime(); + break; + case SG_NFC_CMD_MIFARE_SET_KEY_BANA: + sg_res_init(); + break; + case SG_NFC_CMD_RADIO_ON: + sg_nfc_cmd_radio_on(); + break; + case SG_NFC_CMD_RADIO_OFF: + sg_nfc_cmd_radio_off(); + break; + case SG_RGB_CMD_RESET: + sg_led_cmd_reset(); + break; + case SG_RGB_CMD_GET_INFO: + sg_led_cmd_get_info(); + break; + case SG_RGB_CMD_SET_COLOR: + sg_led_cmd_set_color(); + break; + } +} + +void setup() { + SerialUSB.begin(38400); + SerialUSB.setTimeout(0); + FastLED.addLeds(leds, NUM_LEDS); + nfc.begin(); + nfc.SAMConfig(); + memset(&req, 0, sizeof(req.bytes)); + memset(&res, 0, sizeof(res.bytes)); +} + +void loop() { + SerialCheck(); + packet_write(); +} + +static uint8_t packet_read() { + uint8_t len, r, checksum; + bool escape = false; + while (SerialUSB.available()) { + r = SerialUSB.read(); + if (r == 0xE0) { + req.frame_len = 0xFF; + continue; + } + if (req.frame_len == 0xFF) { + req.frame_len = r; + len = 1; + checksum = r; + continue; + } + if (r == 0xD0) { + escape = true; + continue; + } + if (escape) { + r++; + escape = false; + } + if (len == req.frame_len && checksum == r) { + return req.cmd; + } + req.bytes[len++] = r; + checksum += r; + } + return 0; +} + +static void packet_write() { + if (res.cmd == 0) + return; + uint8_t checksum = 0; + SerialUSB.write(0xE0); + for (uint8_t i = 0; i < res.frame_len; i++) { + uint8_t w = res.bytes[i]; + checksum += w; + if (SerialUSB.availableForWrite() < 2) + return; + if (w == 0xE0 || w == 0xD0) { + SerialUSB.write(0xD0); + SerialUSB.write(--w); + } else { + SerialUSB.write(w); + } + } + SerialUSB.write(checksum); + res.cmd = 0; +} diff --git a/card.txt b/card.txt new file mode 100644 index 0000000..4c52079 --- /dev/null +++ b/card.txt @@ -0,0 +1,14 @@ +E0 [05] 00 [7D] [42] 00 [C4] //读卡 +E0 [19] 00 [7D] [42] 00 13 01 20 10 [ 8 byte IDm ] [ 8 byte PMm ] [ ] + + +E0 [13] 00 [80] [71] 0E [ 8 byte IDm ] 06 [00] FF FF 01 0F [ ] +E0 [1A] 00 [80] [71] 00 [14] 14 01 [ 8 byte IDm ] [ 8 byte PMm ] 00 00 [ ] + + +E0 [17] 00 [81] [71] 12 [ 8 byte IDm ] 0A [0C] [ 8 byte IDm ] [ ] +E0 [13] 00 [81] [71] 00 [0D] 0D 0D [ 8 byte IDm ] 01 00 00 [ ] + + +E0 [18] 00 [82] [71] 13 [ 8 byte IDm ] 0B [A4] [ 8 byte IDm ] 00 [ ] +E0 [11] 00 [82] [71] 00 [0B] 0B A5 [ 8 byte IDm ] 00 [ ] diff --git a/chunihook.dll b/chunihook.dll new file mode 100644 index 0000000..6a0b29e Binary files /dev/null and b/chunihook.dll differ diff --git a/cmd.h b/cmd.h new file mode 100644 index 0000000..d6066a3 --- /dev/null +++ b/cmd.h @@ -0,0 +1,239 @@ +#include "FastLED.h" +#define NUM_LEDS 6 +#define DATA_PIN A3 +#define BRI 50 +CRGB leds[NUM_LEDS]; + +#include "Adafruit_PN532.h" +#define PN532_IRQ (4) +Adafruit_PN532 nfc(PN532_IRQ, 16); +boolean readerDisabled = false; + +uint8_t AimeKey[6]; +uint8_t MifareKey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +enum { + SG_NFC_CMD_GET_FW_VERSION = 0x30,//获取FW版本 + SG_NFC_CMD_GET_HW_VERSION = 0x32,//获取HW版本 + 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, + SG_NFC_CMD_RESET = 0x62,//重置读卡器 + SG_NFC_CMD_FELICA_ENCAP = 0x71, + SG_RGB_CMD_SET_COLOR = 0x81,//LED颜色设置 + SG_RGB_CMD_RESET = 0xF5,//LED重置 + SG_RGB_CMD_GET_INFO = 0xF0,//LED信息获取 + FELICA_CMD_POLL = 0x00,//ENCAP用 + FELICA_CMD_GET_SYSTEM_CODE = 0x0c, + FELICA_CMD_NDA_A4 = 0xa4, +}; + +typedef union packet_req { + uint8_t bytes[256]; + struct { + uint8_t frame_len; + uint8_t addr; + uint8_t seq_no; + uint8_t cmd; + uint8_t payload_len; + union { + uint8_t key[6];// sg_nfc_req_mifare_set_key + struct {// sg_nfc_cmd_mifare_read_block + uint8_t uid[4]; + uint8_t block_no; + }; + struct {// sg_nfc_req_felica_encap + uint8_t IDm[8]; + uint8_t encap_len; + uint8_t code; + uint8_t felica_payload[241]; + }; + uint8_t color_payload[3];//sg_led_req_set_color + }; + }; + +} packet_req_t; + +typedef union packet_res { + uint8_t bytes[256]; + struct { + uint8_t frame_len; + uint8_t addr; + uint8_t seq_no; + uint8_t cmd; + uint8_t status; + uint8_t payload_len; + union { + char version[23];// sg_nfc_res_get_fw_version,sg_nfc_res_get_hw_version + struct {// sg_nfc_res_poll + uint8_t count; + uint8_t type; + uint8_t id_len; + union { + uint8_t uid[4]; + uint8_t IDPM[16]; + }; + }; + uint8_t block[16];// sg_nfc_res_mifare_read_block + struct {// sg_nfc_res_felica_encap + uint8_t encap_len; + uint8_t code; + uint8_t IDm[8]; + union { + struct { + uint8_t PMm[8]; + uint8_t system_code[2]; + }; + uint8_t felica_payload[241]; + }; + }; + uint8_t reset_payload; //sg_led_res_reset + uint8_t info_payload[9]; //sg_led_res_get_info + }; + }; +} packet_res_t; + +static packet_req_t req; +static packet_res_t res; + +static void sg_res_init(uint8_t payload_len = 0) { //初始化模板 + res.frame_len = 6 + payload_len; + res.addr = req.addr; + res.seq_no = req.seq_no; + res.cmd = req.cmd; + res.status = 0; + res.payload_len = payload_len; +} + +static void sg_nfc_cmd_reset() {//重置读卡器 + sg_res_init(); + res.status = 3; + readerDisabled = true; +} + +static void sg_nfc_cmd_get_fw_version() { + sg_res_init(sizeof(res.version)); + memcpy(res.version, "TN32MSEC003S F/W Ver1.2E", sizeof(res.version)); +} + +static void sg_nfc_cmd_get_hw_version() { + sg_res_init(sizeof(res.version)); + memcpy(res.version, "TN32MSEC003S H/W Ver3.0J", sizeof(res.version)); +} + +static void sg_nfc_cmd_mifare_set_key_aime() { + sg_res_init(); + memcpy(req.key, AimeKey, sizeof(AimeKey)); +} + +static void sg_led_cmd_reset() { + sg_res_init(sizeof(res.reset_payload)); + res.reset_payload = 0; + fill_solid(leds, NUM_LEDS, 0x000000); + FastLED[0].show(leds, NUM_LEDS, BRI); +} + +static void sg_led_cmd_get_info() { + sg_res_init(sizeof(res.info_payload)); + static uint8_t info[] = {'1', '5', '0', '8', '4', 0xFF, 0x10, 0x00, 0x12}; + memcpy(res.info_payload, info, 9); +} + +static void sg_led_cmd_set_color() { + uint8_t r = req.color_payload[0]; + uint8_t g = req.color_payload[1]; + uint8_t b = req.color_payload[2]; + fill_solid(leds, NUM_LEDS, CRGB(r, g, b)); + FastLED[0].show(leds, NUM_LEDS, BRI); +} + +static void sg_nfc_cmd_radio_on() { + sg_res_init(); + if (!readerDisabled) { + return; + } + nfc.startPassiveTargetIDDetection(PN532_MIFARE_ISO14443A); + readerDisabled = false; +} + +static void sg_nfc_cmd_radio_off() { + sg_res_init(); + readerDisabled = true; +} + +static void sg_nfc_cmd_poll() { //卡号发送 + uint8_t uid[4]; + uint8_t t; + //读aime(实验性) + nfc.startPassiveTargetIDDetection(PN532_MIFARE_ISO14443A); + delay(10); + if (!digitalRead(PN532_IRQ)) { + nfc.readDetectedPassiveTargetID(uid, &t); + if (nfc.mifareclassic_AuthenticateBlock(uid, t, 1, MIFARE_CMD_AUTH_B, AimeKey)) { //aime + res.type = 0x10; + res.id_len = t; + memcpy(res.uid, uid, t);//默认ID为0x01020304 + sg_res_init(7); + res.count = 1; + return; + } + } + //读普通mifare当作felica + nfc.startPassiveTargetIDDetection(PN532_MIFARE_ISO14443A); + delay(10); + if (!digitalRead(PN532_IRQ)) { + nfc.readDetectedPassiveTargetID(uid, &t); + if (nfc.mifareclassic_AuthenticateBlock(uid, t, 1, MIFARE_CMD_AUTH_A, MifareKey)) { + if (nfc.mifareclassic_ReadDataBlock(1, res.IDPM)) {//此处略过了IDm和PMm的分别读取 + res.type = 0x20; + res.id_len = sizeof(res.IDPM); + sg_res_init(19);//count,type,id_len,IDm,PMm + res.count = 1; + return; + } + } + } + sg_res_init(1); + res.count = 0; +} + +static void sg_nfc_cmd_mifare_read_block() { + if (req.block_no > 3) + return; + sg_res_init(sizeof(res.block)); + if (nfc.mifareclassic_AuthenticateBlock(req.uid, 4, req.block_no, MIFARE_CMD_AUTH_B, AimeKey)) { + if (nfc.mifareclassic_ReadDataBlock(req.block_no, res.block)) { + return; + } + } + sg_res_init(); +} + +static void sg_nfc_cmd_felica_encap() { + uint8_t code = req.code; + res.code = code + 1; + memcpy(res.IDm, req.IDm, sizeof(req.IDm)); + switch (code) { + case FELICA_CMD_POLL: + sg_res_init(0x14); + res.system_code[0] = 0x00; + res.system_code[1] = 0x00; + break; + case FELICA_CMD_GET_SYSTEM_CODE: + sg_res_init(0x0D); + res.felica_payload[0] = 0x01; + res.felica_payload[1] = 0x00; + res.felica_payload[2] = 0x00; + break; + case FELICA_CMD_NDA_A4: + sg_res_init(0x0B); + res.felica_payload[0] = 0x00; + break; + } + res.encap_len = res.payload_len; +} diff --git a/nfc.txt b/nfc.txt new file mode 100644 index 0000000..3e47c40 --- /dev/null +++ b/nfc.txt @@ -0,0 +1,244 @@ +N.B. Quoted strings are NOT NUL-terminated unless otherwise noted. +Useful reading: https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf + +(AiMe branded cards are Mifare Classic cards. Other technologies exist though) + +Summary +------- + +Hardware: + Assembly connector: + 5V Host in + Tx;Rx;GND Host RS232 in + Tx;Rx;GND Daisy-chain out + Main board (probably LED controller): + Silk "837-15084" + CN1: Host 5V power in, ?V NFC-sub power out + CN2: Host RS232 Tx;Rx;GND in, NFC-sub Tx;Rx out + CN3: LED-Sub power and data(?) out + DIPSW1: Set to hex nibble 8. + Contains ADM3222 RS232 transceiver IC + Contains ATMega32 MCU + NFC subboard: + Sticker: Model "TN32MSEC003S" + CN1: ?V power and Tx;Rx;GND in, Tx;Rx;GND ass'y CN daisy out. + DIPSW1: Set to hex nibble 0. + Contains ATmega168 MCU + Contains ADM3202A RS232 transceiver IC + Contains shielded RF circuit + Entire non-ground-plane PCB area is visible through the chassis + torx screws lol + LED subboard: + Silk: "837-15120" + CN1: ?V power and Tx;Rx;GND in. + Five RGB LEDs and a bunch of resistors + No visible logic ICs..? + No DIPSW. + +JVS framing: + E0 sync + D0 escape (+1 to unescape) + Checksum is sum of bytes after unescaping + +Frame header: + Frame length (including length byte itself) + Address + Sequence no, hopefully loops before hitting esc byte... + Command byte + +Bus addressing: + Low nibble set using DIPSWs + High nibble ??? + Daisy chaining mechanism unknown (RS232 wires probably multi-tap) + +Startup +------- + +Addr 00 Command 62: + Req: + 00 Payload length + Resp: + 00 Status byte + 00 Payload length + Description: + Unknown. Reset? + +Addr 00 Command 30: + Req: + 00 Payload length + Resp: + 00 Status byte + 17 Payload length + .. "TN32MSEC003S F/W Ver1.2E" + Description: + Get firmware version + +Addr 00 Command 32: + Req: + 00 Payload length + Resp: + 00 Status byte + 17 Payload length + .. "TN32MSEC003S H/W Ver3.0J" + Description: + Get hardware version + +Addr 08 Command f5: + Req: + 00 Payload length + Resp: + 00 Status byte + 00 Payload length + Description: + LED sub-board reset. + Won't accept LED commands until you do this. + +Addr 08 Command f0: + Req: + 00 Payload length + Resp: + 00 Status byte + 09 Payload length + .. "15084" (part nr for LED board) + FF ?? + 11 ?? + 00 ?? + 12 ?? + Description: + Get board "info" + +Addr 00 Command 54: + Req: + 06 Payload length + 57 'W' + 43 'C' + 43 'C' + 46 'F' + 76 'v' + 32 '2' + Resp: + 00 Status byte? + 00 ?? + Description: + Set Mifare KeyA. + "WCCF" might refer this this SEGA arcade game: + https://en.wikipedia.org/wiki/World_Club_Champion_Football + It's quite old and has AiMe readers, maybe where they first appeared? + +Addr 00 Command 50: + Req: + 06 Payload length + 60 ?? + 90 ?? + D0 ?? (This is escaped of course) + 06 ?? + 32 ?? + F5 ?? + Resp: + 00 Status byte + 00 Payload length + Description: + Possibly Mifare KeyB. + +Polling +------- + +Addr 00 Command 40: + Req: + 01 Payload length + 03 ?? + Resp: + 00 Status byte + 00 Payload length + Description: + Poll some other NFC technology? + +Addr 00 Command 42: + Req: + 00 Payload length + Resp if no MiFare card: + 00 Status byte + 01 Payload length + 00 (represents nothing i guess) + Resp if MiFare card: + 00 Status byte? + 07 Payload length + 01 Chunk length? + 10 ?? Block size maybe? + 04 Chunk length? + .. Mifare UID, four bytes. + Description: + Check for Mifare card presence? + +Addr 00 Command 41: + Req: + 00 Payload length + Resp: + 00 Status byte + 00 Payload length + Description: + Unknown. Poll some other NFC technology? + +Card read +--------- + +Addr 00 Command 43: + Req: + 04 Payload length + .. Mifare UID, four bytes. + Resp: + 00 Status byte + 00 Payload length + Description: + Select MiFare by UID? + +Addr 00 Command 55: + Req: + 05 Payload length + .. Mifare UID, four bytes. + 03 ?? + Resp: + 00 Status byte + 00 Payload length + Description: + Unknown. + Block 3 on a Mifare sector contains keys and an access control list. + It is generally not accessed directly (unless being provisioned?) + +Addr 00 Command 52: + Req: + 05 Payload length + .. Mifare UID, four bytes. + .. Block number, 1 or 2. + Resp for Block 1: + 00 Status byte + 10 Payload length (1 block) + .. "SBSD" + 00 00 00 00 + 00 00 00 00 + 00 4E C6 22 + Resp for Block 2: + 00 Status byte + 10 Payload length (1 block) + .. 00 00 00 00 00 00 xx xx + xx xx xx xx xx xx xx xx + Description: + Probably reads blocks 1 and 2 from Mifare sector 0. + Block 0 contains the "vendor information" and UID. + Block 1 contents are unknown, probably AiMe DB info. + Block 2 last 10 bytes hex are printed on the card ("local unique id"). + (Block 3 contains encryption keys so is not allowed to be read) + +LED +--- + +Addr 08 Command 81: + Req: + 03 Payload length + ff Red intensity + ff Green intensity + ff Blue intensity + Resp: + None! Command is not acknowledged + Description: + Set LED color diff --git a/sg-cmd.c b/sg-cmd.c new file mode 100644 index 0000000..84d00f5 --- /dev/null +++ b/sg-cmd.c @@ -0,0 +1,144 @@ +#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) +{ + printf("req: "); + for (uint8_t i = 0; i < req_nbytes; i++) + printf("%02X ", req_bytes[i]); + printf("\n"); + + 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); + + printf("res: "); + for (uint8_t i = 0; i < res_frame->pos; i++) + printf("%02X ", res_frame->bytes[i]); + printf("\n"); + } +} + +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; +}