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: 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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
int32_t finishCycle;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBASIOLockstepPlayer {
|
||||||
|
struct GBASIOLockstepDriver* driver;
|
||||||
|
int playerId;
|
||||||
enum GBASIOMode mode;
|
enum GBASIOMode mode;
|
||||||
bool transferFinished;
|
enum GBASIOMode otherModes[MAX_GBAS];
|
||||||
#ifndef NDEBUG
|
bool asleep;
|
||||||
int transferId;
|
int32_t cycleOffset;
|
||||||
enum mLockstepPhase phase;
|
struct GBASIOLockstepEvent* queue;
|
||||||
#endif
|
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 GBASIOLockstepCoordinatorInit(struct GBASIOLockstepCoordinator*);
|
||||||
void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
|
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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
value &= ~0xC;
|
||||||
case GBA_REG_SIOCNT:
|
value |= 0x8;
|
||||||
value &= ~0xC;
|
|
||||||
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);
|
||||||
|
|
||||||
|
@ -146,7 +119,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
|
||||||
case 0xA3D0:
|
case 0xA3D0:
|
||||||
// EXE 4
|
// EXE 4
|
||||||
case 0xA6C0:
|
case 0xA6C0:
|
||||||
mLOG(GBA_BATTLECHIP, DEBUG, "Resync detected");
|
mLOG(GBA_BATTLECHIP, DEBUG, "Resync detected");
|
||||||
gate->state = BATTLECHIP_STATE_SYNC;
|
gate->state = BATTLECHIP_STATE_SYNC;
|
||||||
break;
|
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);
|
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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
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");
|
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,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) {
|
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) {
|
||||||
if ((value ^ sio->siocnt) & 0x3000) {
|
if ((value ^ sio->siocnt) & 0x3000) {
|
||||||
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;
|
||||||
|
default:
|
||||||
|
mLOG(GBA_SIO, GAME_ERROR, "JOY write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value);
|
||||||
|
handled = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
case GBA_SIO_NORMAL_8:
|
||||||
// TODO
|
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;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,35 +101,41 @@ 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) {
|
||||||
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
|
UNUSED(driver);
|
||||||
if (address == GBA_REG_SIOCNT) {
|
return value & 0x78FB;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
bool _gbpSioStart(struct GBASIODriver* driver) {
|
||||||
UNUSED(timing);
|
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
|
||||||
UNUSED(cyclesLate);
|
uint32_t rx = gbp->p->memory.io[GBA_REG(SIODATA32_LO)] | (gbp->p->memory.io[GBA_REG(SIODATA32_HI)] << 16);
|
||||||
struct GBASIOPlayer* gbp = user;
|
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;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
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):
|
||||||
|
|
|
@ -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;
|
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) {
|
||||||
|
|
|
@ -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;
|
||||||
num = m_lockstep.attached;
|
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;
|
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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue