flycast/core/hw/naomi/systemsp.cpp

2683 lines
69 KiB
C++

/*
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 <https://www.gnu.org/licenses/>.
*/
// 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 <cerrno>
#include <deque>
#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<u8> toSend;
std::array<u8, 128> cardData;
u8 expectedBytes = 0;
std::vector<u8> 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<u8, 19> 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<u8, 8> 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<u8> toSend;
std::vector<u8> 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<u64 On, u64 Period>
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<u64 Width>
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<typename T>
T readMemArea0(u32 addr)
{
verify(SystemSpCart::Instance != nullptr);
return SystemSpCart::Instance->readMemArea0<T>(addr);
}
template u32 readMemArea0<>(u32 addr);
template u16 readMemArea0<>(u32 addr);
template u8 readMemArea0<>(u32 addr);
template<typename T>
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<u8>(addr);
ret |= readMemArea0<u8>(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<typename T>
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<typename T>
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<u8[]>(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<u8[]>(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<u8[]> buffer = std::make_unique<u8[]>(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<u8[]>(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<u8[]> 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<u8> *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<IsshoniIOManager>();
}
else if (!strcmp(game->name, "manpuku")) {
ioPortManager = std::make_unique<ManpukuIOManager>();
}
else if (!strncmp(game->name, "kingyo", 6) || !strcmp(game->name, "shateki")) {
ioPortManager = std::make_unique<KingyoIOManager>();
}
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<MedalIOManager>();
}
if (!strncmp(game->name, "dinoki", 6) || !strncmp(game->name, "loveber", 7))
ioPortManager = std::make_unique<CardReaderIOManager>();
if (!ioPortManager)
ioPortManager = std::make_unique<DefaultIOManager>();
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<u32>(offset);
break;
case 2:
*(u16 *)dst = readNetMem<u16>(offset);
break;
case 1:
default:
*(u8 *)dst = readNetMem<u8>(offset);
break;
}
u16 d = readNetMem<u16>(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<u8>(0x200);
int cmd = readNetMem<u16>(0x202);
DEBUG_LOG(NAOMI, "SystemSpCart::process cmd %03x", cmd);
const u8 netmem4 = readNetMem<u8>(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<u32>(0x24);
netmask = readNetMem<u32>(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<u32>(0x208);
INFO_LOG(NAOMI, "process: network_test(%x, %x)", readNetMem<u32>(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<u32>(0x208));
writeNetMem(2, NET_ERROR);
break;
case 0x402: // bind
{
u32 addr = readNetMem<u32>(0x20c);
u32 len = readNetMem<u32>(0x210);
sockaddr_in sa{};
memcpy(&sa, &netmem[addr & (sizeof(netmem) - 1)], std::min<size_t>(len, sizeof(sa)));
INFO_LOG(NAOMI, "process: bind(%d, %08x:%d)", readNetMem<u32>(0x208), htonl(sa.sin_addr.s_addr), htons(sa.sin_port));
writeNetMem(2, NET_OK);
}
break;
case 0x403: // close socket
{
const int sockidx = readNetMem<int>(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<u32>(0x20c);
u32 len = readNetMem<u32>(0x210);
sockaddr_in sa{};
memcpy(&sa, &netmem[addr & (sizeof(netmem) - 1)], std::min<size_t>(len, sizeof(sa)));
INFO_LOG(NAOMI, "process: connect(%d, %08x:%d)", readNetMem<u32>(0x208), htonl(sa.sin_addr.s_addr), htons(sa.sin_port));
writeNetMem(2, NET_OK);
}
break;
case 0x406: // inet_addr
{
u32 addr = readNetMem<u32>(0x208);
//u32 len = readNetMem<u32>(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<u32>(0x208)); // backlog??? or 208 is backlog and 201 is sockfd?
writeNetMem(2, NET_OK);
break;
case 0x409: // recv
{
u32 addr = readNetMem<u32>(0x20c);
u32 len = readNetMem<u32>(0x210);
INFO_LOG(NAOMI, "process: recv(%d, %x, %x)", readNetMem<u32>(0x208), addr, len);
writeNetMem(2, NET_OK);
//writeNetMem(4, len); // ??
}
break;
case 0x40a: // send
{
u32 addr = readNetMem<u32>(0x20c);
u32 len = readNetMem<u32>(0x210);
INFO_LOG(NAOMI, "process: send(%d, %x, %x)", readNetMem<u32>(0x208), addr, len);
writeNetMem(2, NET_OK);
writeNetMem(4, len); // ??
}
break;
case 0x40b: // openSocket
{
int domain = readNetMem<int>(0x208);
int type = readNetMem<int>(0x20c);
int protocol = readNetMem<int>(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<u32>(0x214);
//u32 len = readNetMem<u32>(0x218);
INFO_LOG(NAOMI, "process: setsockopt(%d, level: %x, name: %x, value:%x)", readNetMem<u32>(0x208), readNetMem<u32>(0x20c), readNetMem<u32>(0x210),
readNetMem<u32>(addr));
writeNetMem(2, NET_OK);
}
break;
case 0x410: // settimeout
INFO_LOG(NAOMI, "process: settimeout(%d, %d, %d, %d)", readNetMem<u32>(0x208), readNetMem<u32>(0x20c), readNetMem<u32>(0x210), readNetMem<u32>(0x214));
writeNetMem(2, NET_OK);
break;
case 0x411: // geterrno
INFO_LOG(NAOMI, "process: getterrno(%d)", readNetMem<u32>(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<u32>(0x208);
//u32 len = readNetMem<u32>(0x20c);
INFO_LOG(NAOMI, "process: modifyMyIPaddr(%s, %08x)", &netmem[addr & (sizeof(netmem) - 1)], readNetMem<u32>(0x210));
writeNetMem(2, NET_OK);
}
break;
default:
WARN_LOG(NAOMI, "Unknown netdimm command: %x", cmd);
writeNetMem(2, NET_ERROR);
break;
}
updateInterrupt(INT_DIMM);
}
}