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);
|
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 {
|
enum GBASIOBattleChipGateFlavor {
|
||||||
GBA_FLAVOR_BATTLECHIP_GATE = 4,
|
GBA_FLAVOR_BATTLECHIP_GATE = 4,
|
||||||
GBA_FLAVOR_PROGRESS_GATE = 5,
|
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);
|
void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value);
|
||||||
uint16_t GBASIOWriteRegister(struct GBASIO* sio, uint32_t address, 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
|
CXX_GUARD_END
|
||||||
|
|
||||||
#endif
|
#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)
|
video.c)
|
||||||
|
|
||||||
set(SIO_FILES
|
set(SIO_FILES
|
||||||
|
sio/dolphin.c
|
||||||
sio/joybus.c
|
sio/joybus.c
|
||||||
sio/lockstep.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;
|
m_threadContext.userData = this;
|
||||||
updateROMInfo();
|
updateROMInfo();
|
||||||
|
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
GBASIODolphinCreate(&m_dolphin);
|
||||||
|
#endif
|
||||||
|
|
||||||
m_resetActions.append([this]() {
|
m_resetActions.append([this]() {
|
||||||
if (m_autoload) {
|
if (m_autoload) {
|
||||||
mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);
|
mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);
|
||||||
|
@ -113,6 +117,7 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
controller->clearMultiplayerController();
|
controller->clearMultiplayerController();
|
||||||
|
controller->detachDolphin();
|
||||||
QMetaObject::invokeMethod(controller, "stopping");
|
QMetaObject::invokeMethod(controller, "stopping");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -357,6 +362,29 @@ mCacheSet* CoreController::graphicCaches() {
|
||||||
return m_cacheSet.get();
|
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) {
|
void CoreController::setOverride(std::unique_ptr<Override> override) {
|
||||||
Interrupter interrupter(this);
|
Interrupter interrupter(this);
|
||||||
m_override = std::move(override);
|
m_override = std::move(override);
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
#include <mgba/internal/gb/sio/printer.h>
|
#include <mgba/internal/gb/sio/printer.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
#include <mgba/internal/gba/sio/dolphin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
#include <mgba/gba/interface.h>
|
#include <mgba/gba/interface.h>
|
||||||
|
@ -105,6 +108,9 @@ public:
|
||||||
void clearMultiplayerController();
|
void clearMultiplayerController();
|
||||||
MultiplayerController* multiplayerController() { return m_multiplayer; }
|
MultiplayerController* multiplayerController() { return m_multiplayer; }
|
||||||
|
|
||||||
|
bool connectDolphin(uint32_t ipv4);
|
||||||
|
void detachDolphin();
|
||||||
|
|
||||||
mCacheSet* graphicCaches();
|
mCacheSet* graphicCaches();
|
||||||
int stateSlot() const { return m_stateSlot; }
|
int stateSlot() const { return m_stateSlot; }
|
||||||
|
|
||||||
|
@ -269,6 +275,9 @@ private:
|
||||||
InputController* m_inputController = nullptr;
|
InputController* m_inputController = nullptr;
|
||||||
LogController* m_log = nullptr;
|
LogController* m_log = nullptr;
|
||||||
MultiplayerController* m_multiplayer = nullptr;
|
MultiplayerController* m_multiplayer = nullptr;
|
||||||
|
#ifdef M_CORE_GBA
|
||||||
|
GBASIODolphin m_dolphin;
|
||||||
|
#endif
|
||||||
|
|
||||||
mVideoLogContext* m_vl = nullptr;
|
mVideoLogContext* m_vl = nullptr;
|
||||||
VFile* m_vlVf = nullptr;
|
VFile* m_vlVf = nullptr;
|
||||||
|
|
|
@ -1192,6 +1192,17 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
GBAApp::app()->newWindow();
|
GBAApp::app()->newWindow();
|
||||||
}, "file");
|
}, "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
|
#ifndef Q_OS_MAC
|
||||||
m_actions.addSeparator("file");
|
m_actions.addSeparator("file");
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue