mirror of https://github.com/mgba-emu/mgba.git
GBA SIO: Dolphin connectivity
This commit is contained in:
parent
66093288e2
commit
b1828dbc59
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/timing.h>
|
||||
#include <mgba/internal/gba/sio.h>
|
||||
|
||||
#include <mgba-util/socket.h>
|
||||
|
||||
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
|
|
@ -34,6 +34,7 @@ set(SOURCE_FILES
|
|||
video.c)
|
||||
|
||||
set(SIO_FILES
|
||||
sio/dolphin.c
|
||||
sio/joybus.c
|
||||
sio/lockstep.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 <mgba/internal/gba/sio/dolphin.h>
|
||||
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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<GBA*>(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<GBA*>(m_threadContext.core->board);
|
||||
GBASIOSetDriver(&gba->sio, nullptr, SIO_JOYBUS);
|
||||
}
|
||||
GBASIODolphinDestroy(&m_dolphin);
|
||||
}
|
||||
|
||||
void CoreController::setOverride(std::unique_ptr<Override> override) {
|
||||
Interrupter interrupter(this);
|
||||
m_override = std::move(override);
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/sio/printer.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/sio/dolphin.h>
|
||||
#endif
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/gba/interface.h>
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue