mirror of https://github.com/mgba-emu/mgba.git
GB MBC: Pocket Cam support
This commit is contained in:
parent
55330698cb
commit
31b9100f38
1
CHANGES
1
CHANGES
|
@ -1,6 +1,7 @@
|
||||||
0.7.0: (Future)
|
0.7.0: (Future)
|
||||||
Features:
|
Features:
|
||||||
- ELF support
|
- ELF support
|
||||||
|
- Game Boy Camera support
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
|
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
|
||||||
- Python: Fix importing .gb or .gba before .core
|
- Python: Fix importing .gb or .gba before .core
|
||||||
|
|
|
@ -61,6 +61,7 @@ struct mKeyCallback {
|
||||||
enum mPeripheral {
|
enum mPeripheral {
|
||||||
mPERIPH_ROTATION = 1,
|
mPERIPH_ROTATION = 1,
|
||||||
mPERIPH_RUMBLE,
|
mPERIPH_RUMBLE,
|
||||||
|
mPERIPH_IMAGE_SOURCE,
|
||||||
mPERIPH_CUSTOM = 0x1000
|
mPERIPH_CUSTOM = 0x1000
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -82,6 +83,12 @@ struct mRTCSource {
|
||||||
bool (*deserialize)(struct mRTCSource*, const struct mStateExtdataItem*);
|
bool (*deserialize)(struct mRTCSource*, const struct mStateExtdataItem*);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mImageSource {
|
||||||
|
void (*startRequestImage)(struct mImageSource*);
|
||||||
|
void (*stopRequestImage)(struct mImageSource*);
|
||||||
|
void (*requestImage)(struct mImageSource*, unsigned w, unsigned h, const uint32_t** buffer, size_t* stride);
|
||||||
|
};
|
||||||
|
|
||||||
enum mRTCGenericType {
|
enum mRTCGenericType {
|
||||||
RTC_NO_OVERRIDE,
|
RTC_NO_OVERRIDE,
|
||||||
RTC_FIXED,
|
RTC_FIXED,
|
||||||
|
|
|
@ -21,6 +21,11 @@ void GBMBCSwitchBank(struct GB* gb, int bank);
|
||||||
void GBMBCSwitchBank0(struct GB* gb, int bank);
|
void GBMBCSwitchBank0(struct GB* gb, int bank);
|
||||||
void GBMBCSwitchSramBank(struct GB* gb, int bank);
|
void GBMBCSwitchSramBank(struct GB* gb, int bank);
|
||||||
|
|
||||||
|
enum GBCam {
|
||||||
|
GBCAM_WIDTH = 128,
|
||||||
|
GBCAM_HEIGHT = 112
|
||||||
|
};
|
||||||
|
|
||||||
struct GBMBCRTCSaveBuffer {
|
struct GBMBCRTCSaveBuffer {
|
||||||
uint32_t sec;
|
uint32_t sec;
|
||||||
uint32_t min;
|
uint32_t min;
|
||||||
|
|
|
@ -117,6 +117,7 @@ struct GBMBC7State {
|
||||||
|
|
||||||
struct GBPocketCamState {
|
struct GBPocketCamState {
|
||||||
bool registersActive;
|
bool registersActive;
|
||||||
|
uint8_t registers[0x36];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GBTAMA5State {
|
struct GBTAMA5State {
|
||||||
|
@ -179,6 +180,7 @@ struct GBMemory {
|
||||||
struct mRTCSource* rtc;
|
struct mRTCSource* rtc;
|
||||||
struct mRotationSource* rotation;
|
struct mRotationSource* rotation;
|
||||||
struct mRumble* rumble;
|
struct mRumble* rumble;
|
||||||
|
struct mImageSource* cam;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LR35902Core;
|
struct LR35902Core;
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -479,6 +479,9 @@ static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) {
|
||||||
case mPERIPH_RUMBLE:
|
case mPERIPH_RUMBLE:
|
||||||
gb->memory.rumble = periph;
|
gb->memory.rumble = periph;
|
||||||
break;
|
break;
|
||||||
|
case mPERIPH_IMAGE_SOURCE:
|
||||||
|
gb->memory.cam = periph;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -294,6 +294,9 @@ void GBUnloadROM(struct GB* gb) {
|
||||||
}
|
}
|
||||||
gb->sramRealVf = NULL;
|
gb->sramRealVf = NULL;
|
||||||
gb->sramVf = NULL;
|
gb->sramVf = NULL;
|
||||||
|
if (gb->memory.cam && gb->memory.cam->stopRequestImage) {
|
||||||
|
gb->memory.cam->stopRequestImage(gb->memory.cam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBSynthesizeROM(struct VFile* vf) {
|
void GBSynthesizeROM(struct VFile* vf) {
|
||||||
|
|
56
src/gb/mbc.c
56
src/gb/mbc.c
|
@ -37,6 +37,7 @@ static void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t valu
|
||||||
static uint8_t _GBTAMA5Read(struct GBMemory*, uint16_t address);
|
static uint8_t _GBTAMA5Read(struct GBMemory*, uint16_t address);
|
||||||
|
|
||||||
static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address);
|
static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address);
|
||||||
|
static void _GBPocketCamCapture(struct GBMemory*);
|
||||||
|
|
||||||
void GBMBCSwitchBank(struct GB* gb, int bank) {
|
void GBMBCSwitchBank(struct GB* gb, int bank) {
|
||||||
size_t bankStart = bank * GB_SIZE_CART_BANK0;
|
size_t bankStart = bank * GB_SIZE_CART_BANK0;
|
||||||
|
@ -242,6 +243,9 @@ void GBMBCInit(struct GB* gb) {
|
||||||
case GB_POCKETCAM:
|
case GB_POCKETCAM:
|
||||||
gb->memory.mbcWrite = _GBPocketCam;
|
gb->memory.mbcWrite = _GBPocketCam;
|
||||||
gb->memory.mbcRead = _GBPocketCamRead;
|
gb->memory.mbcRead = _GBPocketCamRead;
|
||||||
|
if (gb->memory.cam && gb->memory.cam->startRequestImage) {
|
||||||
|
gb->memory.cam->startRequestImage(gb->memory.cam);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -757,6 +761,16 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
memory->mbcState.pocketCam.registersActive = true;
|
memory->mbcState.pocketCam.registersActive = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 0x5:
|
||||||
|
address &= 0x7F;
|
||||||
|
if (address == 0 && value & 1) {
|
||||||
|
value &= 6; // TODO: Timing
|
||||||
|
_GBPocketCamCapture(memory);
|
||||||
|
}
|
||||||
|
if (address < sizeof(memory->mbcState.pocketCam.registers)) {
|
||||||
|
memory->mbcState.pocketCam.registers[address] = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
mLOG(GB_MBC, STUB, "Pocket Cam unknown address: %04X:%02X", address, value);
|
mLOG(GB_MBC, STUB, "Pocket Cam unknown address: %04X:%02X", address, value);
|
||||||
break;
|
break;
|
||||||
|
@ -765,11 +779,53 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
|
|
||||||
uint8_t _GBPocketCamRead(struct GBMemory* memory, uint16_t address) {
|
uint8_t _GBPocketCamRead(struct GBMemory* memory, uint16_t address) {
|
||||||
if (memory->mbcState.pocketCam.registersActive) {
|
if (memory->mbcState.pocketCam.registersActive) {
|
||||||
|
if ((address & 0x7F) == 0) {
|
||||||
|
return memory->mbcState.pocketCam.registers[0];
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
|
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _GBPocketCamCapture(struct GBMemory* memory) {
|
||||||
|
if (!memory->cam) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint32_t* image = NULL;
|
||||||
|
size_t stride;
|
||||||
|
memory->cam->requestImage(memory->cam, GBCAM_WIDTH, GBCAM_HEIGHT, &image, &stride);
|
||||||
|
if (!image) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memset(&memory->sram[0x100], 0, GBCAM_HEIGHT * GBCAM_WIDTH / 4);
|
||||||
|
struct GBPocketCamState* pocketCam = &memory->mbcState.pocketCam;
|
||||||
|
size_t x, y;
|
||||||
|
for (y = 0; y < GBCAM_HEIGHT; ++y) {
|
||||||
|
for (x = 0; x < GBCAM_WIDTH; ++x) {
|
||||||
|
uint32_t color = image[y * stride + x];
|
||||||
|
uint32_t gray = ((color & 0xFF) + ((color >> 8) & 0xFF) + ((color >> 16) & 0xFF));
|
||||||
|
uint16_t exposure = (pocketCam->registers[2] << 8) | (pocketCam->registers[3]);
|
||||||
|
gray = (gray + 1) * exposure / 0x300;
|
||||||
|
// TODO: Additional processing
|
||||||
|
int matrixEntry = 3 * ((x & 3) + 4 * (y & 3));
|
||||||
|
if (gray < pocketCam->registers[matrixEntry + 6]) {
|
||||||
|
gray = 0x101;
|
||||||
|
} else if (gray < pocketCam->registers[matrixEntry + 7]) {
|
||||||
|
gray = 0x100;
|
||||||
|
} else if (gray < pocketCam->registers[matrixEntry + 8]) {
|
||||||
|
gray = 0x001;
|
||||||
|
} else {
|
||||||
|
gray = 0;
|
||||||
|
}
|
||||||
|
int coord = (((x >> 3) & 0xF) * 8 + (y & 0x7)) * 2 + (y & ~0x7) * 0x20;
|
||||||
|
uint16_t existing;
|
||||||
|
LOAD_16LE(existing, coord + 0x100, memory->sram);
|
||||||
|
existing |= gray << (7 - (x & 7));
|
||||||
|
STORE_16LE(existing, coord + 0x100, memory->sram);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
|
void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
struct GBMemory* memory = &gb->memory;
|
struct GBMemory* memory = &gb->memory;
|
||||||
struct GBTAMA5State* tama5 = &memory->mbcState.tama5;
|
struct GBTAMA5State* tama5 = &memory->mbcState.tama5;
|
||||||
|
|
|
@ -105,6 +105,7 @@ void GBMemoryInit(struct GB* gb) {
|
||||||
gb->memory.rtc = NULL;
|
gb->memory.rtc = NULL;
|
||||||
gb->memory.rotation = NULL;
|
gb->memory.rotation = NULL;
|
||||||
gb->memory.rumble = NULL;
|
gb->memory.rumble = NULL;
|
||||||
|
gb->memory.cam = NULL;
|
||||||
|
|
||||||
GBIOInit(gb);
|
GBIOInit(gb);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,6 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
||||||
|
|
||||||
m_threadContext.startCallback = [](mCoreThread* context) {
|
m_threadContext.startCallback = [](mCoreThread* context) {
|
||||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||||
context->core->setPeripheral(context->core, mPERIPH_ROTATION, controller->m_inputController->rotationSource());
|
|
||||||
context->core->setPeripheral(context->core, mPERIPH_RUMBLE, controller->m_inputController->rumble());
|
|
||||||
|
|
||||||
switch (context->core->platform(context->core)) {
|
switch (context->core->platform(context->core)) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
|
@ -285,6 +283,9 @@ void CoreController::setOverride(std::unique_ptr<Override> override) {
|
||||||
|
|
||||||
void CoreController::setInputController(InputController* inputController) {
|
void CoreController::setInputController(InputController* inputController) {
|
||||||
m_inputController = inputController;
|
m_inputController = inputController;
|
||||||
|
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_ROTATION, m_inputController->rotationSource());
|
||||||
|
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_RUMBLE, m_inputController->rumble());
|
||||||
|
m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_IMAGE_SOURCE, m_inputController->imageSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::setLogger(LogController* logger) {
|
void CoreController::setLogger(LogController* logger) {
|
||||||
|
|
|
@ -78,6 +78,27 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren
|
||||||
};
|
};
|
||||||
setLuminanceLevel(0);
|
setLuminanceLevel(0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
m_image.startRequestImage = [](mImageSource* context) {
|
||||||
|
InputControllerImage* image = static_cast<InputControllerImage*>(context);
|
||||||
|
image->image.load(":/res/no-cam.png");
|
||||||
|
};
|
||||||
|
m_image.stopRequestImage = nullptr;
|
||||||
|
m_image.requestImage = [](mImageSource* context, unsigned w, unsigned h, const uint32_t** buffer, size_t* stride) {
|
||||||
|
InputControllerImage* image = static_cast<InputControllerImage*>(context);
|
||||||
|
image->resizedImage = image->image.scaled(w, h, Qt::KeepAspectRatioByExpanding);
|
||||||
|
image->resizedImage = image->resizedImage.convertToFormat(QImage::Format_RGB32);
|
||||||
|
const uint32_t* bits = reinterpret_cast<const uint32_t*>(image->resizedImage.constBits());
|
||||||
|
QSize size = image->resizedImage.size();
|
||||||
|
if (size.width() > w) {
|
||||||
|
bits += size.width() / 2;
|
||||||
|
}
|
||||||
|
if (size.height() > h) {
|
||||||
|
bits += (size.height() / 2) * size.width();
|
||||||
|
}
|
||||||
|
*buffer = bits;
|
||||||
|
*stride = size.width();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
InputController::~InputController() {
|
InputController::~InputController() {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "GamepadAxisEvent.h"
|
#include "GamepadAxisEvent.h"
|
||||||
#include "GamepadHatEvent.h"
|
#include "GamepadHatEvent.h"
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
@ -82,6 +83,7 @@ public:
|
||||||
|
|
||||||
mRumble* rumble();
|
mRumble* rumble();
|
||||||
mRotationSource* rotationSource();
|
mRotationSource* rotationSource();
|
||||||
|
mImageSource* imageSource() { return &m_image; }
|
||||||
GBALuminanceSource* luminance() { return &m_lux; }
|
GBALuminanceSource* luminance() { return &m_lux; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -115,6 +117,12 @@ private:
|
||||||
uint8_t m_luxValue;
|
uint8_t m_luxValue;
|
||||||
int m_luxLevel;
|
int m_luxLevel;
|
||||||
|
|
||||||
|
struct InputControllerImage : mImageSource {
|
||||||
|
InputController* p;
|
||||||
|
QImage image;
|
||||||
|
QImage resizedImage;
|
||||||
|
} m_image;
|
||||||
|
|
||||||
mInputMap m_inputMap;
|
mInputMap m_inputMap;
|
||||||
ConfigController* m_config = nullptr;
|
ConfigController* m_config = nullptr;
|
||||||
int m_playerId;
|
int m_playerId;
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
<file>../../../res/mgba-1024.png</file>
|
<file>../../../res/mgba-1024.png</file>
|
||||||
<file>../../../res/keymap.qpic</file>
|
<file>../../../res/keymap.qpic</file>
|
||||||
<file>../../../res/patrons.txt</file>
|
<file>../../../res/patrons.txt</file>
|
||||||
|
<file>../../../res/no-cam.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
Loading…
Reference in New Issue