From c4c4c2fde0fda8ef4b53f236d0fdf2bf282cbad7 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 18 Nov 2023 18:20:41 +0100 Subject: [PATCH] naomi: club kart card reader support --- core/hw/naomi/card_reader.cpp | 378 +++++++++++++++++++++++++----- core/hw/naomi/card_reader.h | 1 + core/hw/naomi/naomi_cart.cpp | 3 + core/hw/naomi/naomi_roms.cpp | 8 +- core/hw/naomi/naomi_roms_eeprom.h | 12 - 5 files changed, 333 insertions(+), 69 deletions(-) diff --git a/core/hw/naomi/card_reader.cpp b/core/hw/naomi/card_reader.cpp index 928b3b23e7..5b33789eab 100644 --- a/core/hw/naomi/card_reader.cpp +++ b/core/hw/naomi/card_reader.cpp @@ -27,6 +27,67 @@ namespace card_reader { +class CardReaderWriter +{ +public: + virtual ~CardReaderWriter() = default; + + void insertCard() + { + cardInserted = loadCard(); + if (cardInserted) + INFO_LOG(NAOMI, "Card inserted"); + } + +protected: + virtual bool loadCard() = 0; + + bool loadCard(u8 *cardData, u32 len) + { + std::string path = hostfs::getArcadeFlashPath() + ".card"; + FILE *fp = nowide::fopen(path.c_str(), "rb"); + if (fp == nullptr) + return false; + + INFO_LOG(NAOMI, "Loading card file from %s", path.c_str()); + if (fread(cardData, 1, len, fp) != len) + WARN_LOG(NAOMI, "Truncated or empty card file: %s" ,path.c_str()); + fclose(fp); + + return true; + } + + void saveCard(const u8 *cardData, u32 len) + { + std::string path = hostfs::getArcadeFlashPath() + ".card"; + FILE *fp = nowide::fopen(path.c_str(), "wb"); + if (fp == nullptr) + { + WARN_LOG(NAOMI, "Can't create card file %s: errno %d", path.c_str(), errno); + return; + } + INFO_LOG(NAOMI, "Saving card file to %s", path.c_str()); + if (fwrite(cardData, 1, len, fp) != len) + WARN_LOG(NAOMI, "Truncated write to file: %s", path.c_str()); + fclose(fp); + } + + static u8 calcCrc(u8 *data, u32 len) + { + u32 crc = 0; + for (u32 i = 0; i < len; i++) + crc ^= data[i]; + return crc; + } + + bool cardInserted = false; + + static constexpr u8 STX = 2; + static constexpr u8 ETX = 3; + static constexpr u8 ENQ = 5; + static constexpr u8 ACK = 6; +}; + /* Sanwa CRP-1231BR-10 card reader/writer protocol (from my good friend Metallic) used in InitialD and Derby Owners Club @@ -64,12 +125,12 @@ namespace card_reader { Copyright (C) 2022-2023 tugpoat (https://github.com/tugpoat) */ -class SanwaCRP1231BR : public SerialPort::Pipe +class SanwaCRP1231BR : public CardReaderWriter, public SerialPort::Pipe { public: void write(u8 b) override { - if (inBufferIdx == 0 && b == 5) + if (inBufferIdx == 0 && b == ENQ) { DEBUG_LOG(NAOMI, "Received RQ(5)"); handleCommand(); @@ -78,7 +139,7 @@ class SanwaCRP1231BR : public SerialPort::Pipe inBuffer[inBufferIdx++] = b; if (inBufferIdx >= 3) { - if (inBuffer[0] != 2) + if (inBuffer[0] != STX) { INFO_LOG(NAOMI, "Unexpected cmd start byte %x", inBuffer[0]); inBufferIdx = 0; @@ -102,7 +163,7 @@ class SanwaCRP1231BR : public SerialPort::Pipe return; } DEBUG_LOG(NAOMI, "Received cmd %x len %d", inBuffer[2], inBuffer[1]); - outBuffer[outBufferLen++] = 6; // ACK + outBuffer[outBufferLen++] = ACK; rxCommandLen = inBufferIdx - 3; memcpy(rxCommand, inBuffer + 2, rxCommandLen); inBufferIdx = 0; @@ -123,13 +184,6 @@ class SanwaCRP1231BR : public SerialPort::Pipe return outBufferLen - outBufferIdx; } - void insertCard() - { - cardInserted = loadCard(cardData, sizeof(cardData)); - if (cardInserted) - INFO_LOG(NAOMI, "Card inserted"); - } - protected: enum Commands { @@ -149,6 +203,10 @@ class SanwaCRP1231BR : public SerialPort::Pipe CARD_NEW = 0xB0, }; + bool loadCard() override { + return CardReaderWriter::loadCard(cardData, sizeof(cardData)); + } + virtual u8 getStatus1() { return ((doorOpen ? 2 : 1) << 6) | 0x20 | (cardInserted ? 0x18 : 0); @@ -158,7 +216,7 @@ class SanwaCRP1231BR : public SerialPort::Pipe { if (rxCommandLen == 0) return; - outBuffer[outBufferLen++] = 2; + outBuffer[outBufferLen++] = STX; u32 crcIdx = outBufferLen; u8 status1 = getStatus1(); u8 status2 = '0'; @@ -242,7 +300,7 @@ class SanwaCRP1231BR : public SerialPort::Pipe WARN_LOG(NAOMI, "Unknown command %x", rxCommand[0]); break; } - outBuffer[outBufferLen++] = 6; + outBuffer[outBufferLen++] = ACK; outBuffer[outBufferLen++] = rxCommand[0]; outBuffer[outBufferLen++] = status1; outBuffer[outBufferLen++] = status2; @@ -286,49 +344,11 @@ class SanwaCRP1231BR : public SerialPort::Pipe outBufferLen += size; outBuffer[crcIdx] += size; } - outBuffer[outBufferLen++] = 3; + outBuffer[outBufferLen++] = ETX; outBuffer[outBufferLen] = calcCrc(&outBuffer[crcIdx], outBufferLen - crcIdx); outBufferLen++; } - u8 calcCrc(u8 *data, u32 len) - { - u32 crc = 0; - for (u32 i = 0; i < len; i++) - crc ^= data[i]; - return crc; - } - - bool loadCard(u8 *cardData, u32 len) - { - std::string path = hostfs::getArcadeFlashPath() + ".card"; - FILE *fp = nowide::fopen(path.c_str(), "rb"); - if (fp == nullptr) - return false; - - DEBUG_LOG(NAOMI, "Loading card file from %s", path.c_str()); - if (fread(cardData, 1, len, fp) != len) - WARN_LOG(NAOMI, "Truncated or empty card file: %s" ,path.c_str()); - fclose(fp); - - return true; - } - - void saveCard(const u8 *cardData, u32 len) - { - std::string path = hostfs::getArcadeFlashPath() + ".card"; - FILE *fp = nowide::fopen(path.c_str(), "wb"); - if (fp == nullptr) - { - WARN_LOG(NAOMI, "Can't create card file %s: errno %d", path.c_str(), errno); - return; - } - DEBUG_LOG(NAOMI, "Saving card file to %s", path.c_str()); - if (fwrite(cardData, 1, len, fp) != len) - WARN_LOG(NAOMI, "Truncated write to file: %s", path.c_str()); - fclose(fp); - } - u8 inBuffer[256]; u32 inBufferIdx = 0; @@ -396,7 +416,254 @@ class DerbyLRCardReader final : public SanwaCRP1231LR } }; -static std::unique_ptr cardReader; +/* + Club Kart - Sanwa CR-1231R + +>>> SEND CMD: [START 02][CMD char1][CMD char2]{parameter char}{data}[STOP 03][CRC] +<<< RECV ACK: [OK 06] or [ERR 15] +<<< RECV STX: [START 02]{REPLY}{data}[STOP 03][CRC] + note: it seems reply packet sent only after command fully completed or error happened + + REPLY: 2chars + OK - RESULT_OK + O1 - RESULT_CANCEL_INSERT + N0 - ERROR_CONNECT / Unknown Error + N1 - ERROR_COMMAND / Connection Error + N2 - ERROR_MOTOR / Mechanic Error 1 + N3 - ERROR_HEAD_UPDOWN / Mechanic Error 2 + N4 - ERROR_CARD_STUCK / Card Stuffed + N5 - ERROR_VERIFY / OK ???? + N6 - ERROR_HEAD_TEMP / Mechanic Error 3 + N7 - ERROR_CARD_EMPTY / Card Empty + N8 - ERROR_CARD_LOAD / Draw Card Error + N9 - ERROR_NO_HOPPER / Card Empty + NA - ERROR_CARD_PRESENT + NB - ERROR_CARD_EJECT + NC - ERROR_CANT_CANCEL + ND - ERROR_NOT_INSERT + NE - ERROR_NOT_WAIT + NF - ERROR_BAD_CARD + + CMD SS REPLY: 6 ASCII characters + 5chars '0'/'1' - Card Sensors Status, MSB first + 0 + 10 18 + 1C C E 8 7 3 + other + 1char '0'/'1' - Dispenser Status + '0' - Empty + '1' - Full + +Commands: +IN - init +CA - cancel command +OT0 - eject card +HI - get new card from dispenser +CL - cleaning +RT5 - unknown +RL - read data/load card into reader +WL - write data (followed by 69 bytes of data) +SS - get status + +reply for commands is simple +06 +02 'O' 'K' 03 crc +except +- RL command, which have 69 bytes of card data after OK (or no any reply if no card insterted) +- SS command, which is +06 +02 '0/1' '0/1' '0/1' '0/1' '0/1' '0/1' 03 crc +there 0/1 - encoded in char binary value described earlier + */ +class ClubKartCardReader : public CardReaderWriter, SerialPort::Pipe +{ +public: + ClubKartCardReader() { + SCIFSerialPort::Instance().setPipe(this); + } + ~ClubKartCardReader() { + SCIFSerialPort::Instance().setPipe(nullptr); + } + + void write(u8 data) override + { + inBuffer[inBufferIdx++] = data; + if (inBufferIdx == 5) + { + if ((inBuffer[1] != 'W' || inBuffer[2] != 'L') && inBuffer[2] != 'T') + { + handleCommand(); + inBufferIdx = 0; + } + } + else if (inBufferIdx == 6 && inBuffer[2] == 'T') // OT0, RT5 + { + handleCommand(); + inBufferIdx = 0; + } + else if (inBufferIdx == TRACK_SIZE + 5) // WL + { + handleCommand(); + inBufferIdx = 0; + } + } + + int available() override { + return outBufferLen - outBufferIdx; + } + + u8 read() override + { + if (outBufferIdx >= outBufferLen) + return 0; + u8 b = outBuffer[outBufferIdx++]; + if (outBufferIdx == outBufferLen) + outBufferIdx = outBufferLen = 0; + return b; + } + +private: + enum Commands { + CARD_INIT, + CARD_CANCEL_CMD, + CARD_EJECT, + CARD_NEW, + CARD_CLEAN, + CARD_RT5, + CARD_READ, + CARD_WRITE, + CARD_STATUS, + + CARD_MAX + }; + static const u8 CommandBytes[][2]; + + bool loadCard() override + { + bool rc = CardReaderWriter::loadCard(cardData, sizeof(cardData)); + if (rc && readPending) + { + sendReply(CARD_READ); + readPending = false; + } + return rc; + } + + void handleCommand() + { + readPending = false; + int cmd; + for (cmd = 0; cmd < CARD_MAX; cmd++) + if (inBuffer[1] == CommandBytes[cmd][0] && inBuffer[2] == CommandBytes[cmd][1]) + break; + if (cmd == CARD_MAX) + { + WARN_LOG(NAOMI, "Unhandled command '%c%c'", inBuffer[1], inBuffer[2]); + return; + } + u32 crc = calcCrc(&inBuffer[1], inBufferIdx - 2); + if (crc != inBuffer[inBufferIdx - 1]) + { + WARN_LOG(NAOMI, "Wrong crc: expected %x got %x", crc, inBuffer[inBufferIdx - 1]); + return; + } + outBuffer[outBufferLen++] = ACK; + switch (cmd) + { + case CARD_WRITE: + INFO_LOG(NAOMI, "Card write"); + memcpy(cardData, &inBuffer[3], sizeof(cardData)); + saveCard(cardData, sizeof(cardData)); + break; + case CARD_READ: + INFO_LOG(NAOMI, "Card read"); + if (!cardInserted) { + readPending = true; + return; + } + break; + case CARD_EJECT: + INFO_LOG(NAOMI, "Card eject"); + cardInserted = false; + break; + case CARD_NEW: + INFO_LOG(NAOMI, "New card"); + cardInserted = true; + break; + case CARD_INIT: + DEBUG_LOG(NAOMI, "Card init"); + break; + case CARD_CANCEL_CMD: + DEBUG_LOG(NAOMI, "Cancel cmd"); + break; + case CARD_CLEAN: + DEBUG_LOG(NAOMI, "Card clean"); + break; + case CARD_RT5: + DEBUG_LOG(NAOMI, "Card RT5"); + break; + case CARD_STATUS: + DEBUG_LOG(NAOMI, "Card status (cardInserted %d)", cardInserted); + break; + } + sendReply(cmd); + } + + void sendReply(int cmd) + { + outBuffer[outBufferLen++] = STX; + u32 crcIndex = outBufferLen; + if (cmd == CARD_STATUS) + { + outBuffer[outBufferLen++] = '0'; + outBuffer[outBufferLen++] = '0'; + outBuffer[outBufferLen++] = '0'; + outBuffer[outBufferLen++] = cardInserted ? '1' : '0'; + outBuffer[outBufferLen++] = cardInserted ? '1' : '0'; + outBuffer[outBufferLen++] = '1'; // dispenser full + } + else + { + outBuffer[outBufferLen++] = 'O'; + outBuffer[outBufferLen++] = 'K'; + if (cmd == CARD_READ) + { + memcpy(&outBuffer[outBufferLen], cardData, sizeof(cardData)); + outBufferLen += sizeof(cardData); + } + } + outBuffer[outBufferLen++] = ETX; + outBuffer[outBufferLen] = calcCrc(&outBuffer[crcIndex], outBufferLen - crcIndex); + outBufferLen++; + } + + static constexpr u32 TRACK_SIZE = 0x45; + u8 cardData[TRACK_SIZE]; + + u8 inBuffer[256]; + u32 inBufferIdx = 0; + + u8 outBuffer[256]; + u32 outBufferIdx = 0; + u32 outBufferLen = 0; + + bool readPending = false; +}; + +const u8 ClubKartCardReader::CommandBytes[][2] +{ + { 'I', 'N' }, // init + { 'C', 'A' }, // cancel command + { 'O', 'T' }, // ...0 - eject card + { 'H', 'I' }, // get new card from dispenser + { 'C', 'L' }, // cleaning + { 'R', 'T' }, // ...5 - unknown + { 'R', 'L' }, // read data/load card into reader + { 'W', 'L' }, // write data (followed by 69 bytes of data) + { 'S', 'S' }, // get status +}; + +static std::unique_ptr cardReader; void initdInit() { term(); @@ -412,6 +679,11 @@ void derbyInit() cardReader = std::make_unique(); } +void clubkInit() { + term(); + cardReader = std::make_unique(); +} + void term() { cardReader.reset(); } diff --git a/core/hw/naomi/card_reader.h b/core/hw/naomi/card_reader.h index 24707f683a..e74b4e3cb0 100644 --- a/core/hw/naomi/card_reader.h +++ b/core/hw/naomi/card_reader.h @@ -23,6 +23,7 @@ namespace card_reader { void initdInit(); void derbyInit(); +void clubkInit(); void term(); void barcodeInit(); diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp index 8333856cdd..8fa96f88f5 100644 --- a/core/hw/naomi/naomi_cart.cpp +++ b/core/hw/naomi/naomi_cart.cpp @@ -660,6 +660,9 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo { if (settings.naomi.drivingSimSlave == 0) initMidiForceFeedback(); + if (romName == "clubkrt" || romName == "clubkrto" + || romName == "clubkrta" || romName == "clubkrtc") + card_reader::clubkInit(); } else if (gameId == "POKASUKA GHOST (JAPANESE)" // Manic Panic Ghosts || gameId == "TOUCH DE ZUNO (JAPAN)") diff --git a/core/hw/naomi/naomi_roms.cpp b/core/hw/naomi/naomi_roms.cpp index e64e859fcf..f359f2128c 100644 --- a/core/hw/naomi/naomi_roms.cpp +++ b/core/hw/naomi/naomi_roms.cpp @@ -7314,7 +7314,7 @@ const Game Games[] = }, nullptr, &clubkart_inputs, - clubkrt_eeprom_dump, + nullptr, }, { "clubkrta", @@ -7341,7 +7341,7 @@ const Game Games[] = }, nullptr, &clubkart_inputs, - clubkrt_eeprom_dump, + nullptr, }, { "clubkrtc", @@ -7368,7 +7368,7 @@ const Game Games[] = }, nullptr, &clubkart_inputs, - clubkrt_eeprom_dump, + nullptr, }, { "clubkrt", @@ -7395,7 +7395,7 @@ const Game Games[] = }, nullptr, &clubkart_inputs, - clubkrt_eeprom_dump, + nullptr, }, { "clubkprz", diff --git a/core/hw/naomi/naomi_roms_eeprom.h b/core/hw/naomi/naomi_roms_eeprom.h index 1f6f0f3ae9..5882c1e2ef 100644 --- a/core/hw/naomi/naomi_roms_eeprom.h +++ b/core/hw/naomi/naomi_roms_eeprom.h @@ -583,18 +583,6 @@ static u8 tduno2_eeprom_dump[] { 0x63, 0x46, }; -// card reader disabled -static u8 clubkrt_eeprom_dump[] { - 0x95, 0x95, 0x10, 0x42, 0x44, 0x42, 0x31, 0x09, 0x00, 0x1A, 0x01, 0x01, 0x01, 0x00, 0x11, 0x11, 0x11, 0x11, - 0x95, 0x95, 0x10, 0x42, 0x44, 0x42, 0x31, 0x09, 0x00, 0x1A, 0x01, 0x01, 0x01, 0x00, 0x11, 0x11, 0x11, 0x11, - 0x0C, 0xBA, 0x20, 0x20, 0x0C, 0xBA, 0x20, 0x20, 0x25, 0x52, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x30, 0xC0, 0x30, 0xC0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x25, 0x52, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xC0, 0x30, - 0xC0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, -}; - // card reader disabled static u8 clubk2k3_eeprom_dump[] { 0x73, 0x25, 0x10, 0x42, 0x48, 0x4C, 0x30, 0x09, 0x00, 0x1A, 0x01, 0x01, 0x01, 0x00, 0x11, 0x11, 0x11, 0x11,