Merge branch 'feature/new-sio'

This commit is contained in:
Vicki Pfau 2024-09-29 19:59:41 -07:00
commit d57bb82d22
25 changed files with 1798 additions and 1160 deletions

View File

@ -46,6 +46,7 @@ Misc:
- GBA: Improve detection of valid ELF ROMs - GBA: Improve detection of valid ELF ROMs
- GBA Audio: Remove broken XQ audio pending rewrite - GBA Audio: Remove broken XQ audio pending rewrite
- GBA Memory: Improve VRAM access stall cycle estimation - GBA Memory: Improve VRAM access stall cycle estimation
- GBA SIO: Rewrite lockstep driver for improved stability
- GBA Video: Add special circlular window handling in OpenGL renderer - GBA Video: Add special circlular window handling in OpenGL renderer
- Libretro: Add Super Game Boy Color support (closes mgba.io/i/3188) - Libretro: Add Super Game Boy Color support (closes mgba.io/i/3188)
- mGUI: Enable auto-softpatching (closes mgba.io/i/2899) - mGUI: Enable auto-softpatching (closes mgba.io/i/2899)

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau /* Copyright (c) 2013-2024 Jeffrey Pfau
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -53,6 +53,25 @@ static inline void mLockstepUnlock(struct mLockstep* lockstep) {
} }
} }
struct mLockstepUser {
void (*sleep)(struct mLockstepUser*);
void (*wake)(struct mLockstepUser*);
int (*requestedId)(struct mLockstepUser*);
void (*playerIdChanged)(struct mLockstepUser*, int id);
};
#ifndef DISABLE_THREADING
struct mCoreThread;
struct mLockstepThreadUser {
struct mLockstepUser d;
struct mCoreThread* thread;
};
void mLockstepThreadUserInit(struct mLockstepThreadUser* lockstep, struct mCoreThread* thread);
#endif
CXX_GUARD_END CXX_GUARD_END
#endif #endif

View File

@ -81,7 +81,7 @@ extern MGBA_EXPORT const int GBA_LUX_LEVELS[10];
enum { enum {
mPERIPH_GBA_LUMINANCE = 0x1000, mPERIPH_GBA_LUMINANCE = 0x1000,
mPERIPH_GBA_BATTLECHIP_GATE, mPERIPH_GBA_LINK_PORT,
}; };
struct GBACartridgeOverride { struct GBACartridgeOverride {
@ -110,13 +110,22 @@ struct GBASIODriver {
bool (*init)(struct GBASIODriver* driver); bool (*init)(struct GBASIODriver* driver);
void (*deinit)(struct GBASIODriver* driver); void (*deinit)(struct GBASIODriver* driver);
bool (*load)(struct GBASIODriver* driver); void (*reset)(struct GBASIODriver* driver);
bool (*unload)(struct GBASIODriver* driver); uint32_t (*driverId)(const struct GBASIODriver* renderer);
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); bool (*loadState)(struct GBASIODriver* renderer, const void* state, size_t size);
void (*saveState)(struct GBASIODriver* renderer, void** state, size_t* size);
void (*setMode)(struct GBASIODriver* driver, enum GBASIOMode mode);
bool (*handlesMode)(struct GBASIODriver* driver, enum GBASIOMode mode);
int (*connectedDevices)(struct GBASIODriver* driver);
int (*deviceId)(struct GBASIODriver* driver);
uint16_t (*writeSIOCNT)(struct GBASIODriver* driver, uint16_t value);
uint16_t (*writeRCNT)(struct GBASIODriver* driver, uint16_t value);
bool (*start)(struct GBASIODriver* driver);
void (*finishMultiplayer)(struct GBASIODriver* driver, uint16_t data[4]);
uint8_t (*finishNormal8)(struct GBASIODriver* driver);
uint32_t (*finishNormal32)(struct GBASIODriver* driver);
}; };
void GBASIOJOYCreate(struct GBASIODriver* sio);
enum GBASIOBattleChipGateFlavor { enum GBASIOBattleChipGateFlavor {
GBA_FLAVOR_BATTLECHIP_GATE = 4, GBA_FLAVOR_BATTLECHIP_GATE = 4,
GBA_FLAVOR_PROGRESS_GATE = 5, GBA_FLAVOR_PROGRESS_GATE = 5,
@ -126,7 +135,6 @@ enum GBASIOBattleChipGateFlavor {
struct GBASIOBattlechipGate { struct GBASIOBattlechipGate {
struct GBASIODriver d; struct GBASIODriver d;
struct mTimingEvent event;
uint16_t chipId; uint16_t chipId;
uint16_t data[2]; uint16_t data[2];
int state; int state;

View File

@ -191,7 +191,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* | bits 2 - 3: GB Player inputs posted * | bits 2 - 3: GB Player inputs posted
* | bits 4 - 8: GB Player transmit position * | bits 4 - 8: GB Player transmit position
* | bits 9 - 23: Reserved * | bits 9 - 23: Reserved
* 0x002C4 - 0x002C7: Game Boy Player next event * 0x002C4 - 0x002C7: SIO next event
* 0x002C8 - 0x002CB: Current DMA transfer word * 0x002C8 - 0x002CB: Current DMA transfer word
* 0x002CC - 0x002CF: Last DMA transfer PC * 0x002CC - 0x002CF: Last DMA transfer PC
* 0x002D0 - 0x002DF: Matrix memory command buffer * 0x002D0 - 0x002DF: Matrix memory command buffer
@ -287,6 +287,7 @@ DECL_BITS(GBASerializedMiscFlags, KeyIRQKeys, 4, 11);
enum { enum {
GBA_SUBSYSTEM_VIDEO_RENDERER = 0, GBA_SUBSYSTEM_VIDEO_RENDERER = 0,
GBA_SUBSYSTEM_SIO_DRIVER = 1,
GBA_SUBSYSTEM_MAX, GBA_SUBSYSTEM_MAX,
}; };
@ -370,7 +371,7 @@ struct GBASerializedState {
uint8_t lightSample; uint8_t lightSample;
GBASerializedHWFlags2 flags2; GBASerializedHWFlags2 flags2;
GBASerializedHWFlags3 flags3; GBASerializedHWFlags3 flags3;
uint32_t gbpNextEvent; uint32_t sioNextEvent;
} hw; } hw;
uint32_t dmaTransferRegister; uint32_t dmaTransferRegister;

View File

@ -16,8 +16,6 @@ CXX_GUARD_START
#define MAX_GBAS 4 #define MAX_GBAS 4
extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
mLOG_DECLARE_CATEGORY(GBA_SIO); mLOG_DECLARE_CATEGORY(GBA_SIO);
enum { enum {
@ -54,37 +52,45 @@ DECL_BITS(GBASIOMultiplayer, Id, 4, 2);
DECL_BIT(GBASIOMultiplayer, Error, 6); DECL_BIT(GBASIOMultiplayer, Error, 6);
DECL_BIT(GBASIOMultiplayer, Busy, 7); DECL_BIT(GBASIOMultiplayer, Busy, 7);
DECL_BIT(GBASIOMultiplayer, Irq, 14); DECL_BIT(GBASIOMultiplayer, Irq, 14);
DECL_BITFIELD(GBASIORegisterRCNT, uint16_t);
struct GBASIODriverSet { DECL_BIT(GBASIORegisterRCNT, Sc, 0);
struct GBASIODriver* normal; DECL_BIT(GBASIORegisterRCNT, Sd, 1);
struct GBASIODriver* multiplayer; DECL_BIT(GBASIORegisterRCNT, Si, 2);
struct GBASIODriver* joybus; DECL_BIT(GBASIORegisterRCNT, So, 3);
}; DECL_BIT(GBASIORegisterRCNT, ScDirection, 4);
DECL_BIT(GBASIORegisterRCNT, SdDirection, 5);
DECL_BIT(GBASIORegisterRCNT, SiDirection, 6);
DECL_BIT(GBASIORegisterRCNT, SoDirection, 7);
struct GBASIO { struct GBASIO {
struct GBA* p; struct GBA* p;
enum GBASIOMode mode; enum GBASIOMode mode;
struct GBASIODriverSet drivers; struct GBASIODriver* driver;
struct GBASIODriver* activeDriver;
uint16_t rcnt; uint16_t rcnt;
uint16_t siocnt; uint16_t siocnt;
struct GBASIOPlayer gbp; struct GBASIOPlayer gbp;
struct mTimingEvent completeEvent;
}; };
void GBASIOInit(struct GBASIO* sio); void GBASIOInit(struct GBASIO* sio);
void GBASIODeinit(struct GBASIO* sio); void GBASIODeinit(struct GBASIO* sio);
void GBASIOReset(struct GBASIO* sio); void GBASIOReset(struct GBASIO* sio);
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers); void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver);
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode);
void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value); 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);
int32_t GBASIOTransferCycles(enum GBASIOMode mode, uint16_t siocnt, int connected);
void GBASIOMultiplayerFinishTransfer(struct GBASIO* sio, uint16_t data[4], uint32_t cyclesLate);
void GBASIONormal8FinishTransfer(struct GBASIO* sio, uint8_t data, uint32_t cyclesLate);
void GBASIONormal32FinishTransfer(struct GBASIO* sio, uint32_t data, uint32_t cyclesLate);
int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data);
CXX_GUARD_END CXX_GUARD_END

View File

@ -21,7 +21,6 @@ struct GBASIOPlayer {
struct GBA* p; struct GBA* p;
unsigned inputsPosted; unsigned inputsPosted;
int txPosition; int txPosition;
struct mTimingEvent event;
struct GBASIOPlayerKeyCallback callback; struct GBASIOPlayerKeyCallback callback;
bool oldOpposingDirections; bool oldOpposingDirections;
struct mKeyCallback* oldCallback; struct mKeyCallback* oldCallback;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau /* Copyright (c) 2013-2024 Jeffrey Pfau
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -13,40 +13,82 @@ CXX_GUARD_START
#include <mgba/core/lockstep.h> #include <mgba/core/lockstep.h>
#include <mgba/core/timing.h> #include <mgba/core/timing.h>
#include <mgba/internal/gba/sio.h> #include <mgba/internal/gba/sio.h>
#include <mgba-util/circle-buffer.h>
#include <mgba-util/table.h>
#include <mgba-util/threading.h>
struct GBASIOLockstep { #define MAX_LOCKSTEP_EVENTS 8
struct mLockstep d;
struct GBASIOLockstepNode* players[MAX_GBAS];
int attachedMulti;
int attachedNormal;
uint16_t multiRecv[MAX_GBAS]; enum GBASIOLockstepEventType {
uint32_t normalRecv[MAX_GBAS]; SIO_EV_ATTACH,
SIO_EV_DETACH,
SIO_EV_HARD_SYNC,
SIO_EV_MODE_SET,
SIO_EV_TRANSFER_START,
}; };
struct GBASIOLockstepNode { struct GBASIOLockstepCoordinator {
struct GBASIODriver d; struct Table players;
struct GBASIOLockstep* p; Mutex mutex;
struct mTimingEvent event;
volatile int32_t nextEvent; unsigned nextId;
int32_t eventDiff;
bool normalSO; unsigned attachedPlayers[MAX_GBAS];
int id; int nAttached;
uint32_t waiting;
bool transferActive;
enum GBASIOMode transferMode;
int32_t cycle;
int32_t nextHardSync;
uint16_t multiData[4];
uint32_t normalData[4];
};
struct GBASIOLockstepEvent {
enum GBASIOLockstepEventType type;
int32_t timestamp;
struct GBASIOLockstepEvent* next;
int playerId;
union {
enum GBASIOMode mode; enum GBASIOMode mode;
bool transferFinished; int32_t finishCycle;
#ifndef NDEBUG };
int transferId;
enum mLockstepPhase phase;
#endif
}; };
void GBASIOLockstepInit(struct GBASIOLockstep*); struct GBASIOLockstepPlayer {
struct GBASIOLockstepDriver* driver;
int playerId;
enum GBASIOMode mode;
enum GBASIOMode otherModes[MAX_GBAS];
bool asleep;
int32_t cycleOffset;
struct GBASIOLockstepEvent* queue;
bool dataReceived;
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*); struct GBASIOLockstepEvent buffer[MAX_LOCKSTEP_EVENTS];
struct GBASIOLockstepEvent* freeList;
};
bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); struct GBASIOLockstepDriver {
void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); struct GBASIODriver d;
struct GBASIOLockstepCoordinator* coordinator;
struct mTimingEvent event;
unsigned lockstepId;
struct mLockstepUser* user;
};
void GBASIOLockstepCoordinatorInit(struct GBASIOLockstepCoordinator*);
void GBASIOLockstepCoordinatorDeinit(struct GBASIOLockstepCoordinator*);
void GBASIOLockstepCoordinatorAttach(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepDriver*);
void GBASIOLockstepCoordinatorDetach(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepDriver*);
size_t GBASIOLockstepCoordinatorAttached(struct GBASIOLockstepCoordinator*);
void GBASIOLockstepDriverCreate(struct GBASIOLockstepDriver*, struct mLockstepUser*);
CXX_GUARD_END CXX_GUARD_END

View File

@ -1,10 +1,14 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau /* Copyright (c) 2013-2024 Jeffrey Pfau
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/lockstep.h> #include <mgba/core/lockstep.h>
#ifndef DISABLE_THREADING
#include <mgba/core/thread.h>
#endif
void mLockstepInit(struct mLockstep* lockstep) { void mLockstepInit(struct mLockstep* lockstep) {
lockstep->attached = 0; lockstep->attached = 0;
lockstep->transferActive = 0; lockstep->transferActive = 0;
@ -19,4 +23,21 @@ void mLockstepDeinit(struct mLockstep* lockstep) {
UNUSED(lockstep); UNUSED(lockstep);
} }
// TODO: Migrate nodes #ifndef DISABLE_THREADING
static void mLockstepThreadUserSleep(struct mLockstepUser* user) {
struct mLockstepThreadUser* lockstep = (struct mLockstepThreadUser*) user;
mCoreThreadWaitFromThread(lockstep->thread);
}
static void mLockstepThreadUserWake(struct mLockstepUser* user) {
struct mLockstepThreadUser* lockstep = (struct mLockstepThreadUser*) user;
mCoreThreadStopWaiting(lockstep->thread);
}
void mLockstepThreadUserInit(struct mLockstepThreadUser* lockstep, struct mCoreThread* thread) {
memset(lockstep, 0, sizeof(*lockstep));
lockstep->d.sleep = mLockstepThreadUserSleep;
lockstep->d.wake = mLockstepThreadUserWake;
lockstep->thread = thread;
}
#endif

View File

@ -31,7 +31,6 @@ set(SOURCE_FILES
sharkport.c sharkport.c
sio.c sio.c
sio/gbp.c sio/gbp.c
sio/joybus.c
timer.c timer.c
video.c) video.c)

View File

@ -486,10 +486,10 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState); flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState);
flags2 = GBASerializedHWFlags1SetLightCounter(flags2, hw->lightCounter); flags2 = GBASerializedHWFlags1SetLightCounter(flags2, hw->lightCounter);
// GBP stuff is only here for legacy reasons // GBP/SIO stuff is only here for legacy reasons
flags2 = GBASerializedHWFlags2SetGbpInputsPosted(flags2, hw->p->sio.gbp.inputsPosted); flags2 = GBASerializedHWFlags2SetGbpInputsPosted(flags2, hw->p->sio.gbp.inputsPosted);
flags2 = GBASerializedHWFlags2SetGbpTxPosition(flags2, hw->p->sio.gbp.txPosition); flags2 = GBASerializedHWFlags2SetGbpTxPosition(flags2, hw->p->sio.gbp.txPosition);
STORE_32(hw->p->sio.gbp.event.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.gbpNextEvent); STORE_32(hw->p->sio.completeEvent.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.sioNextEvent);
state->hw.flags2 = flags2; state->hw.flags2 = flags2;
} }
@ -532,16 +532,16 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
hw->lightSample = state->hw.lightSample; hw->lightSample = state->hw.lightSample;
hw->lightEdge = GBASerializedHWFlags1GetLightEdge(flags1); hw->lightEdge = GBASerializedHWFlags1GetLightEdge(flags1);
// GBP stuff is only here for legacy reasons // GBP/SIO stuff is only here for legacy reasons
hw->p->sio.gbp.inputsPosted = GBASerializedHWFlags2GetGbpInputsPosted(state->hw.flags2); hw->p->sio.gbp.inputsPosted = GBASerializedHWFlags2GetGbpInputsPosted(state->hw.flags2);
hw->p->sio.gbp.txPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2); hw->p->sio.gbp.txPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2);
uint32_t when; uint32_t when;
LOAD_32(when, 0, &state->hw.gbpNextEvent); LOAD_32(when, 0, &state->hw.sioNextEvent);
if (hw->devices & HW_GB_PLAYER) { if (hw->devices & HW_GB_PLAYER) {
GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d, GBA_SIO_NORMAL_32); GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d);
if (hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) { }
mTimingSchedule(&hw->p->timing, &hw->p->sio.gbp.event, when); if ((hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) && when < 0x20000) {
} mTimingSchedule(&hw->p->timing, &hw->p->sio.completeEvent, when);
} }
} }

View File

@ -842,7 +842,21 @@ static bool _GBACoreLoadExtraState(struct mCore* core, const struct mStateExtdat
if (type == gba->video.renderer->rendererId(gba->video.renderer)) { if (type == gba->video.renderer->rendererId(gba->video.renderer)) {
ok = gba->video.renderer->loadState(gba->video.renderer, ok = gba->video.renderer->loadState(gba->video.renderer,
(void*) ((uintptr_t) item.data + sizeof(uint32_t)), (void*) ((uintptr_t) item.data + sizeof(uint32_t)),
item.size - sizeof(type)); item.size - sizeof(type)) && ok;
}
} else if (item.data) {
ok = false;
}
}
if (gba->sio.driver && gba->sio.driver->driverId && gba->sio.driver->loadState &&
mStateExtdataGet(extdata, EXTDATA_SUBSYSTEM_START + GBA_SUBSYSTEM_SIO_DRIVER, &item)) {
if ((uint32_t) item.size > sizeof(uint32_t)) {
uint32_t type;
LOAD_32(type, 0, item.data);
if (type == gba->sio.driver->driverId(gba->sio.driver)) {
ok = gba->sio.driver->loadState(gba->sio.driver,
(void*) ((uintptr_t) item.data + sizeof(uint32_t)),
item.size - sizeof(type)) && ok;
} }
} else if (item.data) { } else if (item.data) {
ok = false; ok = false;
@ -868,6 +882,27 @@ static bool _GBACoreSaveExtraState(struct mCore* core, struct mStateExtdata* ext
} }
if (buffer) { if (buffer) {
free(buffer); free(buffer);
buffer = NULL;
}
size = 0;
if (gba->sio.driver && gba->sio.driver->driverId && gba->sio.driver->saveState) {
gba->sio.driver->saveState(gba->sio.driver, &buffer, &size);
if (size > 0 && buffer) {
struct mStateExtdataItem item;
item.size = size + sizeof(uint32_t);
item.data = malloc(item.size);
item.clean = free;
uint32_t type = gba->sio.driver->driverId(gba->sio.driver);
STORE_32(type, 0, item.data);
memcpy((void*) ((uintptr_t) item.data + sizeof(uint32_t)), buffer, size);
mStateExtdataPut(extdata, EXTDATA_SUBSYSTEM_START + GBA_SUBSYSTEM_SIO_DRIVER, &item);
}
if (buffer) {
free(buffer);
buffer = NULL;
}
size = 0;
} }
return true; return true;
@ -927,9 +962,8 @@ static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) {
case mPERIPH_GBA_LUMINANCE: case mPERIPH_GBA_LUMINANCE:
gba->luminanceSource = periph; gba->luminanceSource = periph;
break; break;
case mPERIPH_GBA_BATTLECHIP_GATE: case mPERIPH_GBA_LINK_PORT:
GBASIOSetDriver(&gba->sio, periph, GBA_SIO_MULTI); GBASIOSetDriver(&gba->sio, periph);
GBASIOSetDriver(&gba->sio, periph, GBA_SIO_NORMAL_32);
break; break;
default: default:
return; return;

View File

@ -33,28 +33,25 @@ enum {
BATTLECHIP_CONTINUE = 0xFFFF, BATTLECHIP_CONTINUE = 0xFFFF,
}; };
static bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver); static bool GBASIOBattlechipGateInit(struct GBASIODriver* driver);
static uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); static uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value);
static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static void _battlechipTransfer(struct GBASIOBattlechipGate* gate); static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver);
static void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cyclesLate); static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]);
void GBASIOBattlechipGateCreate(struct GBASIOBattlechipGate* gate) { void GBASIOBattlechipGateCreate(struct GBASIOBattlechipGate* gate) {
gate->d.init = NULL; memset(&gate->d, 0, sizeof(gate->d));
gate->d.deinit = NULL; gate->d.init = GBASIOBattlechipGateInit;
gate->d.load = GBASIOBattlechipGateLoad; gate->d.writeSIOCNT = GBASIOBattlechipGateWriteSIOCNT;
gate->d.unload = NULL; gate->d.handlesMode = GBASIOBattlechipGateHandlesMode;
gate->d.writeRegister = GBASIOBattlechipGateWriteRegister; gate->d.connectedDevices = GBASIOBattlechipGateConnectedDevices;
gate->d.finishMultiplayer = GBASIOBattlechipGateFinishMultiplayer;
gate->event.context = gate;
gate->event.callback = _battlechipTransferEvent;
gate->event.priority = 0x80;
gate->chipId = 0; gate->chipId = 0;
gate->flavor = GBA_FLAVOR_BATTLECHIP_GATE; gate->flavor = GBA_FLAVOR_BATTLECHIP_GATE;
} }
bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) { bool GBASIOBattlechipGateInit(struct GBASIODriver* driver) {
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver; struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
gate->state = BATTLECHIP_STATE_SYNC; gate->state = BATTLECHIP_STATE_SYNC;
gate->data[0] = 0x00FE; gate->data[0] = 0x00FE;
@ -62,58 +59,34 @@ bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) {
return true; return true;
} }
uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) {
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver; UNUSED(driver);
switch (address) {
case GBA_REG_SIOCNT:
value &= ~0xC; value &= ~0xC;
value |= 0x8; value |= 0x8;
if (value & 0x80) {
_battlechipTransfer(gate);
}
break;
case GBA_REG_SIOMLT_SEND:
break;
case GBA_REG_RCNT:
break;
default:
break;
}
return value; return value;
} }
void _battlechipTransfer(struct GBASIOBattlechipGate* gate) { static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
int32_t cycles; UNUSED(driver);
if (gate->d.p->mode == GBA_SIO_NORMAL_32) { switch (mode) {
cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000; case GBA_SIO_NORMAL_32:
} else { case GBA_SIO_MULTI:
cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1]; return true;
default:
return false;
} }
mTimingDeschedule(&gate->d.p->p->timing, &gate->event);
mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles);
} }
void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cyclesLate) { static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver) {
UNUSED(timing); UNUSED(driver);
struct GBASIOBattlechipGate* gate = user; return 1;
}
if (gate->d.p->mode == GBA_SIO_NORMAL_32) { static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]) {
gate->d.p->p->memory.io[GBA_REG(SIODATA32_LO)] = 0; struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
gate->d.p->p->memory.io[GBA_REG(SIODATA32_HI)] = 0;
gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt);
if (GBASIONormalIsIrq(gate->d.p->siocnt)) {
GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate);
}
return;
}
uint16_t cmd = gate->d.p->p->memory.io[GBA_REG(SIOMLT_SEND)]; uint16_t cmd = gate->d.p->p->memory.io[GBA_REG(SIOMLT_SEND)];
uint16_t reply = 0xFFFF; uint16_t reply = 0xFFFF;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI0)] = cmd;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF;
gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt);
gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0);
mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state); mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state);
@ -191,9 +164,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
mLOG(GBA_BATTLECHIP, DEBUG, "Gate: %04X (%i)", reply, gate->state); mLOG(GBA_BATTLECHIP, DEBUG, "Gate: %04X (%i)", reply, gate->state);
++gate->state; ++gate->state;
gate->d.p->p->memory.io[GBA_REG(SIOMULTI1)] = reply; data[0] = cmd;
data[1] = reply;
if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) { data[2] = 0xFFFF;
GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate); data[3] = 0xFFFF;
}
} }

View File

@ -263,8 +263,8 @@ void GBAReset(struct ARMCore* cpu) {
// GB Player SIO control should not be engaged before detection, even if we already know it's GBP // GB Player SIO control should not be engaged before detection, even if we already know it's GBP
gba->memory.hw.devices &= ~HW_GB_PLAYER; gba->memory.hw.devices &= ~HW_GB_PLAYER;
if (gba->sio.drivers.normal == &gba->sio.gbp.d) { if (gba->sio.driver == &gba->sio.gbp.d) {
GBASIOSetDriver(&gba->sio, NULL, GBA_SIO_NORMAL_32); GBASIOSetDriver(&gba->sio, NULL);
} }
bool isELF = false; bool isELF = false;

View File

@ -214,8 +214,8 @@ static const int _isRSpecialRegister[GBA_REG(INTERNAL_MAX)] = {
/* 10 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 10 */ 1, 1, 1, 1, 1, 1, 1, 1,
/* 11 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 11 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* SIO */ /* SIO */
/* 12 */ 1, 1, 1, 1, 1, 0, 0, 0, /* 12 */ 1, 1, 1, 1, 0, 0, 0, 0,
/* 13 */ 1, 1, 1, 0, 0, 0, 0, 0, /* 13 */ 1, 1, 0, 0, 0, 0, 0, 0,
/* 14 */ 1, 0, 0, 0, 0, 0, 0, 0, /* 14 */ 1, 0, 0, 0, 0, 0, 0, 0,
/* 15 */ 1, 1, 1, 1, 1, 0, 0, 0, /* 15 */ 1, 1, 1, 1, 1, 0, 0, 0,
/* 16 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 16 */ 0, 0, 0, 0, 0, 0, 0, 0,
@ -483,6 +483,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
// SIO // SIO
case GBA_REG_SIOCNT: case GBA_REG_SIOCNT:
value &= 0x7FFF;
GBASIOWriteSIOCNT(&gba->sio, value); GBASIOWriteSIOCNT(&gba->sio, value);
break; break;
case GBA_REG_RCNT: case GBA_REG_RCNT:

View File

@ -11,26 +11,14 @@
mLOG_DEFINE_CATEGORY(GBA_SIO, "GBA Serial I/O", "gba.sio"); mLOG_DEFINE_CATEGORY(GBA_SIO, "GBA Serial I/O", "gba.sio");
const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = { static const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = {
{ 31976, 63427, 94884, 125829 }, { 31976, 63427, 94884, 125829 },
{ 8378, 16241, 24104, 31457 }, { 8378, 16241, 24104, 31457 },
{ 5750, 10998, 16241, 20972 }, { 5750, 10998, 16241, 20972 },
{ 3140, 5755, 8376, 10486 } { 3140, 5755, 8376, 10486 }
}; };
static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mode) { static void _sioFinish(struct mTiming* timing, void* user, uint32_t cyclesLate);
switch (mode) {
case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32:
return sio->drivers.normal;
case GBA_SIO_MULTI:
return sio->drivers.multiplayer;
case GBA_SIO_JOYBUS:
return sio->drivers.joybus;
default:
return 0;
}
}
static const char* _modeName(enum GBASIOMode mode) { static const char* _modeName(enum GBASIOMode mode) {
switch (mode) { switch (mode) {
@ -58,25 +46,36 @@ static void _switchMode(struct GBASIO* sio) {
newMode = (enum GBASIOMode) (mode & 0xC); newMode = (enum GBASIOMode) (mode & 0xC);
} }
if (newMode != sio->mode) { if (newMode != sio->mode) {
if (sio->activeDriver && sio->activeDriver->unload) {
sio->activeDriver->unload(sio->activeDriver);
}
if (sio->mode != (enum GBASIOMode) -1) { if (sio->mode != (enum GBASIOMode) -1) {
mLOG(GBA_SIO, DEBUG, "Switching mode from %s to %s", _modeName(sio->mode), _modeName(newMode)); mLOG(GBA_SIO, DEBUG, "Switching mode from %s to %s", _modeName(sio->mode), _modeName(newMode));
} }
sio->mode = newMode; sio->mode = newMode;
sio->activeDriver = _lookupDriver(sio, sio->mode); if (sio->driver && sio->driver->setMode) {
if (sio->activeDriver && sio->activeDriver->load) { sio->driver->setMode(sio->driver, newMode);
sio->activeDriver->load(sio->activeDriver); }
int id = 0;
switch (newMode) {
case GBA_SIO_MULTI:
if (sio->driver && sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
}
sio->rcnt = GBASIORegisterRCNTSetSi(sio->rcnt, !!id);
break;
default:
// TODO
break;
} }
} }
} }
void GBASIOInit(struct GBASIO* sio) { void GBASIOInit(struct GBASIO* sio) {
sio->drivers.normal = 0; sio->driver = NULL;
sio->drivers.multiplayer = 0;
sio->drivers.joybus = 0; sio->completeEvent.context = sio;
sio->activeDriver = 0; sio->completeEvent.name = "GBA SIO Complete";
sio->completeEvent.callback = _sioFinish;
sio->completeEvent.priority = 0x80;
sio->gbp.p = sio->p; sio->gbp.p = sio->p;
GBASIOPlayerInit(&sio->gbp); GBASIOPlayerInit(&sio->gbp);
@ -85,64 +84,28 @@ void GBASIOInit(struct GBASIO* sio) {
} }
void GBASIODeinit(struct GBASIO* sio) { void GBASIODeinit(struct GBASIO* sio) {
if (sio->activeDriver && sio->activeDriver->unload) { if (sio->driver && sio->driver->deinit) {
sio->activeDriver->unload(sio->activeDriver); sio->driver->deinit(sio->driver);
}
if (sio->drivers.multiplayer && sio->drivers.multiplayer->deinit) {
sio->drivers.multiplayer->deinit(sio->drivers.multiplayer);
}
if (sio->drivers.joybus && sio->drivers.joybus->deinit) {
sio->drivers.joybus->deinit(sio->drivers.joybus);
}
if (sio->drivers.normal && sio->drivers.normal->deinit) {
sio->drivers.normal->deinit(sio->drivers.normal);
} }
} }
void GBASIOReset(struct GBASIO* sio) { void GBASIOReset(struct GBASIO* sio) {
if (sio->activeDriver && sio->activeDriver->unload) { if (sio->driver && sio->driver->reset) {
sio->activeDriver->unload(sio->activeDriver); sio->driver->reset(sio->driver);
} }
sio->rcnt = RCNT_INITIAL; sio->rcnt = RCNT_INITIAL;
sio->siocnt = 0; sio->siocnt = 0;
sio->mode = -1; sio->mode = -1;
sio->activeDriver = NULL;
_switchMode(sio); _switchMode(sio);
GBASIOPlayerReset(&sio->gbp); GBASIOPlayerReset(&sio->gbp);
} }
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers) { void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver) {
GBASIOSetDriver(sio, drivers->normal, GBA_SIO_NORMAL_8); if (sio->driver && sio->driver->deinit) {
GBASIOSetDriver(sio, drivers->multiplayer, GBA_SIO_MULTI); sio->driver->deinit(sio->driver);
GBASIOSetDriver(sio, drivers->joybus, GBA_SIO_JOYBUS);
}
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode) {
struct GBASIODriver** driverLoc;
switch (mode) {
case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32:
driverLoc = &sio->drivers.normal;
break;
case GBA_SIO_MULTI:
driverLoc = &sio->drivers.multiplayer;
break;
case GBA_SIO_JOYBUS:
driverLoc = &sio->drivers.joybus;
break;
default:
mLOG(GBA_SIO, ERROR, "Setting an unsupported SIO driver: %x", mode);
return;
}
if (*driverLoc) {
if ((*driverLoc)->unload) {
(*driverLoc)->unload(*driverLoc);
}
if ((*driverLoc)->deinit) {
(*driverLoc)->deinit(*driverLoc);
}
} }
sio->driver = driver;
if (driver) { if (driver) {
driver->p = sio; driver->p = sio;
@ -154,22 +117,42 @@ void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASI
} }
} }
} }
if (sio->activeDriver == *driverLoc) {
sio->activeDriver = driver;
if (driver && driver->load) {
driver->load(driver);
}
}
*driverLoc = driver;
} }
void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value) { void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value) {
sio->rcnt &= 0xF; sio->rcnt &= 0x1FF;
sio->rcnt |= value & ~0xF; sio->rcnt |= value & 0xC000;
_switchMode(sio); _switchMode(sio);
if (sio->activeDriver && sio->activeDriver->writeRegister) { if (sio->driver && sio->driver->writeRCNT) {
sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_RCNT, value); switch (sio->mode) {
case GBA_SIO_GPIO:
sio->rcnt = (sio->driver->writeRCNT(sio->driver, value) & 0x01FF) | (sio->rcnt & 0xC000);
break;
default:
sio->rcnt = (sio->driver->writeRCNT(sio->driver, value) & 0x01F0) | (sio->rcnt & 0xC00F);
} }
} else if (sio->mode == GBA_SIO_GPIO) {
sio->rcnt &= 0xC000;
sio->rcnt |= value & 0x1FF;
} else {
sio->rcnt &= 0xC00F;
sio->rcnt |= value & 0x1F0;
}
}
static void _startTransfer(struct GBASIO* sio) {
if (sio->driver && sio->driver->start) {
if (!sio->driver->start(sio->driver)) {
// Transfer completion is handled internally to the driver
return;
}
}
int connected = 0;
if (sio->driver && sio->driver->connectedDevices) {
connected = sio->driver->connectedDevices(sio->driver);
}
mTimingDeschedule(&sio->p->timing, &sio->completeEvent);
mTimingSchedule(&sio->p->timing, &sio->completeEvent, GBASIOTransferCycles(sio->mode, sio->siocnt, connected));
} }
void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
@ -177,25 +160,79 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
sio->siocnt = value & 0x3000; sio->siocnt = value & 0x3000;
_switchMode(sio); _switchMode(sio);
} }
if (sio->activeDriver && sio->activeDriver->writeRegister) { int id = 0;
value = sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_SIOCNT, value); int connected = 0;
bool handled = false;
if (sio->driver) {
handled = sio->driver->handlesMode(sio->driver, sio->mode);
if (handled) {
if (sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
}
connected = sio->driver->connectedDevices(sio->driver);
handled = !!sio->driver->writeSIOCNT;
}
}
switch (sio->mode) {
case GBA_SIO_MULTI:
value &= 0xFF83;
value = GBASIOMultiplayerSetSlave(value, id || !connected);
value = GBASIOMultiplayerSetId(value, id);
value |= sio->siocnt & 0x00FC;
// SC appears to float in multi mode when not doing a transfer. While
// it does spike at the end of a transfer, it appears to die down after
// around 20-30 microseconds. However, the docs on akkit.org
// (http://www.akkit.org/info/gba_comms.html) say this is high until
// a transfer starts and low while active. Further, the Mario Bros.
// multiplayer expects SC to be high in multi mode. This needs better
// investigation than I managed, apparently.
sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt);
if (GBASIOMultiplayerIsBusy(value) && !GBASIOMultiplayerIsBusy(sio->siocnt)) {
if (!id) {
sio->p->memory.io[GBA_REG(SIOMULTI0)] = 0xFFFF;
sio->p->memory.io[GBA_REG(SIOMULTI1)] = 0xFFFF;
sio->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF;
sio->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF;
sio->rcnt = GBASIORegisterRCNTClearSc(sio->rcnt);
_startTransfer(sio);
} else {
// TODO
}
}
break;
case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32:
// This line is pulled up by the clock owner while the clock is idle.
// If there is no clock owner it's just hi-Z.
if (GBASIONormalGetSc(value)) {
sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt);
}
if (GBASIONormalIsStart(value) && !GBASIONormalIsStart(sio->siocnt)) {
if (GBASIONormalIsSc(value)) {
_startTransfer(sio);
} else {
// TODO
}
}
break;
default:
// TODO
break;
}
if (handled) {
value = sio->driver->writeSIOCNT(sio->driver, value);
} else { } else {
// Dummy drivers // Dummy drivers
switch (sio->mode) { switch (sio->mode) {
case GBA_SIO_NORMAL_8: case GBA_SIO_NORMAL_8:
case GBA_SIO_NORMAL_32: case GBA_SIO_NORMAL_32:
value = GBASIONormalFillSi(value); value = GBASIONormalFillSi(value);
if ((value & 0x0081) == 0x0081) {
if (GBASIONormalIsIrq(value)) {
// TODO: Test this on hardware to see if this is correct
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0);
}
value = GBASIONormalClearStart(value);
}
break; break;
case GBA_SIO_MULTI: case GBA_SIO_MULTI:
value &= 0xFF83; value = GBASIOMultiplayerFillReady(value);
value |= 0xC;
break; break;
default: default:
// TODO // TODO
@ -206,22 +243,252 @@ 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) {
if (sio->activeDriver && sio->activeDriver->writeRegister) { int id = 0;
return sio->activeDriver->writeRegister(sio->activeDriver, address, value); if (sio->driver && sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
} }
// Dummy drivers
bool handled = true;
switch (sio->mode) { switch (sio->mode) {
case GBA_SIO_JOYBUS: case GBA_SIO_JOYBUS:
switch (address) { switch (address) {
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "JOY write: SIODATA8 (?) <- %04X", value);
break;
case GBA_REG_JOYCNT: case GBA_REG_JOYCNT:
return (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); mLOG(GBA_SIO, DEBUG, "JOY write: CNT <- %04X", value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
case GBA_REG_JOYSTAT: case GBA_REG_JOYSTAT:
return (value & 0x0030) | (sio->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30); mLOG(GBA_SIO, DEBUG, "JOY write: STAT <- %04X", value);
} value = (value & 0x0030) | (sio->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30);
break;
case GBA_REG_JOY_TRANS_LO:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_LO <- %04X", value);
break;
case GBA_REG_JOY_TRANS_HI:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_HI <- %04X", value);
break; break;
default: default:
// TODO mLOG(GBA_SIO, GAME_ERROR, "JOY write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
handled = false;
break; break;
} }
break;
case GBA_SIO_NORMAL_8:
switch (address) {
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "NORMAL8 %i write: SIODATA8 <- %04X", id, value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "NORMAL8 %i write: JOYCNT (?) <- %04X", id, value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "NORMAL8 %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_NORMAL_32:
switch (address) {
case GBA_REG_SIODATA32_LO:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA32_LO <- %04X", id, value);
break;
case GBA_REG_SIODATA32_HI:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA32_HI <- %04X", id, value);
break;
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA8 (?) <- %04X", id, value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: JOYCNT (?) <- %04X", id, value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "NORMAL32 %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_MULTI:
switch (address) {
case GBA_REG_SIOMLT_SEND:
mLOG(GBA_SIO, DEBUG, "MULTI %i write: SIOMLT_SEND <- %04X", id, value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "MULTI %i write: JOYCNT (?) <- %04X", id, value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "MULTI %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_UART:
switch (address) {
case GBA_REG_SIODATA8:
mLOG(GBA_SIO, DEBUG, "UART write: SIODATA8 <- %04X", value);
break;
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "UART write: JOYCNT (?) <- %04X", value);
value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
break;
default:
mLOG(GBA_SIO, GAME_ERROR, "UART write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
break;
case GBA_SIO_GPIO:
mLOG(GBA_SIO, STUB, "GPIO write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
handled = false;
break;
}
if (!handled) {
value = sio->p->memory.io[address >> 1];
}
return value; return value;
} }
int32_t GBASIOTransferCycles(enum GBASIOMode mode, uint16_t siocnt, int connected) {
if (connected < 0 || connected >= MAX_GBAS) {
mLOG(GBA_SIO, ERROR, "Invalid device count %i", connected);
return 0;
}
switch (mode) {
case GBA_SIO_MULTI:
return GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(siocnt)][connected];
case GBA_SIO_NORMAL_8:
return 8 * GBA_ARM7TDMI_FREQUENCY / ((GBASIONormalIsInternalSc(siocnt) ? 2048 : 256) * 1024);
case GBA_SIO_NORMAL_32:
return 32 * GBA_ARM7TDMI_FREQUENCY / ((GBASIONormalIsInternalSc(siocnt) ? 2048 : 256) * 1024);
default:
mLOG(GBA_SIO, STUB, "No cycle count implemented for mode %s", _modeName(mode));
break;
}
return 0;
}
void GBASIOMultiplayerFinishTransfer(struct GBASIO* sio, uint16_t data[4], uint32_t cyclesLate) {
int id = 0;
if (sio->driver && sio->driver->deviceId) {
id = sio->driver->deviceId(sio->driver);
}
sio->p->memory.io[GBA_REG(SIOMULTI0)] = data[0];
sio->p->memory.io[GBA_REG(SIOMULTI1)] = data[1];
sio->p->memory.io[GBA_REG(SIOMULTI2)] = data[2];
sio->p->memory.io[GBA_REG(SIOMULTI3)] = data[3];
sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, id);
sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt);
if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate);
}
}
void GBASIONormal8FinishTransfer(struct GBASIO* sio, uint8_t data, uint32_t cyclesLate) {
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
sio->p->memory.io[GBA_REG(SIODATA8)] = data;
if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate);
}
}
void GBASIONormal32FinishTransfer(struct GBASIO* sio, uint32_t data, uint32_t cyclesLate) {
sio->siocnt = GBASIONormalClearStart(sio->siocnt);
sio->p->memory.io[GBA_REG(SIODATA32_LO)] = data;
sio->p->memory.io[GBA_REG(SIODATA32_HI)] = data >> 16;
if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate);
}
}
static void _sioFinish(struct mTiming* timing, void* user, uint32_t cyclesLate) {
UNUSED(timing);
struct GBASIO* sio = user;
union {
uint16_t multi[4];
uint8_t normal8;
uint32_t normal32;
} data = {0};
switch (sio->mode) {
case GBA_SIO_MULTI:
if (sio->driver && sio->driver->finishMultiplayer) {
sio->driver->finishMultiplayer(sio->driver, data.multi);
}
GBASIOMultiplayerFinishTransfer(sio, data.multi, cyclesLate);
break;
case GBA_SIO_NORMAL_8:
if (sio->driver && sio->driver->finishNormal8) {
data.normal8 = sio->driver->finishNormal8(sio->driver);
}
GBASIONormal8FinishTransfer(sio, data.normal8, cyclesLate);
break;
case GBA_SIO_NORMAL_32:
if (sio->driver && sio->driver->finishNormal32) {
data.normal32 = sio->driver->finishNormal32(sio->driver);
}
GBASIONormal32FinishTransfer(sio, data.normal32, cyclesLate);
break;
default:
// TODO
mLOG(GBA_SIO, STUB, "No dummy finish implemented for mode %s", _modeName(sio->mode));
break;
}
}
int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) {
switch (command) {
case JOY_RESET:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RESET;
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
// Fall through
case JOY_POLL:
data[0] = 0x00;
data[1] = 0x04;
data[2] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY %s: %02X (%02X)", command == JOY_POLL ? "poll" : "reset", data[2], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
return 3;
case JOY_RECV:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RECV;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] |= JOYSTAT_RECV;
sio->p->p->memory.io[GBA_REG(JOY_RECV_LO)] = data[0] | (data[1] << 8);
sio->p->p->memory.io[GBA_REG(JOY_RECV_HI)] = data[2] | (data[3] << 8);
data[0] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY recv: %02X (%02X)", data[0], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 1;
case JOY_TRANS:
data[0] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)];
data[1] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)] >> 8;
data[2] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)];
data[3] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)] >> 8;
data[4] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_TRANS;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] &= ~JOYSTAT_TRANS;
mLOG(GBA_SIO, DEBUG, "JOY trans: %02X%02X%02X%02X:%02X (%02X)", data[0], data[1], data[2], data[3], data[4], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 5;
}
return 0;
}

View File

@ -23,18 +23,22 @@ enum {
}; };
static bool GBASIODolphinInit(struct GBASIODriver* driver); static bool GBASIODolphinInit(struct GBASIODriver* driver);
static bool GBASIODolphinLoad(struct GBASIODriver* driver); static void GBASIODolphinReset(struct GBASIODriver* driver);
static bool GBASIODolphinUnload(struct GBASIODriver* driver); static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver);
static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate); static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate);
static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate); static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate);
static void _flush(struct GBASIODolphin* dol); static void _flush(struct GBASIODolphin* dol);
void GBASIODolphinCreate(struct GBASIODolphin* dol) { void GBASIODolphinCreate(struct GBASIODolphin* dol) {
GBASIOJOYCreate(&dol->d); memset(&dol->d, 0, sizeof(dol->d));
dol->d.init = GBASIODolphinInit; dol->d.init = GBASIODolphinInit;
dol->d.load = GBASIODolphinLoad; dol->d.reset = GBASIODolphinReset;
dol->d.unload = GBASIODolphinUnload; dol->d.setMode = GBASIODolphinSetMode;
dol->d.handlesMode = GBASIODolphinHandlesMode;
dol->d.connectedDevices = GBASIODolphinConnectedDevices;
dol->event.context = dol; dol->event.context = dol;
dol->event.name = "GB SIO Lockstep"; dol->event.name = "GB SIO Lockstep";
dol->event.callback = GBASIODolphinProcessEvents; dol->event.callback = GBASIODolphinProcessEvents;
@ -94,26 +98,33 @@ bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* addre
static bool GBASIODolphinInit(struct GBASIODriver* driver) { static bool GBASIODolphinInit(struct GBASIODriver* driver) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = false;
dol->clockSlice = 0; dol->clockSlice = 0;
dol->state = WAIT_FOR_FIRST_CLOCK; dol->state = WAIT_FOR_FIRST_CLOCK;
_flush(dol); GBASIODolphinReset(driver);
return true; return true;
} }
static bool GBASIODolphinLoad(struct GBASIODriver* driver) { static void GBASIODolphinReset(struct GBASIODriver* driver) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = true; dol->active = false;
_flush(dol); _flush(dol);
mTimingDeschedule(&dol->d.p->p->timing, &dol->event); mTimingDeschedule(&dol->d.p->p->timing, &dol->event);
mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0); mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0);
return true;
} }
static bool GBASIODolphinUnload(struct GBASIODriver* driver) { static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
dol->active = false; dol->active = mode == GBA_SIO_JOYBUS;
return true; }
static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
UNUSED(driver);
return mode == GBA_SIO_JOYBUS;
}
static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver) {
UNUSED(driver);
return 1;
} }
void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) { void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) {

View File

@ -13,8 +13,11 @@
#include <mgba-util/memory.h> #include <mgba-util/memory.h>
static uint16_t _gbpRead(struct mKeyCallback*); static uint16_t _gbpRead(struct mKeyCallback*);
static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); static uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value);
static void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate); static bool _gbpSioHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
static int _gbpSioConnectedDevices(struct GBASIODriver* driver);
static bool _gbpSioStart(struct GBASIODriver* driver);
static uint32_t _gbpSioFinishNormal32(struct GBASIODriver* driver);
static const uint8_t _logoPalette[] = { static const uint8_t _logoPalette[] = {
0xDF, 0xFF, 0x0C, 0x64, 0x0C, 0xE4, 0x2D, 0xE4, 0x4E, 0x64, 0x4E, 0xE4, 0x6E, 0xE4, 0xAF, 0x68, 0xDF, 0xFF, 0x0C, 0x64, 0x0C, 0xE4, 0x2D, 0xE4, 0x4E, 0x64, 0x4E, 0xE4, 0x6E, 0xE4, 0xAF, 0x68,
@ -43,20 +46,17 @@ void GBASIOPlayerInit(struct GBASIOPlayer* gbp) {
gbp->callback.d.readKeys = _gbpRead; gbp->callback.d.readKeys = _gbpRead;
gbp->callback.d.requireOpposingDirections = true; gbp->callback.d.requireOpposingDirections = true;
gbp->callback.p = gbp; gbp->callback.p = gbp;
gbp->d.init = 0; memset(&gbp->d, 0, sizeof(gbp->d));
gbp->d.deinit = 0; gbp->d.writeSIOCNT = _gbpSioWriteSIOCNT;
gbp->d.load = 0; gbp->d.handlesMode = _gbpSioHandlesMode;
gbp->d.unload = 0; gbp->d.connectedDevices = _gbpSioConnectedDevices;
gbp->d.writeRegister = _gbpSioWriteRegister; gbp->d.start = _gbpSioStart;
gbp->event.context = gbp; gbp->d.finishNormal32 = _gbpSioFinishNormal32;
gbp->event.name = "GBA SIO Game Boy Player";
gbp->event.callback = _gbpSioProcessEvents;
gbp->event.priority = 0x80;
} }
void GBASIOPlayerReset(struct GBASIOPlayer* gbp) { void GBASIOPlayerReset(struct GBASIOPlayer* gbp) {
if (gbp->p->sio.drivers.normal == &gbp->d) { if (gbp->p->sio.driver == &gbp->d) {
GBASIOSetDriver(&gbp->p->sio, NULL, GBA_SIO_NORMAL_32); GBASIOSetDriver(&gbp->p->sio, NULL);
} }
} }
@ -87,8 +87,9 @@ void GBASIOPlayerUpdate(struct GBA* gba) {
gba->sio.gbp.inputsPosted = 0; gba->sio.gbp.inputsPosted = 0;
gba->sio.gbp.oldCallback = gba->keyCallback; gba->sio.gbp.oldCallback = gba->keyCallback;
gba->keyCallback = &gba->sio.gbp.callback.d; gba->keyCallback = &gba->sio.gbp.callback.d;
// TODO: Check if the SIO driver is actually used first if (!gba->sio.driver) {
GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d, GBA_SIO_NORMAL_32); GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d);
}
} }
} }
@ -100,10 +101,13 @@ uint16_t _gbpRead(struct mKeyCallback* callback) {
return 0; return 0;
} }
uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) {
UNUSED(driver);
return value & 0x78FB;
}
bool _gbpSioStart(struct GBASIODriver* driver) {
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver; struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
if (address == GBA_REG_SIOCNT) {
if (value & 0x0080) {
uint32_t rx = gbp->p->memory.io[GBA_REG(SIODATA32_LO)] | (gbp->p->memory.io[GBA_REG(SIODATA32_HI)] << 16); uint32_t rx = gbp->p->memory.io[GBA_REG(SIODATA32_LO)] | (gbp->p->memory.io[GBA_REG(SIODATA32_HI)] << 16);
if (gbp->txPosition < 12 && gbp->txPosition > 0) { if (gbp->txPosition < 12 && gbp->txPosition > 0) {
// TODO: Check expected // TODO: Check expected
@ -117,18 +121,21 @@ uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uin
gbp->p->lastRumble = currentTime; gbp->p->lastRumble = currentTime;
} }
} }
mTimingDeschedule(&gbp->p->timing, &gbp->event); return true;
mTimingSchedule(&gbp->p->timing, &gbp->event, 2048);
}
value &= 0x78FB;
}
return value;
} }
void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) { static bool _gbpSioHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
UNUSED(timing); UNUSED(driver);
UNUSED(cyclesLate); return mode == GBA_SIO_NORMAL_32;
struct GBASIOPlayer* gbp = user; }
static int _gbpSioConnectedDevices(struct GBASIODriver* driver) {
UNUSED(driver);
return 1;
}
uint32_t _gbpSioFinishNormal32(struct GBASIODriver* driver) {
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
uint32_t tx = 0; uint32_t tx = 0;
int txPosition = gbp->txPosition; int txPosition = gbp->txPosition;
if (txPosition > 16) { if (txPosition > 16) {
@ -139,11 +146,5 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
} }
tx = _gbpTxData[txPosition]; tx = _gbpTxData[txPosition];
++gbp->txPosition; ++gbp->txPosition;
gbp->p->memory.io[GBA_REG(SIODATA32_LO)] = tx; return tx;
gbp->p->memory.io[GBA_REG(SIODATA32_HI)] = tx >> 16;
if (GBASIONormalIsIrq(gbp->d.p->siocnt)) {
GBARaiseIRQ(gbp->p, GBA_IRQ_SIO, cyclesLate);
}
gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt);
gbp->p->memory.io[GBA_REG(SIOCNT)] = gbp->d.p->siocnt & ~0x0080;
} }

View File

@ -1,92 +0,0 @@
/* 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.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/io.h>
static uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value);
void GBASIOJOYCreate(struct GBASIODriver* sio) {
sio->init = NULL;
sio->deinit = NULL;
sio->load = NULL;
sio->unload = NULL;
sio->writeRegister = GBASIOJOYWriteRegister;
}
uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value) {
switch (address) {
case GBA_REG_JOYCNT:
mLOG(GBA_SIO, DEBUG, "JOY write: CNT <- %04X", value);
return (value & 0x0040) | (sio->p->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040);
case GBA_REG_JOYSTAT:
mLOG(GBA_SIO, DEBUG, "JOY write: STAT <- %04X", value);
return (value & 0x0030) | (sio->p->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30);
case GBA_REG_JOY_TRANS_LO:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_LO <- %04X", value);
break;
case GBA_REG_JOY_TRANS_HI:
mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_HI <- %04X", value);
break;
default:
mLOG(GBA_SIO, DEBUG, "JOY write: Unknown reg %03X <- %04X", address, value);
// Fall through
case GBA_REG_RCNT:
break;
}
return value;
}
int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) {
switch (command) {
case JOY_RESET:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RESET;
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
// Fall through
case JOY_POLL:
data[0] = 0x00;
data[1] = 0x04;
data[2] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY %s: %02X (%02X)", command == JOY_POLL ? "poll" : "reset", data[2], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
return 3;
case JOY_RECV:
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RECV;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] |= JOYSTAT_RECV;
sio->p->p->memory.io[GBA_REG(JOY_RECV_LO)] = data[0] | (data[1] << 8);
sio->p->p->memory.io[GBA_REG(JOY_RECV_HI)] = data[2] | (data[3] << 8);
data[0] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
mLOG(GBA_SIO, DEBUG, "JOY recv: %02X (%02X)", data[0], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 1;
case JOY_TRANS:
data[0] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)];
data[1] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)] >> 8;
data[2] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)];
data[3] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)] >> 8;
data[4] = sio->p->p->memory.io[GBA_REG(JOYSTAT)];
sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_TRANS;
sio->p->p->memory.io[GBA_REG(JOYSTAT)] &= ~JOYSTAT_TRANS;
mLOG(GBA_SIO, DEBUG, "JOY trans: %02X%02X%02X%02X:%02X (%02X)", data[0], data[1], data[2], data[3], data[4], sio->p->p->memory.io[GBA_REG(JOYCNT)]);
if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) {
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 5;
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -52,72 +52,6 @@ class GBA(Core):
super(GBA, self)._load() super(GBA, self)._load()
self.memory = GBAMemory(self._core, self._native.memory.romSize) self.memory = GBAMemory(self._core, self._native.memory.romSize)
def attach_sio(self, link, mode=lib.GBA_SIO_MULTI):
self._sio.add(mode)
lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode)
def __del__(self):
for mode in self._sio:
lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode)
create_callback("GBASIOPythonDriver", "init")
create_callback("GBASIOPythonDriver", "deinit")
create_callback("GBASIOPythonDriver", "load")
create_callback("GBASIOPythonDriver", "unload")
create_callback("GBASIOPythonDriver", "writeRegister")
class GBASIODriver(object):
def __init__(self):
super(GBASIODriver, self).__init__()
self._handle = ffi.new_handle(self)
self._native = ffi.gc(lib.GBASIOPythonDriverCreate(self._handle), lib.free)
def init(self):
return True
def deinit(self):
pass
def load(self):
return True
def unload(self):
return True
def write_register(self, address, value):
return value
class GBASIOJOYDriver(GBASIODriver):
RESET = lib.JOY_RESET
POLL = lib.JOY_POLL
TRANS = lib.JOY_TRANS
RECV = lib.JOY_RECV
def __init__(self):
super(GBASIOJOYDriver, self).__init__()
self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free)
def send_command(self, cmd, data):
buffer = ffi.new('uint8_t[5]')
try:
buffer[0] = data[0]
buffer[1] = data[1]
buffer[2] = data[2]
buffer[3] = data[3]
buffer[4] = data[4]
except IndexError:
pass
outlen = lib.GBASIOJOYSendCommand(self._native, cmd, buffer)
if outlen > 0 and outlen <= 5:
return bytes(buffer[0:outlen])
return None
class GBAMemory(Memory): class GBAMemory(Memory):
def __init__(self, core, romSize=lib.SIZE_CART0): def __init__(self, core, romSize=lib.SIZE_CART0):

View File

@ -1,88 +0,0 @@
/* 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/flags.h>
#define CREATE_SHIM(PLAT, NAME, RETURN) \
RETURN _py ## PLAT ## SIOPythonDriver ## NAME (void* driver); \
static RETURN _py ## PLAT ## SIOPythonDriver ## NAME ## Shim(struct PLAT ## SIODriver* driver) { \
struct PLAT ## SIODriver* py = (struct PLAT ## SIODriver*) driver; \
return _py ## PLAT ## SIOPythonDriver ## NAME(py); \
}
#define CREATE_SHIM_ARGS(PLAT, NAME, RETURN, TYPES, ...) \
RETURN _py ## PLAT ## SIOPythonDriver ## NAME TYPES; \
static RETURN _py ## PLAT ## SIOPythonDriver ## NAME ## Shim TYPES { \
struct PLAT ## SIODriver* py = (struct PLAT ## SIODriver*) driver; \
return _py ## PLAT ## SIOPythonDriver ## NAME(py, __VA_ARGS__); \
}
#ifdef M_CORE_GBA
#include <mgba/gba/interface.h>
struct GBASIOPythonDriver {
struct GBASIODriver d;
void* pyobj;
};
CREATE_SHIM(GBA, Init, bool);
CREATE_SHIM(GBA, Deinit, void);
CREATE_SHIM(GBA, Load, bool);
CREATE_SHIM(GBA, Unload, bool);
CREATE_SHIM_ARGS(GBA, WriteRegister, uint16_t, (struct GBASIODriver* driver, uint32_t address, uint16_t value), address, value);
struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj) {
struct GBASIOPythonDriver* driver = malloc(sizeof(*driver));
driver->d.init = _pyGBASIOPythonDriverInitShim;
driver->d.deinit = _pyGBASIOPythonDriverDeinitShim;
driver->d.load = _pyGBASIOPythonDriverLoadShim;
driver->d.unload = _pyGBASIOPythonDriverUnloadShim;
driver->d.writeRegister = _pyGBASIOPythonDriverWriteRegisterShim;
driver->pyobj = pyobj;
return &driver->d;
}
struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj) {
struct GBASIOPythonDriver* driver = malloc(sizeof(*driver));
GBASIOJOYCreate(&driver->d);
driver->d.init = _pyGBASIOPythonDriverInitShim;
driver->d.deinit = _pyGBASIOPythonDriverDeinitShim;
driver->d.load = _pyGBASIOPythonDriverLoadShim;
driver->d.unload = _pyGBASIOPythonDriverUnloadShim;
driver->pyobj = pyobj;
return &driver->d;
}
#endif
#ifdef M_CORE_GB
#include <mgba/gb/interface.h>
struct GBSIOPythonDriver {
struct GBSIODriver d;
void* pyobj;
};
CREATE_SHIM(GB, Init, bool);
CREATE_SHIM(GB, Deinit, void);
CREATE_SHIM_ARGS(GB, WriteSB, void, (struct GBSIODriver* driver, uint8_t value), value);
CREATE_SHIM_ARGS(GB, WriteSC, uint8_t, (struct GBSIODriver* driver, uint8_t value), value);
struct GBSIODriver* GBSIOPythonDriverCreate(void* pyobj) {
struct GBSIOPythonDriver* driver = malloc(sizeof(*driver));
driver->d.init = _pyGBSIOPythonDriverInitShim;
driver->d.deinit = _pyGBSIOPythonDriverDeinitShim;
driver->d.writeSB = _pyGBSIOPythonDriverWriteSBShim;
driver->d.writeSC = _pyGBSIOPythonDriverWriteSCShim;
driver->pyobj = pyobj;
return &driver->d;
}
#endif

View File

@ -1,42 +0,0 @@
/* 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/. */
#ifdef M_CORE_GBA
#include <mgba/gba/interface.h>
struct GBASIOPythonDriver {
struct GBASIODriver d;
void* pyobj;
};
struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj);
struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj);
PYEXPORT bool _pyGBASIOPythonDriverInit(void* driver);
PYEXPORT void _pyGBASIOPythonDriverDeinit(void* driver);
PYEXPORT bool _pyGBASIOPythonDriverLoad(void* driver);
PYEXPORT bool _pyGBASIOPythonDriverUnload(void* driver);
PYEXPORT uint16_t _pyGBASIOPythonDriverWriteRegister(void* driver, uint32_t address, uint16_t value);
#endif
#ifdef M_CORE_GB
#include <mgba/gb/interface.h>
struct GBSIOPythonDriver {
struct GBSIODriver d;
void* pyobj;
};
struct GBSIODriver* GBSIOPythonDriverCreate(void* pyobj);
PYEXPORT bool _pyGBSIOPythonDriverInit(void* driver);
PYEXPORT void _pyGBSIOPythonDriverDeinit(void* driver);
PYEXPORT void _pyGBSIOPythonDriverWriteSB(void* driver, uint8_t value);
PYEXPORT uint8_t _pyGBSIOPythonDriverWriteSC(void* driver, uint8_t value);
#endif

View File

@ -423,8 +423,8 @@ bool CoreController::attachDolphin(const Address& address) {
return false; return false;
} }
if (GBASIODolphinConnect(&m_dolphin, &address, 0, 0)) { if (GBASIODolphinConnect(&m_dolphin, &address, 0, 0)) {
GBA* gba = static_cast<GBA*>(m_threadContext.core->board); clearMultiplayerController();
GBASIOSetDriver(&gba->sio, &m_dolphin.d, GBA_SIO_JOYBUS); m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, &m_dolphin.d);
return true; return true;
} }
return false; return false;
@ -432,8 +432,8 @@ bool CoreController::attachDolphin(const Address& address) {
void CoreController::detachDolphin() { void CoreController::detachDolphin() {
if (platform() == mPLATFORM_GBA) { if (platform() == mPLATFORM_GBA) {
GBA* gba = static_cast<GBA*>(m_threadContext.core->board); // TODO: Reattach to multiplayer controller
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_JOYBUS); m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, NULL);
} }
GBASIODolphinDestroy(&m_dolphin); GBASIODolphinDestroy(&m_dolphin);
} }
@ -1094,7 +1094,7 @@ void CoreController::attachBattleChipGate() {
Interrupter interrupter(this); Interrupter interrupter(this);
clearMultiplayerController(); clearMultiplayerController();
GBASIOBattlechipGateCreate(&m_battlechip); GBASIOBattlechipGateCreate(&m_battlechip);
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, &m_battlechip); m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, &m_battlechip);
} }
void CoreController::detachBattleChipGate() { void CoreController::detachBattleChipGate() {
@ -1102,7 +1102,7 @@ void CoreController::detachBattleChipGate() {
return; return;
} }
Interrupter interrupter(this); Interrupter interrupter(this);
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, nullptr); m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, nullptr);
} }
void CoreController::setBattleChipId(uint16_t id) { void CoreController::setBattleChipId(uint16_t id) {

View File

@ -7,6 +7,7 @@
#include "CoreController.h" #include "CoreController.h"
#include "LogController.h" #include "LogController.h"
#include "utils.h"
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
#include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/gba.h>
@ -27,8 +28,14 @@ MultiplayerController::Player::Player(CoreController* coreController)
int MultiplayerController::Player::id() const { int MultiplayerController::Player::id() const {
switch (controller->platform()) { switch (controller->platform()) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: case mPLATFORM_GBA: {
return node.gba->id; int id = node.gba->d.deviceId(&node.gba->d);
if (id >= 0) {
return id;
} else {
return preferredId;
}
}
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
case mPLATFORM_GB: case mPLATFORM_GB:
@ -89,25 +96,7 @@ MultiplayerController::MultiplayerController() {
switch (player->controller->platform()) { switch (player->controller->platform()) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: case mPLATFORM_GBA:
if (!id) { abort();
for (int i = 1; i < controller->m_players.count(); ++i) {
player = controller->player(i);
if (player->node.gba->d.p->mode > GBA_SIO_MULTI) {
player->controller->setSync(true);
continue;
}
player->controller->setSync(false);
player->cyclesPosted += cycles;
if (player->awake < 1) {
player->node.gba->nextEvent += player->cyclesPosted;
}
mCoreThreadStopWaiting(player->controller->thread());
player->awake = 1;
}
} else {
player->controller->setSync(true);
player->cyclesPosted += cycles;
}
break; break;
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
@ -169,7 +158,6 @@ MultiplayerController::MultiplayerController() {
switch (player->controller->platform()) { switch (player->controller->platform()) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: case mPLATFORM_GBA:
player->cyclesPosted += reinterpret_cast<GBASIOLockstep*>(lockstep)->players[0]->eventDiff;
break; break;
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
@ -184,7 +172,6 @@ MultiplayerController::MultiplayerController() {
switch (player->controller->platform()) { switch (player->controller->platform()) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: case mPLATFORM_GBA:
player->node.gba->nextEvent += player->cyclesPosted;
break; break;
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
@ -214,11 +201,12 @@ bool MultiplayerController::attachGame(CoreController* controller) {
interrupters.append(p.controller); interrupters.append(p.controller);
} }
if (m_lockstep.attached == 0) { bool doDelayedAttach = false;
if (m_platform == mPLATFORM_NONE) {
switch (controller->platform()) { switch (controller->platform()) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: case mPLATFORM_GBA:
GBASIOLockstepInit(&m_gbaLockstep); GBASIOLockstepCoordinatorInit(&m_gbaCoordinator);
break; break;
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
@ -240,28 +228,50 @@ bool MultiplayerController::attachGame(CoreController* controller) {
} }
Player player{controller}; Player player{controller};
for (int i = 0; i < MAX_GBAS; ++i) {
if (m_claimedIds & (1 << i)) {
continue;
}
player.preferredId = i;
m_claimedIds |= 1 << i;
break;
}
switch (controller->platform()) { switch (controller->platform()) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: { case mPLATFORM_GBA: {
if (m_lockstep.attached >= MAX_GBAS) { if (attached() >= MAX_GBAS) {
return false; return false;
} }
GBA* gba = static_cast<GBA*>(thread->core->board); GBASIOLockstepDriver* node = new GBASIOLockstepDriver;
LockstepUser* user = new LockstepUser;
mLockstepThreadUserInit(user, thread);
user->controller = this;
user->pid = m_nextPid;
user->d.requestedId = [](mLockstepUser* ctx) {
mLockstepThreadUser* tctx = reinterpret_cast<mLockstepThreadUser*>(ctx);
LockstepUser* user = static_cast<LockstepUser*>(tctx);
MultiplayerController* controller = user->controller;
const auto iter = controller->m_pids.find(user->pid);
if (iter == controller->m_pids.end()) {
return -1;
}
const Player& p = iter.value();
return p.preferredId;
};
GBASIOLockstepNode* node = new GBASIOLockstepNode; GBASIOLockstepDriverCreate(node, &user->d);
GBASIOLockstepNodeCreate(node);
GBASIOLockstepAttachNode(&m_gbaLockstep, node);
player.node.gba = node; player.node.gba = node;
GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_MULTI); if (m_pids.size()) {
GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_NORMAL_32); doDelayedAttach = true;
}
break; break;
} }
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB
case mPLATFORM_GB: { case mPLATFORM_GB: {
if (m_lockstep.attached >= 2) { if (attached() >= 2) {
return false; return false;
} }
@ -271,6 +281,7 @@ bool MultiplayerController::attachGame(CoreController* controller) {
GBSIOLockstepNodeCreate(node); GBSIOLockstepNodeCreate(node);
GBSIOLockstepAttachNode(&m_gbLockstep, node); GBSIOLockstepAttachNode(&m_gbLockstep, node);
player.node.gb = node; player.node.gb = node;
player.attached = true;
GBSIOSetDriver(&gb->sio, &node->d); GBSIOSetDriver(&gb->sio, &node->d);
break; break;
@ -281,7 +292,7 @@ bool MultiplayerController::attachGame(CoreController* controller) {
} }
QPair<QString, QString> path(controller->path(), controller->baseDirectory()); QPair<QString, QString> path(controller->path(), controller->baseDirectory());
int claimed = m_claimed[path]; int claimed = m_claimedSaves[path];
int saveId = 0; int saveId = 0;
mCoreConfigGetIntValue(&controller->thread()->core->config, "savePlayerId", &saveId); mCoreConfigGetIntValue(&controller->thread()->core->config, "savePlayerId", &saveId);
@ -304,12 +315,25 @@ bool MultiplayerController::attachGame(CoreController* controller) {
} else { } else {
player.saveId = 1; player.saveId = 1;
} }
m_claimed[path] |= 1 << (player.saveId - 1); m_claimedSaves[path] |= 1 << (player.saveId - 1);
m_pids.insert(m_nextPid, player); m_pids.insert(m_nextPid, player);
++m_nextPid; ++m_nextPid;
fixOrder(); fixOrder();
if (doDelayedAttach) {
for (auto pid: m_players) {
Player& player = m_pids.find(pid).value();
if (player.attached) {
continue;
}
struct mCore* core = player.controller->thread()->core;
GBASIOLockstepCoordinatorAttach(&m_gbaCoordinator, player.node.gba);
core->setPeripheral(core, mPERIPH_GBA_LINK_PORT, &player.node.gba->d);
player.attached = true;
}
}
emit gameAttached(); emit gameAttached();
return true; return true;
} }
@ -328,8 +352,7 @@ void MultiplayerController::detachGame(CoreController* controller) {
for (int i = 0; i < m_players.count(); ++i) { for (int i = 0; i < m_players.count(); ++i) {
Player* p = player(i); Player* p = player(i);
if (!p) { if (!p) {
LOG(QT, ERROR) << tr("Trying to detach a multiplayer player that's not attached"); continue;
return;
} }
CoreController* playerController = p->controller; CoreController* playerController = p->controller;
if (playerController == controller) { if (playerController == controller) {
@ -337,17 +360,24 @@ void MultiplayerController::detachGame(CoreController* controller) {
} }
interrupters.append(playerController); interrupters.append(playerController);
} }
if (pid < 0) {
LOG(QT, WARN) << tr("Trying to detach a multiplayer player that's not attached");
return;
}
switch (controller->platform()) { switch (controller->platform()) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: { case mPLATFORM_GBA: {
GBA* gba = static_cast<GBA*>(thread->core->board); GBA* gba = static_cast<GBA*>(thread->core->board);
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer); Player& p = m_pids.find(pid).value();
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_MULTI); GBASIODriver* node = gba->sio.driver;
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_NORMAL_32); if (node == &p.node.gba->d) {
if (node) { thread->core->setPeripheral(thread->core, mPERIPH_GBA_LINK_PORT, NULL);
GBASIOLockstepDetachNode(&m_gbaLockstep, node);
delete node;
} }
if (p.attached) {
GBASIOLockstepCoordinatorDetach(&m_gbaCoordinator, p.node.gba);
}
delete p.node.gba->user;
delete p.node.gba;
break; break;
} }
#endif #endif
@ -371,14 +401,20 @@ void MultiplayerController::detachGame(CoreController* controller) {
QPair<QString, QString> path(controller->path(), controller->baseDirectory()); QPair<QString, QString> path(controller->path(), controller->baseDirectory());
Player& p = m_pids.find(pid).value(); Player& p = m_pids.find(pid).value();
if (!p.saveId) { if (!p.saveId) {
LOG(QT, ERROR) << "Clearing invalid save ID"; LOG(QT, WARN) << "Clearing invalid save ID";
} else { } else {
m_claimed[path] &= ~(1 << (p.saveId - 1)); m_claimedSaves[path] &= ~(1 << (p.saveId - 1));
if (!m_claimed[path]) { if (!m_claimedSaves[path]) {
m_claimed.remove(path); m_claimedSaves.remove(path);
} }
} }
if (p.preferredId < 0) {
LOG(QT, WARN) << "Clearing invalid preferred ID";
} else {
m_claimedIds &= ~(1 << p.preferredId);
}
m_pids.remove(pid); m_pids.remove(pid);
if (m_pids.size() == 0) { if (m_pids.size() == 0) {
m_platform = mPLATFORM_NONE; m_platform = mPLATFORM_NONE;
@ -417,8 +453,17 @@ int MultiplayerController::saveId(CoreController* controller) const {
} }
int MultiplayerController::attached() { int MultiplayerController::attached() {
int num; int num = 0;
switch (m_platform) {
case mPLATFORM_GB:
num = m_lockstep.attached; num = m_lockstep.attached;
break;
case mPLATFORM_GBA:
num = saturateCast<int>(GBASIOLockstepCoordinatorAttached(&m_gbaCoordinator));
break;
default:
break;
}
return num; return num;
} }
@ -453,12 +498,13 @@ void MultiplayerController::fixOrder() {
switch (m_platform) { switch (m_platform) {
#ifdef M_CORE_GBA #ifdef M_CORE_GBA
case mPLATFORM_GBA: case mPLATFORM_GBA:
for (int pid : m_pids.keys()) { // TODO: fix
/*for (int pid : m_pids.keys()) {
Player& p = m_pids.find(pid).value(); Player& p = m_pids.find(pid).value();
GBA* gba = static_cast<GBA*>(p.controller->thread()->core->board); GBA* gba = static_cast<GBA*>(p.controller->thread()->core->board);
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer); GBASIOLockstepDriver* node = reinterpret_cast<GBASIOLockstepDriver*>(gba->sio.driver);
m_players[node->id] = pid; m_players[node->d.deviceId(&node->d)] = pid;
} }*/
break; break;
#endif #endif
#ifdef M_CORE_GB #ifdef M_CORE_GB

View File

@ -49,7 +49,7 @@ signals:
private: private:
union Node { union Node {
GBSIOLockstepNode* gb; GBSIOLockstepNode* gb;
GBASIOLockstepNode* gba; GBASIOLockstepDriver* gba;
}; };
struct Player { struct Player {
Player(CoreController* controller); Player(CoreController* controller);
@ -63,6 +63,12 @@ private:
int32_t cyclesPosted = 0; int32_t cyclesPosted = 0;
unsigned waitMask = 0; unsigned waitMask = 0;
int saveId = 1; int saveId = 1;
int preferredId = 0;
bool attached = false;
};
struct LockstepUser : mLockstepThreadUser {
MultiplayerController* controller;
int pid;
}; };
Player* player(int id); Player* player(int id);
@ -73,18 +79,20 @@ private:
mLockstep m_lockstep; mLockstep m_lockstep;
#ifdef M_CORE_GB #ifdef M_CORE_GB
GBSIOLockstep m_gbLockstep; GBSIOLockstep m_gbLockstep;
#endif
#ifdef M_CORE_GBA
GBASIOLockstep m_gbaLockstep;
#endif #endif
}; };
#ifdef M_CORE_GBA
GBASIOLockstepCoordinator m_gbaCoordinator;
#endif
mPlatform m_platform = mPLATFORM_NONE; mPlatform m_platform = mPLATFORM_NONE;
int m_nextPid = 0; int m_nextPid = 0;
int m_claimedIds = 0;
QHash<int, Player> m_pids; QHash<int, Player> m_pids;
QList<int> m_players; QList<int> m_players;
QMutex m_lock; QMutex m_lock;
QHash<QPair<QString, QString>, int> m_claimed; QHash<QPair<QString, QString>, int> m_claimedSaves;
}; };
} }