mirror of https://github.com/mgba-emu/mgba.git
GBA Cart: Refactor hardware.c into cart/gpio.c etc
This commit is contained in:
parent
9a192d9ab1
commit
bc16a1bfe3
|
@ -13,13 +13,10 @@ CXX_GUARD_START
|
||||||
#include <mgba/core/log.h>
|
#include <mgba/core/log.h>
|
||||||
#include <mgba/core/timing.h>
|
#include <mgba/core/timing.h>
|
||||||
#include <mgba/gba/interface.h>
|
#include <mgba/gba/interface.h>
|
||||||
|
#include <mgba/internal/gba/cart/ereader.h>
|
||||||
|
|
||||||
mLOG_DECLARE_CATEGORY(GBA_HW);
|
mLOG_DECLARE_CATEGORY(GBA_HW);
|
||||||
|
|
||||||
#define EREADER_DOTCODE_STRIDE 1420
|
|
||||||
#define EREADER_DOTCODE_SIZE (EREADER_DOTCODE_STRIDE * 40)
|
|
||||||
#define EREADER_CARDS_MAX 16
|
|
||||||
|
|
||||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||||
|
|
||||||
struct GBARTCGenericSource {
|
struct GBARTCGenericSource {
|
||||||
|
@ -82,57 +79,8 @@ struct GBARTC {
|
||||||
uint8_t time[7];
|
uint8_t time[7];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GBAGBPKeyCallback {
|
|
||||||
struct mKeyCallback d;
|
|
||||||
struct GBACartridgeHardware* p;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBAGBPSIODriver {
|
|
||||||
struct GBASIODriver d;
|
|
||||||
struct GBACartridgeHardware* p;
|
|
||||||
};
|
|
||||||
|
|
||||||
DECL_BITFIELD(GPIOPin, uint16_t);
|
DECL_BITFIELD(GPIOPin, uint16_t);
|
||||||
|
|
||||||
DECL_BITFIELD(EReaderControl0, uint8_t);
|
|
||||||
DECL_BIT(EReaderControl0, Data, 0);
|
|
||||||
DECL_BIT(EReaderControl0, Clock, 1);
|
|
||||||
DECL_BIT(EReaderControl0, Direction, 2);
|
|
||||||
DECL_BIT(EReaderControl0, LedEnable, 3);
|
|
||||||
DECL_BIT(EReaderControl0, Scan, 4);
|
|
||||||
DECL_BIT(EReaderControl0, Phi, 5);
|
|
||||||
DECL_BIT(EReaderControl0, PowerEnable, 6);
|
|
||||||
DECL_BITFIELD(EReaderControl1, uint8_t);
|
|
||||||
DECL_BIT(EReaderControl1, Scanline, 1);
|
|
||||||
DECL_BIT(EReaderControl1, Unk1, 4);
|
|
||||||
DECL_BIT(EReaderControl1, Voltage, 5);
|
|
||||||
|
|
||||||
enum EReaderStateMachine {
|
|
||||||
EREADER_SERIAL_INACTIVE = 0,
|
|
||||||
EREADER_SERIAL_STARTING,
|
|
||||||
EREADER_SERIAL_BIT_0,
|
|
||||||
EREADER_SERIAL_BIT_1,
|
|
||||||
EREADER_SERIAL_BIT_2,
|
|
||||||
EREADER_SERIAL_BIT_3,
|
|
||||||
EREADER_SERIAL_BIT_4,
|
|
||||||
EREADER_SERIAL_BIT_5,
|
|
||||||
EREADER_SERIAL_BIT_6,
|
|
||||||
EREADER_SERIAL_BIT_7,
|
|
||||||
EREADER_SERIAL_END_BIT,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum EReaderCommand {
|
|
||||||
EREADER_COMMAND_IDLE = 0, // TODO: Verify on hardware
|
|
||||||
EREADER_COMMAND_WRITE_DATA = 1,
|
|
||||||
EREADER_COMMAND_SET_INDEX = 0x22,
|
|
||||||
EREADER_COMMAND_READ_DATA = 0x23,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EReaderCard {
|
|
||||||
void* data;
|
|
||||||
size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GBACartridgeHardware {
|
struct GBACartridgeHardware {
|
||||||
struct GBA* p;
|
struct GBA* p;
|
||||||
uint32_t devices;
|
uint32_t devices;
|
||||||
|
@ -155,12 +103,6 @@ struct GBACartridgeHardware {
|
||||||
uint16_t tiltY;
|
uint16_t tiltY;
|
||||||
int tiltState;
|
int tiltState;
|
||||||
|
|
||||||
unsigned gbpInputsPosted;
|
|
||||||
int gbpTxPosition;
|
|
||||||
struct mTimingEvent gbpNextEvent;
|
|
||||||
struct GBAGBPKeyCallback gbpCallback;
|
|
||||||
struct GBAGBPSIODriver gbpDriver;
|
|
||||||
|
|
||||||
uint16_t eReaderData[44];
|
uint16_t eReaderData[44];
|
||||||
uint8_t eReaderSerial[92];
|
uint8_t eReaderSerial[92];
|
||||||
uint16_t eReaderRegisterUnk;
|
uint16_t eReaderRegisterUnk;
|
||||||
|
@ -193,17 +135,6 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* gpio, uint32_t address, u
|
||||||
void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint8_t value);
|
void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint8_t value);
|
||||||
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
||||||
|
|
||||||
struct GBAVideo;
|
|
||||||
void GBAHardwarePlayerUpdate(struct GBA* gba);
|
|
||||||
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video);
|
|
||||||
|
|
||||||
void GBAHardwareInitEReader(struct GBACartridgeHardware* hw);
|
|
||||||
void GBAHardwareEReaderWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value);
|
|
||||||
void GBAHardwareEReaderWriteFlash(struct GBACartridgeHardware* hw, uint32_t address, uint8_t value);
|
|
||||||
uint16_t GBAHardwareEReaderRead(struct GBACartridgeHardware* hw, uint32_t address);
|
|
||||||
uint8_t GBAHardwareEReaderReadFlash(struct GBACartridgeHardware* hw, uint32_t address);
|
|
||||||
void GBAHardwareEReaderScan(struct GBACartridgeHardware* hw, const void* data, size_t size);
|
|
||||||
|
|
||||||
void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba);
|
void GBARTCGenericSourceInit(struct GBARTCGenericSource* rtc, struct GBA* gba);
|
||||||
|
|
||||||
struct GBASerializedState;
|
struct GBASerializedState;
|
|
@ -14,10 +14,10 @@ CXX_GUARD_START
|
||||||
|
|
||||||
#include <mgba/internal/arm/arm.h>
|
#include <mgba/internal/arm/arm.h>
|
||||||
#include <mgba/internal/gba/dma.h>
|
#include <mgba/internal/gba/dma.h>
|
||||||
#include <mgba/internal/gba/hardware.h>
|
|
||||||
#include <mgba/internal/gba/savedata.h>
|
#include <mgba/internal/gba/savedata.h>
|
||||||
#include <mgba/internal/gba/vfame.h>
|
#include <mgba/internal/gba/cart/gpio.h>
|
||||||
#include <mgba/internal/gba/matrix.h>
|
#include <mgba/internal/gba/cart/matrix.h>
|
||||||
|
#include <mgba/internal/gba/cart/vfame.h>
|
||||||
|
|
||||||
enum GBAMemoryRegion {
|
enum GBAMemoryRegion {
|
||||||
REGION_BIOS = 0x0,
|
REGION_BIOS = 0x0,
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
|
|
||||||
CXX_GUARD_START
|
CXX_GUARD_START
|
||||||
|
|
||||||
#include <mgba/gba/interface.h>
|
|
||||||
#include <mgba/core/log.h>
|
#include <mgba/core/log.h>
|
||||||
|
#include <mgba/gba/interface.h>
|
||||||
|
#include <mgba/internal/gba/sio/gbp.h>
|
||||||
|
|
||||||
#define MAX_GBAS 4
|
#define MAX_GBAS 4
|
||||||
|
|
||||||
|
@ -69,6 +70,8 @@ struct GBASIO {
|
||||||
|
|
||||||
uint16_t rcnt;
|
uint16_t rcnt;
|
||||||
uint16_t siocnt;
|
uint16_t siocnt;
|
||||||
|
|
||||||
|
struct GBASIOPlayer gbp;
|
||||||
};
|
};
|
||||||
|
|
||||||
void GBASIOInit(struct GBASIO* sio);
|
void GBASIOInit(struct GBASIO* sio);
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* Copyright (c) 2013-2021 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#ifndef GBA_GBP_H
|
||||||
|
#define GBA_GBP_H
|
||||||
|
|
||||||
|
#include <mgba-util/common.h>
|
||||||
|
|
||||||
|
CXX_GUARD_START
|
||||||
|
|
||||||
|
struct GBASIOPlayer;
|
||||||
|
struct GBASIOPlayerKeyCallback {
|
||||||
|
struct mKeyCallback d;
|
||||||
|
struct GBASIOPlayer* p;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GBASIOPlayer {
|
||||||
|
struct GBASIODriver d;
|
||||||
|
struct GBA* p;
|
||||||
|
unsigned inputsPosted;
|
||||||
|
int txPosition;
|
||||||
|
struct mTimingEvent event;
|
||||||
|
struct GBASIOPlayerKeyCallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GBASIOPlayerInit(struct GBASIOPlayer* gbp);
|
||||||
|
void GBASIOPlayerReset(struct GBASIOPlayer* gbp);
|
||||||
|
|
||||||
|
struct GBAVideo;
|
||||||
|
void GBASIOPlayerUpdate(struct GBA* gba);
|
||||||
|
bool GBASIOPlayerCheckScreen(const struct GBAVideo* video);
|
||||||
|
|
||||||
|
CXX_GUARD_END
|
||||||
|
|
||||||
|
#endif
|
|
@ -3,19 +3,20 @@ set(SOURCE_FILES
|
||||||
../gb/audio.c
|
../gb/audio.c
|
||||||
audio.c
|
audio.c
|
||||||
bios.c
|
bios.c
|
||||||
|
cart/ereader.c
|
||||||
|
cart/gpio.c
|
||||||
|
cart/matrix.c
|
||||||
|
cart/vfame.c
|
||||||
cheats.c
|
cheats.c
|
||||||
cheats/codebreaker.c
|
cheats/codebreaker.c
|
||||||
cheats/gameshark.c
|
cheats/gameshark.c
|
||||||
cheats/parv3.c
|
cheats/parv3.c
|
||||||
core.c
|
core.c
|
||||||
dma.c
|
dma.c
|
||||||
ereader.c
|
|
||||||
gba.c
|
gba.c
|
||||||
hardware.c
|
|
||||||
hle-bios.c
|
hle-bios.c
|
||||||
input.c
|
input.c
|
||||||
io.c
|
io.c
|
||||||
matrix.c
|
|
||||||
memory.c
|
memory.c
|
||||||
overrides.c
|
overrides.c
|
||||||
renderers/cache-set.c
|
renderers/cache-set.c
|
||||||
|
@ -29,13 +30,13 @@ set(SOURCE_FILES
|
||||||
serialize.c
|
serialize.c
|
||||||
sharkport.c
|
sharkport.c
|
||||||
sio.c
|
sio.c
|
||||||
|
sio/gbp.c
|
||||||
|
sio/joybus.c
|
||||||
timer.c
|
timer.c
|
||||||
vfame.c
|
|
||||||
video.c)
|
video.c)
|
||||||
|
|
||||||
set(SIO_FILES
|
set(SIO_FILES
|
||||||
sio/dolphin.c
|
sio/dolphin.c
|
||||||
sio/joybus.c
|
|
||||||
sio/lockstep.c)
|
sio/lockstep.c)
|
||||||
|
|
||||||
set(EXTRA_FILES
|
set(EXTRA_FILES
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* 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/internal/gba/hardware.h>
|
#include <mgba/internal/gba/cart/ereader.h>
|
||||||
|
|
||||||
#include <mgba/internal/arm/macros.h>
|
#include <mgba/internal/arm/macros.h>
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
|
@ -1,9 +1,9 @@
|
||||||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
/* Copyright (c) 2013-2021 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/internal/gba/hardware.h>
|
#include <mgba/internal/gba/cart/gpio.h>
|
||||||
|
|
||||||
#include <mgba/internal/arm/macros.h>
|
#include <mgba/internal/arm/macros.h>
|
||||||
#include <mgba/internal/gba/io.h>
|
#include <mgba/internal/gba/io.h>
|
||||||
|
@ -33,10 +33,6 @@ static void _rumbleReadPins(struct GBACartridgeHardware* hw);
|
||||||
|
|
||||||
static void _lightReadPins(struct GBACartridgeHardware* hw);
|
static void _lightReadPins(struct GBACartridgeHardware* hw);
|
||||||
|
|
||||||
static uint16_t _gbpRead(struct mKeyCallback*);
|
|
||||||
static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
|
||||||
static void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
|
||||||
|
|
||||||
static const int RTC_BYTES[8] = {
|
static const int RTC_BYTES[8] = {
|
||||||
0, // Force reset
|
0, // Force reset
|
||||||
0, // Empty
|
0, // Empty
|
||||||
|
@ -53,19 +49,6 @@ void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) {
|
||||||
hw->eReaderDots = NULL;
|
hw->eReaderDots = NULL;
|
||||||
memset(hw->eReaderCards, 0, sizeof(hw->eReaderCards));
|
memset(hw->eReaderCards, 0, sizeof(hw->eReaderCards));
|
||||||
GBAHardwareClear(hw);
|
GBAHardwareClear(hw);
|
||||||
|
|
||||||
hw->gbpCallback.d.readKeys = _gbpRead;
|
|
||||||
hw->gbpCallback.p = hw;
|
|
||||||
hw->gbpDriver.d.init = 0;
|
|
||||||
hw->gbpDriver.d.deinit = 0;
|
|
||||||
hw->gbpDriver.d.load = 0;
|
|
||||||
hw->gbpDriver.d.unload = 0;
|
|
||||||
hw->gbpDriver.d.writeRegister = _gbpSioWriteRegister;
|
|
||||||
hw->gbpDriver.p = hw;
|
|
||||||
hw->gbpNextEvent.context = &hw->gbpDriver;
|
|
||||||
hw->gbpNextEvent.name = "GBA SIO Game Boy Player";
|
|
||||||
hw->gbpNextEvent.callback = _gbpSioProcessEvents;
|
|
||||||
hw->gbpNextEvent.priority = 0x80;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
||||||
|
@ -87,10 +70,6 @@ void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
||||||
hw->eReaderCards[i].data = NULL;
|
hw->eReaderCards[i].data = NULL;
|
||||||
hw->eReaderCards[i].size = 0;
|
hw->eReaderCards[i].size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hw->p->sio.drivers.normal == &hw->gbpDriver.d) {
|
|
||||||
GBASIOSetDriver(&hw->p->sio, 0, SIO_NORMAL_32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) {
|
void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) {
|
||||||
|
@ -504,124 +483,10 @@ uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) {
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// == Game Boy Player
|
|
||||||
|
|
||||||
static const uint8_t _logoPalette[] = {
|
|
||||||
0xDF, 0xFF, 0x0C, 0x64, 0x0C, 0xE4, 0x2D, 0xE4, 0x4E, 0x64, 0x4E, 0xE4, 0x6E, 0xE4, 0xAF, 0x68,
|
|
||||||
0xB0, 0xE8, 0xD0, 0x68, 0xF0, 0x68, 0x11, 0x69, 0x11, 0xE9, 0x32, 0x6D, 0x32, 0xED, 0x73, 0xED,
|
|
||||||
0x93, 0x6D, 0x94, 0xED, 0xB4, 0x6D, 0xD5, 0xF1, 0xF5, 0x71, 0xF6, 0xF1, 0x16, 0x72, 0x57, 0x72,
|
|
||||||
0x57, 0xF6, 0x78, 0x76, 0x78, 0xF6, 0x99, 0xF6, 0xB9, 0xF6, 0xD9, 0x76, 0xDA, 0xF6, 0x1B, 0x7B,
|
|
||||||
0x1B, 0xFB, 0x3C, 0xFB, 0x5C, 0x7B, 0x7D, 0x7B, 0x7D, 0xFF, 0x9D, 0x7F, 0xBE, 0x7F, 0xFF, 0x7F,
|
|
||||||
0x2D, 0x64, 0x8E, 0x64, 0x8F, 0xE8, 0xF1, 0xE8, 0x52, 0x6D, 0x73, 0x6D, 0xB4, 0xF1, 0x16, 0xF2,
|
|
||||||
0x37, 0x72, 0x98, 0x76, 0xFA, 0x7A, 0xFA, 0xFA, 0x5C, 0xFB, 0xBE, 0xFF, 0xDE, 0x7F, 0xFF, 0xFF,
|
|
||||||
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint32_t _logoHash = 0xEEDA6963;
|
|
||||||
|
|
||||||
static const uint32_t _gbpTxData[] = {
|
|
||||||
0x0000494E, 0x0000494E,
|
|
||||||
0xB6B1494E, 0xB6B1544E,
|
|
||||||
0xABB1544E, 0xABB14E45,
|
|
||||||
0xB1BA4E45, 0xB1BA4F44,
|
|
||||||
0xB0BB4F44, 0xB0BB8002,
|
|
||||||
0x10000010, 0x20000013,
|
|
||||||
0x30000003
|
|
||||||
};
|
|
||||||
|
|
||||||
bool GBAHardwarePlayerCheckScreen(const struct GBAVideo* video) {
|
|
||||||
if (memcmp(video->palette, _logoPalette, sizeof(_logoPalette)) != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
uint32_t hash = hash32(&video->renderer->vram[0x4000], 0x4000, 0);
|
|
||||||
return hash == _logoHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GBAHardwarePlayerUpdate(struct GBA* gba) {
|
|
||||||
if (gba->memory.hw.devices & HW_GB_PLAYER) {
|
|
||||||
if (GBAHardwarePlayerCheckScreen(&gba->video)) {
|
|
||||||
++gba->memory.hw.gbpInputsPosted;
|
|
||||||
gba->memory.hw.gbpInputsPosted %= 3;
|
|
||||||
gba->keyCallback = &gba->memory.hw.gbpCallback.d;
|
|
||||||
} else {
|
|
||||||
// TODO: Save and restore
|
|
||||||
gba->keyCallback = 0;
|
|
||||||
}
|
|
||||||
gba->memory.hw.gbpTxPosition = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gba->keyCallback) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (GBAHardwarePlayerCheckScreen(&gba->video)) {
|
|
||||||
gba->memory.hw.devices |= HW_GB_PLAYER;
|
|
||||||
gba->memory.hw.gbpInputsPosted = 0;
|
|
||||||
gba->keyCallback = &gba->memory.hw.gbpCallback.d;
|
|
||||||
// TODO: Check if the SIO driver is actually used first
|
|
||||||
GBASIOSetDriver(&gba->sio, &gba->memory.hw.gbpDriver.d, SIO_NORMAL_32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t _gbpRead(struct mKeyCallback* callback) {
|
|
||||||
struct GBAGBPKeyCallback* gbpCallback = (struct GBAGBPKeyCallback*) callback;
|
|
||||||
if (gbpCallback->p->gbpInputsPosted == 2) {
|
|
||||||
return 0xF0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
|
||||||
struct GBAGBPSIODriver* gbp = (struct GBAGBPSIODriver*) driver;
|
|
||||||
if (address == REG_SIOCNT) {
|
|
||||||
if (value & 0x0080) {
|
|
||||||
uint32_t rx = gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] | (gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] << 16);
|
|
||||||
if (gbp->p->gbpTxPosition < 12 && gbp->p->gbpTxPosition > 0) {
|
|
||||||
// TODO: Check expected
|
|
||||||
} else if (gbp->p->gbpTxPosition >= 12) {
|
|
||||||
uint32_t mask = 0x33;
|
|
||||||
// 0x00 = Stop
|
|
||||||
// 0x11 = Hard Stop
|
|
||||||
// 0x22 = Start
|
|
||||||
if (gbp->p->p->rumble) {
|
|
||||||
gbp->p->p->rumble->setRumble(gbp->p->p->rumble, (rx & mask) == 0x22);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mTimingDeschedule(&gbp->p->p->timing, &gbp->p->gbpNextEvent);
|
|
||||||
mTimingSchedule(&gbp->p->p->timing, &gbp->p->gbpNextEvent, 2048);
|
|
||||||
}
|
|
||||||
value &= 0x78FB;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
|
||||||
UNUSED(timing);
|
|
||||||
UNUSED(cyclesLate);
|
|
||||||
struct GBAGBPSIODriver* gbp = user;
|
|
||||||
uint32_t tx = 0;
|
|
||||||
int txPosition = gbp->p->gbpTxPosition;
|
|
||||||
if (txPosition > 16) {
|
|
||||||
gbp->p->gbpTxPosition = 0;
|
|
||||||
txPosition = 0;
|
|
||||||
} else if (txPosition > 12) {
|
|
||||||
txPosition = 12;
|
|
||||||
}
|
|
||||||
tx = _gbpTxData[txPosition];
|
|
||||||
++gbp->p->gbpTxPosition;
|
|
||||||
gbp->p->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
|
||||||
gbp->p->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
|
|
||||||
if (GBASIONormalIsIrq(gbp->d.p->siocnt)) {
|
|
||||||
GBARaiseIRQ(gbp->p->p, IRQ_SIO, cyclesLate);
|
|
||||||
}
|
|
||||||
gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt);
|
|
||||||
gbp->p->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080;
|
|
||||||
}
|
|
||||||
|
|
||||||
// == Serialization
|
// == Serialization
|
||||||
|
|
||||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||||
GBASerializedHWFlags1 flags1 = 0;
|
GBASerializedHWFlags1 flags1 = 0;
|
||||||
GBASerializedHWFlags2 flags2 = 0;
|
|
||||||
flags1 = GBASerializedHWFlags1SetReadWrite(flags1, hw->readWrite);
|
flags1 = GBASerializedHWFlags1SetReadWrite(flags1, hw->readWrite);
|
||||||
STORE_16(hw->pinState, 0, &state->hw.pinState);
|
STORE_16(hw->pinState, 0, &state->hw.pinState);
|
||||||
STORE_16(hw->direction, 0, &state->hw.pinDirection);
|
STORE_16(hw->direction, 0, &state->hw.pinDirection);
|
||||||
|
@ -640,14 +505,19 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
|
||||||
flags1 = GBASerializedHWFlags1SetGyroEdge(flags1, hw->gyroEdge);
|
flags1 = GBASerializedHWFlags1SetGyroEdge(flags1, hw->gyroEdge);
|
||||||
STORE_16(hw->tiltX, 0, &state->hw.tiltSampleX);
|
STORE_16(hw->tiltX, 0, &state->hw.tiltSampleX);
|
||||||
STORE_16(hw->tiltY, 0, &state->hw.tiltSampleY);
|
STORE_16(hw->tiltY, 0, &state->hw.tiltSampleY);
|
||||||
flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState);
|
|
||||||
flags2 = GBASerializedHWFlags1SetLightCounter(flags2, hw->lightCounter);
|
|
||||||
state->hw.lightSample = hw->lightSample;
|
state->hw.lightSample = hw->lightSample;
|
||||||
flags1 = GBASerializedHWFlags1SetLightEdge(flags1, hw->lightEdge);
|
flags1 = GBASerializedHWFlags1SetLightEdge(flags1, hw->lightEdge);
|
||||||
flags2 = GBASerializedHWFlags2SetGbpInputsPosted(flags2, hw->gbpInputsPosted);
|
|
||||||
flags2 = GBASerializedHWFlags2SetGbpTxPosition(flags2, hw->gbpTxPosition);
|
|
||||||
STORE_32(hw->gbpNextEvent.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.gbpNextEvent);
|
|
||||||
STORE_16(flags1, 0, &state->hw.flags1);
|
STORE_16(flags1, 0, &state->hw.flags1);
|
||||||
|
|
||||||
|
GBASerializedHWFlags2 flags2 = 0;
|
||||||
|
flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState);
|
||||||
|
flags2 = GBASerializedHWFlags1SetLightCounter(flags2, hw->lightCounter);
|
||||||
|
|
||||||
|
// GBP stuff is only here for legacy reasons
|
||||||
|
flags2 = GBASerializedHWFlags2SetGbpInputsPosted(flags2, hw->p->sio.gbp.inputsPosted);
|
||||||
|
flags2 = GBASerializedHWFlags2SetGbpTxPosition(flags2, hw->p->sio.gbp.txPosition);
|
||||||
|
STORE_32(hw->p->sio.gbp.event.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.gbpNextEvent);
|
||||||
|
|
||||||
state->hw.flags2 = flags2;
|
state->hw.flags2 = flags2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,15 +546,17 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
||||||
hw->lightCounter = GBASerializedHWFlags1GetLightCounter(flags1);
|
hw->lightCounter = GBASerializedHWFlags1GetLightCounter(flags1);
|
||||||
hw->lightSample = state->hw.lightSample;
|
hw->lightSample = state->hw.lightSample;
|
||||||
hw->lightEdge = GBASerializedHWFlags1GetLightEdge(flags1);
|
hw->lightEdge = GBASerializedHWFlags1GetLightEdge(flags1);
|
||||||
hw->gbpInputsPosted = GBASerializedHWFlags2GetGbpInputsPosted(state->hw.flags2);
|
|
||||||
hw->gbpTxPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2);
|
// GBP stuff is only here for legacy reasons
|
||||||
|
hw->p->sio.gbp.inputsPosted = GBASerializedHWFlags2GetGbpInputsPosted(state->hw.flags2);
|
||||||
|
hw->p->sio.gbp.txPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2);
|
||||||
|
|
||||||
uint32_t when;
|
uint32_t when;
|
||||||
LOAD_32(when, 0, &state->hw.gbpNextEvent);
|
LOAD_32(when, 0, &state->hw.gbpNextEvent);
|
||||||
if (hw->devices & HW_GB_PLAYER) {
|
if (hw->devices & HW_GB_PLAYER) {
|
||||||
GBASIOSetDriver(&hw->p->sio, &hw->gbpDriver.d, SIO_NORMAL_32);
|
GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d, SIO_NORMAL_32);
|
||||||
if (hw->p->memory.io[REG_SIOCNT >> 1] & 0x0080) {
|
if (hw->p->memory.io[REG_SIOCNT >> 1] & 0x0080) {
|
||||||
mTimingSchedule(&hw->p->timing, &hw->gbpNextEvent, when);
|
mTimingSchedule(&hw->p->timing, &hw->p->sio.gbp.event, when);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* 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/internal/gba/matrix.h>
|
#include <mgba/internal/gba/cart/matrix.h>
|
||||||
|
|
||||||
#include <mgba/internal/arm/macros.h>
|
#include <mgba/internal/arm/macros.h>
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
|
@ -3,7 +3,7 @@
|
||||||
* 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/internal/gba/vfame.h>
|
#include <mgba/internal/gba/cart/vfame.h>
|
||||||
|
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba/internal/gba/memory.h>
|
#include <mgba/internal/gba/memory.h>
|
|
@ -219,7 +219,7 @@ 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->memory.hw.gbpDriver.d) {
|
if (gba->sio.drivers.normal == &gba->sio.gbp.d) {
|
||||||
GBASIOSetDriver(&gba->sio, NULL, SIO_NORMAL_32);
|
GBASIOSetDriver(&gba->sio, NULL, SIO_NORMAL_32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -855,7 +855,7 @@ void GBAFrameEnded(struct GBA* gba) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gba->memory.hw.devices & (HW_GB_PLAYER | HW_GB_PLAYER_DETECTION)) {
|
if (gba->memory.hw.devices & (HW_GB_PLAYER | HW_GB_PLAYER_DETECTION)) {
|
||||||
GBAHardwarePlayerUpdate(gba);
|
GBASIOPlayerUpdate(gba);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t c;
|
size_t c;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <mgba/internal/gba/overrides.h>
|
#include <mgba/internal/gba/overrides.h>
|
||||||
|
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba/internal/gba/hardware.h>
|
#include <mgba/internal/gba/cart/gpio.h>
|
||||||
|
|
||||||
#include <mgba-util/configuration.h>
|
#include <mgba-util/configuration.h>
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba/internal/gba/io.h>
|
#include <mgba/internal/gba/io.h>
|
||||||
|
#include <mgba/internal/gba/sio/gbp.h>
|
||||||
|
|
||||||
mLOG_DEFINE_CATEGORY(GBA_SIO, "GBA Serial I/O", "gba.sio");
|
mLOG_DEFINE_CATEGORY(GBA_SIO, "GBA Serial I/O", "gba.sio");
|
||||||
|
|
||||||
|
@ -76,6 +77,10 @@ void GBASIOInit(struct GBASIO* sio) {
|
||||||
sio->drivers.multiplayer = 0;
|
sio->drivers.multiplayer = 0;
|
||||||
sio->drivers.joybus = 0;
|
sio->drivers.joybus = 0;
|
||||||
sio->activeDriver = 0;
|
sio->activeDriver = 0;
|
||||||
|
|
||||||
|
sio->gbp.p = sio->p;
|
||||||
|
GBASIOPlayerInit(&sio->gbp);
|
||||||
|
|
||||||
GBASIOReset(sio);
|
GBASIOReset(sio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +108,8 @@ void GBASIOReset(struct GBASIO* sio) {
|
||||||
sio->mode = -1;
|
sio->mode = -1;
|
||||||
sio->activeDriver = NULL;
|
sio->activeDriver = NULL;
|
||||||
_switchMode(sio);
|
_switchMode(sio);
|
||||||
|
|
||||||
|
GBASIOPlayerReset(&sio->gbp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers) {
|
void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers) {
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
/* Copyright (c) 2013-2021 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/arm/macros.h>
|
||||||
|
#include <mgba/internal/gba/io.h>
|
||||||
|
#include <mgba/internal/gba/serialize.h>
|
||||||
|
#include <mgba-util/formatting.h>
|
||||||
|
#include <mgba-util/hash.h>
|
||||||
|
#include <mgba-util/memory.h>
|
||||||
|
|
||||||
|
static uint16_t _gbpRead(struct mKeyCallback*);
|
||||||
|
static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||||
|
static void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||||
|
|
||||||
|
static const uint8_t _logoPalette[] = {
|
||||||
|
0xDF, 0xFF, 0x0C, 0x64, 0x0C, 0xE4, 0x2D, 0xE4, 0x4E, 0x64, 0x4E, 0xE4, 0x6E, 0xE4, 0xAF, 0x68,
|
||||||
|
0xB0, 0xE8, 0xD0, 0x68, 0xF0, 0x68, 0x11, 0x69, 0x11, 0xE9, 0x32, 0x6D, 0x32, 0xED, 0x73, 0xED,
|
||||||
|
0x93, 0x6D, 0x94, 0xED, 0xB4, 0x6D, 0xD5, 0xF1, 0xF5, 0x71, 0xF6, 0xF1, 0x16, 0x72, 0x57, 0x72,
|
||||||
|
0x57, 0xF6, 0x78, 0x76, 0x78, 0xF6, 0x99, 0xF6, 0xB9, 0xF6, 0xD9, 0x76, 0xDA, 0xF6, 0x1B, 0x7B,
|
||||||
|
0x1B, 0xFB, 0x3C, 0xFB, 0x5C, 0x7B, 0x7D, 0x7B, 0x7D, 0xFF, 0x9D, 0x7F, 0xBE, 0x7F, 0xFF, 0x7F,
|
||||||
|
0x2D, 0x64, 0x8E, 0x64, 0x8F, 0xE8, 0xF1, 0xE8, 0x52, 0x6D, 0x73, 0x6D, 0xB4, 0xF1, 0x16, 0xF2,
|
||||||
|
0x37, 0x72, 0x98, 0x76, 0xFA, 0x7A, 0xFA, 0xFA, 0x5C, 0xFB, 0xBE, 0xFF, 0xDE, 0x7F, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32_t _logoHash = 0xEEDA6963;
|
||||||
|
|
||||||
|
static const uint32_t _gbpTxData[] = {
|
||||||
|
0x0000494E, 0x0000494E,
|
||||||
|
0xB6B1494E, 0xB6B1544E,
|
||||||
|
0xABB1544E, 0xABB14E45,
|
||||||
|
0xB1BA4E45, 0xB1BA4F44,
|
||||||
|
0xB0BB4F44, 0xB0BB8002,
|
||||||
|
0x10000010, 0x20000013,
|
||||||
|
0x30000003
|
||||||
|
};
|
||||||
|
|
||||||
|
void GBASIOPlayerInit(struct GBASIOPlayer* gbp) {
|
||||||
|
gbp->callback.d.readKeys = _gbpRead;
|
||||||
|
gbp->callback.p = gbp;
|
||||||
|
gbp->d.init = 0;
|
||||||
|
gbp->d.deinit = 0;
|
||||||
|
gbp->d.load = 0;
|
||||||
|
gbp->d.unload = 0;
|
||||||
|
gbp->d.writeRegister = _gbpSioWriteRegister;
|
||||||
|
gbp->event.context = gbp;
|
||||||
|
gbp->event.name = "GBA SIO Game Boy Player";
|
||||||
|
gbp->event.callback = _gbpSioProcessEvents;
|
||||||
|
gbp->event.priority = 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBASIOPlayerReset(struct GBASIOPlayer* gbp) {
|
||||||
|
if (gbp->p->sio.drivers.normal == &gbp->d) {
|
||||||
|
GBASIOSetDriver(&gbp->p->sio, NULL, SIO_NORMAL_32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBASIOPlayerCheckScreen(const struct GBAVideo* video) {
|
||||||
|
if (memcmp(video->palette, _logoPalette, sizeof(_logoPalette)) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint32_t hash = hash32(&video->renderer->vram[0x4000], 0x4000, 0);
|
||||||
|
return hash == _logoHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBASIOPlayerUpdate(struct GBA* gba) {
|
||||||
|
if (gba->memory.hw.devices & HW_GB_PLAYER) {
|
||||||
|
if (GBASIOPlayerCheckScreen(&gba->video)) {
|
||||||
|
++gba->sio.gbp.inputsPosted;
|
||||||
|
gba->sio.gbp.inputsPosted %= 3;
|
||||||
|
gba->keyCallback = &gba->sio.gbp.callback.d;
|
||||||
|
} else {
|
||||||
|
// TODO: Save and restore
|
||||||
|
gba->keyCallback = 0;
|
||||||
|
}
|
||||||
|
gba->sio.gbp.txPosition = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gba->keyCallback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (GBASIOPlayerCheckScreen(&gba->video)) {
|
||||||
|
gba->memory.hw.devices |= HW_GB_PLAYER;
|
||||||
|
gba->sio.gbp.inputsPosted = 0;
|
||||||
|
gba->keyCallback = &gba->sio.gbp.callback.d;
|
||||||
|
// TODO: Check if the SIO driver is actually used first
|
||||||
|
GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d, SIO_NORMAL_32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t _gbpRead(struct mKeyCallback* callback) {
|
||||||
|
struct GBASIOPlayerKeyCallback* gbpCallback = (struct GBASIOPlayerKeyCallback*) callback;
|
||||||
|
if (gbpCallback->p->inputsPosted == 2) {
|
||||||
|
return 0xF0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||||
|
struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver;
|
||||||
|
if (address == REG_SIOCNT) {
|
||||||
|
if (value & 0x0080) {
|
||||||
|
uint32_t rx = gbp->p->memory.io[REG_SIODATA32_LO >> 1] | (gbp->p->memory.io[REG_SIODATA32_HI >> 1] << 16);
|
||||||
|
if (gbp->txPosition < 12 && gbp->txPosition > 0) {
|
||||||
|
// TODO: Check expected
|
||||||
|
} else if (gbp->txPosition >= 12) {
|
||||||
|
uint32_t mask = 0x33;
|
||||||
|
// 0x00 = Stop
|
||||||
|
// 0x11 = Hard Stop
|
||||||
|
// 0x22 = Start
|
||||||
|
if (gbp->p->rumble) {
|
||||||
|
gbp->p->rumble->setRumble(gbp->p->rumble, (rx & mask) == 0x22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
UNUSED(timing);
|
||||||
|
UNUSED(cyclesLate);
|
||||||
|
struct GBASIOPlayer* gbp = user;
|
||||||
|
uint32_t tx = 0;
|
||||||
|
int txPosition = gbp->txPosition;
|
||||||
|
if (txPosition > 16) {
|
||||||
|
gbp->txPosition = 0;
|
||||||
|
txPosition = 0;
|
||||||
|
} else if (txPosition > 12) {
|
||||||
|
txPosition = 12;
|
||||||
|
}
|
||||||
|
tx = _gbpTxData[txPosition];
|
||||||
|
++gbp->txPosition;
|
||||||
|
gbp->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
|
||||||
|
gbp->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
|
||||||
|
if (GBASIONormalIsIrq(gbp->d.p->siocnt)) {
|
||||||
|
GBARaiseIRQ(gbp->p, IRQ_SIO, cyclesLate);
|
||||||
|
}
|
||||||
|
gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt);
|
||||||
|
gbp->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080;
|
||||||
|
}
|
Loading…
Reference in New Issue