2683 lines
69 KiB
C++
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);
|
|
}
|
|
|
|
}
|