mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'feature/new-sio'
This commit is contained in:
commit
d57bb82d22
1
CHANGES
1
CHANGES
|
@ -46,6 +46,7 @@ Misc:
|
|||
- GBA: Improve detection of valid ELF ROMs
|
||||
- GBA Audio: Remove broken XQ audio pending rewrite
|
||||
- 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
|
||||
- Libretro: Add Super Game Boy Color support (closes mgba.io/i/3188)
|
||||
- mGUI: Enable auto-softpatching (closes mgba.io/i/2899)
|
||||
|
|
|
@ -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
|
||||
* 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
|
||||
|
||||
#endif
|
||||
|
|
|
@ -81,7 +81,7 @@ extern MGBA_EXPORT const int GBA_LUX_LEVELS[10];
|
|||
|
||||
enum {
|
||||
mPERIPH_GBA_LUMINANCE = 0x1000,
|
||||
mPERIPH_GBA_BATTLECHIP_GATE,
|
||||
mPERIPH_GBA_LINK_PORT,
|
||||
};
|
||||
|
||||
struct GBACartridgeOverride {
|
||||
|
@ -110,13 +110,22 @@ struct GBASIODriver {
|
|||
|
||||
bool (*init)(struct GBASIODriver* driver);
|
||||
void (*deinit)(struct GBASIODriver* driver);
|
||||
bool (*load)(struct GBASIODriver* driver);
|
||||
bool (*unload)(struct GBASIODriver* driver);
|
||||
uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
void (*reset)(struct GBASIODriver* driver);
|
||||
uint32_t (*driverId)(const struct GBASIODriver* renderer);
|
||||
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 {
|
||||
GBA_FLAVOR_BATTLECHIP_GATE = 4,
|
||||
GBA_FLAVOR_PROGRESS_GATE = 5,
|
||||
|
@ -126,7 +135,6 @@ enum GBASIOBattleChipGateFlavor {
|
|||
|
||||
struct GBASIOBattlechipGate {
|
||||
struct GBASIODriver d;
|
||||
struct mTimingEvent event;
|
||||
uint16_t chipId;
|
||||
uint16_t data[2];
|
||||
int state;
|
||||
|
|
|
@ -191,7 +191,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | bits 2 - 3: GB Player inputs posted
|
||||
* | bits 4 - 8: GB Player transmit position
|
||||
* | bits 9 - 23: Reserved
|
||||
* 0x002C4 - 0x002C7: Game Boy Player next event
|
||||
* 0x002C4 - 0x002C7: SIO next event
|
||||
* 0x002C8 - 0x002CB: Current DMA transfer word
|
||||
* 0x002CC - 0x002CF: Last DMA transfer PC
|
||||
* 0x002D0 - 0x002DF: Matrix memory command buffer
|
||||
|
@ -287,6 +287,7 @@ DECL_BITS(GBASerializedMiscFlags, KeyIRQKeys, 4, 11);
|
|||
|
||||
enum {
|
||||
GBA_SUBSYSTEM_VIDEO_RENDERER = 0,
|
||||
GBA_SUBSYSTEM_SIO_DRIVER = 1,
|
||||
GBA_SUBSYSTEM_MAX,
|
||||
};
|
||||
|
||||
|
@ -370,7 +371,7 @@ struct GBASerializedState {
|
|||
uint8_t lightSample;
|
||||
GBASerializedHWFlags2 flags2;
|
||||
GBASerializedHWFlags3 flags3;
|
||||
uint32_t gbpNextEvent;
|
||||
uint32_t sioNextEvent;
|
||||
} hw;
|
||||
|
||||
uint32_t dmaTransferRegister;
|
||||
|
|
|
@ -16,8 +16,6 @@ CXX_GUARD_START
|
|||
|
||||
#define MAX_GBAS 4
|
||||
|
||||
extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
|
||||
|
||||
mLOG_DECLARE_CATEGORY(GBA_SIO);
|
||||
|
||||
enum {
|
||||
|
@ -54,37 +52,45 @@ DECL_BITS(GBASIOMultiplayer, Id, 4, 2);
|
|||
DECL_BIT(GBASIOMultiplayer, Error, 6);
|
||||
DECL_BIT(GBASIOMultiplayer, Busy, 7);
|
||||
DECL_BIT(GBASIOMultiplayer, Irq, 14);
|
||||
|
||||
struct GBASIODriverSet {
|
||||
struct GBASIODriver* normal;
|
||||
struct GBASIODriver* multiplayer;
|
||||
struct GBASIODriver* joybus;
|
||||
};
|
||||
DECL_BITFIELD(GBASIORegisterRCNT, uint16_t);
|
||||
DECL_BIT(GBASIORegisterRCNT, Sc, 0);
|
||||
DECL_BIT(GBASIORegisterRCNT, Sd, 1);
|
||||
DECL_BIT(GBASIORegisterRCNT, Si, 2);
|
||||
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 GBA* p;
|
||||
|
||||
enum GBASIOMode mode;
|
||||
struct GBASIODriverSet drivers;
|
||||
struct GBASIODriver* activeDriver;
|
||||
struct GBASIODriver* driver;
|
||||
|
||||
uint16_t rcnt;
|
||||
uint16_t siocnt;
|
||||
|
||||
struct GBASIOPlayer gbp;
|
||||
struct mTimingEvent completeEvent;
|
||||
};
|
||||
|
||||
void GBASIOInit(struct GBASIO* sio);
|
||||
void GBASIODeinit(struct GBASIO* sio);
|
||||
void GBASIOReset(struct GBASIO* sio);
|
||||
|
||||
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers);
|
||||
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode);
|
||||
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
|
|
@ -21,7 +21,6 @@ struct GBASIOPlayer {
|
|||
struct GBA* p;
|
||||
unsigned inputsPosted;
|
||||
int txPosition;
|
||||
struct mTimingEvent event;
|
||||
struct GBASIOPlayerKeyCallback callback;
|
||||
bool oldOpposingDirections;
|
||||
struct mKeyCallback* oldCallback;
|
||||
|
|
|
@ -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
|
||||
* 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/timing.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 {
|
||||
struct mLockstep d;
|
||||
struct GBASIOLockstepNode* players[MAX_GBAS];
|
||||
int attachedMulti;
|
||||
int attachedNormal;
|
||||
#define MAX_LOCKSTEP_EVENTS 8
|
||||
|
||||
uint16_t multiRecv[MAX_GBAS];
|
||||
uint32_t normalRecv[MAX_GBAS];
|
||||
enum GBASIOLockstepEventType {
|
||||
SIO_EV_ATTACH,
|
||||
SIO_EV_DETACH,
|
||||
SIO_EV_HARD_SYNC,
|
||||
SIO_EV_MODE_SET,
|
||||
SIO_EV_TRANSFER_START,
|
||||
};
|
||||
|
||||
struct GBASIOLockstepNode {
|
||||
struct GBASIODriver d;
|
||||
struct GBASIOLockstep* p;
|
||||
struct mTimingEvent event;
|
||||
struct GBASIOLockstepCoordinator {
|
||||
struct Table players;
|
||||
Mutex mutex;
|
||||
|
||||
volatile int32_t nextEvent;
|
||||
int32_t eventDiff;
|
||||
bool normalSO;
|
||||
int id;
|
||||
unsigned nextId;
|
||||
|
||||
unsigned attachedPlayers[MAX_GBAS];
|
||||
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;
|
||||
int32_t finishCycle;
|
||||
};
|
||||
};
|
||||
|
||||
struct GBASIOLockstepPlayer {
|
||||
struct GBASIOLockstepDriver* driver;
|
||||
int playerId;
|
||||
enum GBASIOMode mode;
|
||||
bool transferFinished;
|
||||
#ifndef NDEBUG
|
||||
int transferId;
|
||||
enum mLockstepPhase phase;
|
||||
#endif
|
||||
enum GBASIOMode otherModes[MAX_GBAS];
|
||||
bool asleep;
|
||||
int32_t cycleOffset;
|
||||
struct GBASIOLockstepEvent* queue;
|
||||
bool dataReceived;
|
||||
|
||||
struct GBASIOLockstepEvent buffer[MAX_LOCKSTEP_EVENTS];
|
||||
struct GBASIOLockstepEvent* freeList;
|
||||
};
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep*);
|
||||
struct GBASIOLockstepDriver {
|
||||
struct GBASIODriver d;
|
||||
struct GBASIOLockstepCoordinator* coordinator;
|
||||
struct mTimingEvent event;
|
||||
unsigned lockstepId;
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*);
|
||||
struct mLockstepUser* user;
|
||||
};
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
|
||||
void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
* 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/core/lockstep.h>
|
||||
|
||||
#ifndef DISABLE_THREADING
|
||||
#include <mgba/core/thread.h>
|
||||
#endif
|
||||
|
||||
void mLockstepInit(struct mLockstep* lockstep) {
|
||||
lockstep->attached = 0;
|
||||
lockstep->transferActive = 0;
|
||||
|
@ -19,4 +23,21 @@ void mLockstepDeinit(struct mLockstep* 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
|
||||
|
|
|
@ -31,7 +31,6 @@ set(SOURCE_FILES
|
|||
sharkport.c
|
||||
sio.c
|
||||
sio/gbp.c
|
||||
sio/joybus.c
|
||||
timer.c
|
||||
video.c)
|
||||
|
||||
|
|
|
@ -486,10 +486,10 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
|
|||
flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState);
|
||||
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 = 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;
|
||||
}
|
||||
|
@ -532,16 +532,16 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
|||
hw->lightSample = state->hw.lightSample;
|
||||
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.txPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2);
|
||||
|
||||
uint32_t when;
|
||||
LOAD_32(when, 0, &state->hw.gbpNextEvent);
|
||||
LOAD_32(when, 0, &state->hw.sioNextEvent);
|
||||
if (hw->devices & HW_GB_PLAYER) {
|
||||
GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d, GBA_SIO_NORMAL_32);
|
||||
if (hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) {
|
||||
mTimingSchedule(&hw->p->timing, &hw->p->sio.gbp.event, when);
|
||||
}
|
||||
GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d);
|
||||
}
|
||||
if ((hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) && when < 0x20000) {
|
||||
mTimingSchedule(&hw->p->timing, &hw->p->sio.completeEvent, when);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -842,7 +842,21 @@ static bool _GBACoreLoadExtraState(struct mCore* core, const struct mStateExtdat
|
|||
if (type == gba->video.renderer->rendererId(gba->video.renderer)) {
|
||||
ok = gba->video.renderer->loadState(gba->video.renderer,
|
||||
(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) {
|
||||
ok = false;
|
||||
|
@ -868,6 +882,27 @@ static bool _GBACoreSaveExtraState(struct mCore* core, struct mStateExtdata* ext
|
|||
}
|
||||
if (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;
|
||||
|
@ -927,9 +962,8 @@ static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) {
|
|||
case mPERIPH_GBA_LUMINANCE:
|
||||
gba->luminanceSource = periph;
|
||||
break;
|
||||
case mPERIPH_GBA_BATTLECHIP_GATE:
|
||||
GBASIOSetDriver(&gba->sio, periph, GBA_SIO_MULTI);
|
||||
GBASIOSetDriver(&gba->sio, periph, GBA_SIO_NORMAL_32);
|
||||
case mPERIPH_GBA_LINK_PORT:
|
||||
GBASIOSetDriver(&gba->sio, periph);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
|
|
@ -33,28 +33,25 @@ enum {
|
|||
BATTLECHIP_CONTINUE = 0xFFFF,
|
||||
};
|
||||
|
||||
static bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver);
|
||||
static uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
|
||||
static void _battlechipTransfer(struct GBASIOBattlechipGate* gate);
|
||||
static void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static bool GBASIOBattlechipGateInit(struct GBASIODriver* driver);
|
||||
static uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value);
|
||||
static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode);
|
||||
static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver);
|
||||
static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]);
|
||||
|
||||
void GBASIOBattlechipGateCreate(struct GBASIOBattlechipGate* gate) {
|
||||
gate->d.init = NULL;
|
||||
gate->d.deinit = NULL;
|
||||
gate->d.load = GBASIOBattlechipGateLoad;
|
||||
gate->d.unload = NULL;
|
||||
gate->d.writeRegister = GBASIOBattlechipGateWriteRegister;
|
||||
|
||||
gate->event.context = gate;
|
||||
gate->event.callback = _battlechipTransferEvent;
|
||||
gate->event.priority = 0x80;
|
||||
memset(&gate->d, 0, sizeof(gate->d));
|
||||
gate->d.init = GBASIOBattlechipGateInit;
|
||||
gate->d.writeSIOCNT = GBASIOBattlechipGateWriteSIOCNT;
|
||||
gate->d.handlesMode = GBASIOBattlechipGateHandlesMode;
|
||||
gate->d.connectedDevices = GBASIOBattlechipGateConnectedDevices;
|
||||
gate->d.finishMultiplayer = GBASIOBattlechipGateFinishMultiplayer;
|
||||
|
||||
gate->chipId = 0;
|
||||
gate->flavor = GBA_FLAVOR_BATTLECHIP_GATE;
|
||||
}
|
||||
|
||||
bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) {
|
||||
bool GBASIOBattlechipGateInit(struct GBASIODriver* driver) {
|
||||
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
|
||||
gate->state = BATTLECHIP_STATE_SYNC;
|
||||
gate->data[0] = 0x00FE;
|
||||
|
@ -62,58 +59,34 @@ bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) {
|
|||
return true;
|
||||
}
|
||||
|
||||
uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
|
||||
switch (address) {
|
||||
case GBA_REG_SIOCNT:
|
||||
value &= ~0xC;
|
||||
value |= 0x8;
|
||||
if (value & 0x80) {
|
||||
_battlechipTransfer(gate);
|
||||
}
|
||||
break;
|
||||
case GBA_REG_SIOMLT_SEND:
|
||||
break;
|
||||
case GBA_REG_RCNT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) {
|
||||
UNUSED(driver);
|
||||
value &= ~0xC;
|
||||
value |= 0x8;
|
||||
return value;
|
||||
}
|
||||
|
||||
void _battlechipTransfer(struct GBASIOBattlechipGate* gate) {
|
||||
int32_t cycles;
|
||||
if (gate->d.p->mode == GBA_SIO_NORMAL_32) {
|
||||
cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000;
|
||||
} else {
|
||||
cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1];
|
||||
static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
|
||||
UNUSED(driver);
|
||||
switch (mode) {
|
||||
case GBA_SIO_NORMAL_32:
|
||||
case GBA_SIO_MULTI:
|
||||
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) {
|
||||
UNUSED(timing);
|
||||
struct GBASIOBattlechipGate* gate = user;
|
||||
static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver) {
|
||||
UNUSED(driver);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (gate->d.p->mode == GBA_SIO_NORMAL_32) {
|
||||
gate->d.p->p->memory.io[GBA_REG(SIODATA32_LO)] = 0;
|
||||
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;
|
||||
}
|
||||
static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]) {
|
||||
struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver;
|
||||
|
||||
uint16_t cmd = gate->d.p->p->memory.io[GBA_REG(SIOMLT_SEND)];
|
||||
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);
|
||||
|
||||
|
@ -146,7 +119,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
|
|||
case 0xA3D0:
|
||||
// EXE 4
|
||||
case 0xA6C0:
|
||||
mLOG(GBA_BATTLECHIP, DEBUG, "Resync detected");
|
||||
mLOG(GBA_BATTLECHIP, DEBUG, "Resync detected");
|
||||
gate->state = BATTLECHIP_STATE_SYNC;
|
||||
break;
|
||||
}
|
||||
|
@ -191,9 +164,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
|
|||
mLOG(GBA_BATTLECHIP, DEBUG, "Gate: %04X (%i)", reply, gate->state);
|
||||
++gate->state;
|
||||
|
||||
gate->d.p->p->memory.io[GBA_REG(SIOMULTI1)] = reply;
|
||||
|
||||
if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) {
|
||||
GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate);
|
||||
}
|
||||
data[0] = cmd;
|
||||
data[1] = reply;
|
||||
data[2] = 0xFFFF;
|
||||
data[3] = 0xFFFF;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
gba->memory.hw.devices &= ~HW_GB_PLAYER;
|
||||
if (gba->sio.drivers.normal == &gba->sio.gbp.d) {
|
||||
GBASIOSetDriver(&gba->sio, NULL, GBA_SIO_NORMAL_32);
|
||||
if (gba->sio.driver == &gba->sio.gbp.d) {
|
||||
GBASIOSetDriver(&gba->sio, NULL);
|
||||
}
|
||||
|
||||
bool isELF = false;
|
||||
|
|
|
@ -214,8 +214,8 @@ static const int _isRSpecialRegister[GBA_REG(INTERNAL_MAX)] = {
|
|||
/* 10 */ 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
/* 11 */ 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* SIO */
|
||||
/* 12 */ 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
/* 13 */ 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
/* 12 */ 1, 1, 1, 1, 0, 0, 0, 0,
|
||||
/* 13 */ 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
/* 14 */ 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 15 */ 1, 1, 1, 1, 1, 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
|
||||
case GBA_REG_SIOCNT:
|
||||
value &= 0x7FFF;
|
||||
GBASIOWriteSIOCNT(&gba->sio, value);
|
||||
break;
|
||||
case GBA_REG_RCNT:
|
||||
|
|
461
src/gba/sio.c
461
src/gba/sio.c
|
@ -11,26 +11,14 @@
|
|||
|
||||
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 },
|
||||
{ 8378, 16241, 24104, 31457 },
|
||||
{ 5750, 10998, 16241, 20972 },
|
||||
{ 3140, 5755, 8376, 10486 }
|
||||
};
|
||||
|
||||
static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mode) {
|
||||
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 void _sioFinish(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
|
||||
static const char* _modeName(enum GBASIOMode mode) {
|
||||
switch (mode) {
|
||||
|
@ -58,25 +46,36 @@ static void _switchMode(struct GBASIO* sio) {
|
|||
newMode = (enum GBASIOMode) (mode & 0xC);
|
||||
}
|
||||
if (newMode != sio->mode) {
|
||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||
sio->activeDriver->unload(sio->activeDriver);
|
||||
}
|
||||
if (sio->mode != (enum GBASIOMode) -1) {
|
||||
mLOG(GBA_SIO, DEBUG, "Switching mode from %s to %s", _modeName(sio->mode), _modeName(newMode));
|
||||
}
|
||||
sio->mode = newMode;
|
||||
sio->activeDriver = _lookupDriver(sio, sio->mode);
|
||||
if (sio->activeDriver && sio->activeDriver->load) {
|
||||
sio->activeDriver->load(sio->activeDriver);
|
||||
if (sio->driver && sio->driver->setMode) {
|
||||
sio->driver->setMode(sio->driver, newMode);
|
||||
}
|
||||
|
||||
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) {
|
||||
sio->drivers.normal = 0;
|
||||
sio->drivers.multiplayer = 0;
|
||||
sio->drivers.joybus = 0;
|
||||
sio->activeDriver = 0;
|
||||
sio->driver = NULL;
|
||||
|
||||
sio->completeEvent.context = sio;
|
||||
sio->completeEvent.name = "GBA SIO Complete";
|
||||
sio->completeEvent.callback = _sioFinish;
|
||||
sio->completeEvent.priority = 0x80;
|
||||
|
||||
sio->gbp.p = sio->p;
|
||||
GBASIOPlayerInit(&sio->gbp);
|
||||
|
@ -85,64 +84,28 @@ void GBASIOInit(struct GBASIO* sio) {
|
|||
}
|
||||
|
||||
void GBASIODeinit(struct GBASIO* sio) {
|
||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||
sio->activeDriver->unload(sio->activeDriver);
|
||||
}
|
||||
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);
|
||||
if (sio->driver && sio->driver->deinit) {
|
||||
sio->driver->deinit(sio->driver);
|
||||
}
|
||||
}
|
||||
|
||||
void GBASIOReset(struct GBASIO* sio) {
|
||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||
sio->activeDriver->unload(sio->activeDriver);
|
||||
if (sio->driver && sio->driver->reset) {
|
||||
sio->driver->reset(sio->driver);
|
||||
}
|
||||
sio->rcnt = RCNT_INITIAL;
|
||||
sio->siocnt = 0;
|
||||
sio->mode = -1;
|
||||
sio->activeDriver = NULL;
|
||||
_switchMode(sio);
|
||||
|
||||
GBASIOPlayerReset(&sio->gbp);
|
||||
}
|
||||
|
||||
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers) {
|
||||
GBASIOSetDriver(sio, drivers->normal, GBA_SIO_NORMAL_8);
|
||||
GBASIOSetDriver(sio, drivers->multiplayer, GBA_SIO_MULTI);
|
||||
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);
|
||||
}
|
||||
void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver) {
|
||||
if (sio->driver && sio->driver->deinit) {
|
||||
sio->driver->deinit(sio->driver);
|
||||
}
|
||||
sio->driver = driver;
|
||||
if (driver) {
|
||||
driver->p = sio;
|
||||
|
||||
|
@ -154,48 +117,122 @@ 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) {
|
||||
sio->rcnt &= 0xF;
|
||||
sio->rcnt |= value & ~0xF;
|
||||
sio->rcnt &= 0x1FF;
|
||||
sio->rcnt |= value & 0xC000;
|
||||
_switchMode(sio);
|
||||
if (sio->activeDriver && sio->activeDriver->writeRegister) {
|
||||
sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_RCNT, value);
|
||||
if (sio->driver && sio->driver->writeRCNT) {
|
||||
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) {
|
||||
if ((value ^ sio->siocnt) & 0x3000) {
|
||||
sio->siocnt = value & 0x3000;
|
||||
_switchMode(sio);
|
||||
}
|
||||
if (sio->activeDriver && sio->activeDriver->writeRegister) {
|
||||
value = sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_SIOCNT, value);
|
||||
int id = 0;
|
||||
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 {
|
||||
// Dummy drivers
|
||||
switch (sio->mode) {
|
||||
case GBA_SIO_NORMAL_8:
|
||||
case GBA_SIO_NORMAL_32:
|
||||
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;
|
||||
case GBA_SIO_MULTI:
|
||||
value &= 0xFF83;
|
||||
value |= 0xC;
|
||||
value = GBASIOMultiplayerFillReady(value);
|
||||
break;
|
||||
default:
|
||||
// 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) {
|
||||
if (sio->activeDriver && sio->activeDriver->writeRegister) {
|
||||
return sio->activeDriver->writeRegister(sio->activeDriver, address, value);
|
||||
int id = 0;
|
||||
if (sio->driver && sio->driver->deviceId) {
|
||||
id = sio->driver->deviceId(sio->driver);
|
||||
}
|
||||
// Dummy drivers
|
||||
|
||||
bool handled = true;
|
||||
switch (sio->mode) {
|
||||
case GBA_SIO_JOYBUS:
|
||||
switch (address) {
|
||||
case GBA_REG_SIODATA8:
|
||||
mLOG(GBA_SIO, DEBUG, "JOY write: SIODATA8 (?) <- %04X", value);
|
||||
break;
|
||||
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:
|
||||
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;
|
||||
default:
|
||||
mLOG(GBA_SIO, GAME_ERROR, "JOY write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// TODO
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -23,18 +23,22 @@ enum {
|
|||
};
|
||||
|
||||
static bool GBASIODolphinInit(struct GBASIODriver* driver);
|
||||
static bool GBASIODolphinLoad(struct GBASIODriver* driver);
|
||||
static bool GBASIODolphinUnload(struct GBASIODriver* driver);
|
||||
static void GBASIODolphinReset(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 int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate);
|
||||
static void _flush(struct GBASIODolphin* dol);
|
||||
|
||||
void GBASIODolphinCreate(struct GBASIODolphin* dol) {
|
||||
GBASIOJOYCreate(&dol->d);
|
||||
memset(&dol->d, 0, sizeof(dol->d));
|
||||
dol->d.init = GBASIODolphinInit;
|
||||
dol->d.load = GBASIODolphinLoad;
|
||||
dol->d.unload = GBASIODolphinUnload;
|
||||
dol->d.reset = GBASIODolphinReset;
|
||||
dol->d.setMode = GBASIODolphinSetMode;
|
||||
dol->d.handlesMode = GBASIODolphinHandlesMode;
|
||||
dol->d.connectedDevices = GBASIODolphinConnectedDevices;
|
||||
dol->event.context = dol;
|
||||
dol->event.name = "GB SIO Lockstep";
|
||||
dol->event.callback = GBASIODolphinProcessEvents;
|
||||
|
@ -94,26 +98,33 @@ bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* addre
|
|||
|
||||
static bool GBASIODolphinInit(struct GBASIODriver* driver) {
|
||||
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
|
||||
dol->active = false;
|
||||
dol->clockSlice = 0;
|
||||
dol->state = WAIT_FOR_FIRST_CLOCK;
|
||||
_flush(dol);
|
||||
GBASIODolphinReset(driver);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool GBASIODolphinLoad(struct GBASIODriver* driver) {
|
||||
static void GBASIODolphinReset(struct GBASIODriver* driver) {
|
||||
struct GBASIODolphin* dol = (struct GBASIODolphin*) driver;
|
||||
dol->active = true;
|
||||
dol->active = false;
|
||||
_flush(dol);
|
||||
mTimingDeschedule(&dol->d.p->p->timing, &dol->event);
|
||||
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;
|
||||
dol->active = false;
|
||||
return true;
|
||||
dol->active = mode == GBA_SIO_JOYBUS;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
#include <mgba-util/memory.h>
|
||||
|
||||
static uint16_t _gbpRead(struct mKeyCallback*);
|
||||
static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value);
|
||||
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[] = {
|
||||
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.requireOpposingDirections = true;
|
||||
gbp->callback.p = gbp;
|
||||
gbp->d.init = 0;
|
||||
gbp->d.deinit = 0;
|
||||
gbp->d.load = 0;
|
||||
gbp->d.unload = 0;
|
||||
gbp->d.writeRegister = _gbpSioWriteRegister;
|
||||
gbp->event.context = gbp;
|
||||
gbp->event.name = "GBA SIO Game Boy Player";
|
||||
gbp->event.callback = _gbpSioProcessEvents;
|
||||
gbp->event.priority = 0x80;
|
||||
memset(&gbp->d, 0, sizeof(gbp->d));
|
||||
gbp->d.writeSIOCNT = _gbpSioWriteSIOCNT;
|
||||
gbp->d.handlesMode = _gbpSioHandlesMode;
|
||||
gbp->d.connectedDevices = _gbpSioConnectedDevices;
|
||||
gbp->d.start = _gbpSioStart;
|
||||
gbp->d.finishNormal32 = _gbpSioFinishNormal32;
|
||||
}
|
||||
|
||||
void GBASIOPlayerReset(struct GBASIOPlayer* gbp) {
|
||||
if (gbp->p->sio.drivers.normal == &gbp->d) {
|
||||
GBASIOSetDriver(&gbp->p->sio, NULL, GBA_SIO_NORMAL_32);
|
||||
if (gbp->p->sio.driver == &gbp->d) {
|
||||
GBASIOSetDriver(&gbp->p->sio, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,8 +87,9 @@ void GBASIOPlayerUpdate(struct GBA* gba) {
|
|||
gba->sio.gbp.inputsPosted = 0;
|
||||
gba->sio.gbp.oldCallback = gba->keyCallback;
|
||||
gba->keyCallback = &gba->sio.gbp.callback.d;
|
||||
// TODO: Check if the SIO driver is actually used first
|
||||
GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d, GBA_SIO_NORMAL_32);
|
||||
if (!gba->sio.driver) {
|
||||
GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,35 +101,41 @@ uint16_t _gbpRead(struct mKeyCallback* callback) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
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);
|
||||
if (gbp->txPosition < 12 && gbp->txPosition > 0) {
|
||||
// TODO: Check expected
|
||||
} else if (gbp->txPosition >= 12) {
|
||||
// 0x00 = Stop
|
||||
// 0x11 = Hard Stop
|
||||
// 0x22 = Start
|
||||
if (gbp->p->rumble) {
|
||||
int32_t currentTime = mTimingCurrentTime(&gbp->p->timing);
|
||||
gbp->p->rumble->setRumble(gbp->p->rumble, (rx & 0x33) == 0x22, currentTime - gbp->p->lastRumble);
|
||||
gbp->p->lastRumble = currentTime;
|
||||
}
|
||||
}
|
||||
mTimingDeschedule(&gbp->p->timing, &gbp->event);
|
||||
mTimingSchedule(&gbp->p->timing, &gbp->event, 2048);
|
||||
}
|
||||
value &= 0x78FB;
|
||||
}
|
||||
return value;
|
||||
uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) {
|
||||
UNUSED(driver);
|
||||
return value & 0x78FB;
|
||||
}
|
||||
|
||||
void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
struct GBASIOPlayer* gbp = user;
|
||||
bool _gbpSioStart(struct GBASIODriver* driver) {
|
||||
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
|
||||
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) {
|
||||
// TODO: Check expected
|
||||
} else if (gbp->txPosition >= 12) {
|
||||
// 0x00 = Stop
|
||||
// 0x11 = Hard Stop
|
||||
// 0x22 = Start
|
||||
if (gbp->p->rumble) {
|
||||
int32_t currentTime = mTimingCurrentTime(&gbp->p->timing);
|
||||
gbp->p->rumble->setRumble(gbp->p->rumble, (rx & 0x33) == 0x22, currentTime - gbp->p->lastRumble);
|
||||
gbp->p->lastRumble = currentTime;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _gbpSioHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) {
|
||||
UNUSED(driver);
|
||||
return mode == GBA_SIO_NORMAL_32;
|
||||
}
|
||||
|
||||
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;
|
||||
int txPosition = gbp->txPosition;
|
||||
if (txPosition > 16) {
|
||||
|
@ -139,11 +146,5 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
|
|||
}
|
||||
tx = _gbpTxData[txPosition];
|
||||
++gbp->txPosition;
|
||||
gbp->p->memory.io[GBA_REG(SIODATA32_LO)] = 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;
|
||||
return tx;
|
||||
}
|
||||
|
|
|
@ -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
|
@ -52,72 +52,6 @@ class GBA(Core):
|
|||
super(GBA, self)._load()
|
||||
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):
|
||||
def __init__(self, core, romSize=lib.SIZE_CART0):
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -423,8 +423,8 @@ bool CoreController::attachDolphin(const Address& address) {
|
|||
return false;
|
||||
}
|
||||
if (GBASIODolphinConnect(&m_dolphin, &address, 0, 0)) {
|
||||
GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
|
||||
GBASIOSetDriver(&gba->sio, &m_dolphin.d, GBA_SIO_JOYBUS);
|
||||
clearMultiplayerController();
|
||||
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, &m_dolphin.d);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -432,8 +432,8 @@ bool CoreController::attachDolphin(const Address& address) {
|
|||
|
||||
void CoreController::detachDolphin() {
|
||||
if (platform() == mPLATFORM_GBA) {
|
||||
GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
|
||||
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_JOYBUS);
|
||||
// TODO: Reattach to multiplayer controller
|
||||
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, NULL);
|
||||
}
|
||||
GBASIODolphinDestroy(&m_dolphin);
|
||||
}
|
||||
|
@ -1094,7 +1094,7 @@ void CoreController::attachBattleChipGate() {
|
|||
Interrupter interrupter(this);
|
||||
clearMultiplayerController();
|
||||
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() {
|
||||
|
@ -1102,7 +1102,7 @@ void CoreController::detachBattleChipGate() {
|
|||
return;
|
||||
}
|
||||
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) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "CoreController.h"
|
||||
#include "LogController.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
|
@ -27,8 +28,14 @@ MultiplayerController::Player::Player(CoreController* coreController)
|
|||
int MultiplayerController::Player::id() const {
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
return node.gba->id;
|
||||
case mPLATFORM_GBA: {
|
||||
int id = node.gba->d.deviceId(&node.gba->d);
|
||||
if (id >= 0) {
|
||||
return id;
|
||||
} else {
|
||||
return preferredId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
|
@ -89,25 +96,7 @@ MultiplayerController::MultiplayerController() {
|
|||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
if (!id) {
|
||||
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;
|
||||
}
|
||||
abort();
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
@ -169,7 +158,6 @@ MultiplayerController::MultiplayerController() {
|
|||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
player->cyclesPosted += reinterpret_cast<GBASIOLockstep*>(lockstep)->players[0]->eventDiff;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
@ -184,7 +172,6 @@ MultiplayerController::MultiplayerController() {
|
|||
switch (player->controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
player->node.gba->nextEvent += player->cyclesPosted;
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
@ -214,11 +201,12 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
interrupters.append(p.controller);
|
||||
}
|
||||
|
||||
if (m_lockstep.attached == 0) {
|
||||
bool doDelayedAttach = false;
|
||||
if (m_platform == mPLATFORM_NONE) {
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
GBASIOLockstepInit(&m_gbaLockstep);
|
||||
GBASIOLockstepCoordinatorInit(&m_gbaCoordinator);
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
@ -240,28 +228,50 @@ bool MultiplayerController::attachGame(CoreController* 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()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA: {
|
||||
if (m_lockstep.attached >= MAX_GBAS) {
|
||||
if (attached() >= MAX_GBAS) {
|
||||
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;
|
||||
GBASIOLockstepNodeCreate(node);
|
||||
GBASIOLockstepAttachNode(&m_gbaLockstep, node);
|
||||
GBASIOLockstepDriverCreate(node, &user->d);
|
||||
player.node.gba = node;
|
||||
|
||||
GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_MULTI);
|
||||
GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_NORMAL_32);
|
||||
if (m_pids.size()) {
|
||||
doDelayedAttach = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB: {
|
||||
if (m_lockstep.attached >= 2) {
|
||||
if (attached() >= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -271,6 +281,7 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
GBSIOLockstepNodeCreate(node);
|
||||
GBSIOLockstepAttachNode(&m_gbLockstep, node);
|
||||
player.node.gb = node;
|
||||
player.attached = true;
|
||||
|
||||
GBSIOSetDriver(&gb->sio, &node->d);
|
||||
break;
|
||||
|
@ -281,7 +292,7 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
}
|
||||
|
||||
QPair<QString, QString> path(controller->path(), controller->baseDirectory());
|
||||
int claimed = m_claimed[path];
|
||||
int claimed = m_claimedSaves[path];
|
||||
|
||||
int saveId = 0;
|
||||
mCoreConfigGetIntValue(&controller->thread()->core->config, "savePlayerId", &saveId);
|
||||
|
@ -304,12 +315,25 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
} else {
|
||||
player.saveId = 1;
|
||||
}
|
||||
m_claimed[path] |= 1 << (player.saveId - 1);
|
||||
m_claimedSaves[path] |= 1 << (player.saveId - 1);
|
||||
|
||||
m_pids.insert(m_nextPid, player);
|
||||
++m_nextPid;
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
|
@ -328,8 +352,7 @@ void MultiplayerController::detachGame(CoreController* controller) {
|
|||
for (int i = 0; i < m_players.count(); ++i) {
|
||||
Player* p = player(i);
|
||||
if (!p) {
|
||||
LOG(QT, ERROR) << tr("Trying to detach a multiplayer player that's not attached");
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
CoreController* playerController = p->controller;
|
||||
if (playerController == controller) {
|
||||
|
@ -337,17 +360,24 @@ void MultiplayerController::detachGame(CoreController* controller) {
|
|||
}
|
||||
interrupters.append(playerController);
|
||||
}
|
||||
if (pid < 0) {
|
||||
LOG(QT, WARN) << tr("Trying to detach a multiplayer player that's not attached");
|
||||
return;
|
||||
}
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA: {
|
||||
GBA* gba = static_cast<GBA*>(thread->core->board);
|
||||
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer);
|
||||
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_MULTI);
|
||||
GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_NORMAL_32);
|
||||
if (node) {
|
||||
GBASIOLockstepDetachNode(&m_gbaLockstep, node);
|
||||
delete node;
|
||||
Player& p = m_pids.find(pid).value();
|
||||
GBASIODriver* node = gba->sio.driver;
|
||||
if (node == &p.node.gba->d) {
|
||||
thread->core->setPeripheral(thread->core, mPERIPH_GBA_LINK_PORT, NULL);
|
||||
}
|
||||
if (p.attached) {
|
||||
GBASIOLockstepCoordinatorDetach(&m_gbaCoordinator, p.node.gba);
|
||||
}
|
||||
delete p.node.gba->user;
|
||||
delete p.node.gba;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
@ -371,14 +401,20 @@ void MultiplayerController::detachGame(CoreController* controller) {
|
|||
QPair<QString, QString> path(controller->path(), controller->baseDirectory());
|
||||
Player& p = m_pids.find(pid).value();
|
||||
if (!p.saveId) {
|
||||
LOG(QT, ERROR) << "Clearing invalid save ID";
|
||||
LOG(QT, WARN) << "Clearing invalid save ID";
|
||||
} else {
|
||||
m_claimed[path] &= ~(1 << (p.saveId - 1));
|
||||
if (!m_claimed[path]) {
|
||||
m_claimed.remove(path);
|
||||
m_claimedSaves[path] &= ~(1 << (p.saveId - 1));
|
||||
if (!m_claimedSaves[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);
|
||||
if (m_pids.size() == 0) {
|
||||
m_platform = mPLATFORM_NONE;
|
||||
|
@ -417,8 +453,17 @@ int MultiplayerController::saveId(CoreController* controller) const {
|
|||
}
|
||||
|
||||
int MultiplayerController::attached() {
|
||||
int num;
|
||||
num = m_lockstep.attached;
|
||||
int num = 0;
|
||||
switch (m_platform) {
|
||||
case mPLATFORM_GB:
|
||||
num = m_lockstep.attached;
|
||||
break;
|
||||
case mPLATFORM_GBA:
|
||||
num = saturateCast<int>(GBASIOLockstepCoordinatorAttached(&m_gbaCoordinator));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
|
@ -453,12 +498,13 @@ void MultiplayerController::fixOrder() {
|
|||
switch (m_platform) {
|
||||
#ifdef M_CORE_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();
|
||||
GBA* gba = static_cast<GBA*>(p.controller->thread()->core->board);
|
||||
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(gba->sio.drivers.multiplayer);
|
||||
m_players[node->id] = pid;
|
||||
}
|
||||
GBASIOLockstepDriver* node = reinterpret_cast<GBASIOLockstepDriver*>(gba->sio.driver);
|
||||
m_players[node->d.deviceId(&node->d)] = pid;
|
||||
}*/
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
|
|
@ -49,7 +49,7 @@ signals:
|
|||
private:
|
||||
union Node {
|
||||
GBSIOLockstepNode* gb;
|
||||
GBASIOLockstepNode* gba;
|
||||
GBASIOLockstepDriver* gba;
|
||||
};
|
||||
struct Player {
|
||||
Player(CoreController* controller);
|
||||
|
@ -63,6 +63,12 @@ private:
|
|||
int32_t cyclesPosted = 0;
|
||||
unsigned waitMask = 0;
|
||||
int saveId = 1;
|
||||
int preferredId = 0;
|
||||
bool attached = false;
|
||||
};
|
||||
struct LockstepUser : mLockstepThreadUser {
|
||||
MultiplayerController* controller;
|
||||
int pid;
|
||||
};
|
||||
|
||||
Player* player(int id);
|
||||
|
@ -73,18 +79,20 @@ private:
|
|||
mLockstep m_lockstep;
|
||||
#ifdef M_CORE_GB
|
||||
GBSIOLockstep m_gbLockstep;
|
||||
#endif
|
||||
#ifdef M_CORE_GBA
|
||||
GBASIOLockstep m_gbaLockstep;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
GBASIOLockstepCoordinator m_gbaCoordinator;
|
||||
#endif
|
||||
|
||||
mPlatform m_platform = mPLATFORM_NONE;
|
||||
int m_nextPid = 0;
|
||||
int m_claimedIds = 0;
|
||||
QHash<int, Player> m_pids;
|
||||
QList<int> m_players;
|
||||
QMutex m_lock;
|
||||
QHash<QPair<QString, QString>, int> m_claimed;
|
||||
QHash<QPair<QString, QString>, int> m_claimedSaves;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue