diff --git a/include/mgba/gba/interface.h b/include/mgba/gba/interface.h index b59d265a2..612afc383 100644 --- a/include/mgba/gba/interface.h +++ b/include/mgba/gba/interface.h @@ -78,9 +78,6 @@ struct GBASIODriver { uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); }; -void GBASIOJOYCreate(struct GBASIODriver* sio); -int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); - enum GBASIOBattleChipGateFlavor { GBA_FLAVOR_BATTLECHIP_GATE = 4, GBA_FLAVOR_PROGRESS_GATE = 5, diff --git a/include/mgba/internal/gba/sio.h b/include/mgba/internal/gba/sio.h index 7a4e3ecfb..0f60f8f74 100644 --- a/include/mgba/internal/gba/sio.h +++ b/include/mgba/internal/gba/sio.h @@ -78,6 +78,9 @@ void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value); void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value); uint16_t GBASIOWriteRegister(struct GBASIO* sio, uint32_t address, uint16_t value); +void GBASIOJOYCreate(struct GBASIODriver* sio); +int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); + CXX_GUARD_END #endif diff --git a/include/mgba/internal/gba/sio/dolphin.h b/include/mgba/internal/gba/sio/dolphin.h new file mode 100644 index 000000000..1db9ab092 --- /dev/null +++ b/include/mgba/internal/gba/sio/dolphin.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef SIO_DOLPHIN_H +#define SIO_DOLPHIN_H + +#include + +CXX_GUARD_START + +#include +#include + +#include + +extern const uint16_t DOLPHIN_CLOCK_PORT; +extern const uint16_t DOLPHIN_DATA_PORT; + +struct GBASIODolphin { + struct GBASIODriver d; + struct mTimingEvent event; + + Socket data; + Socket clock; + + int32_t clockSlice; + int state; +}; + +void GBASIODolphinCreate(struct GBASIODolphin*); +void GBASIODolphinDestroy(struct GBASIODolphin*); + +bool GBASIODolphinConnect(struct GBASIODolphin*, const struct Address* address, short dataPort, short clockPort); +bool GBASIODolphinIsConnected(struct GBASIODolphin*); + +CXX_GUARD_END + +#endif diff --git a/src/gba/CMakeLists.txt b/src/gba/CMakeLists.txt index 5b4a52c0c..ba9c298dc 100644 --- a/src/gba/CMakeLists.txt +++ b/src/gba/CMakeLists.txt @@ -34,6 +34,7 @@ set(SOURCE_FILES video.c) set(SIO_FILES + sio/dolphin.c sio/joybus.c sio/lockstep.c) diff --git a/src/gba/sio/dolphin.c b/src/gba/sio/dolphin.c new file mode 100644 index 000000000..abb41e271 --- /dev/null +++ b/src/gba/sio/dolphin.c @@ -0,0 +1,190 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include + +#define BITS_PER_SECOND 115200 // This is wrong, but we need to maintain compat for the time being +#define CYCLES_PER_BIT (GBA_ARM7TDMI_FREQUENCY / BITS_PER_SECOND) +#define CLOCK_GRAIN (CYCLES_PER_BIT * 8) +#define CLOCK_WAIT 500 + +const uint16_t DOLPHIN_CLOCK_PORT = 49420; +const uint16_t DOLPHIN_DATA_PORT = 54970; + +enum { + CMD_RESET = 0xFF, + CMD_POLL = 0x00, + CMD_TRANS = 0x14, + CMD_RECV = 0x15, + + CMD_NONE = 0x80 +}; + +enum { + WAIT_FOR_FIRST_CLOCK = 0, + WAIT_FOR_CLOCK, + WAIT_FOR_COMMAND, +}; + +static bool GBASIODolphinLoad(struct GBASIODriver* driver); +static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate); + +static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate); + +void GBASIODolphinCreate(struct GBASIODolphin* dol) { + GBASIOJOYCreate(&dol->d); + dol->d.load = GBASIODolphinLoad; + dol->event.context = dol; + dol->event.name = "GB SIO Lockstep"; + dol->event.callback = GBASIODolphinProcessEvents; + dol->event.priority = 0x80; + + dol->data = INVALID_SOCKET; + dol->clock = INVALID_SOCKET; +} + +void GBASIODolphinDestroy(struct GBASIODolphin* dol) { + if (!SOCKET_FAILED(dol->data)) { + SocketClose(dol->data); + dol->data = INVALID_SOCKET; + } + + if (!SOCKET_FAILED(dol->clock)) { + SocketClose(dol->clock); + dol->clock = INVALID_SOCKET; + } +} + +bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* address, short dataPort, short clockPort) { + if (!SOCKET_FAILED(dol->data)) { + SocketClose(dol->data); + dol->data = INVALID_SOCKET; + } + if (!dataPort) { + dataPort = DOLPHIN_DATA_PORT; + } + + if (!SOCKET_FAILED(dol->clock)) { + SocketClose(dol->clock); + dol->clock = INVALID_SOCKET; + } + if (!clockPort) { + clockPort = DOLPHIN_CLOCK_PORT; + } + + dol->data = SocketConnectTCP(dataPort, address); + if (SOCKET_FAILED(dol->data)) { + return false; + } + + dol->clock = SocketConnectTCP(clockPort, address); + if (SOCKET_FAILED(dol->clock)) { + SocketClose(dol->data); + dol->data = INVALID_SOCKET; + return false; + } + + SocketSetBlocking(dol->data, false); + SocketSetBlocking(dol->clock, false); + SocketSetTCPPush(dol->data, true); + return true; +} + +static bool GBASIODolphinLoad(struct GBASIODriver* driver) { + struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; + dol->clockSlice = 0; + dol->state = WAIT_FOR_FIRST_CLOCK; + mTimingDeschedule(&dol->d.p->p->timing, &dol->event); + mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0); + return true; +} + +void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBASIODolphin* dol = context; + dol->clockSlice -= cyclesLate; + + int32_t clockSlice; + + int32_t nextEvent = CLOCK_GRAIN; + switch (dol->state) { + case WAIT_FOR_FIRST_CLOCK: + dol->clockSlice = 0; + // Fall through + case WAIT_FOR_CLOCK: + if (dol->clockSlice < 0) { + Socket r = dol->clock; + SocketPoll(1, &r, 0, 0, CLOCK_WAIT); + } + if (SocketRecv(dol->clock, &clockSlice, 4) == 4) { + clockSlice = ntohl(clockSlice); + dol->clockSlice += clockSlice; + dol->state = WAIT_FOR_COMMAND; + nextEvent = 0; + } + // Fall through + case WAIT_FOR_COMMAND: + if (dol->clockSlice < -VIDEO_TOTAL_LENGTH * 4) { + Socket r = dol->data; + SocketPoll(1, &r, 0, 0, CLOCK_WAIT); + } + if (_processCommand(dol, cyclesLate)) { + dol->state = WAIT_FOR_CLOCK; + nextEvent = CLOCK_GRAIN; + } + break; + } + + dol->clockSlice -= nextEvent; + mTimingSchedule(timing, &dol->event, nextEvent); +} + +int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate) { + // This does not include the stop bits due to compatibility reasons + int bitsOnLine = 8; + uint8_t buffer[6]; + int gotten = SocketRecv(dol->data, &buffer, 5); + if (gotten < 1) { + return 0; + } + + switch (buffer[0]) { + case CMD_RESET: + case CMD_POLL: + bitsOnLine += 24; + break; + case CMD_RECV: + mLOG(GBA_SIO, DEBUG, "JOY <: %02X%02X%02X%02X", buffer[1], buffer[2], buffer[3], buffer[4]); + // Fall through + case CMD_TRANS: + bitsOnLine += 40; + break; + } + + int sent = GBASIOJOYSendCommand(&dol->d, buffer[0], &buffer[1]); + SocketSend(dol->data, &buffer[1], sent); + switch (buffer[0]) { + case CMD_TRANS: + mLOG(GBA_SIO, DEBUG, "JOY >: %02X%02X%02X%02X:%02X", buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]); + break; + case CMD_RECV: + mLOG(GBA_SIO, DEBUG, "JOY <: %02X", buffer[1]); + break; + case CMD_RESET: + mLOG(GBA_SIO, DEBUG, "JOY !: %02X", buffer[3]); + break; + case CMD_POLL: + mLOG(GBA_SIO, DEBUG, "JOY ?: %02X", buffer[3]); + break; + } + + return bitsOnLine * CYCLES_PER_BIT - cyclesLate; +} + +bool GBASIODolphinIsConnected(struct GBASIODolphin* dol) { + return dol->data != INVALID_SOCKET; +} diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index c4e4ba3ba..6091c70c0 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -42,6 +42,10 @@ CoreController::CoreController(mCore* core, QObject* parent) m_threadContext.userData = this; updateROMInfo(); +#ifdef M_CORE_GBA + GBASIODolphinCreate(&m_dolphin); +#endif + m_resetActions.append([this]() { if (m_autoload) { mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags); @@ -113,6 +117,7 @@ CoreController::CoreController(mCore* core, QObject* parent) } controller->clearMultiplayerController(); + controller->detachDolphin(); QMetaObject::invokeMethod(controller, "stopping"); }; @@ -357,6 +362,29 @@ mCacheSet* CoreController::graphicCaches() { return m_cacheSet.get(); } +bool CoreController::connectDolphin(uint32_t ipv4) { + if (platform() != mPLATFORM_GBA) { + return false; + } + Address ipaddr; + ipaddr.version = IPV4; + ipaddr.ipv4 = htonl(ipv4); + if (GBASIODolphinConnect(&m_dolphin, &ipaddr, 0, 0)) { + GBA* gba = static_cast(m_threadContext.core->board); + GBASIOSetDriver(&gba->sio, &m_dolphin.d, SIO_JOYBUS); + return true; + } + return false; +} + +void CoreController::detachDolphin() { + if (platform() == mPLATFORM_GBA) { + GBA* gba = static_cast(m_threadContext.core->board); + GBASIOSetDriver(&gba->sio, nullptr, SIO_JOYBUS); + } + GBASIODolphinDestroy(&m_dolphin); +} + void CoreController::setOverride(std::unique_ptr override) { Interrupter interrupter(this); m_override = std::move(override); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 1f0f052bf..3b92ed4e1 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -25,6 +25,9 @@ #ifdef M_CORE_GB #include #endif +#ifdef M_CORE_GBA +#include +#endif #ifdef M_CORE_GBA #include @@ -105,6 +108,9 @@ public: void clearMultiplayerController(); MultiplayerController* multiplayerController() { return m_multiplayer; } + bool connectDolphin(uint32_t ipv4); + void detachDolphin(); + mCacheSet* graphicCaches(); int stateSlot() const { return m_stateSlot; } @@ -269,6 +275,9 @@ private: InputController* m_inputController = nullptr; LogController* m_log = nullptr; MultiplayerController* m_multiplayer = nullptr; +#ifdef M_CORE_GBA + GBASIODolphin m_dolphin; +#endif mVideoLogContext* m_vl = nullptr; VFile* m_vlVf = nullptr; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 7d850d749..d353fc9ac 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1192,6 +1192,17 @@ void Window::setupMenu(QMenuBar* menubar) { GBAApp::app()->newWindow(); }, "file"); + Action* dolphin = m_actions.addAction(tr("Connect to Dolphin"), "connectDolphin", [this]() { + CoreController::Interrupter interrupter; + if (m_controller) { + interrupter.interrupt(m_controller); + } else { + setController(m_manager->loadBIOS(mPLATFORM_GBA, m_config->getOption("gba.bios")), QString()); + } + m_controller->connectDolphin(0x0100007F); + }, "file"); + m_platformActions.insert(mPLATFORM_GBA, dolphin); + #ifndef Q_OS_MAC m_actions.addSeparator("file"); #endif