/* Copyright 2023 flyinghead This file is part of Flycast. Flycast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Flycast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ // based on mame code: // license:BSD-3-Clause // copyright-holders:David Haywood, MetalliC #include "systemsp.h" #include "naomi_cart.h" #include "hw/flashrom/nvmem.h" #include "network/ggpo.h" #include "hw/maple/maple_cfg.h" #include "input/gamepad.h" #include "hw/sh4/sh4_sched.h" #include "hw/holly/holly_intc.h" #include "hw/holly/sb.h" #include "oslib/storage.h" #include "oslib/oslib.h" #include "cfg/option.h" #include "card_reader.h" #include "naomi_roms.h" #include "stdclass.h" #include "hw/mem/addrspace.h" #include #include #ifdef DEBUG_SERIAL #define SERIAL_LOG(...) DEBUG_LOG(NAOMI, __VA_ARGS__) #else #define SERIAL_LOG(...) #endif #ifdef DEBUG_FLASH #define FLASH_LOG(...) DEBUG_LOG(FLASHROM, __VA_ARGS__) #else #define FLASH_LOG(...) #endif #ifdef DEBUG_ATA #define ATA_LOG(...) DEBUG_LOG(NAOMI, __VA_ARGS__) #else #define ATA_LOG(...) #endif #ifdef DEBUG_IO #define IO_LOG(...) DEBUG_LOG(NAOMI, __VA_ARGS__) #else #define IO_LOG(...) #endif namespace systemsp { SystemSpCart *SystemSpCart::Instance; constexpr u8 DINOKING_CHIP_DATA[128] = { 0x12, 0x34, 0x56, 0x78, // Serial No.0 0x31, 0x00, 0x86, 0x07, // Serial No.1 0x00, 0x00, 0x00, 0x00, // Key 0x05, 0x40, 0x00, 0xAA, // Extend Extend Access Mode 0x23, 0xFF, 0xFF, 0xFF, // Counter4 Counter3 Counter2 Counter1 0x00, 0x00, 0x00, 0x00, // User Data (first set date: day bits 0-4, month bits 5-8, year bits 9-... + 2000) 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x23, 0xFF, 0xFF, 0xFF, // User Data (max counters) }; constexpr u8 LOVEBERRY_CHIP_DATA[128] = { 0x10, 0xc5, 0x10, 0x96, // Serial No.0 0xab, 0x2f, 0xf1, 0x74, // Serial No.1 0x99, 0x33, 0x78, 0xa7, // Key 0x05, 0x77, 0x00, 0xAA, // Extend Extend Access Mode 0x23, 0xFF, 0xFF, 0xFF, // Counter4 Counter3 Counter2 Counter1 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x00, 0x00, 0x00, 0x00, // User Data 0x23, 0xFF, 0xFF, 0xFF, // User Data }; // // RS232C I/F board (838-14244) connected to RFID Chip R/W board (838-14243 aka Maxell Picochet ME-MR23) // class RfidReaderWriter : public SerialPort::Pipe { enum Response : u8 { OK = 0xa, RETRY = 0xca, NG = 0x3a, NOT = 0x6a, }; enum Command : u8 { REQ = 0x4e, SEL = 0x2e, READ = 0xe, WRITE = 0xce, WRITE_NOP = 0x8e, WRITE_ST = 0xee, HALT = 0xae, COUNT = 0x6e, RESET = 0x7e, TEST = 0x5e, CHANGE = 0x3e, }; static constexpr u8 TEST_CODE = 0; static constexpr u8 RF_OFF = 0x80; public: RfidReaderWriter(SerialPort *port, int index, const std::string& gameName) : port(port), index(index), gameName(gameName) { port->setPipe(this); FILE *f = nowide::fopen(getCardDataPath().c_str(), "rb"); if (f != nullptr) { if (fread(cardData.data(), 1, cardData.size(), f) != cardData.size()) WARN_LOG(NAOMI, "Rfid card %d: truncated read", index); fclose(f); } else { makeNewCard(); } } void write(u8 v) override { if (expectedBytes > 0) { SERIAL_LOG("UART%d write data out: %02x", index, v); recvBuffer.push_back(v); if (recvBuffer.size() < expectedBytes) return; switch (recvBuffer[0]) { case TEST: if (recvBuffer[1] == RF_OFF) { state = Off; toSend.push_back(OK); SERIAL_LOG("UART%d state Off", index); } else if (state == Off) { state = Connecting; toSend.push_back(NOT); SERIAL_LOG("UART%d state Connecting", index); } else toSend.push_back(OK); break; case SEL: if ((state == Connected || state == FullRead) && (recvBuffer[5] != 0 || recvBuffer[6] != 0 || recvBuffer[7] != 0 || recvBuffer[8] != 0)) state = FullRead; else state = Connected; toSend.push_back(OK); break; case COUNT: { u8 newVal = 0; switch (recvBuffer[1]) { case 0xc0: newVal = --cardData[16]; break; case 0x30: newVal = --cardData[17]; break; case 0x0c: newVal = --cardData[18]; break; case 0x03: newVal = --cardData[19]; if (newVal == 0) makeNewCard(); break; } toSend.push_back(OK); toSend.push_back(newVal); saveData(); SERIAL_LOG("UART%d COUNT %x -> %x", index, recvBuffer[1], newVal); break; } case WRITE: memcpy(&cardData[4 * rowCounter], &recvBuffer[1], 4); saveData(); SERIAL_LOG("UART%d card written @ row %d", index, rowCounter); toSend.push_back(OK); break; case WRITE_NOP: case WRITE_ST: case CHANGE: case HALT: toSend.push_back(OK); break; default: SERIAL_LOG("UART%d unhandled cmd %x (len %d)", index, recvBuffer[0], expectedBytes); break; } expectedBytes = 0; } else { switch (v) { case TEST: SERIAL_LOG("UART%d cmd TEST", index); expect(v, 2); break; case CHANGE: // change baud rate SERIAL_LOG("UART%d cmd CHANGE", index); expect(v, 1); break; case RESET: SERIAL_LOG("UART%d cmd RESET", index); state = Off; toSend.push_back(OK); break; case HALT: SERIAL_LOG("UART%d cmd HALT", index); expect(v, 4); // ser0 break; case READ: SERIAL_LOG("UART%d cmd READ", index); toSend.push_back(OK); if (state == FullRead) { toSend.insert(toSend.end(), cardData.begin(), cardData.end()); } else { toSend.insert(toSend.end(), cardData.begin(), cardData.begin() + 8); static const u8 maskedData[120] = { 0, 0, 0, 0, 0, 0, 0, 0xaa }; toSend.insert(toSend.end(), std::begin(maskedData), std::end(maskedData)); } break; case SEL: SERIAL_LOG("UART%d cmd SEL", index); expect(v, 8); // ser0 key (key is calc'ed from ser1) break; case REQ: SERIAL_LOG("UART%d cmd REQ", index); if (state == Connecting) { state = Connected; toSend.push_back(RETRY); SERIAL_LOG("UART%d state Connected", index); } else if (state == Connected || state == FullRead) { toSend.push_back(OK); toSend.insert(toSend.end(), &cardData[0], &cardData[4]); // ser0 SERIAL_LOG("UART%d exec REQ", index); } else toSend.push_back(RETRY); break; case COUNT: SERIAL_LOG("UART%d cmd COUNT", index); expect(v, 1); break; case WRITE: SERIAL_LOG("UART%d cmd WRITE", index); rowCounter++; expect(v, 4); break; case WRITE_ST: case WRITE_NOP: SERIAL_LOG("UART%d cmd %s", index, v == WRITE_ST ? "WRITE_ST" : "WRITE_NOP"); if (v == WRITE_ST) rowCounter = 0; else rowCounter++; expect(v, 4); break; default: INFO_LOG(NAOMI, "UART%d write data out: unknown cmd %x", index, v); toSend.push_back(NG); break; } } port->updateStatus(); } int available() override { return toSend.size(); } u8 read() override { u8 b = 0; if (!toSend.empty()) { b = toSend.front(); toSend.pop_front(); } if (toSend.empty()) port->updateStatus(); return b; } void serialize(Serializer& ser) const override { ser << (u32)toSend.size(); for (u8 b : toSend) ser << b; ser << expectedBytes; ser << (u32)recvBuffer.size(); ser.serialize(recvBuffer.data(), recvBuffer.size()); ser << state; ser << rowCounter; ser.serialize(cardData.data(), cardData.size()); } void deserialize(Deserializer& deser) override { u32 size; deser >> size; toSend.resize(size); for (u32 i = 0; i < size; i++) deser >> toSend[i]; deser >> expectedBytes; deser >> size; recvBuffer.resize(size); deser.deserialize(recvBuffer.data(), recvBuffer.size()); if (deser.version() >= Deserializer::V41) { deser >> state; deser >> rowCounter; deser.deserialize(cardData.data(), cardData.size()); } else { state = Off; rowCounter = 0; } } private: void expect(u8 cmd, int bytes) { expectedBytes = bytes + 1; recvBuffer.clear(); recvBuffer.push_back(cmd); } void makeNewCard() { INFO_LOG(NAOMI, "Creating new RFID card"); if (gameName.substr(0, 6) == "dinoki") { memcpy(&cardData[0], &DINOKING_CHIP_DATA[0], sizeof(DINOKING_CHIP_DATA)); for (int i = 0; i < 4; i++) cardData[i] = rand() & 0xff; u32 ser1 = ((rand() & 0xffff) << 16) | (rand() & 0xffff); ser1 &= ~0xc0000000; if (config::Region == 1) // USA (dinoking, dinokior) ser1 |= 0x40000000; ser1 = dinoShuffle(ser1); cardData[4] = ser1 >> 24; cardData[5] = ser1 >> 16; cardData[6] = ser1 >> 8; cardData[7] = ser1; } else { memcpy(&cardData[0], &LOVEBERRY_CHIP_DATA[0], sizeof(LOVEBERRY_CHIP_DATA)); for (int i = 0; i < 4; i++) cardData[i] = rand() & 0xff; cardData[5] = rand() & 0xff; cardData[7] = rand() & 0xff; u32 ser1 = (cardData[4] << 24) | (cardData[5] << 16) | (cardData[6] << 8) | cardData[7]; if (config::Region == 2) // Export { ser1 = loveberyDeobfuscate(ser1); ser1 &= ~0xc0000000; ser1 = loveberyObfuscate(ser1); cardData[4] = ser1 >> 24; cardData[5] = ser1 >> 16; cardData[6] = ser1 >> 8; cardData[7] = ser1; } ser1 ^= 0x321c89d3; cardData[8] = ser1 >> 24; cardData[9] = ser1 >> 16; cardData[10] = ser1 >> 8; cardData[11] = ser1; } } std::string getCardDataPath() { std::string path = hostfs::getArcadeFlashPath(); switch (config::Region) { case 0: path += "-jp"; break; case 1: path += "-us"; break; default: path += "-exp"; break; } path += ".rfid" + std::to_string(index); return path; } void saveData() { FILE *f = nowide::fopen(getCardDataPath().c_str(), "wb"); if (f == nullptr) { WARN_LOG(NAOMI, "Can't save RFID card: error %x", errno); return; } fwrite(cardData.data(), 1, cardData.size(), f); fclose(f); } u32 dinoShuffle(u32 v) { return ((v & 1) << 22) | ((v & 2) << 28) | ((v & 4) << 1) | ((v & 8) << 3) | ((v & 0x10) << 21) | ((v & 0x20) << 10) | ((v & 0x40) << 1) | ((v & 0x80) >> 7) | ((v & 0x100) << 22) | ((v & 0x200) >> 1) | ((v & 0x400) >> 1) | ((v & 0x800) << 17) | ((v & 0x1000) << 4) | ((v & 0x2000) << 18) | ((v & 0x4000) >> 4) | ((v & 0x8000) >> 13) | ((v & 0x10000) >> 15) | ((v & 0x20000) >> 4) | ((v & 0x40000) << 1) | ((v & 0x80000) >> 8) | ((v & 0x100000) >> 6) | ((v & 0x200000) << 2) | ((v & 0x400000) >> 18) | ((v & 0x800000) >> 2) | ((v & 0x1000000) >> 12) | ((v & 0x2000000) >> 7) | ((v & 0x4000000) << 1) | ((v & 0x8000000) >> 3) | ((v & 0x10000000) >> 11) | ((v & 0x20000000) >> 9) | ((v & 0x40000000) >> 25) | ((v & 0x80000000) >> 5); } u32 dinoUnshuffle(u32 v) { return ((v & 1) << 7) | ((v & 2) << 15) | ((v & 4) << 13) | ((v & 8) >> 1) | ((v & 0x10) << 18) | ((v & 0x20) << 25) | ((v & 0x40) >> 3) | ((v & 0x80) >> 1) | ((v & 0x100) << 1) | ((v & 0x200) << 1) | ((v & 0x400) << 4) | ((v & 0x800) << 8) | ((v & 0x1000) << 12) | ((v & 0x2000) << 4) | ((v & 0x4000) << 6) | ((v & 0x8000) >> 10) | ((v & 0x10000) >> 4) | ((v & 0x20000) << 11) | ((v & 0x40000) << 7) | ((v & 0x80000) >> 1) | ((v & 0x100000) << 9) | ((v & 0x200000) << 2) | ((v & 0x400000) >> 22) | ((v & 0x800000) >> 2) | ((v & 0x1000000) << 3) | ((v & 0x2000000) >> 21) | ((v & 0x4000000) << 5) | ((v & 0x8000000) >> 1) | ((v & 0x10000000) >> 17) | ((v & 0x20000000) >> 28) | ((v & 0x40000000) >> 22) | ((v & 0x80000000) >> 18); } u32 loveberyDeobfuscate(u32 v) { v ^= 0xa35ec5e3; // 87654321 -> 34781256 return ((v & 0xf00000) >> 20) | ((v & 0xf0000) >> 12) | ((v & 0xf0) << 4) | ((v & 0xf) << 12) | ((v & 0xf0000000) >> 12) | ((v & 0xf000000) >> 4) | ((v & 0xf000) << 12) | ((v & 0xf00) << 20); } u32 loveberyObfuscate(u32 v) { v = ((v & 0xf) << 20) | ((v & 0xf0) << 12) | ((v & 0xf00) >> 4) | ((v & 0xf000) >> 12) | ((v & 0xf0000) << 12) | ((v & 0xf00000) << 4) | ((v & 0xf000000) >> 12) | ((v & 0xf0000000) >> 20); return v ^ 0xa35ec5e3; } SerialPort *port; const int index; const std::string gameName; std::deque toSend; std::array cardData; u8 expectedBytes = 0; std::vector recvBuffer; enum State { Off, Connecting, Connected, FullRead } state = Off; int rowCounter = 0; }; // // Isshoni Wanwan Waiwai Puppy touchscreen // class Touchscreen : public SerialPort::Pipe { public: Touchscreen(SerialPort *port) : port(port) { port->setPipe(this); schedId = sh4_sched_register(0, schedCallback, this); } ~Touchscreen() override { sh4_sched_unregister(schedId); } void write(u8 v) override { if (v == '\r') { if (recvBuffer.size() >= 2 && recvBuffer[0] == 1) { toSend.push_back(1); if (recvBuffer.size() == 3 && recvBuffer[1] == 'O' && recvBuffer[2] == 'I') { SERIAL_LOG("Received cmd OI: get name"); toSend.push_back('A'); toSend.push_back('3'); toSend.push_back('0'); toSend.push_back('9'); toSend.push_back('9'); toSend.push_back('9'); } else if (recvBuffer.size() == 3 && recvBuffer[1] == 'N' && recvBuffer[2] == 'M') { SERIAL_LOG("Received cmd NM: unit verify"); const std::array id { 'E','X','I','I','-','7','7','2','0','S','C',' ','R','e','v',' ','3','.','0' }; toSend.insert(toSend.end(), id.begin(), id.end()); } else if (recvBuffer.size() == 3 && recvBuffer[1] == 'U' && recvBuffer[2] == 'V') { SERIAL_LOG("Received cmd UV: reset"); const std::array resp { 'Q','M','V','*','*','*','0','0' }; toSend.insert(toSend.end(), resp.begin(), resp.end()); } else if (recvBuffer.size() == 2 && recvBuffer[1] == 'R') { SERIAL_LOG("Received cmd R"); toSend.push_back('0'); sh4_sched_request(schedId, SCHED_CYCLES); } else { SERIAL_LOG("Received cmd %c", recvBuffer[1]); toSend.push_back('0'); } toSend.push_back('\r'); port->updateStatus(); // FIXME if (recvBuffer.size() == 2 && recvBuffer[1] == 'Z') sendPosition(0); } else { WARN_LOG(NAOMI, "\\r ignored. buf size %d", (int)recvBuffer.size()); } recvBuffer.clear(); } else { if (recvBuffer.size() == 9) { if (!memcmp(&recvBuffer[0], "Ua0000000", 9)) { SERIAL_LOG("UART receive Ua...%c", v); sendPosition(1); } else WARN_LOG(NAOMI, "Unknown command %.9s", &recvBuffer[0]); recvBuffer.clear(); } else { recvBuffer.push_back(v); } } } int available() override { return toSend.size(); } u8 read() override { u8 data = 0; if (!toSend.empty()) { data = toSend.front(); toSend.pop_front(); } if (toSend.empty()) port->updateStatus(); SERIAL_LOG("UART read data %x", data); return data; } void serialize(Serializer& ser) const override { ser << (u32)toSend.size(); for (u8 b : toSend) ser << b; ser << (u32)recvBuffer.size(); ser.serialize(recvBuffer.data(), recvBuffer.size()); } void deserialize(Deserializer& deser) override { u32 size; deser >> size; toSend.resize(size); for (u32 i = 0; i < size; i++) deser >> toSend[i]; deser >> size; recvBuffer.resize(size); deser.deserialize(recvBuffer.data(), recvBuffer.size()); } private: void sendPosition(int type) { MapleInputState input[4]; ggpo::getInput(input); // 0-1023 ? const u32 x = (640 - input[0].absPos.x) * 1023 / 639; const u32 y = input[0].absPos.y * 1023 / 479; size_t start = toSend.size(); if (type == 1) { toSend.push_back('U'); toSend.push_back('T'); toSend.push_back(0x20); // bit 0 and 1 are checked toSend.push_back(x & 0xff); toSend.push_back((x >> 8) & 0x1f); toSend.push_back(y & 0xff); toSend.push_back((y >> 8) & 0x1f); toSend.push_back(0); // z pos u8 crc = 0xaa; for (; start < toSend.size(); start++) crc += toSend[start]; toSend.push_back(crc); port->updateStatus(); } else { bool button = (input[0].kcode & DC_BTN_A) == 0; if (button != lastButton || x != lastPosX || y != lastPosY) { // bit 6 is touch down if (button) toSend.push_back(0xc0); else toSend.push_back(0x80); toSend.push_back((x & 7) << 4); toSend.push_back((x >> 3) & 0x7f); toSend.push_back((y & 7) << 4); toSend.push_back((y >> 3) & 0x7f); lastButton = button; lastPosX = x; lastPosY = y; port->updateStatus(); } } } static int schedCallback(int tag, int cycles, int jitter, void *p) { ((Touchscreen *)p)->sendPosition(0); return SCHED_CYCLES; } SerialPort *port; std::deque toSend; std::vector recvBuffer; int schedId = 0; u32 lastPosX = ~0; u32 lastPosY = ~0; bool lastButton = false; static constexpr u32 SCHED_CYCLES = SH4_MAIN_CLOCK / 60; }; u8 SerialPort::readReg(u32 addr) { switch ((addr & 0x3f) / 4) { case 0: // data in if (pipe != nullptr) return pipe->read(); else return 0; case 1: // out buffer len //SERIAL_LOG("UART%d read out buf len", index); return 0; case 2: // in buffer len //SERIAL_LOG("UART%d read in buf len %d", index, (int)toSend.size()); if (pipe != nullptr) return pipe->available(); else return 0; case 3: // errors? SERIAL_LOG("UART%d read errors", index); return 0; case 4: // unknown SERIAL_LOG("UART%d read reg4", index); return 0; case 5: // flow control SERIAL_LOG("UART%d read flow control", index); return 0; case 6: // status. bit 3: receive buffer not empty SERIAL_LOG("UART%d read status", index); if (pipe != nullptr && pipe->available() > 0) return 8; else return 0; case 7: // interrupt status/mask? SERIAL_LOG("UART%d read interrupt mask/status?", index); return 0; case 8: // unknown SERIAL_LOG("UART%d read reg8", index); return 0; case 9: // control? SERIAL_LOG("UART%d read control?", index); return 0; case 10: // baudrate (lsb) SERIAL_LOG("UART%d read baudrate(lsb)", index); return 0; case 11: // baudrate (msb) SERIAL_LOG("UART%d read baudrate(msb)", index); return 0; default: INFO_LOG(NAOMI, "Unknown UART%d port %x\n", index, addr); return 0; } } void SerialPort::writeReg(u32 addr, u8 v) { switch ((addr & 0x3f) / 4) { case 0: // data out if (pipe != nullptr) pipe->write(v); else INFO_LOG(NAOMI, "UART%d out: %02x %c", index, v, v); break; case 1: // out buffer len SERIAL_LOG("UART%d write out buffer len: %x", index, v); break; case 2: // in buffer len SERIAL_LOG("UART%d write in buffer len: %x", index, v); break; case 3: // errors? SERIAL_LOG("UART%d write errors: %x", index, v); break; case 4: // unknown SERIAL_LOG("UART%d write reg4: %x", index, v); break; case 5: // flow control SERIAL_LOG("UART%d write flow control: %x", index, v); break; case 6: // status. bit 3: receive buffer not empty SERIAL_LOG("UART%d write status: %x", index, v); break; case 7: // interrupt status/mask? SERIAL_LOG("UART%d write interrupt status/mask?: %x", index, v); break; case 8: // unknown SERIAL_LOG("UART%d write reg8: %x", index, v); break; case 9: // control? SERIAL_LOG("UART%d write control: %x", index, v); break; case 10: // baudrate (lsb) SERIAL_LOG("UART%d write baudrate(lsb): %x", index, v); flush(); break; case 11: // baudrate (msb) SERIAL_LOG("UART%d write baudrate(msb): %x", index, v); flush(); break; default: INFO_LOG(NAOMI, "Unknown UART%d port %x\n", index, addr); break; } } void SerialPort::updateStatus() { cart->updateInterrupt(index == 1 ? SystemSpCart::INT_UART1 : SystemSpCart::INT_UART2); } class DefaultIOManager : public IOPortManager { public: // IN_PORT0 u8 getCN9_17_24() override { u8 v = 0xff; // 0: P1 start // 1: P2 start // 2: P1 right // 3: P2 right // 4: P1 left // 5: P2 left // 6: P1 up // 7: P2 up getInputState(); if (!(mapleInputState[0].kcode & DC_BTN_START)) v &= ~0x01; if (!(mapleInputState[1].kcode & DC_BTN_START)) v &= ~0x02; if (!(mapleInputState[0].kcode & DC_DPAD_RIGHT)) v &= ~0x04; if (!(mapleInputState[1].kcode & DC_DPAD_RIGHT)) v &= ~0x08; if (!(mapleInputState[0].kcode & DC_DPAD_LEFT)) v &= ~0x10; if (!(mapleInputState[1].kcode & DC_DPAD_LEFT)) v &= ~0x20; if (!(mapleInputState[0].kcode & DC_DPAD_UP)) v &= ~0x40; if (!(mapleInputState[1].kcode & DC_DPAD_UP)) v &= ~0x80; IO_LOG("systemsp::read IN_PORT0 %x", v); return v; } // IN_PORT3 / IO-0 u8 getCN9_25_32() override { u8 v = 0xff; // 0: P1 down // 1: P2 down // 2: P1 button 1 // 3: P2 button 1 // 4: P1 button 2 // 5: P2 button 2 // 6: P1 button 3 // 7: P2 button 3 getInputState(); if (!(mapleInputState[0].kcode & DC_DPAD_DOWN)) v &= ~0x01; if (!(mapleInputState[1].kcode & DC_DPAD_DOWN)) v &= ~0x02; if (!(mapleInputState[0].kcode & DC_BTN_A)) v &= ~0x04; if (!(mapleInputState[1].kcode & DC_BTN_A)) v &= ~0x08; if (!(mapleInputState[0].kcode & DC_BTN_B)) v &= ~0x10; if (!(mapleInputState[1].kcode & DC_BTN_B)) v &= ~0x20; if (!(mapleInputState[0].kcode & DC_BTN_C)) v &= ~0x40; if (!(mapleInputState[1].kcode & DC_BTN_C)) v &= ~0x80; IO_LOG("systemsp::read IN_PORT3 %x", v); return v; } // IO-1 u8 getCN9_33_40() override { IO_LOG("systemsp::read IN CN9 33-40"); return 0xff; } // IN_PORT1 u8 getCN9_41_48() override { u8 v = 0xff; // 0: P1 service // 2: P1 test // 4: P1 coin // 5: P2 coin getInputState(); if (!(mapleInputState[0].kcode & DC_DPAD2_UP)) // service v &= ~0x01; if (!(mapleInputState[0].kcode & DC_DPAD2_DOWN)) // test v &= ~0x04; if (!(mapleInputState[0].kcode & DC_BTN_D)) // coin v &= ~0x10; if (!(mapleInputState[1].kcode & DC_BTN_D)) v &= ~0x20; IO_LOG("systemsp::read IN_PORT1 %x", v); return v; } // IN_PORT4 / OUT-0 u8 getCN9_49_56() override { u8 v = 0; // FIXME these are outputs?? // 0: P1 coin meter // 1: P2 coin meter getInputState(); if (!(mapleInputState[0].kcode & DC_BTN_D)) // coin v |= 1; if (!(mapleInputState[1].kcode & DC_BTN_D)) v |= 2; IO_LOG("systemsp::read IN_PORT4 %x", v); return v; } // IN G-PORT u8 getCN10_9_16() override { u8 v = 0; // dinosaur king, love & berry: // 0: 232c sel status 1 (not used) // 1: 232c sel status 2 (not used) // 2: card status1 (not used) // 3: card status2 (not used) // 4: card status3 (not used) // FIXME read sequentially after reading/writing reg24 (c0?), gives 4 shorts (8 reads) IO_LOG("systemsp::read IN G-PORT %d", v); return v; } protected: static constexpr u64 msAsCycles(int ms) { return (u64)SH4_MAIN_CLOCK * ms / 1000; } // On and Period in SH4 ms template class PeriodicSensor { public: bool get(bool actuator = true) { const u64 now = sh4_sched_now64(); if (actuator) position = (position + now - lastTime) % msAsCycles(Period); lastTime = now; return position < msAsCycles(On); } private: u64 position = msAsCycles(Period) / 2; u64 lastTime = 0; }; // Width in SH4 ms template class PulseSensor { public: bool get(bool actuator) { if (!actuator) { startTime = 0; return false; } else { const u64 now = sh4_sched_now64(); if (startTime == 0) startTime = now; return now - startTime < msAsCycles(Width); } } private: u64 startTime = 0; }; void getInputState() { ggpo::getInput(mapleInputState); if (NaomiGameInputs != nullptr) { for (const ButtonDescriptor& bd : NaomiGameInputs->buttons) { if (bd.name == nullptr) break; if (bd.target != 0) { // remap P1 -> P1 and P2 -> P2 if ((mapleInputState[0].kcode & bd.source) == 0) mapleInputState[0].kcode &= ~bd.target; if ((mapleInputState[1].kcode & bd.source) == 0) mapleInputState[1].kcode &= ~bd.target; } else if (bd.p2_target != 0) { // remap P1 -> P2 if ((mapleInputState[0].kcode & bd.source) == 0) mapleInputState[1].kcode &= ~bd.p2_target; } else if (bd.p1_target != 0) { // remap P2 -> P1 if ((mapleInputState[1].kcode & bd.source) == 0) mapleInputState[0].kcode &= ~bd.p1_target; } } } } MapleInputState mapleInputState[4]; }; class CardReaderIOManager : public DefaultIOManager { public: u8 getCN9_17_24() override { getInputState(); for (size_t i = 0; i < 2; i++) { if ((mapleInputState[i].kcode & DC_BTN_INSERT_CARD) == 0 && (last_kcode[i] & DC_BTN_INSERT_CARD) != 0) card_reader::insertCard(i); last_kcode[i] = mapleInputState[i].kcode; } return DefaultIOManager::getCN9_17_24(); } u8 getCN9_33_40() override { IO_LOG("systemsp::read IN CN9 33-40"); // dinosaur king, love & berry: // 0: P1 card set (not used) // 2: CD1 input ok (active low) // 4: CD1 card jam (active low) // 6: CD1 empty (active low) return 0xfb; } private: u32 last_kcode[2] = {}; }; class IsshoniIOManager : public CardReaderIOManager { public: u8 getCN9_17_24() override { CardReaderIOManager::getCN9_17_24(); return 0xff; } u8 getCN9_25_32() override { return 0xff; } }; class HopperIOManager : public DefaultIOManager { // IN_PORT1 u8 getCN9_41_48() override { u8 v = 0xbf; // 0: P1 service // 2: P1 test // 4: P1 coin // 5: P2 coin // 6: must be 0 (hopper: not connected / mount error) // 7: must be 1 (hopper: coin2 chute jammed) getInputState(); if (!(mapleInputState[0].kcode & DC_DPAD2_UP)) // service v &= ~0x01; if (!(mapleInputState[0].kcode & DC_DPAD2_DOWN)) // test v &= ~0x04; if (p1CoinSensor.get(!(mapleInputState[0].kcode & DC_BTN_D))) v &= ~0x10; if (p2CoinSensor.get(!(mapleInputState[1].kcode & DC_BTN_D))) v &= ~0x20; if (hopperSensor.get(hopperActive) && hopperActive) v |= 0x40; IO_LOG("systemsp::read IN_PORT1 %x", v); return v; } void setCN9_49_56(u8 v) override { // 4: hopper // 7: ? hopperActive = (v & 0x10) != 0; if (hopperActive) IO_LOG("HOPPER ON"); } bool hopperActive = false; PulseSensor<100> p1CoinSensor; PulseSensor<100> p2CoinSensor; PeriodicSensor<10, 50> hopperSensor; }; class ManpukuIOManager : public HopperIOManager { // IN_PORT0 u8 getCN9_17_24() override { u8 v = 0xff; // 4: Left // 5: Middle // 6: Right getInputState(); if (!(mapleInputState[0].kcode & DC_DPAD_LEFT)) v &= ~0x10; if (!(mapleInputState[0].kcode & DC_DPAD_DOWN)) v &= ~0x20; if (!(mapleInputState[0].kcode & DC_DPAD_RIGHT)) v &= ~0x40; IO_LOG("systemsp::read IN_PORT0 %x", v); return v; } }; // Yataimura Kingyosukui, Yataimura Shateki class KingyoIOManager : public HopperIOManager { // IN_PORT0 u8 getCN9_17_24() override { u8 v = 0xff; // 0: Left // 1: Right // 2: Down // 3: Up // 4: Button 1 getInputState(); if (!(mapleInputState[0].kcode & DC_DPAD_LEFT)) v &= ~0x01; if (!(mapleInputState[0].kcode & DC_DPAD_RIGHT)) v &= ~0x02; if (!(mapleInputState[0].kcode & DC_DPAD_DOWN)) v &= ~0x04; if (!(mapleInputState[0].kcode & DC_DPAD_UP)) v &= ~0x08; if (!(mapleInputState[0].kcode & DC_BTN_A)) v &= ~0x10; IO_LOG("systemsp::read IN_PORT0 %x", v); return v; } }; // Notes: // COUNT HOPPER JAM - LOCK SENSOR ON 1 SEC // 00:19:308 hw/naomi/systemsp.cpp:1197 D[NAOMI]: OUT CN9_33-40 COUNT HOPPER HOPPER MTR // 00:19:311 hw/naomi/systemsp.cpp:1223 D[NAOMI]: JP SOLENOID ON // -> fixed by timing countHopperRot sensor to 0.5 s max // HOPPER EMPTY/JAM - MOTOR DRIVE BUT SENSOR OFF // 01:56:773 hw/naomi/systemsp.cpp:1197 D[NAOMI]: OUT CN9_33-40 HOPPER MTR // -> fixed by resetting hopper sensor when hopper motor is on. // SLOPE SENSOR TIMEOUT - MOTOR DRIVE BUT SENSOR OFF // 07:52:477 hw/naomi/systemsp.cpp:1218 D[NAOMI]: OUT CN9_33-40 COUNT HOPPER SLOPE MTR // -> set slope sensor low when slope motor on // ILLEGAL CHACKER IN - MANY CHACKER IN BUT NO COIN IN // -> fixed by setting COIN L or R when checker on // COIN IN RATIO TOO HIGH - CTRL COIN IN RATIO OVER 105% // -> not enough coin L/R vs. checker in? // debug flag locations: // magicpop 0x8c2e729e // unomedal 0x8c22baf2 // puyomedal 0x8c2b67de // ochaken 0x8c25c6da // westdmrg 0x8c22d1aa class MedalIOManager : public DefaultIOManager { // IN_PORT0 u8 getCN9_17_24() override { u8 v = 0x50; // 0: slope sensor up (active high) // 1: slope sensor low (active high) // 2: count hopper rot. (active high) // 3: count hopper sensor (active high) // 4: hopper sensor // 5: pusher sensor (active high) // 6: tilt bob sensor // 7: checker 9 (active high) if (countHopperRotSensor.get(countHopperMtr)) v |= 0x04; // count hopper rot sensor // FIXME generates FIELD P/O TOO HIGH errors. Activate the sensor 50% of the time? //if (countHopperSensor.get(countHopperMtr)) // v |= 0x08; // count hopper sensor if (hopperSensor.get(hopperMtr)) v &= ~0x10; // hopper sensor if (slopeMtr) v |= 2; // slope sensor low else v |= 1; // slope sensor up if (pusherSensor.get(pusherMtr)) v |= 0x20; // pusher sensor if (!(mapleInputState[0].kcode & DC_BTN_START)) v |= 0x80; // checker 9 return v; } // IN_PORT1 u8 getCN9_41_48() override { u8 v = 0x2f; // 0: service/reset sw // 1: left sw // 2: test sw // 3: center sw // 4: door sensor up (active high) // 5: right sw // 6: door sensor left (active high) // 7: tilt sensor br (active high) getInputState(); if (!(mapleInputState[0].kcode & DC_DPAD2_UP)) // service/reset v &= ~0x01; if (!(mapleInputState[0].kcode & DC_DPAD2_DOWN)) // test v &= ~0x04; if (!(mapleInputState[0].kcode & DC_DPAD_LEFT)) // left sw v &= ~0x02; if (!(mapleInputState[0].kcode & DC_DPAD_DOWN)) // center sw v &= ~0x08; if (!(mapleInputState[0].kcode & DC_DPAD_RIGHT)) // right sw v &= ~0x20; return v; } // IN_PORT3 / IO-0 u8 getCN9_25_32() override { u8 v = 0; // 0: checker 1 (active high) // 1: checker 2 (active high) // 2: checker 3 (active high) // 3: checker 4 (active high) // 4: checker 5 (active high) // 5: checker 6 (active high) // 6: checker 7 (active high) // 7: checker 8 (active high) if (!(mapleInputState[0].kcode & DC_BTN_A)) v |= 0x01; if (!(mapleInputState[0].kcode & DC_BTN_B)) v |= 0x02; if (!(mapleInputState[0].kcode & DC_BTN_C)) v |= 0x04; if (!(mapleInputState[0].kcode & DC_BTN_X)) v |= 0x08; if (!(mapleInputState[0].kcode & DC_BTN_Y)) v |= 0x10; if (!(mapleInputState[0].kcode & DC_BTN_Z)) v |= 0x20; if (!(mapleInputState[0].kcode & DC_DPAD2_LEFT)) v |= 0x40; if (!(mapleInputState[0].kcode & DC_DPAD2_RIGHT)) v |= 0x80; return v; } // IN G-PORT u8 getCN10_9_16() override { u8 v = 0; // 0: jp mecha sensor u (active high) // 2: jp mecha sensor d (active high) // 3: co. full sensor (active high) // 4: coin in L (active high) // 5: coin in R (active high) // 7: jp solenoid sensor (active high) if (jpmechaMtr) v |= 4; else v |= 1; // FIXME not enough coin ins if multiple checkers pressed at once -> COIN IN RATIO TOO HIGH if ((mapleInputState[0].kcode & (DC_BTN_A | DC_BTN_B | DC_BTN_C | DC_BTN_X | DC_BTN_Y | DC_BTN_Z)) != (DC_BTN_A | DC_BTN_B | DC_BTN_C | DC_BTN_X | DC_BTN_Y | DC_BTN_Z)) v |= 0x10; // coin in L if ((mapleInputState[0].kcode & (DC_DPAD2_LEFT | DC_DPAD2_RIGHT | DC_BTN_START)) != (DC_DPAD2_LEFT | DC_DPAD2_RIGHT | DC_BTN_START)) v |= 0x20; // coin in R if (jpmechaSolenoid) v |= 0x80; return v; } // IO-1 (CN9 33-40) void setCN9_33_40(u8 v) override { // 3: jp mecha motor // 4: count hopper motor // 5: hopper motor // 6: pusher motor // 7: slope motor jpmechaMtr = !(v & 0x08); countHopperMtr = !(v & 0x10); hopperMtr = !(v & 0x20); pusherMtr = !(v & 0x40); slopeMtr = !(v & 0x80); if ((v & 0xf8) != 0xf8) IO_LOG("OUT CN9_33-40 %s %s %s %s %s", v & 0x08 ? "" : "JP MECHA MTR", v & 0x10 ? "" : "COUNT HOPPER", v & 0x20 ? "" : "HOPPER MTR", v & 0x40 ? "" : "PUSHER MTR", v & 0x80 ? "" : "SLOPE MTR"); } // OUT-0 (CN9 49-56) void setCN9_49_56(u8 v) override { // 4: sw lamp L // 5: sw lamp R // 6: patrol lamp // 7: jackpot lamp } // OUT-1 (CN10 17-24) void setCN10_17_24(u8 v) override { // 0: sw.lamp c // 1: jp solenoid // 6: side lamp L // 7: side lamp R jpmechaSolenoid = v & 2; if (jpmechaSolenoid) IO_LOG("JP SOLENOID ON"); } bool jpmechaMtr = false; bool countHopperMtr = false; bool hopperMtr = false; bool pusherMtr = false; bool slopeMtr = false; bool jpmechaSolenoid = false; PeriodicSensor<50, 250> hopperSensor; PeriodicSensor<100, 4300> pusherSensor; PeriodicSensor<100, 500> countHopperRotSensor; PeriodicSensor<50, 250> countHopperSensor; }; template T readMemArea0(u32 addr) { verify(SystemSpCart::Instance != nullptr); return SystemSpCart::Instance->readMemArea0(addr); } template u32 readMemArea0<>(u32 addr); template u16 readMemArea0<>(u32 addr); template u8 readMemArea0<>(u32 addr); template T SystemSpCart::readMemArea0(u32 addr) { addr &= 0x1fffff; if (addr < 0x10000) { // banked access to ROM/NET board address space, mainly backup SRAM and ATA u32 offset = (addr & 0xffff) | ((bank & 0x3fff) << 16); if ((bank & 0x3f00) == 0x3900) { // SRAM FLASH_LOG("systemsp::read(%x) SRAM. offset %x", addr, offset); verify(!(bank & 0x4000)); // 8-bit device on 16-bit bus if constexpr (sizeof(T) == 1) { if (offset & 1) return 0xff; else return nvmem::readFlash(offset / 2, 1); } else if constexpr (sizeof(T) == 2) return 0xff00 | nvmem::readFlash(offset / 2, 1); else return 0xff00ff00 | nvmem::readFlash(offset / 2, 1) | (nvmem::readFlash(offset / 2 + 1, 1) << 16); } else if ((bank & 0x3f00) == 0x3a00) { // CF IDE registers switch (addr & 0xffff) { case 0x00: // RD data if constexpr (sizeof(T) == 2) { addr &= ~1; T ret = readMemArea0(addr); ret |= readMemArea0(addr) << 8; if (bank & 0x4000) // decrypt ret = decrypt(ret); return ret; } else { u8 data = 0; if (ata.bufferIndex < SECTOR_SIZE) { data = ata.buffer[ata.bufferIndex++]; if (ata.bufferIndex == SECTOR_SIZE) { if (ata.sectorCount > 1) { // read next sector ata.sectorCount--; ata.sectorNumber++; if (ata.sectorNumber == 0) ata.cylinder++; if (ata.cylinder == 0) ata.driveHead.head++; readSectors(); updateInterrupt(INT_ATA); } else { // no more data ata.status.drq = 0; } } } ATA_LOG("systemsp::read(%x) CF ATA data %02x %c", addr, data, data >= 32 && data < 127 ? (char)data : ' '); return data; } case 0x04: // error ATA_LOG("systemsp::read(%x) CF ATA error", addr); return 0; case 0x08: // sector count ATA_LOG("systemsp::read(%x) CF ATA sector count %d", addr, ata.sectorCount); return ata.sectorCount; case 0x0c: // sector no ATA_LOG("systemsp::read(%x) CF ATA sector# %x", addr, ata.sectorNumber); return ata.sectorNumber; case 0x10: // cylinder (lsb) ATA_LOG("systemsp::read(%x) CF ATA cylinder(lsb) %x", addr, ata.cylinder & 0xff); return ata.cylinder & 0xff; case 0x14: // cylinder (msb) ATA_LOG("systemsp::read(%x) CF ATA cylinder(msb) %x", addr, ata.cylinder >> 8); return ata.cylinder >> 8; case 0x18: // select card/head ATA_LOG("systemsp::read(%x) CF ATA card/head %x", addr, ata.driveHead.full); return ata.driveHead.full; case 0x1c: // status { // BUSY RDY DWF DSC DRQ CORR 0 ERR ATA_LOG("systemsp::read(%x) CF ATA status %x", addr, ata.status.full); u8 status = ata.status.full; // TODO correct? ata.status.dsc = 0; return status; } default: INFO_LOG(NAOMI, "systemsp::read(%x) CF IDE unknown reg", addr); return -1; } } else if ((bank & 0x3f00) == 0x3b00) { // CF IDE AltStatus/Device Ctrl register if ((addr & 0xffff) == 0x18) { ATA_LOG("systemsp::read(%x) CF IDE AltStatus %x", addr, ata.status.full); return ata.status.full; } INFO_LOG(NAOMI, "systemsp::read(%x) CF IDE AltStatus unknown addr", addr); return 0; } else if ((bank & 0x3f00) == 0x3d00) { // Network aka Media board shared buffer/RAM verify(!(bank & 0x4000)); DEBUG_LOG(NAOMI, "systemsp::read(%x) Network shared RAM. offset %x", addr, offset); return -1; } else if ((bank & 0x3f00) == 0x3f00) { // Network board present flag (0x01) DEBUG_LOG(NAOMI, "systemsp::read(%x) Network board present. offset %x", addr, offset); return 0; } T v; if (!CurrentCartridge->Read(offset, sizeof(T), &v)) return -1; else return v; } else if (addr == 0x10000) { // bank register return (T)bank; } else if (addr < 0x10100) { // IRQ pending/reset, ATA control DEBUG_LOG(NAOMI, "systemsp::read(%x) IRQ pending/reset, ATA control", addr); switch (addr - 0x10000) { case 0x30: return 0; case 0x80: { // interrupt status const u8 intPending = ata.interruptPending; ata.interruptPending = 0; updateInterrupt(); // b0: UART1 // b1: UART2 // b3: DIMM // b4: ATA controller return intPending; } case 0x84: // interrupt mask? // 10084: (dinoking) bit0,1 reset, sometimes set return ata.reg84; default: return 0; } } else if (addr < 0x10128) { // I/O chip for inputs switch (addr - 0x10100) { case 0x0: // IN_PORT0 (CN9 17-24) return ioPortManager->getCN9_17_24(); case 0x4: // IN_PORT1 (CN9 41-48) return ioPortManager->getCN9_41_48(); case 0x8: // IN_PORT3 (CN9 25-32) return ioPortManager->getCN9_25_32(); case 0xc: // IN CN9 33-40 return ioPortManager->getCN9_33_40(); case 0x10: // IN_PORT4 (CN9 49-56) return ioPortManager->getCN9_49_56(); case 0x18: // IN_PORT2 (DIP switches and jumpers, and P1 service for older pcb rev) { // DIP switches // 0: unknown, active low // 1: unknown, active low // 2: monitor (1: 31 kHz, 0: 15 kHz) // 3: unknown, must be on (active low) // 4: JP5 (system service) // 5: JP6 (system test) // 6: JP7 // 7: JP8 IO_LOG("systemsp::read(%x) IN_PORT2 %x", addr, 7); return 0xf7; } case 0x20: // IN G_PORT CN10 9-16 return ioPortManager->getCN10_9_16(); case 0x24: // bios, write too IO_LOG("systemsp::read(%x) ??", addr); return 0; default: IO_LOG("systemsp::read(%x) inputs??", addr); return 0; } } else if (addr == 0x10128) { // eeprom return eeprom.readDO() << 4; } else if (addr == 0x10150) { // CFG rom board debug flags // bit 0 - romboard type, 1 = M4 // bit 1 - debug mode (enable easter eggs in BIOS, can boot game without proper eeproms/settings) IO_LOG("systemsp::read(%x) CFG DIP switches", addr); return 3; // M4 board type, debug on; } else if (addr < 0x10180) { // unknown } else if (addr < 0x101c0) { // custom UART 1 return uart1.readReg(addr); } else if (addr < 0x101f0) { // custom UART 2 return uart2.readReg(addr); } INFO_LOG(NAOMI, "systemsp::readMemArea0<%d>: Unknown addr %x", (int)sizeof(T), addr); return -1; } template void writeMemArea0(u32 addr, T v) { verify(SystemSpCart::Instance != nullptr); SystemSpCart::Instance->writeMemArea0(addr, v); } template void writeMemArea0<>(u32 addr, u32 v); template void writeMemArea0<>(u32 addr, u16 v); template void writeMemArea0<>(u32 addr, u8 v); template void SystemSpCart::writeMemArea0(u32 addr, T v) { //DEBUG_LOG(NAOMI, "SystemSpCart::writeMemArea0(%x, %x)", addr, (u32)v); addr &= 0x1fffff; if (addr < 0x10000) { // banked access to ROM/NET board address space, mainly backup SRAM and ATA u32 offset = (addr & 0xffff) | ((bank & 0x3fff) << 16); if ((bank & 0x3f00) == 0x3900) { FLASH_LOG("systemsp::write(%x) SRAM. offset %x data %x", addr, offset, (u32)v); // 8-bit device on 16-bit bus if constexpr (sizeof(T) == 1) { if (offset & 1) return; } if constexpr (sizeof(T) == 4) nvmem::writeFlash(offset / 2 + 1, (u8)(v >> 16), 1); nvmem::writeFlash(offset / 2, (u8)v, 1); return; } else if ((bank & 0x3f00) == 0x3a00) { // CF IDE registers switch (addr & 0xffff) { case 0x00: // WR data ATA_LOG("systemsp::write(%x) CF ATA data = %x", addr, (u32)v); break; case 0x04: // features ATA_LOG("systemsp::write(%x) CF ATA features = %x", addr, (u32)v); ata.features = v; break; case 0x08: // sector count ATA_LOG("systemsp::write(%x) CF ATA sector count = %x", addr, (u32)v); ata.sectorCount = v; break; case 0x0c: // sector no ATA_LOG("systemsp::write(%x) CF ATA sector# = %x", addr, (u32)v); ata.sectorNumber = v; break; case 0x10: // cylinder (lsb) ATA_LOG("systemsp::write(%x) CF ATA cylinder(lsb) = %x", addr, (u32)v); ata.cylinder = (ata.cylinder & 0xff00) | (v & 0xff); break; case 0x14: // cylinder (msb) ATA_LOG("systemsp::write(%x) CF ATA cylinder(msb) = %x", addr, (u32)v); ata.cylinder = (ata.cylinder & 0xff) | ((v & 0xff) << 8); break; case 0x18: // select card/head ATA_LOG("systemsp::write(%x) CF ATA card/head = %x", addr, (u32)v); ata.driveHead.full = v | 0xa0; break; case 0x1c: // command switch (v) { case 0x20: ATA_LOG("systemsp::write(%x) CF ATA cmd: read %d sector(s): c %x h %x s %x", addr, ata.sectorCount, ata.cylinder, ata.driveHead.head, ata.sectorNumber); ata.status.rdy = 0; ata.status.bsy = 1; ata.status.drq = 1; // FIXME should be done in the callback sh4_sched_request(schedId, 2000); // 10 us readSectors(); break; case 0xe1: ATA_LOG("systemsp::write(%x) CF ATA cmd: idle immediate", addr); ata.status.bsy = 1; ata.status.rdy = 0; sh4_sched_request(schedId, 2000); // 10 us break; default: INFO_LOG(NAOMI, "systemsp::write(%x) CF ATA command unknown: %x", addr, (u32)v); break; } break; default: INFO_LOG(NAOMI, "systemsp::write(%x) CF ATA unknown reg = %x", addr, (u32)v); break; } return; } else if ((bank & 0x3f00) == 0x3b00) { if ((addr & 0xffff) == 0x18) { // CF IDE AltStatus/Device Ctrl register if (ata.devCtrl.srst && !(v & 4)) { // software reset ata.bufferIndex = ~0; ata.error = 0; ata.status.drq = 0; ata.status.err = 0; } ATA_LOG("systemsp::write(%x) CF IDE Device Ctrl = %x", addr, (u32)v); ata.devCtrl.full = v & 0x86; } else INFO_LOG(NAOMI, "systemsp::write(%x) CF IDE unknown reg %x data %x", addr, offset, (u32)v); return; } else if ((bank & 0x3f00) == 0x3d00) { // Network aka Media board shared buffer/RAM DEBUG_LOG(NAOMI, "systemsp::write(%x) Network shared RAM. offset %x data %x", addr, offset, (u32)v); return; } else if ((bank & 0x3f00) == 0x3f00) { // Network board present flag (0x01) DEBUG_LOG(NAOMI, "systemsp::write(%x) Network board present. offset %x data %x", addr, offset, (u32)v); return; } } else if (addr == 0x10000) { // bank register if (bank != (u16)v) DEBUG_LOG(NAOMI, "systemsp: G2 Bank set to %08X%s", (v & 0x3fff) << 16, (v & 0x4000) ? " decrypt ON" : ""); bank = (u16)v; return; } else if (addr < 0x10100) { // IRQ pending/reset, ATA control DEBUG_LOG(NAOMI, "systemsp::write(%x) IRQ pending/reset, ATA control. data %x", addr, (u32)v); switch (addr - 10000) { case 0x84: ata.reg84 = v; break; default: break; } return; } else if (addr < 0x10128) { // I/O chip for outputs switch (addr - 0x10100) { case 0x8: // OUT_PORT3 (CN9 25-32)? IO_LOG("systemsp::write(%x) OUT CN9 25-32? %x", addr, v); break; case 0xc: // IO-1 CN9 33-40 ioPortManager->setCN9_33_40(v); break; case 0x10: // OUT_PORT4 (CN9 49-56) // 0: (P1 coin meter) // 1: (P2 coin meter) // giant tetris: // 2: P1 start lamp // 3: P2 start lamp // 4: P1 button 1 lamp // 5: P2 button 1 lamp // 6: P1 button 2 lamp // 7: P2 button 2 lamp // dinosaur king, love & berry: // 2: coin blocker 1 (not used) // 3: coin blocker 2 (not used) // 4: cd1 card pickout // 5: cd2 card pickout (not used) // 6: cd1 jam reset // 7: cd2 jam reset (not used) // hopper: // 4: payout? // 7: ? ioPortManager->setCN9_49_56(v); break; case 0x14: // OUT CN10 17-24 // dinosaur king, love & berry: // 0: 232c select1 (not used) // 1: 232c select2 (not used) // 2: 232c reset (not used) // 3: card trigger (not used) // 4: rfid chip1 reset // 5: rfid chip2 reset // 6: rfid chip1 empty lamp // 7: rfid chip2 empty lamp ioPortManager->setCN10_17_24(v); break; case 0x24: // read too default: IO_LOG("systemsp::write(%x) outputs? data %x", addr, (u32)v); break; } return; } else if (addr == 0x10128) { // eeprom eeprom.writeDI((v & 1) != 0); eeprom.writeCS((v & 2) != 0); eeprom.writeCLK((v & 4) != 0); return; } else if (addr < 0x10180) { // rom board dip switches? IO_LOG("systemsp::write(%x) DIP switches? data %x", addr, (u32)v); return; } else if (addr < 0x101c0) { // custom UART 1 uart1.writeReg(addr, v); return; } else if (addr < 0x101f0) { // custom UART 2 uart2.writeReg(addr, v); return; } INFO_LOG(NAOMI, "systemsp::writeMemArea0<%d>: Unknown addr %x = %x", (int)sizeof(T), addr, (int)v); } void SystemSpCart::updateInterrupt(u32 mask) { ata.interruptPending |= mask; if ((ata.interruptPending & (INT_UART1 | INT_UART2 | INT_DIMM)) || ((ata.interruptPending & INT_ATA) && ata.devCtrl.nien == 0)) asic_RaiseInterrupt(holly_EXP_PCI); else asic_CancelInterrupt(holly_EXP_PCI); } SystemSpCart::SystemSpCart(u32 size) : M4Cartridge(size), uart1(this, 1), uart2(this, 2) { schedId = sh4_sched_register(0, schedCallback, this); Instance = this; // mb_serial.ic57 static const u8 eepromData[0x80] = { 0xf5, 0x90, 0x53, 0x45, 0x47, 0x41, 0x20, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x50, 0x52, 0x49, 0x53, 0x45, 0x53, 0x2c, 0x4c, 0x54, 0x44, 0x2e, 0x00, 0x4e, 0x41, 0x4f, 0x4d, 0x49, 0x00, 0x00, 0x00, 0x41, 0x41, 0x46, 0x45, 0x30, 0x31, 0x44, 0x31, 0x35, 0x39, 0x32, 0x34, 0x38, 0x31, 0x36, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; eeprom.Load(eepromData, sizeof(eepromData)); } SystemSpCart::~SystemSpCart() { EventManager::unlisten(Event::Pause, handleEvent, this); if (chd != nullptr) chd_close(chd); if (chdFile != nullptr) fclose(chdFile); sh4_sched_unregister(schedId); Instance = nullptr; } chd_file *SystemSpCart::openChd(const std::string path) { chdFile = hostfs::storage().openFile(path, "rb"); if (chdFile == nullptr) { WARN_LOG(NAOMI, "Cannot open file '%s' errno %d", path.c_str(), errno); return nullptr; } chd_file *chd; chd_error err = chd_open_file(chdFile, CHD_OPEN_READ, 0, &chd); if (err != CHDERR_NONE) { WARN_LOG(NAOMI, "Invalid CHD file %s", path.c_str()); fclose(chdFile); chdFile = nullptr; return nullptr; } INFO_LOG(NAOMI, "compact flash: parsing file %s", path.c_str()); const chd_header* head = chd_get_header(chd); hunkbytes = head->hunkbytes; hunkmem = std::make_unique(hunkbytes); return chd; } void SystemSpCart::readSectors() { verify(ata.driveHead.lba == 1); u32 lba = (ata.driveHead.head << 24) | (ata.cylinder << 8) | ata.sectorNumber; u32 newHunk = lba * SECTOR_SIZE / hunkbytes; u32 offset = (lba * SECTOR_SIZE) % hunkbytes; if (hunknum != newHunk) { hunknum = newHunk; if (chd_read(chd, hunknum, &hunkmem[0]) != CHDERR_NONE) WARN_LOG(NAOMI, "CHD read failed"); } memcpy(ata.buffer, &hunkmem[offset], SECTOR_SIZE); ata.bufferIndex = 0; } std::string SystemSpCart::getEepromPath() const { std::string path = hostfs::getArcadeFlashPath(); switch (region) { case 0: path += "-jp"; break; case 1: path += "-us"; break; default: path += "-exp"; break; } path += ".eeprom"; return path; } class BootIdLoader { static constexpr u32 SECSIZE = 512; public: BootIdLoader(SystemSpCart& cart) : cart(cart) { const chd_header* head = chd_get_header(cart.chd); hunkbytes = head->hunkbytes; hunkmem = std::make_unique(hunkbytes); } RomBootID *load() { // Read master boot record if (chd_read(cart.chd, 0, &hunkmem[0]) != CHDERR_NONE) return nullptr; // Check boot signature if (hunkmem[510] != 0x55 || hunkmem[511] != 0xaa) return nullptr; // Get partition 1 info if (hunkmem[446 + 4] != 6) // Not a FAT16B partition return nullptr; u32 start = *(u32 *)&hunkmem[446 + 8]; // Read partition Bios Parameter Block if (chd_read(cart.chd, start * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) return nullptr; int offset = (start * SECSIZE) % hunkbytes; // Partition attributes sectorsPerCluster = hunkmem[offset + 13]; u16 resSectors = *(u16 *)&hunkmem[offset + 14]; u8 fatCount = hunkmem[offset + 16]; rootFolderSize = *(u16 *)&hunkmem[offset + 17]; // in entries u16 fatSize = *(u16 *)&hunkmem[offset + 22]; // in sectors // FATs start after reserved sectors fatOffset = start + resSectors; rootOffset = fatOffset + fatSize * fatCount; dataStartOffset = rootOffset + rootFolderSize * sizeof(DirEntry) / SECSIZE; if (!openFile("1STREAD.BIN")) return nullptr; std::unique_ptr buffer = std::make_unique(file.size); cart.enc_reset(); for (u32 i = 0; i < file.size; i += 2) *(u16 *)&buffer[i] = cart.decrypt(*(u16 *)&hunkmem[file.offset + i]); if (memcmp(&buffer[0], "SPCF", 4)) // wrong signature return nullptr; std::string imageName((const char *)&buffer[0x80]); if (imageName.length() > 12) // make short 8.3 name imageName = imageName.substr(0, 6) + "~1" + imageName.substr(imageName.find('.')); string_toupper(imageName); if (!openFile(imageName)) return nullptr; buffer = std::make_unique(sizeof(RomBootID)); cart.enc_reset(); for (size_t i = 0; i < sizeof(RomBootID); i += 2) { if (file.offset + i >= hunkbytes) { u32 fileOffset = dataStartOffset + (file.cluster - 2) * sectorsPerCluster + i / SECSIZE; if (chd_read(cart.chd, fileOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) return nullptr; file.offset = (fileOffset * SECSIZE) % hunkbytes - i; file.size -= i; } *(u16 *)&buffer[i] = cart.decrypt(*(u16 *)&hunkmem[file.offset + i]); } if (memcmp(&buffer[0], "SystemSP", 8)) { // Yes, it is encrypted twice (except tetgiano) cart.enc_reset(); for (size_t i = 0; i < sizeof(RomBootID); i += 2) *(u16 *)&buffer[i] = cart.decrypt(*(u16 *)&buffer[i]); } RomBootID *bootId = new RomBootID(); memcpy(bootId, &buffer[0], sizeof(RomBootID)); return bootId; } private: struct DirEntry { char name[8]; char ext[3]; enum Attribute : u8 { ReadOnly = 0x01, Hidden = 0x02, System = 0x04, VolumeName = 0x8, Directory = 0x10, Archive = 0x20, LongName = 0xf, }; Attribute attributes; u8 _reserved; u8 creationTimeMs; u16 creationTime; u16 creationDate; u16 accessDate; u16 clusterHigh; u16 updateTime; u16 updateDate; u16 cluster; u32 size; }; bool openFile(const std::string& fileName) { u32 curRootOffset = rootOffset; if (chd_read(cart.chd, curRootOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) return false; int offset = (curRootOffset * SECSIZE) % hunkbytes; for (int i = 0; i < rootFolderSize; i++) { if (offset + i * sizeof(DirEntry) >= hunkbytes) { ++curRootOffset; if (chd_read(cart.chd, curRootOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) return false; offset = (curRootOffset * SECSIZE) % hunkbytes - i * sizeof(DirEntry); } const DirEntry *entry = (const DirEntry *)&hunkmem[offset + i * sizeof(DirEntry)]; u8 b = (u8)entry->name[0]; if (b == 0) // end of directory return false; if (b == 0xe5 || b == 0x2e) // unused or ./.. entry continue; if (entry->attributes == DirEntry::LongName || (entry->attributes & (DirEntry::VolumeName | DirEntry::System))) // long name or volume name/system continue; std::string name(std::begin(entry->name), std::end(entry->name)); name = trim_trailing_ws(name); std::string ext(std::begin(entry->ext), std::end(entry->ext)); ext = trim_trailing_ws(ext); if (!ext.empty()) name += "." + ext; //printf("%s%c\t%d\n", name.c_str(), entry->attributes & DirEntry::Directory ? '/' : ' ', entry->size); if (name != fileName) continue; file.cluster = entry->cluster; file.size = entry->size; u32 fileOffset = dataStartOffset + (file.cluster - 2) * sectorsPerCluster; if (chd_read(cart.chd, fileOffset * SECSIZE / hunkbytes, &hunkmem[0]) != CHDERR_NONE) return false; file.offset = (fileOffset * SECSIZE) % hunkbytes; return true; } return false; } SystemSpCart& cart; u32 hunkbytes; std::unique_ptr hunkmem; u8 sectorsPerCluster = 0; u32 fatOffset = 0; u16 rootFolderSize = 0; u32 rootOffset = 0; u32 dataStartOffset = 0; struct { u32 cluster; u32 size; int offset; } file; }; void SystemSpCart::Init(LoadProgress *progress, std::vector *digest) { M4Cartridge::Init(progress, digest); if (mediaName != nullptr) { std::string parent = hostfs::storage().getParentPath(settings.content.path); std::string gdrom_path = get_file_basename(settings.content.fileName) + "/" + std::string(mediaName) + ".chd"; try { gdrom_path = hostfs::storage().getSubPath(parent, gdrom_path); chd = openChd(gdrom_path); } catch (const FlycastException& e) { } if (parentName != nullptr && chd == nullptr) { try { std::string gdrom_parent_path = hostfs::storage().getSubPath(parent, std::string(parentName) + "/" + std::string(mediaName) + ".chd"); chd = openChd(gdrom_parent_path); } catch (const FlycastException& e) { } } if (chd == nullptr) throw NaomiCartException("SystemSP: Cannot open CompactFlash file " + gdrom_path); BootIdLoader loader(*this); romBootId.reset(loader.load()); } else { ata.status.rdy = 0; ata.status.df = 1; } RomBootID bootId; if (GetBootId(&bootId) && bootId.country != 0 && (bootId.country & (2 << config::Region)) == 0) { if (bootId.country & 4) { NOTICE_LOG(NAOMI, "Forcing region USA"); config::Region.override(1); } else if (bootId.country & 8) { NOTICE_LOG(NAOMI, "Forcing region Export"); config::Region.override(2); } else if (bootId.country & 2) { NOTICE_LOG(NAOMI, "Forcing region Japan"); config::Region.override(0); } // Force BIOS reload now to get the default eeprom for the correct region naomi_default_eeprom = nullptr; naomi_cart_LoadBios(settings.content.fileName.c_str()); } region = config::Region; // Region must be set before loading the eeprom if (!eeprom.Load(getEepromPath()) && naomi_default_eeprom != nullptr) memcpy(eeprom.data, naomi_default_eeprom, 128); // dinoki4 doesn't use rfid chips. dinokich uses a different reader/writer protocol if ((!strncmp(game->name, "dinoki", 6) && strcmp(game->name, "dinoki4") != 0 && strcmp(game->name, "dinokich") != 0) || !strncmp(game->name, "loveber", 7)) { new RfidReaderWriter(&uart1, 1, game->name); new RfidReaderWriter(&uart2, 2, game->name); } else if (!strcmp(game->name, "isshoni")) { new Touchscreen(&uart1); ioPortManager = std::make_unique(); } else if (!strcmp(game->name, "manpuku")) { ioPortManager = std::make_unique(); } else if (!strncmp(game->name, "kingyo", 6) || !strcmp(game->name, "shateki")) { ioPortManager = std::make_unique(); } else if (!strcmp(game->name, "magicpop") || !strcmp(game->name, "ochaken") || !strcmp(game->name, "puyomedal") || !strcmp(game->name, "unomedal") || !strcmp(game->name, "westdrmg")) { ioPortManager = std::make_unique(); } if (!strncmp(game->name, "dinoki", 6) || !strncmp(game->name, "loveber", 7)) ioPortManager = std::make_unique(); if (!ioPortManager) ioPortManager = std::make_unique(); EventManager::listen(Event::Pause, handleEvent, this); } u32 SystemSpCart::ReadMem(u32 address, u32 size) { if (address == NAOMI_DIMM_STATUS) { u32 rc = DIMM_STATUS & ~(((SB_ISTEXT >> 3) & 1) << 8); DEBUG_LOG(NAOMI, "DIMM STATUS read -> %x", rc); return rc; } else { return M4Cartridge::ReadMem(address, size); } } void SystemSpCart::WriteMem(u32 address, u32 data, u32 size) { if (address == NAOMI_DIMM_STATUS) { DEBUG_LOG(NAOMI, "DIMM STATUS Write<%d>: %x", size, data); if (data & 0x100) asic_CancelInterrupt(holly_EXP_PCI); if ((data & 2) == 0) // irq to dimm process(); } else { M4Cartridge::WriteMem(address, data, size); } } bool SystemSpCart::Read(u32 offset, u32 size, void *dst) { // TODO sram? if ((offset & 0x3f000000) == 0x39000000) if ((offset & 0x3f000000) == 0x3f000000) { // network card present int rc = 1; DEBUG_LOG(NAOMI, "SystemSpCart::Read<%d>%x: net card present -> %d", size, offset, rc); memcpy(dst, &rc, size); return true; } if ((offset & 0x3f000000) == 0x3d000000) { // network card mem switch (size) { case 4: *(u32 *)dst = readNetMem(offset); break; case 2: *(u16 *)dst = readNetMem(offset); break; case 1: default: *(u8 *)dst = readNetMem(offset); break; } u16 d = readNetMem(offset); DEBUG_LOG(NAOMI, "SystemSpCart::Read<%d>%x: net mem -> %x", size, offset, d); return true; } return M4Cartridge::Read(offset, size, dst); } bool SystemSpCart::Write(u32 offset, u32 size, u32 data) { if ((offset & 0x3f000000) == 0x3d000000) { // network card mem switch (size) { case 4: writeNetMem(offset, data); break; case 2: writeNetMem(offset, (u16)data); break; case 1: default: writeNetMem(offset, (u8)data); break; } DEBUG_LOG(NAOMI, "SystemSpCart::Write<%d>%x: net mem = %x", size, offset, data); int idx = offset & (sizeof(netmem) - 2); // FIXME if (idx == 2) memcpy(&netmem[idx + 0x200], &data, size); return true; } // ??? if ((offset & 0x3f000000) == 0x39000000) switch (flash.cmdState) { case CmdState::INIT: if ((offset & 0xfff) == 0xaaa && data == 0xaa) { flash.cmdState = CmdState::AAA_AA_1; return true; } else if (offset == flash.progAddress && data == 0x29) { // write buffer to flash FLASH_LOG("Flash cmd PROGRAM BUF TO FLASH %x", offset); flash.progAddress = ~0; return true; } break; case CmdState::AAA_AA_1: if (((offset & 0xfff) == 0x555 || (offset & 0xfff) == 0x554) && data == 0x55) { flash.cmdState = CmdState::_555_55_1; return true; } FLASH_LOG("Unexpected command %x %x in state aaa_aa_1", offset, data); flash.cmdState = CmdState::INIT; break; case CmdState::_555_55_1: if ((offset & 0xfff) == 0xaaa) { if (data == 0xa0) { flash.cmdState = CmdState::PROGRAM; return true; } else if (data == 0x80) { flash.cmdState = CmdState::AAA_80; return true; } } else if (data == 0x25) { flash.cmdState = CmdState::WRITE_BUF_1; flash.progAddress = offset; return true; } FLASH_LOG("Unexpected command %x %x in state 555_55_1", offset, data); flash.cmdState = CmdState::INIT; break; case CmdState::PROGRAM: FLASH_LOG("Flash cmd PROGRAM %x %x", offset, data); *(u16 *)&RomPtr[offset & (RomSize - 1)] = data; flash.cmdState = CmdState::INIT; return true; case CmdState::WRITE_BUF_1: flash.wordCount = data + 1; flash.cmdState = CmdState::WRITE_BUF_2; FLASH_LOG("Flash cmd WRITE BUFFFER addr %x count %x", flash.progAddress, flash.wordCount); return true; case CmdState::WRITE_BUF_2: *(u16 *)&RomPtr[offset & (RomSize - 1)] = data; if (--flash.wordCount == 0) flash.cmdState = CmdState::INIT; return true; case CmdState::AAA_80: if ((offset & 0xfff) == 0xaaa && data == 0xaa) { flash.cmdState = CmdState::AAA_AA_2; return true; } INFO_LOG(NAOMI, "Unexpected command %x %x in state aaa_80", offset, data); flash.cmdState = CmdState::INIT; break; case CmdState::AAA_AA_2: if (((offset & 0xfff) == 0x555 || (offset & 0xfff) == 0x554) && data == 0x55) { flash.cmdState = CmdState::_555_55_2; return true; } INFO_LOG(NAOMI, "Unexpected command %x %x in state aaa_aa_2", offset, data); flash.cmdState = CmdState::INIT; break; case CmdState::_555_55_2: if ((offset & 0xfff) == 0xaaa && data == 0x10) { // Erase chip FLASH_LOG("Flash cmd CHIP ERASE"); if ((offset & 0x1fffffff) < RomSize) memset(&RomPtr[offset & (0x1fffffff & ~(64_MB - 1))], 0xff, 64_MB); flash.cmdState = CmdState::INIT; return true; } else if (data == 0x30) { // Erase sector FLASH_LOG("Flash cmd SECTOR ERASE %x", offset); if ((offset & 0x1fffffff) < RomSize) memset(&RomPtr[offset & (RomSize - 1) & 0xffff0000], 0xff, 0x1000); // 64k sector size? flash.cmdState = CmdState::INIT; return true; } INFO_LOG(NAOMI, "Unexpected command %x %x in state aaa_aa_2", offset, data); flash.cmdState = CmdState::INIT; break; } FLASH_LOG("SystemSpCart::Write<%d>%x: %x", size, offset, data); return M4Cartridge::Write(offset, size, data); } int SystemSpCart::schedCallback(int tag, int sch_cycl, int jitter, void *arg) { return ((SystemSpCart *)arg)->schedCallback(); } int SystemSpCart::schedCallback() { ata.status.rdy = 1; ata.status.bsy = 0; updateInterrupt(INT_ATA); return 0; } void SystemSpCart::Serialize(Serializer& ser) const { M4Cartridge::Serialize(ser); sh4_sched_serialize(ser, schedId); uart1.serialize(ser); uart2.serialize(ser); eeprom.Serialize(ser); ser << bank; ser << ata.features; ser << ata.cylinder; ser << ata.sectorCount; ser << ata.sectorNumber; ser << ata.status.full; ser << ata.error; ser << ata.driveHead.full; ser << ata.devCtrl.full; ser << ata.interruptPending; ser << ata.reg84; ser << ata.buffer; ser << ata.bufferIndex; ser << flash.cmdState; ser << flash.progAddress; ser << flash.wordCount; if (mediaName != nullptr) ser.serialize(RomPtr, RomSize); } void SystemSpCart::Deserialize(Deserializer& deser) { M4Cartridge::Deserialize(deser); sh4_sched_deserialize(deser, schedId); uart1.deserialize(deser); uart2.deserialize(deser); eeprom.Deserialize(deser); deser >> bank; deser >> ata.features; deser >> ata.cylinder; deser >> ata.sectorCount; deser >> ata.sectorNumber; deser >> ata.status.full; deser >> ata.error; deser >> ata.driveHead.full; deser >> ata.devCtrl.full; deser >> ata.interruptPending; deser >> ata.reg84; deser >> ata.buffer; deser >> ata.bufferIndex; deser >> flash.cmdState; deser >> flash.progAddress; deser >> flash.wordCount; if (mediaName != nullptr) deser.deserialize(RomPtr, RomSize); } void SystemSpCart::saveFiles() { eeprom.Save(getEepromPath()); } // TODO implement actual networking features. Only socket() and close() are currently functional. void SystemSpCart::process() { DmaOffset |= 0x40000000; // needed by BIOS. why? u8 reqId = readNetMem(0x200); int cmd = readNetMem(0x202); DEBUG_LOG(NAOMI, "SystemSpCart::process cmd %03x", cmd); const u8 netmem4 = readNetMem(4); // FIXME memset should be done later on memset(&netmem[0], 0, 32); writeNetMem(0, reqId); writeNetMem(1, (u8)0); switch (cmd) { case 1: // init? nop? INFO_LOG(NAOMI, "process: init/nop"); writeNetMem(2, NET_OK); break; case 0x100: // get dimm status INFO_LOG(NAOMI, "process: get dimm status"); writeNetMem(2, NET_OK); // 0 initializing // 1 checking network // 2 ? // 3 testing prg // 4 loading prg // 5 ready writeNetMem(4, 5); break; case 0x101: // get firmware version INFO_LOG(NAOMI, "process: get firmware version"); writeNetMem(2, NET_OK); writeNetMem(4, (u8)0x25); // minor writeNetMem(5, (u8)1); // major break; case 0x104: // get network type and address INFO_LOG(NAOMI, "process: get network type"); writeNetMem(2, NET_OK); writeNetMem(4, 5); // 4: ether (dhcp), 5: ether (static ip) writeNetMem(0x24, inetaddr); writeNetMem(0x28, netmask); break; case 0x204: // set network type and address // 4: type (0 none, 3,4 ether) inetaddr = readNetMem(0x24); netmask = readNetMem(0x28); INFO_LOG(NAOMI, "process: set net type %d ip %08x mask %08x", netmem4, inetaddr, netmask); writeNetMem(2, NET_OK); break; case 0x301: // network test { // 204: 4 u32 addr = readNetMem(0x208); INFO_LOG(NAOMI, "process: network_test(%x, %x)", readNetMem(0x204), addr); bool isMem; char *p = (char *)addrspace::writeConst(addr, isMem, 4); strcpy(p, "CHECKING NETWORK\n"); p = (char *)addrspace::writeConst(addr + 0x11, isMem, 4); strcpy(p, "PRETENDING... :P\n"); p = (char *)addrspace::writeConst(addr + 0x22, isMem, 4); strcpy(p, "--- COMPLETED---\n"); writeNetMem(2, NET_OK); writeNetMem(4, 1); // TODO how to signal the test progress/completion? } break; case 0x401: // accept INFO_LOG(NAOMI, "process: accept(%d)", readNetMem(0x208)); writeNetMem(2, NET_ERROR); break; case 0x402: // bind { u32 addr = readNetMem(0x20c); u32 len = readNetMem(0x210); sockaddr_in sa{}; memcpy(&sa, &netmem[addr & (sizeof(netmem) - 1)], std::min(len, sizeof(sa))); INFO_LOG(NAOMI, "process: bind(%d, %08x:%d)", readNetMem(0x208), htonl(sa.sin_addr.s_addr), htons(sa.sin_port)); writeNetMem(2, NET_OK); } break; case 0x403: // close socket { const int sockidx = readNetMem(0x208); const sock_t sockfd = sockidx < 1 || sockidx > 0x3f || sockidx > (int)sockets.size() ? INVALID_SOCKET : sockets[sockidx - 1].fd; u16 rc; if (sockfd == INVALID_SOCKET) { INFO_LOG(NAOMI, "process: closesocket(%d) invalid socket", sockidx); rc = NET_ERROR; } else { rc = sockets[sockidx - 1].close() != 0 ? NET_ERROR : NET_OK; INFO_LOG(NAOMI, "process: closesocket(%d) %d -> %x", sockidx, sockfd, rc); } writeNetMem(2, rc); } break; case 0x404: // connect { u32 addr = readNetMem(0x20c); u32 len = readNetMem(0x210); sockaddr_in sa{}; memcpy(&sa, &netmem[addr & (sizeof(netmem) - 1)], std::min(len, sizeof(sa))); INFO_LOG(NAOMI, "process: connect(%d, %08x:%d)", readNetMem(0x208), htonl(sa.sin_addr.s_addr), htons(sa.sin_port)); writeNetMem(2, NET_OK); } break; case 0x406: // inet_addr { u32 addr = readNetMem(0x208); //u32 len = readNetMem(0x20c); const char *s = (const char *)&netmem[addr & (sizeof(netmem) - 1)]; INFO_LOG(NAOMI, "process: inet_addr(%s)", s); u32 ipaddr = inet_addr(s); writeNetMem(2, NET_OK); writeNetMem(4, ipaddr); } break; case 0x408: // listen INFO_LOG(NAOMI, "process: listen(%d)", readNetMem(0x208)); // backlog??? or 208 is backlog and 201 is sockfd? writeNetMem(2, NET_OK); break; case 0x409: // recv { u32 addr = readNetMem(0x20c); u32 len = readNetMem(0x210); INFO_LOG(NAOMI, "process: recv(%d, %x, %x)", readNetMem(0x208), addr, len); writeNetMem(2, NET_OK); //writeNetMem(4, len); // ?? } break; case 0x40a: // send { u32 addr = readNetMem(0x20c); u32 len = readNetMem(0x210); INFO_LOG(NAOMI, "process: send(%d, %x, %x)", readNetMem(0x208), addr, len); writeNetMem(2, NET_OK); writeNetMem(4, len); // ?? } break; case 0x40b: // openSocket { int domain = readNetMem(0x208); int type = readNetMem(0x20c); int protocol = readNetMem(0x210); const sock_t fd = socket(domain, type, protocol); int sockidx = -1; if (fd != INVALID_SOCKET) { // TODO? //set_non_blocking(fd); size_t i = 0; for (; i < sockets.size(); i++) if (sockets[i].fd == INVALID_SOCKET) break; if (i == sockets.size()) sockets.emplace_back(fd); else sockets[i].fd = fd; sockidx = i + 1; } INFO_LOG(NAOMI, "process: openSocket(%d, %d, %d) %d -> %d", domain, type, protocol, fd, sockidx); writeNetMem(2, sockidx != -1 ? NET_OK : NET_ERROR); if (sockidx != -1) writeNetMem(4, sockidx); } break; case 0x40e: // setsockopt { // TODO socket option level/name are different (mips linux?) u32 addr = readNetMem(0x214); //u32 len = readNetMem(0x218); INFO_LOG(NAOMI, "process: setsockopt(%d, level: %x, name: %x, value:%x)", readNetMem(0x208), readNetMem(0x20c), readNetMem(0x210), readNetMem(addr)); writeNetMem(2, NET_OK); } break; case 0x410: // settimeout INFO_LOG(NAOMI, "process: settimeout(%d, %d, %d, %d)", readNetMem(0x208), readNetMem(0x20c), readNetMem(0x210), readNetMem(0x214)); writeNetMem(2, NET_OK); break; case 0x411: // geterrno INFO_LOG(NAOMI, "process: getterrno(%d)", readNetMem(0x208)); writeNetMem(2, NET_OK); writeNetMem(4, 111); // FIXME errno values TBD break; case 0x414: // getParambyDHCP? INFO_LOG(NAOMI, "process: getParambyDHCP"); writeNetMem(2, NET_OK); writeNetMem(4, 1); // ??? break; case 0x415: // modifyMyIPaddr? { u32 addr = readNetMem(0x208); //u32 len = readNetMem(0x20c); INFO_LOG(NAOMI, "process: modifyMyIPaddr(%s, %08x)", &netmem[addr & (sizeof(netmem) - 1)], readNetMem(0x210)); writeNetMem(2, NET_OK); } break; default: WARN_LOG(NAOMI, "Unknown netdimm command: %x", cmd); writeNetMem(2, NET_ERROR); break; } updateInterrupt(INT_DIMM); } }