From 4efacd7d714c74bce701edd47819bcc788be0bc1 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 23 Nov 2023 16:40:20 +0100 Subject: [PATCH] Battle cable support work in progress f355 and aero dancing seem to work vonot and tetris don't maxspeed is broken --- core/cfg/option.cpp | 1 + core/cfg/option.h | 1 + core/emulator.cpp | 1 + core/hw/hwreg.h | 4 + core/hw/naomi/hopper.cpp | 1 + core/hw/naomi/touchscreen.cpp | 1 + core/hw/sh4/modules/modules.h | 44 ++- core/hw/sh4/modules/serial.cpp | 527 ++++++++++++++++++++++++----- core/hw/sh4/sh4_mmr.cpp | 2 + core/hw/sh4/sh4_mmr.h | 74 +--- core/hw/sh4/sh4_sched.cpp | 5 + core/hw/sh4/sh4_sched.h | 7 +- core/network/net_handshake.cpp | 12 +- core/network/net_serial_maxspeed.h | 7 +- core/network/null_modem.h | 271 +++++++++++++++ core/rend/gui.cpp | 140 ++++++-- core/serialize.h | 3 +- shell/libretro/option.cpp | 1 + tests/src/serialize_test.cpp | 2 +- 19 files changed, 907 insertions(+), 197 deletions(-) create mode 100644 core/network/null_modem.h diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index 48e98f3327..d39ccbfb6d 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -155,6 +155,7 @@ Option GGPOChatTimeoutToggle("GGPOChatTimeoutToggle", true, "network"); Option GGPOChatTimeout("GGPOChatTimeout", 10, "network"); Option NetworkOutput("NetworkOutput", false, "network"); Option MultiboardSlaves("MultiboardSlaves", 1, "network"); +Option BattleCableEnable("BattleCable", false, "network"); #ifdef SUPPORT_DISPMANX Option DispmanxMaintainAspect("maintain_aspect", true, "dispmanx"); diff --git a/core/cfg/option.h b/core/cfg/option.h index 8b382c3087..65d41f3948 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -521,6 +521,7 @@ extern Option GGPOChatTimeoutToggle; extern Option GGPOChatTimeout; extern Option NetworkOutput; extern Option MultiboardSlaves; +extern Option BattleCableEnable; #ifdef SUPPORT_DISPMANX extern Option DispmanxMaintainAspect; diff --git a/core/emulator.cpp b/core/emulator.cpp index cf2b65b281..38a14c942d 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -834,6 +834,7 @@ void Emulator::start() Get_Sh4Interpreter(&sh4_cpu); INFO_LOG(DYNAREC, "Using Interpreter"); } + setupPtyPipe(); memwatch::protect(); diff --git a/core/hw/hwreg.h b/core/hw/hwreg.h index cc6bdb8cdf..902bdf5b67 100644 --- a/core/hw/hwreg.h +++ b/core/hw/hwreg.h @@ -300,6 +300,7 @@ class SerialPort public: // Serial TX virtual void write(u8 data) { } + virtual void sendBreak() { } // RX buffer Size virtual int available() { return 0; } // Serial RX @@ -310,6 +311,9 @@ class SerialPort virtual void setPipe(Pipe *pipe) = 0; virtual void updateStatus() = 0; + virtual void receiveBreak() { + die("unsupported"); + } virtual ~SerialPort() = default; }; diff --git a/core/hw/naomi/hopper.cpp b/core/hw/naomi/hopper.cpp index 523c2dd6f5..3fc057eb43 100644 --- a/core/hw/naomi/hopper.cpp +++ b/core/hw/naomi/hopper.cpp @@ -1287,6 +1287,7 @@ void init() void term() { + SCIFSerialPort::Instance().setPipe(nullptr); delete hopper; hopper = nullptr; } diff --git a/core/hw/naomi/touchscreen.cpp b/core/hw/naomi/touchscreen.cpp index 53de119281..4d2f55a996 100644 --- a/core/hw/naomi/touchscreen.cpp +++ b/core/hw/naomi/touchscreen.cpp @@ -45,6 +45,7 @@ class TouchscreenPipe final : public SerialPort::Pipe ~TouchscreenPipe() { + SCIFSerialPort::Instance().setPipe(nullptr); sh4_sched_unregister(schedId); } diff --git a/core/hw/sh4/modules/modules.h b/core/hw/sh4/modules/modules.h index 4b6e56f8a8..d7dfed99a7 100644 --- a/core/hw/sh4/modules/modules.h +++ b/core/hw/sh4/modules/modules.h @@ -1,5 +1,6 @@ #pragma once #include "hw/hwreg.h" +#include extern u32 UBC[9]; extern u32 BSC[19]; @@ -99,16 +100,47 @@ class SCIFSerialPort : public SerialPort void setPipe(Pipe *pipe) override { this->pipe = pipe; } - void updateStatus() override; + Pipe *getPipe() const { + return pipe; + } + void updateStatus() override {} + void receiveBreak() override; + void init(); + void term(); + void serialize(Serializer& ser); + void deserialize(Deserializer& deser); - static u8 readData(u32 addr); - static void writeData(u32 addr, u8 data); - static u16 readStatus(u32 addr); - static void writeStatus(u32 addr, u16 data); - static u16 readSCFDR2(u32 addr); + u8 SCFRDR2_read(); + void SCFTDR2_write(u8 data); + u16 readStatus(); + void writeStatus(u16 data); + u16 SCFDR2_read(); + static u16 SCFCR2_read(u32 addr); + void SCFCR2_write(u16 data); + void SCSPTR2_write(u16 data); + static void SCBRR2_write(u32 addr, u8 data); + static void SCSMR2_write(u32 addr, u16 data); + static void SCSCR2_write(u32 addr, u16 data); static SCIFSerialPort& Instance(); private: + void updateBaudRate(); + void setBreak(bool on); + void sendBreak(); + bool txDone(); + void rxSched(); + static int schedCallback(int tag, int cycles, int lag, void *arg); + Pipe *pipe = nullptr; + int schedId = -1; + int brkSchedId = -1; + int frameSize = 10; // default 8 data bits, 1 stop bit, no parity + int cyclesPerBit = SH4_MAIN_CLOCK / 6103; + u16 statusLastRead = 0; + std::deque txFifo; + std::deque rxFifo; + bool transmitting = false; }; + +void setupPtyPipe(); diff --git a/core/hw/sh4/modules/serial.cpp b/core/hw/sh4/modules/serial.cpp index 39421b4b85..26c11fdd81 100644 --- a/core/hw/sh4/modules/serial.cpp +++ b/core/hw/sh4/modules/serial.cpp @@ -1,6 +1,5 @@ /* Dreamcast serial port. - This is missing most of the functionality, but works for KOS (And thats all that uses it) */ #include #include @@ -17,6 +16,16 @@ #include "hw/sh4/sh4_interrupts.h" #include "cfg/option.h" #include "modules.h" +#include "hw/sh4/sh4_sched.h" +#include "serialize.h" + +//#define DEBUG_SCIF + +#ifdef DEBUG_SCIF +#define SCIF_LOG(...) INFO_LOG(SH4, __VA_ARGS__) +#else +#define SCIF_LOG(...) +#endif SCIRegisters sci; SCIFRegisters scif; @@ -28,77 +37,189 @@ static void updateInterrupts() InterruptPend(sh4_SCIF_RXI, SCIF_SCFSR2.RDF || SCIF_SCFSR2.DR); InterruptMask(sh4_SCIF_RXI, SCIF_SCSCR2.RIE); -} -// SCIF SCFTDR2 -void SCIFSerialPort::writeData(u32 addr, u8 data) -{ - //DEBUG_LOG(COMMON, "serial out %02x %c", data, data); - if (Instance().pipe != nullptr) - Instance().pipe->write(data); + InterruptPend(sh4_SCIF_BRI, SCIF_SCFSR2.BRK); + InterruptMask(sh4_SCIF_BRI, SCIF_SCSCR2.RIE || SCIF_SCSCR2.REIE); - SCIF_SCFSR2.TDFE = 1; - SCIF_SCFSR2.TEND = 1; + InterruptPend(sh4_SCIF_ERI, SCIF_SCFSR2.ER || SCIF_SCFSR2.FER || SCIF_SCFSR2.PER); + InterruptMask(sh4_SCIF_ERI, SCIF_SCSCR2.RIE || SCIF_SCSCR2.REIE); +} - updateInterrupts(); +int SCIFSerialPort::schedCallback(int tag, int cycles, int lag, void *arg) +{ + SCIFSerialPort& scif = *(SCIFSerialPort *)arg; + if (tag == 0) + { + bool reschedule = scif.txDone(); + scif.rxSched(); + if (reschedule || scif.pipe != nullptr) + return scif.frameSize * scif.cyclesPerBit; + else + return 0; + } + else + { + scif.sendBreak(); + return 0; + } } -// SCIF_SCFSR2 read -u16 SCIFSerialPort::readStatus(u32 addr) +bool SCIFSerialPort::txDone() { - Instance().updateStatus(); - return SCIF_SCFSR2.full; + if (!transmitting || SCIF_SCFCR2.TFRST == 1) + return false; + if (txFifo.empty()) + { + SCIF_SCFSR2.TEND = 1; + SCIF_SCFSR2.TDFE = 1; // should not be set since the tx fifo hasn't changed but vonot needs it + updateInterrupts(); + transmitting = false; + return false; // don't reschedule + } + u8 v = txFifo.front(); + txFifo.pop_front(); + if (pipe != nullptr) + pipe->write(v); + u32 txTrigger = 1 << (3 - SCIF_SCFCR2.TTRG); + if (txFifo.size() <= txTrigger) { + SCIF_SCFSR2.TDFE = 1; + updateInterrupts(); + } + return true; } -// SCIF_SCFSR2 write -void SCIFSerialPort::writeStatus(u32 addr, u16 data) +void SCIFSerialPort::rxSched() { - if (!SCIF_SCFSR2.BRK) - data &= ~0x10; + if (pipe == nullptr) + return; + // FIXME fifo size checked to avoid overruns but incorrect + if (rxFifo.size() >= 16) + { + SCIF_SCFSR2.RDF = 1; + updateInterrupts(); + return; + } - SCIF_SCFSR2.full = data & 0x00f3; + if (pipe->available() > 0) + { + u8 v = pipe->read(); + if (SCIF_SCSCR2.RE == 0 || SCIF_SCFCR2.RFRST == 1) + return; + if (rxFifo.size() == 16) + { + // rx overrun + SCIF_SCLSR2.ORER = 1; + updateInterrupts(); + WARN_LOG(SH4, "scif: Receive overrun"); + } + else + { + rxFifo.push_back(v); + constexpr u32 trigLevels[] { 1, 4, 8, 14 }; + if (rxFifo.size() >= trigLevels[SCIF_SCFCR2.RTRG]) { + SCIF_SCFSR2.RDF = 1; + updateInterrupts(); + } + } + } + // TODO fifo might have been emptied since last rx + else if (!rxFifo.empty()) + { + SCIF_SCFSR2.DR = 1; + updateInterrupts(); + } +} - SCIF_SCFSR2.TDFE = 1; - SCIF_SCFSR2.TEND = 1; +void SCIFSerialPort::updateBaudRate() +{ + // 1 start bit, 7 or 8 data bits, optional parity bit, 1 or 2 stop bits + frameSize = 1 + 8 - SCIF_SCSMR2.CHR + SCIF_SCSMR2.PE + 1 + SCIF_SCSMR2.STOP; + int bauds = SH4_MAIN_CLOCK / 4 / (SCIF_SCBRR2 + 1) / 32 / (1 << (SCIF_SCSMR2.CKS * 2)); + cyclesPerBit = SH4_MAIN_CLOCK / bauds; + INFO_LOG(SH4, "SCIF: Frame size %d cycles/bit %d (%d bauds)", frameSize, cyclesPerBit, bauds); + if (sh4_sched_is_scheduled(schedId)) + sh4_sched_request(schedId, frameSize * cyclesPerBit); +} - Instance().updateStatus(); +// SCIF SCFTDR2 - Transmit FIFO Data Register +void SCIFSerialPort::SCFTDR2_write(u8 data) +{ + SCIF_LOG("serial out %02x %c fifo_sz %d", data, data == '\0' ? ' ' : data, (int)txFifo.size()); + if (SCIF_SCFCR2.TFRST == 1) + return; + if (SCIF_SCSMR2.CHR == 1) + data &= 0x7f; + if (txFifo.empty() && !transmitting && SCIF_SCSCR2.TE == 1) + { + if (pipe != nullptr) + pipe->write(data); + transmitting = true; + // Need to reschedule so it's doesn't happen too early (f355) + sh4_sched_request(schedId, frameSize * cyclesPerBit); + SCIF_SCFSR2.TDFE = 1; // immediately transfer SCFTDR2 into the shift register + updateInterrupts(); + } + else if (txFifo.size() < 16) { + txFifo.push_back(data); + } } -//SCIF_SCFDR2 - 16b -u16 SCIFSerialPort::readSCFDR2(u32 addr) +// SCIF_SCFSR2 read - Serial Status Register +u16 SCIFSerialPort::readStatus() { - if (Instance().pipe != nullptr) - return std::min(16, Instance().pipe->available()); - else - return 0; +// SCIF_LOG("SCIF_SCFSR2.read %s%s%s%s%s%s%s%s", +// SCIF_SCFSR2.ER ? "ER " : "", +// SCIF_SCFSR2.TEND ? "TEND " : "", +// SCIF_SCFSR2.TDFE ? "TDFE " : "", +// SCIF_SCFSR2.BRK ? "BRK " : "", +// SCIF_SCFSR2.FER ? "FER " : "", +// SCIF_SCFSR2.PER ? "PER " : "", +// SCIF_SCFSR2.RDF ? "RDF " : "", +// SCIF_SCFSR2.DR ? "DR" : ""); + statusLastRead = SCIF_SCFSR2.full; + return SCIF_SCFSR2.full; } -//SCIF_SCFRDR2 -u8 SCIFSerialPort::readData(u32 addr) +// SCIF_SCFSR2 write - Serial Status Register +void SCIFSerialPort::writeStatus(u16 data) { - u8 data = 0; - if (Instance().pipe != nullptr) { - data = Instance().pipe->read(); - //DEBUG_LOG(COMMON, "serial in %02x %c", data, data); - } - Instance().updateStatus(); + data = data | ~0x00f3 | ~statusLastRead; + SCIF_LOG("SCIF_SCFSR2.reset %s%s%s%s%s%s%s%s", + (data & 0x80) ? "" : "ER ", + (data & 0x40) ? "" : "TEND ", + (data & 0x20) ? "" : "TDFE ", + (data & 0x10) ? "" : "BRK ", + (data & 0x08) ? "" : "FER ", + (data & 0x04) ? "" : "PER ", + (data & 0x02) ? "" : "RDF ", + (data & 0x01) ? "" : "DR"); + + SCIF_SCFSR2.full &= data; + statusLastRead &= data; - return data; + updateInterrupts(); } -void SCIFSerialPort::updateStatus() +//SCIF_SCFDR2 - FIFO Data Count Register +u16 SCIFSerialPort::SCFDR2_read() { - if (pipe == nullptr) - return; + u16 rv = rxFifo.size() | (txFifo.size() << 8); + SCIF_LOG("SCIF: fifo count rx %d tx %d", rv & 0xff, rv >> 8); - constexpr int trigLevels[] { 1, 4, 8, 14 }; - int avail = pipe->available(); + return rv; +} - if (avail >= trigLevels[SCIF_SCFCR2.RTRG1 * 2 + SCIF_SCFCR2.RTRG0]) - SCIF_SCFSR2.RDF = 1; - if (avail >= 1) - SCIF_SCFSR2.DR = 1; - updateInterrupts(); +//SCIF_SCFRDR2 - Receive FIFO Data Register +u8 SCIFSerialPort::SCFRDR2_read() +{ + if (rxFifo.empty()) { + WARN_LOG(SH4, "Empty rx fifo read"); + return 0; + } + u8 data = rxFifo.front(); + rxFifo.pop_front(); + SCIF_LOG("serial in %02x %c", data, data); + return data; } SCIFSerialPort& SCIFSerialPort::Instance() @@ -108,25 +229,210 @@ SCIFSerialPort& SCIFSerialPort::Instance() return instance; } -//SCSCR2 - +//SCSCR2 - Serial Control Register static u16 SCSCR2_read(u32 addr) { return SCIF_SCSCR2.full; } -static void SCSCR2_write(u32 addr, u16 data) +void SCIFSerialPort::SCSCR2_write(u32 addr, u16 data) { SCIF_SCSCR2.full = data & 0x00fa; + if (SCIF_SCSCR2.TE == 0) + { + SCIF_SCFSR2.TEND = 1; + //SCIF_SCFSR2.TDFE = 1; // TODO not sure about this one + // TE must be cleared to send a break + Instance().setBreak(SCIF_SCSPTR2.SPB2IO == 1 && SCIF_SCSPTR2.SPB2DT == 0); + } + else { + Instance().setBreak(false); + } + updateInterrupts(); + SCIF_LOG("SCIF_SCSCR2= %s%s%s%s%s", + SCIF_SCSCR2.TIE ? "TIE " : "", + SCIF_SCSCR2.RIE ? "RIE " : "", + SCIF_SCSCR2.TE ? "TE " : "", + SCIF_SCSCR2.RE ? "RE " : "", + SCIF_SCSCR2.REIE ? "REIE" : ""); +} + +// SCSPTR2 - Serial Port Register +static u16 SCSPTR2_read(u32 addr) +{ + SCIF_LOG("SCIF_SCSPTR2.read %x", SCIF_SCSPTR2.full); + return SCIF_SCSPTR2.full & ~0x10; // CTS active/low +} + +void SCIFSerialPort::setBreak(bool on) +{ + if (on) { + // tetris needs to send/receive breaks + if (!sh4_sched_is_scheduled(brkSchedId)) + sh4_sched_request(brkSchedId, cyclesPerBit * frameSize); + } + else { + if (sh4_sched_is_scheduled(brkSchedId)) + sh4_sched_request(brkSchedId, -1); + } +} + +void SCIFSerialPort::SCSPTR2_write(u16 data) +{ + SCIF_SCSPTR2.full = data & 0x00f3; + if (SCIF_SCSPTR2.SPB2IO == 1) + setBreak(SCIF_SCSPTR2.SPB2DT == 0 && SCIF_SCSCR2.TE == 0); + else + setBreak(false); + + SCIF_LOG("SCIF_SCSPTR2= %s%s%s%s%s%s", + SCIF_SCSPTR2.RTSIO ? "RTSIO " : "", + SCIF_SCSPTR2.RTSDT ? "RTSDT " : "", + SCIF_SCSPTR2.CTSIO ? "CTSIO " : "", + SCIF_SCSPTR2.CTSDT ? "CTSDT " : "", + SCIF_SCSPTR2.SPB2IO ? "SPB2IO " : "", + SCIF_SCSPTR2.SPB2DT ? "SPB2DT" : ""); +} +// SCFCR2 - FIFO Control Register +u16 SCIFSerialPort::SCFCR2_read(u32 addr) +{ +// SCIF_LOG("SCIF_SCFCR2.read %x", SCIF_SCFCR2.full); + return SCIF_SCFCR2.full; +} + +void SCIFSerialPort::SCFCR2_write(u16 data) +{ + if (SCIF_SCFCR2.TFRST == 1 && !(data & 4)) + { + // when TFRST 1 -> 0 + // seems to help tetris send data during sync + SCIF_SCFSR2.TEND = 1; + SCIF_SCFSR2.TDFE = 1; + updateInterrupts(); + } + SCIF_SCFCR2.full = data & 0x00ff; + if (SCIF_SCFCR2.TFRST == 1) + { + txFifo.clear(); + if (pipe == nullptr) + sh4_sched_request(schedId, -1); + transmitting = false; + } + if (SCIF_SCFCR2.RFRST == 1) + rxFifo.clear(); + SCIF_LOG("SCIF_SCFCR2= %s%s%sTTRG %d RTRG %d", + SCIF_SCFCR2.RFRST ? "RFRST " : "", + SCIF_SCFCR2.TFRST ? "TFRST " : "", + SCIF_SCFCR2.MCE ? "MCE " : "", + SCIF_SCFCR2.TTRG, + SCIF_SCFCR2.RTRG); +} + +// SCBRR2 - Bit Rate Register +void SCIFSerialPort::SCBRR2_write(u32 addr, u8 data) +{ + SCIF_SCBRR2 = data; + Instance().updateBaudRate(); +} + +// SCSMR2 - Serial Mode Register +void SCIFSerialPort::SCSMR2_write(u32 addr, u16 data) +{ + SCIF_SCSMR2.full = data & 0x007b; + Instance().updateBaudRate(); +} + +void SCIFSerialPort::receiveBreak() +{ + SCIF_LOG("Break received"); + SCIF_SCFSR2.BRK = 1; updateInterrupts(); } +void SCIFSerialPort::sendBreak() +{ + if (pipe != nullptr) + pipe->sendBreak(); +} + + +void SCIFSerialPort::init() +{ + if (schedId == -1) + schedId = sh4_sched_register(0, schedCallback, this); + if (brkSchedId == -1) + brkSchedId = sh4_sched_register(1, schedCallback, this); +} + +void SCIFSerialPort::term() +{ + if (schedId != -1) { + sh4_sched_unregister(schedId); + schedId = -1; + } + if (brkSchedId != -1) { + sh4_sched_unregister(brkSchedId); + brkSchedId = -1; + } +} + +void SCIFSerialPort::serialize(Serializer& ser) +{ + sh4_sched_serialize(ser, schedId); + sh4_sched_serialize(ser, brkSchedId); + ser << statusLastRead; + ser << (int)txFifo.size(); + for (u8 b : txFifo) + ser << b; + ser << (int)rxFifo.size(); + for (u8 b : txFifo) + ser << b; + ser << transmitting; +} + +void SCIFSerialPort::deserialize(Deserializer& deser) +{ + txFifo.clear(); + rxFifo.clear(); + if (deser.version() >= Deserializer::V43) + { + sh4_sched_deserialize(deser, schedId); + sh4_sched_deserialize(deser, brkSchedId); + deser >> statusLastRead; + int size; + deser >> size; + for (int i = 0; i < size; i++) + { + u8 b; + deser >> b; + txFifo.push_back(b); + } + deser >> size; + for (int i = 0; i < size; i++) + { + u8 b; + deser >> b; + rxFifo.push_back(b); + } + deser >> transmitting; + } + else + { + statusLastRead = 0; + transmitting = false; + } + updateBaudRate(); +} + struct PTYPipe : public SerialPort::Pipe { - void write(u8 data) override { - if (config::SerialConsole) - ::write(tty, &data, 1); + void write(u8 data) override + { + if (config::SerialConsole) { + int rc = ::write(tty, &data, 1); + (void)rc; + } } int available() override { @@ -138,10 +444,13 @@ struct PTYPipe : public SerialPort::Pipe return count; } - u8 read() override { + u8 read() override + { u8 data = 0; - if (tty != 1) - ::read(tty, &data, 1); + if (tty != 1) { + int rc = ::read(tty, &data, 1); + (void)rc; + } return data; } @@ -189,12 +498,46 @@ struct PTYPipe : public SerialPort::Pipe ::close(tty); tty = 1; } + SCIFSerialPort::Instance().setPipe(nullptr); } private: int tty = 1; }; -static PTYPipe ptyPipe; + +void setupPtyPipe() +{ + static PTYPipe ptyPipe; + + if (config::SerialConsole || config::SerialPTY) + { + if (SCIFSerialPort::Instance().getPipe() == nullptr) + ptyPipe.init(); + } + else + { + if (SCIFSerialPort::Instance().getPipe() == &ptyPipe) + ptyPipe.term(); + } +} + +template +class SingletonForward { + +}; + +template +struct SingletonForward +{ + static Ret(*forward(Ret(*function)(u32 addr, Args...)))(u32 addr, Args...) { + return function; + } +}; + +#define SINGLETON_FORWARD(accessor, function) \ + SingletonForward::type::function)>::forward([](u32 addr, auto... args) { \ + return accessor.function(args...); \ + }) //Init term res void SCIFRegisters::init() @@ -204,54 +547,41 @@ void SCIFRegisters::init() // Serial Communication Interface with FIFO //SCIF SCSMR2 0xFFE80000 0x1FE80000 16 0x0000 0x0000 Held Held Pclk - setRW(); + setWriteHandler(SCIFSerialPort::SCSMR2_write); //SCIF SCBRR2 0xFFE80004 0x1FE80004 8 0xFF 0xFF Held Held Pclk - setRW(); + setWriteHandler(SCIFSerialPort::SCBRR2_write); //SCIF SCSCR2 0xFFE80008 0x1FE80008 16 0x0000 0x0000 Held Held Pclk - setHandlers(SCSCR2_read, SCSCR2_write); + setHandlers(SCSCR2_read, SCIFSerialPort::SCSCR2_write); //SCIF SCFTDR2 0xFFE8000C 0x1FE8000C 8 Undefined Undefined Held Held Pclk - setWriteOnly(SCIFSerialPort::writeData); + setWriteOnly(SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFTDR2_write)); //SCIF SCFSR2 0xFFE80010 0x1FE80010 16 0x0060 0x0060 Held Held Pclk - setHandlers(SCIFSerialPort::readStatus, SCIFSerialPort::writeStatus); + setHandlers(SINGLETON_FORWARD(SCIFSerialPort::Instance(), readStatus), + SINGLETON_FORWARD(SCIFSerialPort::Instance(), writeStatus)); //READ only //SCIF SCFRDR2 0xFFE80014 0x1FE80014 8 Undefined Undefined Held Held Pclk - setReadOnly(SCIFSerialPort::readData); + setReadOnly(SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFRDR2_read)); //SCIF SCFCR2 0xFFE80018 0x1FE80018 16 0x0000 0x0000 Held Held Pclk - setRW(); + setHandlers(SCIFSerialPort::SCFCR2_read, SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFCR2_write)); //Read only //SCIF SCFDR2 0xFFE8001C 0x1FE8001C 16 0x0000 0x0000 Held Held Pclk - setReadOnly(SCIFSerialPort::readSCFDR2); + setReadOnly(SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCFDR2_read)); //SCIF SCSPTR2 0xFFE80020 0x1FE80020 16 0x0000 0x0000 Held Held Pclk - setRW(); + setHandlers(SCSPTR2_read, SINGLETON_FORWARD(SCIFSerialPort::Instance(), SCSPTR2_write)); //SCIF SCLSR2 0xFFE80024 0x1FE80024 16 0x0000 0x0000 Held Held Pclk setRW(); - reset(true); -} - -void SCIRegisters::init() -{ - super::init(); + SCIFSerialPort::Instance().init(); - // Serial Communication Interface - setRW(); - setRW(); - setRW(); - setRW(); - setRW(); - setReadOnly(); - setRW(); - - reset(); + reset(true); } void SCIFRegisters::reset(bool hard) @@ -274,7 +604,30 @@ void SCIFRegisters::reset(bool hard) SCIF_SCFSR2.full = 0x060; if (hard) - ptyPipe.init(); + SCIFSerialPort::Instance().setPipe(nullptr); +} + +void SCIFRegisters::term() +{ + SCIFSerialPort::Instance().term(); + + super::term(); +} + +void SCIRegisters::init() +{ + super::init(); + + // Serial Communication Interface + setRW(); + setRW(); + setRW(); + setRW(); + setRW(); + setReadOnly(); + setRW(); + + reset(); } void SCIRegisters::reset() @@ -285,9 +638,3 @@ void SCIRegisters::reset() SCI_SCTDR1 = 0xff; SCI_SCSSR1 = 0x84; } - -void SCIFRegisters::term() -{ - super::term(); - ptyPipe.term(); -} diff --git a/core/hw/sh4/sh4_mmr.cpp b/core/hw/sh4/sh4_mmr.cpp index ff408873df..8cd75bd656 100644 --- a/core/hw/sh4/sh4_mmr.cpp +++ b/core/hw/sh4/sh4_mmr.cpp @@ -684,6 +684,7 @@ void serialize(Serializer& ser) ser << TMU; ser << SCI; ser << SCIF; + SCIFSerialPort::Instance().serialize(ser); icache.Serialize(ser); ocache.Serialize(ser); @@ -739,6 +740,7 @@ void deserialize(Deserializer& deser) deser >> SCI; deser >> SCIF; } + SCIFSerialPort::Instance().deserialize(deser); if (deser.version() >= Deserializer::V9 // Note (lr): was added in V11 fa49de29 24/12/2020 but ver not updated until V12 (13/4/2021) || (deser.version() >= Deserializer::V11_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO)) diff --git a/core/hw/sh4/sh4_mmr.h b/core/hw/sh4/sh4_mmr.h index 6f6a437ab9..19cd688c91 100644 --- a/core/hw/sh4/sh4_mmr.h +++ b/core/hw/sh4/sh4_mmr.h @@ -1233,23 +1233,13 @@ union SCIF_SCSMR2_type { struct { - u32 CKS0 : 1; - u32 CKS1 : 1; - u32 res_0 : 1; + u32 CKS : 2; + u32 : 1; u32 STOP : 1; u32 OE_paritymode : 1; u32 PE : 1; u32 CHR : 1; - u32 res_1 : 1; - //8 - u32 res_2 : 1; - u32 res_3 : 1; - u32 res_4 : 1; - u32 res_5 : 1; - u32 res_6 : 1; - u32 res_7 : 1; - u32 res_8 : 1; - u32 res_9 : 1; + u32 : 9; //16 }; u16 full; @@ -1265,23 +1255,16 @@ union SCIF_SCSCR2_type { struct { - u32 res_0 : 1; + u32 : 1; u32 CKE1 : 1; - u32 res_1 : 1; + u32 : 1; u32 REIE : 1; u32 RE : 1; u32 TE : 1; u32 RIE : 1; u32 TIE : 1; //8 - u32 res_2 : 1; - u32 res_3 : 1; - u32 res_4 : 1; - u32 res_5 : 1; - u32 res_6 : 1; - u32 res_7 : 1; - u32 res_8 : 1; - u32 res_9 : 1; + u32 : 8; //16 }; u16 full; @@ -1305,14 +1288,8 @@ union SCIF_SCFSR2_type u32 TEND : 1; u32 ER : 1; //8 - u32 FER0 : 1; - u32 FER1 : 1; - u32 FER2 : 1; - u32 FER3 : 1; - u32 PER0 : 1; - u32 PER1 : 1; - u32 PER2 : 1; - u32 PER3 : 1; + u32 FERn : 4; + u32 PERn : 4; //16 }; u16 full; @@ -1331,19 +1308,10 @@ union SCIF_SCFCR2_type u32 RFRST : 1; u32 TFRST : 1; u32 MCE : 1; - u32 TTRG0 : 1; - u32 TTRG1 : 1; - u32 RTRG0 : 1; - u32 RTRG1 : 1; + u32 TTRG : 2; + u32 RTRG : 2; //8 - u32 res_0 : 1; - u32 res_1 : 1; - u32 res_2 : 1; - u32 res_3 : 1; - u32 res_4 : 1; - u32 res_5 : 1; - u32 res_6 : 1; - u32 res_7 : 1; + u32 : 8; //16 }; u16 full; @@ -1357,10 +1325,10 @@ union SCIF_SCFDR2_type struct { u32 R : 5; - u32 res_0 : 3; + u32 : 3; //8 u32 T : 5; - u32 res_1 : 3; + u32 : 3; //16 }; u16 full; @@ -1373,21 +1341,13 @@ union SCIF_SCSPTR2_type { u32 SPB2DT : 1; u32 SPB2IO : 1; - u32 res_0 : 1; - u32 res_1 : 1; + u32 : 2; u32 CTSDT : 1; u32 CTSIO : 1; u32 RTSDT : 1; u32 RTSIO : 1; //8 - u32 res_2 : 1; - u32 res_3 : 1; - u32 res_4 : 1; - u32 res_5 : 1; - u32 res_6 : 1; - u32 res_7 : 1; - u32 res_8 : 1; - u32 res_9 : 1; + u32 : 8; //16 }; u16 full; @@ -1400,9 +1360,7 @@ union SCIF_SCLSR2_type struct { u32 ORER : 1; - u32 res_0 : 7; - //8 - u32 res_1 : 8; + u32 :15; //16 }; u16 full; diff --git a/core/hw/sh4/sh4_sched.cpp b/core/hw/sh4/sh4_sched.cpp index 1fc5c1c90a..b636221df1 100755 --- a/core/hw/sh4/sh4_sched.cpp +++ b/core/hw/sh4/sh4_sched.cpp @@ -138,6 +138,11 @@ void sh4_sched_request(int id, int cycles) sh4_sched_ffts(); } +bool sh4_sched_is_scheduled(int id) +{ + return sch_list[id].end != -1; +} + /* Returns how much time has passed for this callback */ static int sh4_sched_elapsed(sched_list& sched) { diff --git a/core/hw/sh4/sh4_sched.h b/core/hw/sh4/sh4_sched.h index 237ea9feea..029f9ba8b8 100644 --- a/core/hw/sh4/sh4_sched.h +++ b/core/hw/sh4/sh4_sched.h @@ -31,11 +31,16 @@ u64 sh4_sched_now64(); Schedule a callback to be called sh4 *cycles* after the invocation of this function. *Cycles* range is (0, 200M]. - Passing a value of 0 disables the callback. + Passing a value of -1 disables the callback. If called multiple times, only the last call is in effect */ void sh4_sched_request(int id, int cycles); +/* + Returns true if the callback is scheduled to be called in the future. + */ +bool sh4_sched_is_scheduled(int id); + /* Tick for *cycles* */ diff --git a/core/network/net_handshake.cpp b/core/network/net_handshake.cpp index 62f192667e..5263b5a1b4 100644 --- a/core/network/net_handshake.cpp +++ b/core/network/net_handshake.cpp @@ -20,7 +20,9 @@ #include "cfg/option.h" #include "ggpo.h" #include "naomi_network.h" -#include "net_serial_maxspeed.h" +//#include "net_serial_maxspeed.h" +#include "null_modem.h" +#include "hw/naomi/naomi_flashrom.h" NetworkHandshake *NetworkHandshake::instance; @@ -69,7 +71,13 @@ void NetworkHandshake::init() else if (NaomiNetworkSupported()) instance = new NaomiNetworkHandshake(); else if (config::NetworkEnable && settings.content.gameId == "MAXIMUM SPEED") - instance = new MaxSpeedHandshake(); +// instance = new MaxSpeedHandshake(); + { + configure_maxspeed_flash(true, config::ActAsServer); + instance = new BattleCableHandshake(); + } + else if (config::BattleCableEnable && !settings.platform.isNaomi()) + instance = new BattleCableHandshake(); else instance = nullptr; } diff --git a/core/network/net_serial_maxspeed.h b/core/network/net_serial_maxspeed.h index 73111de388..3214a4f768 100644 --- a/core/network/net_serial_maxspeed.h +++ b/core/network/net_serial_maxspeed.h @@ -88,12 +88,14 @@ struct MaxSpeedNetPipe : public SerialPort::Pipe } createSocket(); + SCIFSerialPort::Instance().setPipe(this); return true; } void shutdown() { + SCIFSerialPort::Instance().setPipe(nullptr); enableNetworkBroadcast(false); if (VALID(sock)) closesocket(sock); @@ -215,7 +217,8 @@ struct MaxSpeedNetPipe : public SerialPort::Pipe peerAddress.sin_family = AF_INET; peerAddress.sin_addr.s_addr = INADDR_BROADCAST; peerAddress.sin_port = htons(NaomiNetwork::SERVER_PORT); - if (!config::NetworkServer.get().empty()) + // ignore server name if acting as server + if (!config::NetworkServer.get().empty() && !config::ActAsServer) { auto pos = config::NetworkServer.get().find_last_of(':'); std::string server; @@ -242,8 +245,6 @@ struct MaxSpeedNetPipe : public SerialPort::Pipe } else enableNetworkBroadcast(true); - - SCIFSerialPort::Instance().setPipe(this); } sock_t sock = INVALID_SOCKET; diff --git a/core/network/null_modem.h b/core/network/null_modem.h new file mode 100644 index 0000000000..69314b09ce --- /dev/null +++ b/core/network/null_modem.h @@ -0,0 +1,271 @@ +/* + 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 . + */ +#include "hw/sh4/modules/modules.h" +#include "net_platform.h" +#include "cfg/option.h" +#include "miniupnp.h" +#include "hw/sh4/sh4_sched.h" +#include "naomi_network.h" +#include "net_handshake.h" +#include + +class NullModemPipe : public SerialPort::Pipe +{ +public: + class Exception : public FlycastException + { + public: + Exception(const std::string& reason) : FlycastException(reason) {} + }; + + // Serial TX + void write(u8 data) override + { + u8 packet[2] = { 'D', data }; + int rc = sendto(sock, (const char *)&packet[0], sizeof(packet), 0, (const sockaddr *)&peerAddress, sizeof(peerAddress)); + if (rc != sizeof(packet)) + ERROR_LOG(NETWORK, "sendto: %d errno %d", rc, get_last_error()); + DEBUG_LOG(NETWORK, "Write %02x %c (buf rx %d)", data, data, (int)rxBuffer.size()); + } + + void sendBreak() override + { + const char b = 'B'; + int rc = sendto(sock, &b, 1, 0, (const sockaddr *)&peerAddress, sizeof(peerAddress)); + if (rc != 1) + ERROR_LOG(NETWORK, "sendto: %d errno %d", rc, get_last_error()); + DEBUG_LOG(NETWORK, "Send Break"); + } + + // RX buffer Size + int available() override { + poll(); + checkBreak(); + int realSize = 0; + for (u32 b : rxBuffer) + if (b != (u32)~0) + realSize++; + return realSize; + } + + // Serial RX + u8 read() override + { + poll(); + if (rxBuffer.empty()) { + WARN_LOG(NETWORK, "NetPipe: empty read"); + return 0; + } + u8 b = rxBuffer.front(); + rxBuffer.pop_front(); + DEBUG_LOG(NETWORK, "Read %02x (buf rx %d)", b, (int)rxBuffer.size()); + checkBreak(); + + return b; + } + + ~NullModemPipe() + { + shutdown(); + } + + bool init() + { +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) + { + ERROR_LOG(NETWORK, "WSAStartup failed. errno=%d", get_last_error()); + throw Exception("WSAStartup failed"); + } +#endif + if (config::EnableUPnP) + { + miniupnp.Init(); + miniupnp.AddPortMapping(config::LocalPort, true); + } + + createSocket(); + SCIFSerialPort::Instance().setPipe(this); + + return true; + } + + void shutdown() + { + enableNetworkBroadcast(false); + if (VALID(sock)) + closesocket(sock); + sock = INVALID_SOCKET; + SCIFSerialPort::Instance().setPipe(nullptr); + } + +private: + void checkBreak() + { + if (!rxBuffer.empty() && rxBuffer.front() == (u32)~0) { + SCIFSerialPort::Instance().receiveBreak(); + rxBuffer.pop_front(); + } + } + + void poll() + { + if (lastPoll == sh4_sched_now64()) + return; + lastPoll = sh4_sched_now64(); + u8 data[0x100]; + sockaddr_in addr; + while (true) + { + socklen_t len = sizeof(addr); + int rc = recvfrom(sock, (char *)data, sizeof(data), 0, (sockaddr *)&addr, &len); + if (rc == -1) + { + int error = get_last_error(); + if (error == L_EWOULDBLOCK || error == L_EAGAIN) + break; +#ifdef _WIN32 + if (error == WSAECONNRESET) + // Happens if the previous send resulted in an ICMP Port Unreachable message + break; +#endif + throw Exception("Receive error: errno " + std::to_string(error)); + } + if (peerAddress.sin_addr.s_addr == INADDR_BROADCAST) + { + if (addr.sin_port != htons(config::LocalPort) || !is_local_address(addr.sin_addr.s_addr)) + { + peerAddress.sin_addr.s_addr = addr.sin_addr.s_addr; + peerAddress.sin_port = addr.sin_port; + enableNetworkBroadcast(false); + NOTICE_LOG(NETWORK, "Data received from peer %x:%d", htonl(addr.sin_addr.s_addr), htons(addr.sin_port)); + } + else + { + // this is coming from us so ignore it + continue; + } + } + if (rc == 2) + { + if (data[0] != 'D') + ERROR_LOG(NETWORK, "Unexpected packet '%c'", data[0]); + else + rxBuffer.push_back(data[1]); + } + else if (rc == 1) + { + if (data[0] != 'B') + ERROR_LOG(NETWORK, "Unexpected packet '%c'", data[0]); + else + rxBuffer.push_back(~0); + } + } + } + + void createSocket() + { + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) + { + ERROR_LOG(NETWORK, "Socket creation failed: errno %d", get_last_error()); + throw Exception("Socket creation failed"); + } + int option = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option)); + + sockaddr_in serveraddr{}; + serveraddr.sin_family = AF_INET; + serveraddr.sin_port = htons(config::LocalPort); + + if (::bind(sock, (sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) + { + ERROR_LOG(NETWORK, "NaomiServer: bind() failed. errno=%d", get_last_error()); + closesocket(sock); + + throw Exception("Socket bind failed"); + } + set_non_blocking(sock); + + // Allow broadcast packets to be sent + int broadcast = 1; + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)) == -1) + WARN_LOG(NETWORK, "setsockopt(SO_BROADCAST) failed. errno=%d", get_last_error()); + + peerAddress.sin_family = AF_INET; + peerAddress.sin_addr.s_addr = INADDR_BROADCAST; + peerAddress.sin_port = htons(NaomiNetwork::SERVER_PORT); + if (!config::NetworkServer.get().empty() + // ignore server name if acting as server (maxspeed) + && (!config::ActAsServer || settings.platform.isConsole())) + { + auto pos = config::NetworkServer.get().find_last_of(':'); + std::string server; + if (pos != std::string::npos) + { + peerAddress.sin_port = htons(atoi(config::NetworkServer.get().substr(pos + 1).c_str())); + server = config::NetworkServer.get().substr(0, pos); + } + else + server = config::NetworkServer; + addrinfo *resultAddr; + if (getaddrinfo(server.c_str(), 0, nullptr, &resultAddr)) + WARN_LOG(NETWORK, "Server %s is unknown", server.c_str()); + else + { + for (addrinfo *ptr = resultAddr; ptr != nullptr; ptr = ptr->ai_next) + if (ptr->ai_family == AF_INET) + { + peerAddress.sin_addr.s_addr = ((sockaddr_in *)ptr->ai_addr)->sin_addr.s_addr; + break; + } + freeaddrinfo(resultAddr); + } + } + else + enableNetworkBroadcast(true); + } + + sock_t sock = INVALID_SOCKET; + MiniUPnP miniupnp; + std::deque rxBuffer; + sockaddr_in peerAddress{}; + u64 lastPoll = 0; +}; + +class BattleCableHandshake : public NetworkHandshake +{ +public: + std::future start() override { + std::promise promise; + promise.set_value(pipe.init()); + return promise.get_future(); + } + void stop() override { + pipe.shutdown(); + } + bool canStartNow() override { + return true; + } + void startNow() override {} + +private: + NullModemPipe pipe; +}; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index eed7d79b9f..599bc8b598 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -671,7 +671,7 @@ const char *maple_device_types[] = "Keyboard", "Mouse", "Twin Stick", - "Ascii Stick", + "Arcade/Ascii Stick", "Maracas Controller", "Fishing Controller", "Pop'n Music controller", @@ -2349,39 +2349,61 @@ static void gui_display_settings() ImGui::PopStyleVar(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Advanced")) + if (ImGui::BeginTabItem("Network")) { + ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); - header("CPU Mode"); - { - ImGui::Columns(2, "cpu_modes", false); - OptionRadioButton("Dynarec", config::DynarecEnabled, true, - "Use the dynamic recompiler. Recommended in most cases"); + + header("Network Type"); + { + DisabledScope scope(game_started); + + int netType = 0; + if (config::GGPOEnable) + netType = 1; + else if (config::NetworkEnable) + netType = 2; + else if (config::BattleCableEnable) + netType = 3; + ImGui::Columns(4, "networkType", false); + ImGui::RadioButton("Disabled", &netType, 0); ImGui::NextColumn(); - OptionRadioButton("Interpreter", config::DynarecEnabled, false, - "Use the interpreter. Very slow but may help in case of a dynarec problem"); - ImGui::Columns(1, NULL, false); + ImGui::RadioButton("GGPO", &netType, 1); + ImGui::SameLine(0, style.ItemInnerSpacing.x); + ShowHelpMarker("Enable networking using GGPO"); + ImGui::NextColumn(); + ImGui::RadioButton("Naomi", &netType, 2); + ImGui::SameLine(0, style.ItemInnerSpacing.x); + ShowHelpMarker("Enable networking for supported Naomi and Atomiswave games"); + ImGui::NextColumn(); + ImGui::RadioButton("Battle Cable", &netType, 3); + ImGui::SameLine(0, style.ItemInnerSpacing.x); + ShowHelpMarker("Emulate the Taisen (Battle) null modem cable for games that support it"); + ImGui::Columns(1, nullptr, false); - OptionSlider("SH4 Clock", config::Sh4Clock, 100, 300, - "Over/Underclock the main SH4 CPU. Default is 200 MHz. Other values may crash, freeze or trigger unexpected nuclear reactions.", - "%d MHz"); - } - ImGui::Spacing(); - header("Network"); - { + config::GGPOEnable = false; + config::NetworkEnable = false; + config::BattleCableEnable = false; + switch (netType) { + case 1: + config::GGPOEnable = true; + break; + case 2: + config::NetworkEnable = true; + break; + case 3: + config::BattleCableEnable = true; + break; + } + } + if (config::GGPOEnable || config::NetworkEnable || config::BattleCableEnable) { + ImGui::Spacing(); + header("Configuration"); + } + { + if (config::GGPOEnable) { - DisabledScope scope(game_started); - - OptionCheckbox("Broadband Adapter Emulation", config::EmulateBBA, - "Emulate the Ethernet Broadband Adapter (BBA) instead of the Modem"); - } - OptionCheckbox("Enable GGPO Networking", config::GGPOEnable, - "Enable networking using GGPO"); - OptionCheckbox("Enable Naomi Networking", config::NetworkEnable, - "Enable networking for supported Naomi games"); - if (config::GGPOEnable) - { - config::NetworkEnable = false; + config::NetworkEnable = false; OptionCheckbox("Play as Player 1", config::ActAsServer, "Deselect to play as player 2"); char server_name[256]; @@ -2415,10 +2437,10 @@ static void gui_display_settings() } } OptionCheckbox("Network Statistics", config::NetworkStats, - "Display network statistics on screen"); - } - else if (config::NetworkEnable) - { + "Display network statistics on screen"); + } + else if (config::NetworkEnable) + { OptionCheckbox("Act as Server", config::ActAsServer, "Create a local server for Naomi network games"); if (!config::ActAsServer) @@ -2436,17 +2458,65 @@ static void gui_display_settings() ImGui::SameLine(); ShowHelpMarker("The local UDP port to use"); config::LocalPort.set(atoi(localPort)); - } + } + else if (config::BattleCableEnable) + { + char server_name[256]; + strcpy(server_name, config::NetworkServer.get().c_str()); + ImGui::InputText("Peer", server_name, sizeof(server_name), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("The peer to connect to. Leave blank to find a player automatically on the default port"); + config::NetworkServer.set(server_name); + char localPort[256]; + sprintf(localPort, "%d", (int)config::LocalPort); + ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("The local UDP port to use"); + config::LocalPort.set(atoi(localPort)); + } + } + ImGui::Spacing(); + header("Network Options"); + { OptionCheckbox("Enable UPnP", config::EnableUPnP, "Automatically configure your network router for netplay"); OptionCheckbox("Broadcast Digital Outputs", config::NetworkOutput, "Broadcast digital outputs and force-feedback state on TCP port 8000. " "Compatible with the \"-output network\" MAME option. Arcade games only."); + { + DisabledScope scope(game_started); + + OptionCheckbox("Broadband Adapter Emulation", config::EmulateBBA, + "Emulate the Ethernet Broadband Adapter (BBA) instead of the Modem"); + } + } #ifdef NAOMI_MULTIBOARD - ImGui::Text("Multiboard Screens:"); + ImGui::Spacing(); + header("Multiboard Screens"); + { //OptionRadioButton("Disabled", config::MultiboardSlaves, 0, "Multiboard disabled (when optional)"); OptionRadioButton("1 (Twin)", config::MultiboardSlaves, 1, "One screen configuration (F355 Twin)"); ImGui::SameLine(); OptionRadioButton("3 (Deluxe)", config::MultiboardSlaves, 2, "Three screens configuration"); + } #endif + ImGui::PopStyleVar(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Advanced")) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + header("CPU Mode"); + { + ImGui::Columns(2, "cpu_modes", false); + OptionRadioButton("Dynarec", config::DynarecEnabled, true, + "Use the dynamic recompiler. Recommended in most cases"); + ImGui::NextColumn(); + OptionRadioButton("Interpreter", config::DynarecEnabled, false, + "Use the interpreter. Very slow but may help in case of a dynarec problem"); + ImGui::Columns(1, NULL, false); + + OptionSlider("SH4 Clock", config::Sh4Clock, 100, 300, + "Over/Underclock the main SH4 CPU. Default is 200 MHz. Other values may crash, freeze or trigger unexpected nuclear reactions.", + "%d MHz"); } ImGui::Spacing(); header("Other"); diff --git a/core/serialize.h b/core/serialize.h index 3d2f142643..26e256a252 100644 --- a/core/serialize.h +++ b/core/serialize.h @@ -68,7 +68,8 @@ class SerializeBase V40, V41, V42, - Current = V42, + V43, + Current = V43, Next = Current + 1, }; diff --git a/shell/libretro/option.cpp b/shell/libretro/option.cpp index 617ebfed79..f7dac40c43 100644 --- a/shell/libretro/option.cpp +++ b/shell/libretro/option.cpp @@ -121,6 +121,7 @@ Option NetworkStats("", false); Option GGPOAnalogAxes("", 0); Option NetworkOutput(CORE_OPTION_NAME "_network_output", false); Option MultiboardSlaves("", 0); +Option BattleCableEnable("", false); // Maple diff --git a/tests/src/serialize_test.cpp b/tests/src/serialize_test.cpp index 16ad3d8837..2275ff1177 100644 --- a/tests/src/serialize_test.cpp +++ b/tests/src/serialize_test.cpp @@ -32,7 +32,7 @@ TEST_F(SerializeTest, SizeTest) std::vector data(30000000); Serializer ser(data.data(), data.size()); dc_serialize(ser); - ASSERT_EQ(28191382u, ser.size()); + ASSERT_EQ(28191417u, ser.size()); }