#include "reader.h" #include #include #include #include #include "util/logging.h" #include "misc/eamuse.h" #include "util/utils.h" #include "structuredmessage.h" static std::vector READER_THREADS; static bool READER_THREAD_RUNNING = false; Reader::Reader(const std::string &port) : port(port) { // open port using an NT path to support COM ports past 9 std::filesystem::path serial_path = fmt::format("\\\\.\\{}", this->port); this->serial_handle = CreateFileW( serial_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); // check if valid this->valid = this->serial_handle != INVALID_HANDLE_VALUE; if (!this->valid) { auto last_error = get_last_error_string(); log_warning("reader", "{}: failed to open serial connection for reader: {}", this->port, last_error); } } Reader::~Reader() { if (this->serial_handle) { CloseHandle(this->serial_handle); log_info("reader", "closed reader on {}", this->port); } } bool Reader::is_valid() { return this->valid; } bool Reader::initialize() { if (!this->set_comm_state(CBR_57600) || !this->wait_for_handshake()) return false; log_info("reader", "{}: card reader connected", this->port); // assign reader ID std::vector set_id_data; set_id_data.push_back(0x00); if (this->msg_write_read(StructuredMessage( 0, this->reinitialized, READER_CMD_SET_ID, this->gen_msg_id(), set_id_data)).empty()) return false; // get version std::vector ret = this->msg_write_cmd_read(READER_CMD_VERSION); if (ret.empty()) return false; // print version info std::vector version_data = ret[ret.size() - 1].get_data(); std::ostringstream model; model << version_data[13] << version_data[14] << version_data[15] << version_data[16]; log_info("reader", "{}: card reader model: {}", this->port, model.str()); std::ostringstream date; date << (const char *) &version_data[17]; log_info("reader", "{}: card reader date: {}", this->port, date.str()); std::ostringstream clock; clock << (const char *) &version_data[33]; log_info("reader", "{}: card reader clock: {}", this->port, clock.str()); // init 2 if (this->msg_write_cmd_read(READER_CMD_INIT2).empty()) return false; // reinitialize this->reinitialized = 1; std::vector reinitialize_data; reinitialize_data.push_back(0x00); if (this->msg_write_cmd_read(READER_CMD_REINITIALIZE, reinitialize_data).empty()) return false; log_info("reader", "{}: card reader init done", this->port); return true; } bool Reader::init_crypt() { // generate game key std::vector gk; for (int i = 0; i < 4; i++) gk.push_back((uint8_t) (rand() % 256)); // reader crypt init std::vector ret = this->msg_write_cmd_read(READER_CMD_KEY_EXCHANGE, gk); if (ret.empty()) return false; // validate message Message msg = ret[ret.size() - 1]; std::vector md = msg.get_data(); if (md.size() != 9) return false; // convert keys to int32 uint32_t game_key = gk[0] << 24 | gk[1] << 16 | gk[2] << 8 | gk[3]; uint32_t reader_key = md[5] << 24 | md[6] << 16 | md[7] << 8 | md[8]; log_info("reader", "{}: reader crypt client key: {}", this->port, bin2hex((char *) &gk[0], 4)); log_info("reader", "{}: reader crypt reader key: {}", this->port, bin2hex((char *) &md[5], 4)); // set crypt keys this->crypt.set_keys(reader_key, game_key); // crypt done log_info("reader", "{}: reader crypt init done", this->port); return true; } bool Reader::read_card() { // read card UID std::vector status_ruid_data; status_ruid_data.push_back(0x00); status_ruid_data.push_back(0x03); status_ruid_data.push_back(0xFF); status_ruid_data.push_back(0xFF); if (this->msg_write_cmd_read(READER_CMD_RFID_READ_UID, status_ruid_data).empty()) { this->valid = false; return false; } Sleep(200); // get reader status std::vector status_req_data; status_req_data.push_back(0x10); std::vector ret = this->msg_write_cmd_read(READER_CMD_GET_STATUS_ENC, status_req_data); if (ret.empty()) { this->valid = false; return false; } // get data Message status_msg = ret[ret.size() - 1]; std::vector status_data = status_msg.get_data(); if (status_data.size() != 23) return false; // decrypt data this->crypt.crypt(&status_data[5], 18); // check CRC uint16_t crc_old = status_data[21] << 8 | status_data[22]; uint16_t crc_new = this->crypt.crc(&status_data[5], 16); if (crc_old != crc_new) { this->valid = false; return false; } // get keypad state this->keypad_started = status_data[16]; this->keypad_state = status_data[19] << 8 | status_data[20]; // check for card input if (status_data[5] == 2) { memcpy(this->card_uid, &status_data[7], 8); return true; } return false; } bool Reader::set_comm_state(DWORD BaudRate) { // settings DCB serial_params{}; serial_params.DCBlength = sizeof(serial_params); if (!GetCommState(this->serial_handle, &serial_params)) { log_warning("reader", "{}: unable to get COM port state: 0x{:x}", this->port, GetLastError()); return false; } serial_params.BaudRate = BaudRate; serial_params.ByteSize = 8; serial_params.StopBits = ONESTOPBIT; serial_params.Parity = NOPARITY; if (!SetCommState(this->serial_handle, &serial_params)) { log_warning("reader", "{}: unable to set COM port state: 0x{:x}", this->port, GetLastError()); return false; } // timeouts COMMTIMEOUTS timeouts{}; timeouts.ReadIntervalTimeout = 30; timeouts.ReadTotalTimeoutConstant = 30; timeouts.ReadTotalTimeoutMultiplier = 5; timeouts.WriteTotalTimeoutConstant = 30; timeouts.WriteTotalTimeoutMultiplier = 5; if (!SetCommTimeouts(this->serial_handle, &timeouts)) { log_warning("reader", "{}: unable to set COM port timeouts: 0x{:x}", this->port, GetLastError()); return false; } return true; } bool Reader::wait_for_handshake() { // baud rates DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 }; // variables DWORD bytes_written = 0; DWORD bytes_read = 0; uint8_t read_buffer[565]; // generate handshake buffer uint8_t handshake_buffer[565]; memset(handshake_buffer, 0, 525); memset(handshake_buffer + 525, 0xAA, 40); // try all the baud rates for (size_t i = 0; i < 4; i++) { this->set_comm_state(baud_rates[i]); // handshake loop for (size_t n = 0; n < 10; n++) { // write handshake if (!WriteFile( this->serial_handle, handshake_buffer, sizeof(handshake_buffer), &bytes_written, nullptr)) { break; } // read handshake bytes_read = 0; if (!ReadFile( this->serial_handle, read_buffer, sizeof(read_buffer), &bytes_read, nullptr)) { break; } // check handshake if (bytes_read > 0 && read_buffer[bytes_read - 1] == 0xAA) { return true; } // sleep Sleep(50); } log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]); } // no handshake on all baud rates log_warning("reader", "{}: no handshake received for any attempted baud rate", this->port); return false; } bool Reader::msg_write(Message msg) { // get message data std::vector msg_encoded = msg.get_data_encoded(); uint8_t chk_sum = msg.chk_sum(); // create write buffer uint8_t write_buffer[512]; DWORD write_buffer_len = 0; // fill write buffer write_buffer[write_buffer_len++] = 0xAA; for (const uint8_t c : msg_encoded) { write_buffer[write_buffer_len++] = c; } // write checksum if (chk_sum == 0xAA || chk_sum == 0xFF) { write_buffer[write_buffer_len++] = 0xFF; write_buffer[write_buffer_len++] = ~chk_sum; } else { write_buffer[write_buffer_len++] = chk_sum; } // write buffer DWORD bytes_written = 0; return WriteFile(this->serial_handle, write_buffer, write_buffer_len, &bytes_written, nullptr) != 0; } std::vector Reader::msg_write_read(Message msg) { this->msg_write(msg); return this->msg_read(); } std::vector Reader::msg_write_cmd_read(uint8_t cmd) { return this->msg_write_cmd_read(cmd, std::vector()); } std::vector Reader::msg_write_cmd_read(uint8_t cmd, std::vector data) { this->msg_write(StructuredMessage( this->node, this->reinitialized, cmd, this->gen_msg_id(), data )); return this->msg_read(); } std::vector Reader::msg_read() { // create buffer std::vector msgs; uint8_t read_buffer[4096]; DWORD read_buffer_len = 0; // read to buffer if (ReadFile( this->serial_handle, read_buffer, sizeof(read_buffer), &read_buffer_len, nullptr) && read_buffer_len > 0) { std::vector msg_data; size_t msg_remaining = 0; bool escape = false; for (size_t i = 0; i < read_buffer_len; i++) { uint8_t b = read_buffer[i]; if (msg_remaining > 0) { // add msg data length if (msg_data.size() < 6 && msg_remaining == 1) msg_remaining += msg_data[4]; if (escape) { // escaped byte b = ~b; msg_data.push_back(b); msg_remaining--; escape = false; } else if (b == 0xAA) { // message start msg_remaining = 6; msg_data.clear(); } else if (b == 0xFF) { // escape escape = true; } else { // normal data msg_data.push_back(b); msg_remaining--; } } else if (b == 0xAA) { // message start msg_remaining = 6; msg_data.clear(); } if (msg_remaining == 0 && msg_data.size() >= 6) { // message done std::vector msg_ext_data; for (size_t n = 0; n < msg_data[4] && n < msg_data.size() - 6; n++) { msg_ext_data.push_back(msg_data[5 + n]); } StructuredMessage msg( msg_data[0], msg_data[1], msg_data[2], msg_data[3], msg_ext_data ); if (msg.chk_sum() == msg_data[msg_data.size() - 1]) { msgs.push_back(msg); } msg_data.clear(); } } } // return message buffer return msgs; } void start_reader_thread(const std::string &port, int id) { READER_THREAD_RUNNING = true; READER_THREADS.push_back(new std::thread([port, id]() { log_info("reader", "{}: starting reader thread", port); while (READER_THREAD_RUNNING) { // create reader Reader reader(port); // check if serial handle is still valid if (!reader.is_valid()) { log_warning("reader", "{}: serial handle no longer valid", port); } else if (!reader.initialize()) { log_warning("reader", "{}: unable to initialize reader", port); } else if (reader.init_crypt()) { // reader loop while (READER_THREAD_RUNNING && reader.is_valid()) { bool did_read_card = reader.read_card(); if (did_read_card) { const uint8_t *uid = reader.get_card_uid(); log_info("reader", "{}: reader input: {}", port, bin2hex(uid, 8)); if (id >= 0) { eamuse_card_insert(id, uid); } else { eamuse_card_insert(GetKeyState(VK_NUMLOCK) & 1, uid); } } if (reader.keypad_started > 0) { if (id >= 0) { eamuse_set_keypad_overrides_reader(id, reader.keypad_state); } else { auto unit = GetKeyState(VK_NUMLOCK) & 1; eamuse_set_keypad_overrides_reader(unit, reader.keypad_state); } } if (did_read_card) { Sleep(2500); } Sleep(20); } } // sleep between reader connection retries if (READER_THREAD_RUNNING) Sleep(5000); } })); // wait for thread to start Sleep(10); } void stop_reader_thread() { // stop threads if (READER_THREAD_RUNNING) { READER_THREAD_RUNNING = false; } // kill threads while (!READER_THREADS.empty()) { delete READER_THREADS.back(); READER_THREADS.pop_back(); } }