all repos — mgba @ 2e0ba4fa1c81694a7805123305f5883d598fc0dd

mGBA Game Boy Advance Emulator

Merge branch 'master' into feature/input-revamp
Vicki Pfau vi@endrift.com
Mon, 31 Jul 2017 15:59:32 -0700
commit

2e0ba4fa1c81694a7805123305f5883d598fc0dd

parent

5122c399f5dbe67a5aad3f2bf0be3012b768022d

53 files changed, 2235 insertions(+), 154 deletions(-)

jump to
M CHANGESCHANGES

@@ -1,17 +1,33 @@

0.7.0: (Future) Features: - ELF support + - Game Boy Camera support + - Qt: Set default Game Boy colors + - Game Boy Printer support Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - Python: Fix importing .gb or .gba before .core - GBA: Reset active region as needed when loading a ROM - Qt: Fix command line debugger closing second game + - GB Serialize: Fix audio state loading + - GB Video: Fix dot clock timing being slightly wrong + - GB MBC: Pocket Cam memory should be accessible without enabling + - GB Memory: Initialize peripheral pointers + - GB MBC: Fix SRAM sizes 4 and 5 + - GB Video: Fix 16-bit screenshots (fixes mgba.io/i/826) + - GB Core: Fix palette loading when loading a foreign config + - Qt: Fix LOG argument order + - GB Memory: Prevent accessing empty SRAM (fixes mgba.io/i/831) + - GB, GBA: Fix crashes when attempting to identify null VFiles + - GB MBC: Fix RTC initialization (fixes mgba.io/i/825) + - GB MBC: Fix RTC loading when file size is off Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) - All: Make FIXED_ROM_BUFFER an option instead of 3DS-only - Qt: Don't rebuild library view if style hasn't changed - Qt: Redo GameController into multiple classes + - SDL: Fix 2.0.5 build on macOS under some circumstances 0.6.0: (2017-07-16) Features:
M CMakeLists.txtCMakeLists.txt

@@ -57,7 +57,7 @@ file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/gui/*.c)

file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c) file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/*.c) -file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/lockstep.c) +file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/*.c) file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c) file(GLOB GB_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/extra/*.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c)
M include/mgba/core/interface.hinclude/mgba/core/interface.h

@@ -37,6 +37,29 @@ #define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19))

struct blip_t; +enum mColorFormat { + mCOLOR_XBGR8 = 0x00001, + mCOLOR_XRGB8 = 0x00002, + mCOLOR_BGRX8 = 0x00004, + mCOLOR_RGBX8 = 0x00008, + mCOLOR_ABGR8 = 0x00010, + mCOLOR_ARGB8 = 0x00020, + mCOLOR_BGRA8 = 0x00040, + mCOLOR_RGBA8 = 0x00080, + mCOLOR_RGB5 = 0x00100, + mCOLOR_BGR5 = 0x00200, + mCOLOR_RGB565 = 0x00400, + mCOLOR_BGR565 = 0x00800, + mCOLOR_ARGB5 = 0x01000, + mCOLOR_ABGR5 = 0x02000, + mCOLOR_RGBA5 = 0x04000, + mCOLOR_BGRA5 = 0x08000, + mCOLOR_RGB8 = 0x10000, + mCOLOR_BGR8 = 0x20000, + + mCOLOR_ANY = -1 +}; + struct mCoreCallbacks { void* context; void (*videoFrameStarted)(void* context);

@@ -61,6 +84,7 @@

enum mPeripheral { mPERIPH_ROTATION = 1, mPERIPH_RUMBLE, + mPERIPH_IMAGE_SOURCE, mPERIPH_CUSTOM = 0x1000 };

@@ -80,6 +104,12 @@ time_t (*unixTime)(struct mRTCSource*);

void (*serialize)(struct mRTCSource*, struct mStateExtdataItem*); bool (*deserialize)(struct mRTCSource*, const struct mStateExtdataItem*); +}; + +struct mImageSource { + void (*startRequestImage)(struct mImageSource*, unsigned w, unsigned h, int colorFormats); + void (*stopRequestImage)(struct mImageSource*); + void (*requestImage)(struct mImageSource*, const void** buffer, size_t* stride, enum mColorFormat* colorFormat); }; enum mRTCGenericType {
M include/mgba/gb/interface.hinclude/mgba/gb/interface.h

@@ -31,6 +31,7 @@ GB_MMM01 = 0x10,

GB_HuC1 = 0x11, GB_HuC3 = 0x12, GB_POCKETCAM = 0x13, + GB_TAMA5 = 0x14, GB_MBC3_RTC = 0x103, GB_MBC5_RUMBLE = 0x105 };
M include/mgba/internal/gb/mbc.hinclude/mgba/internal/gb/mbc.h

@@ -21,6 +21,11 @@ void GBMBCSwitchBank(struct GB* gb, int bank);

void GBMBCSwitchBank0(struct GB* gb, int bank); void GBMBCSwitchSramBank(struct GB* gb, int bank); +enum GBCam { + GBCAM_WIDTH = 128, + GBCAM_HEIGHT = 112 +}; + struct GBMBCRTCSaveBuffer { uint32_t sec; uint32_t min;

@@ -36,8 +41,6 @@ uint64_t unixTime;

}; void GBMBCRTCRead(struct GB* gb); void GBMBCRTCWrite(struct GB* gb); - -void GBMBC7Write(struct GBMemory*, uint16_t address, uint8_t value); CXX_GUARD_END
M include/mgba/internal/gb/memory.hinclude/mgba/internal/gb/memory.h

@@ -86,6 +86,19 @@ GBMBC7_STATE_EEPROM_READ = 0x18,

GBMBC7_STATE_EEPROM_ERASE = 0x1C, }; +enum GBTAMA5Register { + GBTAMA5_BANK_LO = 0x0, + GBTAMA5_BANK_HI = 0x1, + GBTAMA5_WRITE_LO = 0x4, + GBTAMA5_WRITE_HI = 0x5, + GBTAMA5_CS = 0x6, + GBTAMA5_ADDR_LO = 0x7, + GBTAMA5_MAX = 0x8, + GBTAMA5_ACTIVE = 0xA, + GBTAMA5_READ_LO = 0xC, + GBTAMA5_READ_HI = 0xD, +}; + struct GBMBC1State { int mode; int multicartStride;

@@ -104,12 +117,19 @@ };

struct GBPocketCamState { bool registersActive; + uint8_t registers[0x36]; +}; + +struct GBTAMA5State { + uint8_t reg; + uint8_t registers[GBTAMA5_MAX]; }; union GBMBCState { struct GBMBC1State mbc1; struct GBMBC7State mbc7; struct GBPocketCamState pocketCam; + struct GBTAMA5State tama5; }; struct mRotationSource;

@@ -160,6 +180,7 @@ time_t rtcLastLatch;

struct mRTCSource* rtc; struct mRotationSource* rotation; struct mRumble* rumble; + struct mImageSource* cam; }; struct LR35902Core;
A include/mgba/internal/gb/sio/printer.h

@@ -0,0 +1,74 @@

+/* 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/. */ +#ifndef GB_PRINTER_H +#define GB_PRINTER_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/gb/interface.h> + +enum GBPrinterPacketByte { + GB_PRINTER_BYTE_MAGIC_0, + GB_PRINTER_BYTE_MAGIC_1, + GB_PRINTER_BYTE_COMMAND, + GB_PRINTER_BYTE_COMPRESSION, + GB_PRINTER_BYTE_LENGTH_0, + GB_PRINTER_BYTE_LENGTH_1, + GB_PRINTER_BYTE_DATA, + GB_PRINTER_BYTE_CHECKSUM_0, + GB_PRINTER_BYTE_CHECKSUM_1, + GB_PRINTER_BYTE_KEEPALIVE, + GB_PRINTER_BYTE_STATUS, + + GB_PRINTER_BYTE_COMPRESSED_DATUM, + GB_PRINTER_BYTE_UNCOMPRESSED_DATA +}; + +enum GBPrinterStatus { + GB_PRINTER_STATUS_CHECKSUM_ERROR = 0x01, + GB_PRINTER_STATUS_PRINTING = 0x02, + GB_PRINTER_STATUS_PRINT_REQ = 0x04, + GB_PRINTER_STATUS_READY = 0x08, + GB_PRINTER_STATUS_LOW_BATTERY = 0x10, + GB_PRINTER_STATUS_TIMEOUT = 0x20, + GB_PRINTER_STATUS_PAPER_JAM = 0x40, + GB_PRINTER_STATUS_TEMPERATURE_ISSUE = 0x80 +}; + +enum GBPrinterCommand { + GB_PRINTER_COMMAND_INIT = 0x1, + GB_PRINTER_COMMAND_PRINT = 0x2, + GB_PRINTER_COMMAND_DATA = 0x4, + GB_PRINTER_COMMAND_STATUS = 0xF, +}; + +struct GBPrinter { + struct GBSIODriver d; + + void (*print)(struct GBPrinter*, int height, const uint8_t* data); + + uint8_t* buffer; + uint16_t checksum; + enum GBPrinterCommand command; + uint16_t remainingBytes; + uint8_t remainingCmpBytes; + unsigned currentIndex; + bool compression; + + uint8_t byte; + enum GBPrinterPacketByte next; + uint8_t status; + int printWait; +}; + +void GBPrinterCreate(struct GBPrinter* printer); +void GBPrinterDonePrinting(struct GBPrinter* printer); + +CXX_GUARD_END + +#endif
M src/gb/audio.csrc/gb/audio.c

@@ -960,6 +960,8 @@ LOAD_32LE(when, 0, &state->ch1.nextFrame);

mTimingSchedule(audio->timing, &audio->frameEvent, when); LOAD_32LE(flags, 0, flagsIn); + audio->frame = GBSerializedAudioFlagsGetFrame(flags); + LOAD_32LE(ch1Flags, 0, &state->ch1.envelope); audio->ch1.envelope.currentVolume = GBSerializedAudioFlagsGetCh1Volume(flags); audio->ch1.envelope.dead = GBSerializedAudioFlagsGetCh1Dead(flags);
M src/gb/core.csrc/gb/core.c

@@ -176,17 +176,17 @@ }

gb->video.frameskip = core->opts.frameskip; int color; - if (mCoreConfigGetIntValue(&core->config, "gb.pal[0]", &color)) { - GBVideoSetPalette(&gb->video, 0, color); + if (mCoreConfigGetIntValue(config, "gb.pal[0]", &color)) { + GBVideoSetPalette(&gb->video, 0, color); } - if (mCoreConfigGetIntValue(&core->config, "gb.pal[1]", &color)) { - GBVideoSetPalette(&gb->video, 1, color); + if (mCoreConfigGetIntValue(config, "gb.pal[1]", &color)) { + GBVideoSetPalette(&gb->video, 1, color); } - if (mCoreConfigGetIntValue(&core->config, "gb.pal[2]", &color)) { - GBVideoSetPalette(&gb->video, 2, color); + if (mCoreConfigGetIntValue(config, "gb.pal[2]", &color)) { + GBVideoSetPalette(&gb->video, 2, color); } - if (mCoreConfigGetIntValue(&core->config, "gb.pal[3]", &color)) { - GBVideoSetPalette(&gb->video, 3, color); + if (mCoreConfigGetIntValue(config, "gb.pal[3]", &color)) { + GBVideoSetPalette(&gb->video, 3, color); } mCoreConfigCopyValue(&core->config, config, "gb.bios");

@@ -487,6 +487,9 @@ gb->memory.rotation = periph;

break; case mPERIPH_RUMBLE: gb->memory.rumble = periph; + break; + case mPERIPH_IMAGE_SOURCE: + gb->memory.cam = periph; break; default: return;
M src/gb/gb.csrc/gb/gb.c

@@ -294,6 +294,9 @@ gb->sramRealVf->close(gb->sramRealVf);

} gb->sramRealVf = NULL; gb->sramVf = NULL; + if (gb->memory.cam && gb->memory.cam->stopRequestImage) { + gb->memory.cam->stopRequestImage(gb->memory.cam); + } } void GBSynthesizeROM(struct VFile* vf) {

@@ -613,7 +616,7 @@

void GBStop(struct LR35902Core* cpu) { struct GB* gb = (struct GB*) cpu->master; if (cpu->bus) { - mLOG(GB, GAME_ERROR, "Hit illegal stop at address %04X:%02X\n", cpu->pc, cpu->bus); + mLOG(GB, GAME_ERROR, "Hit illegal stop at address %04X:%02X", cpu->pc, cpu->bus); } if (gb->memory.io[REG_KEY1] & 1) { gb->doubleSpeed ^= 1;

@@ -639,7 +642,7 @@ }

void GBIllegal(struct LR35902Core* cpu) { struct GB* gb = (struct GB*) cpu->master; - mLOG(GB, GAME_ERROR, "Hit illegal opcode at address %04X:%02X\n", cpu->pc, cpu->bus); + mLOG(GB, GAME_ERROR, "Hit illegal opcode at address %04X:%02X", cpu->pc, cpu->bus); #ifdef USE_DEBUGGERS if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) { struct mDebuggerEntryInfo info = {

@@ -655,6 +658,9 @@ --cpu->pc;

} bool GBIsROM(struct VFile* vf) { + if (!vf) { + return false; + } vf->seek(vf, 0x104, SEEK_SET); uint8_t header[4];
M src/gb/io.csrc/gb/io.c

@@ -580,6 +580,36 @@

void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) { memcpy(gb->memory.io, state->io, GB_SIZE_IO); gb->memory.ie = state->ie; + + if (GBAudioEnableGetEnable(*gb->audio.nr52)) { + GBIOWrite(gb, REG_NR10, gb->memory.io[REG_NR10]); + GBIOWrite(gb, REG_NR11, gb->memory.io[REG_NR11]); + GBIOWrite(gb, REG_NR12, gb->memory.io[REG_NR12]); + GBIOWrite(gb, REG_NR13, gb->memory.io[REG_NR13]); + gb->audio.ch1.control.frequency &= 0xFF; + gb->audio.ch1.control.frequency |= GBAudioRegisterControlGetFrequency(gb->memory.io[REG_NR14] << 8); + gb->audio.ch1.control.stop = GBAudioRegisterControlGetStop(gb->memory.io[REG_NR14] << 8); + GBIOWrite(gb, REG_NR21, gb->memory.io[REG_NR21]); + GBIOWrite(gb, REG_NR22, gb->memory.io[REG_NR22]); + GBIOWrite(gb, REG_NR22, gb->memory.io[REG_NR23]); + gb->audio.ch2.control.frequency &= 0xFF; + gb->audio.ch2.control.frequency |= GBAudioRegisterControlGetFrequency(gb->memory.io[REG_NR24] << 8); + gb->audio.ch2.control.stop = GBAudioRegisterControlGetStop(gb->memory.io[REG_NR24] << 8); + GBIOWrite(gb, REG_NR30, gb->memory.io[REG_NR30]); + GBIOWrite(gb, REG_NR31, gb->memory.io[REG_NR31]); + GBIOWrite(gb, REG_NR32, gb->memory.io[REG_NR32]); + GBIOWrite(gb, REG_NR32, gb->memory.io[REG_NR33]); + gb->audio.ch3.rate &= 0xFF; + gb->audio.ch3.rate |= GBAudioRegisterControlGetRate(gb->memory.io[REG_NR34] << 8); + gb->audio.ch3.stop = GBAudioRegisterControlGetStop(gb->memory.io[REG_NR34] << 8); + GBIOWrite(gb, REG_NR41, gb->memory.io[REG_NR41]); + GBIOWrite(gb, REG_NR42, gb->memory.io[REG_NR42]); + GBIOWrite(gb, REG_NR43, gb->memory.io[REG_NR43]); + gb->audio.ch4.stop = GBAudioRegisterNoiseControlGetStop(gb->memory.io[REG_NR44]); + GBIOWrite(gb, REG_NR50, gb->memory.io[REG_NR50]); + GBIOWrite(gb, REG_NR51, gb->memory.io[REG_NR51]); + } + gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_LCDC, state->io[REG_LCDC]); gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCY, state->io[REG_SCY]); gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCX, state->io[REG_SCX]);
M src/gb/mbc.csrc/gb/mbc.c

@@ -29,9 +29,15 @@ static void _GBMBC6(struct GB*, uint16_t address, uint8_t value);

static void _GBMBC7(struct GB*, uint16_t address, uint8_t value); static void _GBHuC3(struct GB*, uint16_t address, uint8_t value); static void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value); +static void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value); static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address); +static void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value); + +static uint8_t _GBTAMA5Read(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) { size_t bankStart = bank * GB_SIZE_CART_BANK0;

@@ -105,6 +111,12 @@ break;

case 3: gb->sramSize = 0x8000; break; + case 4: + gb->sramSize = 0x20000; + break; + case 5: + gb->sramSize = 0x10000; + break; } if (gb->memory.mbcType == GB_MBC_AUTODETECT) {

@@ -160,11 +172,14 @@ case 0xFC:

gb->memory.mbcType = GB_POCKETCAM; break; case 0xFD: - gb->memory.mbcType = GB_HuC1; + gb->memory.mbcType = GB_TAMA5; break; case 0xFE: gb->memory.mbcType = GB_HuC3; break; + case 0xFF: + gb->memory.mbcType = GB_HuC1; + break; } } } else {

@@ -211,6 +226,13 @@ break;

case GB_HuC3: gb->memory.mbcWrite = _GBHuC3; break; + case GB_TAMA5: + mLOG(GB_MBC, WARN, "unimplemented MBC: TAMA5"); + memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); + gb->memory.mbcWrite = _GBTAMA5; + gb->memory.mbcRead = _GBTAMA5Read; + gb->sramSize = 0x20; + break; case GB_MBC3_RTC: memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); gb->memory.mbcWrite = _GBMBC3;

@@ -221,6 +243,9 @@ break;

case GB_POCKETCAM: gb->memory.mbcWrite = _GBPocketCam; gb->memory.mbcRead = _GBPocketCamRead; + if (gb->memory.cam && gb->memory.cam->startRequestImage) { + gb->memory.cam->startRequestImage(gb->memory.cam, GBCAM_WIDTH, GBCAM_HEIGHT, mCOLOR_ANY); + } break; }

@@ -230,6 +255,15 @@ gb->memory.sramAccess = false;

gb->memory.rtcAccess = false; gb->memory.activeRtcReg = 0; gb->memory.rtcLatched = false; + gb->memory.rtcLastLatch = 0; + if (gb->memory.rtc) { + if (gb->memory.rtc->sample) { + gb->memory.rtc->sample(gb->memory.rtc); + } + gb->memory.rtcLastLatch = gb->memory.rtc->unixTime(gb->memory.rtc); + } else { + gb->memory.rtcLastLatch = time(0); + } memset(&gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); GBResizeSram(gb, gb->sramSize);

@@ -493,6 +527,8 @@ } else {

gb->memory.mbcState.mbc7.access &= ~2; } break; + case 0x5: + _GBMBC7Write(&gb->memory, address, value); default: // TODO mLOG(GB_MBC, STUB, "MBC7 unknown address: %04X:%02X", address, value);

@@ -547,7 +583,7 @@ return 0xFF;

} } -void GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) { +static void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) { struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; if (mbc7->access != 3) { return;

@@ -734,6 +770,16 @@ } else {

memory->mbcState.pocketCam.registersActive = true; } 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: mLOG(GB_MBC, STUB, "Pocket Cam unknown address: %04X:%02X", address, value); break;

@@ -742,12 +788,170 @@ }

uint8_t _GBPocketCamRead(struct GBMemory* memory, uint16_t address) { if (memory->mbcState.pocketCam.registersActive) { + if ((address & 0x7F) == 0) { + return memory->mbcState.pocketCam.registers[0]; + } return 0; } - if (!memory->sramAccess) { + return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; +} + +void _GBPocketCamCapture(struct GBMemory* memory) { + if (!memory->cam) { + return; + } + const void* image = NULL; + size_t stride; + enum mColorFormat format; + memory->cam->requestImage(memory->cam, &image, &stride, &format); + 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 gray; + uint32_t color; + switch (format) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_ARGB8: + case mCOLOR_ABGR8: + color = ((const uint32_t*) image)[y * stride + x]; + gray = (color & 0xFF) + ((color >> 8) & 0xFF) + ((color >> 16) & 0xFF); + break; + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_RGBA8: + case mCOLOR_BGRA8: + color = ((const uint32_t*) image)[y * stride + x]; + gray = ((color >> 8) & 0xFF) + ((color >> 16) & 0xFF) + ((color >> 24) & 0xFF); + break; + case mCOLOR_BGR5: + case mCOLOR_RGB5: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + color = ((const uint16_t*) image)[y * stride + x]; + gray = ((color << 3) & 0xF8) + ((color >> 2) & 0xF8) + ((color >> 7) & 0xF8); + break; + case mCOLOR_BGR565: + case mCOLOR_RGB565: + color = ((const uint16_t*) image)[y * stride + x]; + gray = ((color << 3) & 0xF8) + ((color >> 3) & 0xFC) + ((color >> 8) & 0xF8); + break; + case mCOLOR_BGRA5: + case mCOLOR_RGBA5: + color = ((const uint16_t*) image)[y * stride + x]; + gray = ((color << 2) & 0xF8) + ((color >> 3) & 0xF8) + ((color >> 8) & 0xF8); + break; + default: + mLOG(GB_MBC, WARN, "Unsupported pixel format: %X", format); + return; + } + 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) { + struct GBMemory* memory = &gb->memory; + struct GBTAMA5State* tama5 = &memory->mbcState.tama5; + switch (address >> 13) { + case 0x5: + if (address & 1) { + tama5->reg = value; + } else { + value &= 0xF; + if (tama5->reg < GBTAMA5_MAX) { + tama5->registers[tama5->reg] = value; + uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO]; + uint8_t out = (tama5->registers[GBTAMA5_WRITE_HI] << 4) | tama5->registers[GBTAMA5_WRITE_LO]; + switch (tama5->reg) { + case GBTAMA5_BANK_LO: + case GBTAMA5_BANK_HI: + GBMBCSwitchBank(gb, tama5->registers[GBTAMA5_BANK_LO] | (tama5->registers[GBTAMA5_BANK_HI] << 4)); + break; + case GBTAMA5_WRITE_LO: + case GBTAMA5_WRITE_HI: + case GBTAMA5_CS: + break; + case GBTAMA5_ADDR_LO: + switch (tama5->registers[GBTAMA5_CS] >> 1) { + case 0x0: // RAM write + memory->sram[address] = out; + break; + case 0x1: // RAM read + break; + default: + mLOG(GB_MBC, STUB, "TAMA5 unknown address: %X-%02X:%02X", tama5->registers[GBTAMA5_CS] >> 1, address, out); + } + break; + default: + mLOG(GB_MBC, STUB, "TAMA5 unknown write: %02X:%X", tama5->reg, value); + break; + } + } else { + mLOG(GB_MBC, STUB, "TAMA5 unknown write: %02X", tama5->reg); + } + } + break; + default: + mLOG(GB_MBC, STUB, "TAMA5 unknown address: %04X:%02X", address, value); + } +} + +uint8_t _GBTAMA5Read(struct GBMemory* memory, uint16_t address) { + struct GBTAMA5State* tama5 = &memory->mbcState.tama5; + if ((address & 0x1FFF) > 1) { + mLOG(GB_MBC, STUB, "TAMA5 unknown address: %04X", address); + } + if (address & 1) { return 0xFF; + } else { + uint8_t value = 0xF0; + uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO]; + switch (tama5->reg) { + case GBTAMA5_ACTIVE: + return 0xF1; + case GBTAMA5_READ_LO: + case GBTAMA5_READ_HI: + switch (tama5->registers[GBTAMA5_CS] >> 1) { + case 1: + value = memory->sram[address]; + break; + default: + mLOG(GB_MBC, STUB, "TAMA5 unknown read: %02X", tama5->reg); + break; + } + if (tama5->reg == GBTAMA5_READ_HI) { + value >>= 4; + } + value |= 0xF0; + return value; + default: + mLOG(GB_MBC, STUB, "TAMA5 unknown read: %02X", tama5->reg); + return 0xF1; + } } - return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; } void GBMBCRTCRead(struct GB* gb) {

@@ -756,17 +960,10 @@ struct VFile* vf = gb->sramVf;

if (!vf) { return; } - ssize_t end = vf->seek(vf, -sizeof(rtcBuffer), SEEK_END); - switch (end & 0x1FFF) { - case 0: - break; - case 0x1FFC: - vf->seek(vf, -sizeof(rtcBuffer) - 4, SEEK_END); - break; - default: + vf->seek(vf, gb->sramSize, SEEK_SET); + if (vf->read(vf, &rtcBuffer, sizeof(rtcBuffer)) < (ssize_t) sizeof(rtcBuffer) - 4) { return; } - vf->read(vf, &rtcBuffer, sizeof(rtcBuffer)); LOAD_32LE(gb->memory.rtcRegs[0], 0, &rtcBuffer.latchedSec); LOAD_32LE(gb->memory.rtcRegs[1], 0, &rtcBuffer.latchedMin);

@@ -798,9 +995,9 @@ STORE_32LE(gb->memory.rtcRegs[1], 0, &rtcBuffer.latchedMin);

STORE_32LE(gb->memory.rtcRegs[2], 0, &rtcBuffer.latchedHour); STORE_32LE(gb->memory.rtcRegs[3], 0, &rtcBuffer.latchedDays); STORE_32LE(gb->memory.rtcRegs[4], 0, &rtcBuffer.latchedDaysHi); - STORE_64LE(rtcLastLatch, 0, &rtcBuffer.unixTime); + STORE_64LE(gb->memory.rtcLastLatch, 0, &rtcBuffer.unixTime); - if (vf->size(vf) == gb->sramSize) { + if ((size_t) vf->size(vf) < gb->sramSize + sizeof(rtcBuffer)) { // Writing past the end of the file can invalidate the file mapping vf->unmap(vf, gb->memory.sram, gb->sramSize); gb->memory.sram = NULL;
M src/gb/memory.csrc/gb/memory.c

@@ -103,6 +103,9 @@ gb->memory.mbcRead = NULL;

gb->memory.mbcWrite = NULL; gb->memory.rtc = NULL; + gb->memory.rotation = NULL; + gb->memory.rumble = NULL; + gb->memory.cam = NULL; GBIOInit(gb); }

@@ -218,7 +221,7 @@ if (memory->rtcAccess) {

return memory->rtcRegs[memory->activeRtcReg]; } else if (memory->mbcRead) { return memory->mbcRead(memory, address); - } else if (memory->sramAccess) { + } else if (memory->sramAccess && memory->sram) { return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; } else if (memory->mbcType == GB_HuC3) { return 0x01; // TODO: Is this supposed to be the current SRAM bank?

@@ -287,10 +290,10 @@ case GB_REGION_EXTERNAL_RAM:

case GB_REGION_EXTERNAL_RAM + 1: if (memory->rtcAccess) { memory->rtcRegs[memory->activeRtcReg] = value; - } else if (memory->sramAccess) { + } else if (memory->sramAccess && memory->sram) { memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value; - } else if (memory->mbcType == GB_MBC7) { - GBMBC7Write(memory, address, value); + } else { + memory->mbcWrite(gb, address, value); } gb->sramDirty |= GB_SRAM_DIRT_NEW; return;

@@ -385,7 +388,7 @@ case GB_REGION_EXTERNAL_RAM + 1:

if (memory->rtcAccess) { return memory->rtcRegs[memory->activeRtcReg]; } else if (memory->sramAccess) { - if (segment < 0) { + if (segment < 0 && memory->sram) { return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; } else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) { return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM];
M src/gb/renderers/software.csrc/gb/renderers/software.c

@@ -448,36 +448,9 @@ }

static void GBVideoSoftwareRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; - // TODO: Share with GBAVideoSoftwareRendererGetPixels -#ifdef COLOR_16_BIT - *stride = GB_VIDEO_HORIZONTAL_PIXELS; - if (!softwareRenderer->temporaryBuffer) { - softwareRenderer->temporaryBuffer = anonymousMemoryMap(GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4); - } - *pixels = softwareRenderer->temporaryBuffer; - unsigned y, x; - for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) { - for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; ++x) { - color_t inColor = softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + x]; - uint32_t outColor; -#ifdef COLOR_5_6_5 - outColor = (inColor & 0x1F) << 19; - outColor |= (inColor & 0x7C0) << 5; - outColor |= (inColor & 0xF800) >> 8; -#else - outColor = (inColor & 0x1F) << 3; - outColor |= (inColor & 0x3E0) << 6; - outColor |= (inColor & 0x7C00) << 9; -#endif - softwareRenderer->temporaryBuffer[GB_VIDEO_HORIZONTAL_PIXELS * y + x] = outColor; - } - } -#else *stride = softwareRenderer->outputBufferStride; *pixels = softwareRenderer->outputBuffer; -#endif } - static void GBVideoSoftwareRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
M src/gb/serialize.csrc/gb/serialize.c

@@ -150,15 +150,15 @@ gb->cpu->irqPending = GBSerializedCpuFlagsGetIrqPending(flags);

gb->doubleSpeed = GBSerializedCpuFlagsGetDoubleSpeed(flags); gb->audio.timingFactor = gb->doubleSpeed + 1; + LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles); + LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent); + gb->timing.root = NULL; + uint32_t when; LOAD_32LE(when, 0, &state->cpu.eiPending); if (GBSerializedCpuFlagsIsEiPending(flags)) { mTimingSchedule(&gb->timing, &gb->eiPending, when); } - - LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles); - LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent); - gb->timing.root = NULL; gb->model = state->model;
A src/gb/sio/printer.c

@@ -0,0 +1,233 @@

+/* 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/gb/sio/printer.h> + +#include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/io.h> + + +static bool GBPrinterInit(struct GBSIODriver* driver); +static void GBPrinterDeinit(struct GBSIODriver* driver); +static void GBPrinterWriteSB(struct GBSIODriver* driver, uint8_t value); +static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value); + +void GBPrinterCreate(struct GBPrinter* printer) { + printer->d.init = GBPrinterInit; + printer->d.deinit = GBPrinterDeinit; + printer->d.writeSB = GBPrinterWriteSB; + printer->d.writeSC = GBPrinterWriteSC; + printer->print = NULL; +} + +bool GBPrinterInit(struct GBSIODriver* driver) { + struct GBPrinter* printer = (struct GBPrinter*) driver; + + printer->checksum = 0; + printer->command = 0; + printer->remainingBytes = 0; + printer->currentIndex = 0; + printer->compression = false; + printer->byte = 0; + printer->next = GB_PRINTER_BYTE_MAGIC_0; + printer->status = 0; + printer->printWait = -1; + + printer->buffer = malloc(GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS / 2); + + return true; +} + +void GBPrinterDeinit(struct GBSIODriver* driver) { + struct GBPrinter* printer = (struct GBPrinter*) driver; + free(printer->buffer); +} + +static void GBPrinterWriteSB(struct GBSIODriver* driver, uint8_t value) { + struct GBPrinter* printer = (struct GBPrinter*) driver; + printer->byte = value; +} + +static void _processByte(struct GBPrinter* printer) { + switch (printer->command) { + case GB_PRINTER_COMMAND_DATA: + if (printer->currentIndex < GB_VIDEO_VERTICAL_PIXELS * GB_VIDEO_HORIZONTAL_PIXELS / 2) { + printer->buffer[printer->currentIndex] = printer->byte; + ++printer->currentIndex; + } + break; + case GB_PRINTER_COMMAND_PRINT: + // TODO + break; + default: + break; + } +} + +static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value) { + struct GBPrinter* printer = (struct GBPrinter*) driver; + if ((value & 0x81) == 0x81) { + switch (printer->next) { + driver->p->pendingSB = 0; + case GB_PRINTER_BYTE_MAGIC_0: + if (printer->byte == 0x88) { + printer->next = GB_PRINTER_BYTE_MAGIC_1; + } else { + printer->next = GB_PRINTER_BYTE_MAGIC_0; + } + break; + case GB_PRINTER_BYTE_MAGIC_1: + if (printer->byte == 0x33) { + printer->next = GB_PRINTER_BYTE_COMMAND; + } else { + printer->next = GB_PRINTER_BYTE_MAGIC_0; + } + break; + case GB_PRINTER_BYTE_COMMAND: + printer->checksum = printer->byte; + printer->command = printer->byte; + printer->next = GB_PRINTER_BYTE_COMPRESSION; + break; + case GB_PRINTER_BYTE_COMPRESSION: + printer->checksum += printer->byte; + printer->compression = printer->byte; + printer->next = GB_PRINTER_BYTE_LENGTH_0; + break; + case GB_PRINTER_BYTE_LENGTH_0: + printer->checksum += printer->byte; + printer->remainingBytes = printer->byte; + printer->next = GB_PRINTER_BYTE_LENGTH_1; + break; + case GB_PRINTER_BYTE_LENGTH_1: + printer->checksum += printer->byte; + printer->remainingBytes |= printer->byte << 8; + if (printer->remainingBytes) { + printer->next = GB_PRINTER_BYTE_DATA; + } else { + printer->next = GB_PRINTER_BYTE_CHECKSUM_0; + } + switch (printer->command) { + case GB_PRINTER_COMMAND_INIT: + printer->currentIndex = 0; + printer->status &= ~(GB_PRINTER_STATUS_PRINT_REQ | GB_PRINTER_STATUS_READY); + break; + default: + break; + } + break; + case GB_PRINTER_BYTE_DATA: + printer->checksum += printer->byte; + if (!printer->compression) { + _processByte(printer); + } else { + printer->next = printer->byte & 0x80 ? GB_PRINTER_BYTE_COMPRESSED_DATUM : GB_PRINTER_BYTE_UNCOMPRESSED_DATA; + printer->remainingCmpBytes = (printer->byte & 0x7F) + 1; + if (printer->byte & 0x80) { + ++printer->remainingCmpBytes; + } + } + --printer->remainingBytes; + if (!printer->remainingBytes) { + printer->next = GB_PRINTER_BYTE_CHECKSUM_0; + } + break; + case GB_PRINTER_BYTE_UNCOMPRESSED_DATA: + printer->checksum += printer->byte; + _processByte(printer); + --printer->remainingCmpBytes; + if (!printer->remainingCmpBytes) { + printer->next = GB_PRINTER_BYTE_DATA; + } + --printer->remainingBytes; + if (!printer->remainingBytes) { + printer->next = GB_PRINTER_BYTE_CHECKSUM_0; + } + break; + case GB_PRINTER_BYTE_COMPRESSED_DATUM: + printer->checksum += printer->byte; + while (printer->remainingCmpBytes) { + _processByte(printer); + --printer->remainingCmpBytes; + } + --printer->remainingBytes; + if (!printer->remainingBytes) { + printer->next = GB_PRINTER_BYTE_CHECKSUM_0; + } else { + printer->next = GB_PRINTER_BYTE_DATA; + } + break; + case GB_PRINTER_BYTE_CHECKSUM_0: + printer->checksum ^= printer->byte; + printer->next = GB_PRINTER_BYTE_CHECKSUM_1; + break; + case GB_PRINTER_BYTE_CHECKSUM_1: + printer->checksum ^= printer->byte << 8; + printer->next = GB_PRINTER_BYTE_KEEPALIVE; + break; + case GB_PRINTER_BYTE_KEEPALIVE: + driver->p->pendingSB = 0x81; + printer->next = GB_PRINTER_BYTE_STATUS; + break; + case GB_PRINTER_BYTE_STATUS: + switch (printer->command) { + case GB_PRINTER_COMMAND_DATA: + if (printer->currentIndex >= 0x280 && !(printer->status & GB_PRINTER_STATUS_CHECKSUM_ERROR)) { + printer->status |= GB_PRINTER_STATUS_READY; + } + break; + case GB_PRINTER_COMMAND_PRINT: + if (printer->currentIndex >= GB_VIDEO_HORIZONTAL_PIXELS * 2) { + printer->printWait = 0; + } + break; + case GB_PRINTER_COMMAND_STATUS: + if (!printer->printWait) { + printer->status &= ~GB_PRINTER_STATUS_READY; + printer->status |= GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ; + if (printer->print) { + size_t y; + for (y = 0; y < printer->currentIndex / (2 * GB_VIDEO_HORIZONTAL_PIXELS); ++y) { + uint8_t lineBuffer[GB_VIDEO_HORIZONTAL_PIXELS * 2]; + uint8_t* buffer = &printer->buffer[sizeof(lineBuffer) * y]; + size_t i; + for (i = 0; i < sizeof(lineBuffer); i += 2) { + uint8_t ilo = buffer[i + 0x0]; + uint8_t ihi = buffer[i + 0x1]; + uint8_t olo = 0; + uint8_t ohi = 0; + olo |= ((ihi & 0x80) >> 0) | ((ilo & 0x80) >> 1); + olo |= ((ihi & 0x40) >> 1) | ((ilo & 0x40) >> 2); + olo |= ((ihi & 0x20) >> 2) | ((ilo & 0x20) >> 3); + olo |= ((ihi & 0x10) >> 3) | ((ilo & 0x10) >> 4); + ohi |= ((ihi & 0x08) << 4) | ((ilo & 0x08) << 3); + ohi |= ((ihi & 0x04) << 3) | ((ilo & 0x04) << 2); + ohi |= ((ihi & 0x02) << 2) | ((ilo & 0x02) << 1); + ohi |= ((ihi & 0x01) << 1) | ((ilo & 0x01) << 0); + lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) & ~1)] = olo; + lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) | 1)] = ohi; + } + memcpy(buffer, lineBuffer, sizeof(lineBuffer)); + } + printer->print(printer, printer->currentIndex * 4 / GB_VIDEO_HORIZONTAL_PIXELS, printer->buffer); + } + } + if (printer->printWait >= 0) { + --printer->printWait; + } + default: + break; + } + driver->p->pendingSB = printer->status; + printer->next = GB_PRINTER_BYTE_MAGIC_0; + break; + } + printer->byte = 0; + } + return value; +} + +void GBPrinterDonePrinting(struct GBPrinter* printer) { + printer->status &= ~GB_PRINTER_STATUS_PRINTING; +}
M src/gb/test/rtc.csrc/gb/test/rtc.c

@@ -14,7 +14,7 @@

struct GBRTCTest { struct mRTCSource d; struct mCore* core; - struct VFile* fakeROM; + struct VFile* fakeSave; time_t nextTime; };

@@ -51,10 +51,13 @@ test->core->init(test->core);

struct VFile* vf = VFileMemChunk(NULL, 2048); GBSynthesizeROM(vf); test->core->loadROM(test->core, vf); - test->core->setRTC(test->core, &test->d); + mCoreSetRTC(test->core, &test->d); + + test->fakeSave = VFileMemChunk(NULL, 0); + test->core->loadSave(test->core, test->fakeSave); + struct GB* gb = test->core->board; - struct GBCartridge* cart = (struct GBCartridge*) &gb->memory.rom[0x100]; - cart->type = 0x0F; + gb->memory.mbcType = GB_MBC3_RTC; *state = test; return 0;

@@ -77,10 +80,12 @@ struct GB* gb = test->core->board;

uint8_t expected[sizeof(gb->memory.rtcRegs)] = { 0, 0, 0, 0, 0 }; assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + assert_int_equal(gb->memory.mbcType, GB_MBC3_RTC); } M_TEST_DEFINE(tickSecond) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 1;

@@ -108,6 +113,7 @@ }

M_TEST_DEFINE(tick30Seconds) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 30;

@@ -142,6 +148,7 @@ }

M_TEST_DEFINE(tickMinute) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 60;

@@ -184,6 +191,7 @@ }

M_TEST_DEFINE(tick90Seconds) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 90;

@@ -240,6 +248,7 @@ }

M_TEST_DEFINE(tick30Minutes) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 1800;

@@ -282,6 +291,7 @@ }

M_TEST_DEFINE(tickHour) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 3600;

@@ -339,6 +349,7 @@ }

M_TEST_DEFINE(tick12Hours) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 3600 * 12;

@@ -407,6 +418,7 @@ }

M_TEST_DEFINE(tickDay) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 3600 * 24;

@@ -505,6 +517,7 @@ }

M_TEST_DEFINE(wideTickDay) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 3600 * 24 * 2001;

@@ -611,6 +624,7 @@ }

M_TEST_DEFINE(rolloverSecond) { struct GBRTCTest* test = *state; + test->nextTime = 0; test->core->reset(test->core); struct GB* gb = test->core->board; test->nextTime = 1;

@@ -672,6 +686,270 @@ _sampleRtc(gb);

assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); } +M_TEST_DEFINE(roundtrip0) { + struct GBRTCTest* test = *state; + test->nextTime = 0; + test->fakeSave->truncate(test->fakeSave, 0); + test->core->reset(test->core); + struct GB* gb = test->core->board; + + uint8_t expected[sizeof(gb->memory.rtcRegs)] = { 0, 0, 0, 0, 0 }; + memset(gb->memory.rtcRegs, 0, sizeof(expected)); + GBMBCRTCWrite(gb); + + GBMBCRTCRead(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + GBMBCRTCWrite(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + GBMBCRTCRead(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3600; + expected[2] = 1; + GBMBCRTCWrite(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + GBMBCRTCWrite(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3600 * 2; + expected[2] = 2; + GBMBCRTCWrite(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); +} + +M_TEST_DEFINE(roundtripSecond) { + struct GBRTCTest* test = *state; + test->nextTime = 0; + test->fakeSave->truncate(test->fakeSave, 0); + test->core->reset(test->core); + struct GB* gb = test->core->board; + + uint8_t expected[sizeof(gb->memory.rtcRegs)] = { 0, 0, 0, 0, 0 }; + memset(gb->memory.rtcRegs, 0, sizeof(expected)); + GBMBCRTCWrite(gb); + + test->nextTime = 1; + GBMBCRTCRead(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + expected[0] = 1; + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + GBMBCRTCWrite(gb); + + test->nextTime = 2; + GBMBCRTCRead(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + _sampleRtcKeepLatch(gb); + expected[0] = 2; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->core->reset(test->core); + expected[0] = 1; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + _sampleRtcKeepLatch(gb); + expected[0] = 2; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->core->reset(test->core); + expected[0] = 1; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + expected[0] = 2; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3; + test->core->reset(test->core); + expected[0] = 1; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + _sampleRtcKeepLatch(gb); + expected[0] = 3; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 1; + test->core->reset(test->core); + expected[0] = 1; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + test->nextTime = 4; + _sampleRtcKeepLatch(gb); + expected[0] = 4; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); +} + +M_TEST_DEFINE(roundtripDay) { + struct GBRTCTest* test = *state; + test->nextTime = 0; + test->fakeSave->truncate(test->fakeSave, 0); + test->core->reset(test->core); + struct GB* gb = test->core->board; + + uint8_t expected[sizeof(gb->memory.rtcRegs)] = { 0, 0, 0, 0, 0 }; + memset(gb->memory.rtcRegs, 0, sizeof(expected)); + GBMBCRTCWrite(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3600 * 24; + GBMBCRTCRead(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 1; + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3600 * 24 * 2; + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + GBMBCRTCWrite(gb); + + test->nextTime = 3600 * 24 * 2; + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3600 * 24 * 3; + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 3; + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3600 * 24 * 2; + test->core->reset(test->core); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 3600 * 24 * 3; + test->core->reset(test->core); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 3; + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); +} + +M_TEST_DEFINE(roundtripHuge) { + struct GBRTCTest* test = *state; + test->nextTime = 1500000000; + test->fakeSave->truncate(test->fakeSave, 0); + test->core->reset(test->core); + struct GB* gb = test->core->board; + + uint8_t expected[sizeof(gb->memory.rtcRegs)] = { 0, 0, 0, 0, 0 }; + memset(gb->memory.rtcRegs, 0, sizeof(expected)); + GBMBCRTCWrite(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 1500000000 + 3600 * 24; + GBMBCRTCRead(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 1; + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 1500000000 + 3600 * 24 * 2; + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + GBMBCRTCWrite(gb); + + test->nextTime = 1500000000 + 3600 * 24 * 2; + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 1500000000 + 3600 * 24 * 3; + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 3; + GBMBCRTCRead(gb); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 1500000000 + 3600 * 24 * 2; + test->core->reset(test->core); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + + test->nextTime = 1500000000 + 3600 * 24 * 3; + test->core->reset(test->core); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 2; + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); + expected[0] = 0; + expected[1] = 0; + expected[2] = 0; + expected[3] = 3; + _sampleRtcKeepLatch(gb); + assert_memory_equal(gb->memory.rtcRegs, expected, sizeof(expected)); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBRTC, cmocka_unit_test(create), cmocka_unit_test(tickSecond),

@@ -683,4 +961,8 @@ cmocka_unit_test(tickHour),

cmocka_unit_test(tick12Hours), cmocka_unit_test(tickDay), cmocka_unit_test(wideTickDay), - cmocka_unit_test(rolloverSecond)) + cmocka_unit_test(rolloverSecond), + cmocka_unit_test(roundtrip0), + cmocka_unit_test(roundtripSecond), + cmocka_unit_test(roundtripDay), + cmocka_unit_test(roundtripHuge))
M src/gb/video.csrc/gb/video.c

@@ -211,7 +211,7 @@ void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) {

struct GBVideo* video = context; _cleanOAM(video, video->ly); video->x = 0; - video->dotClock = timing->masterCycles - cyclesLate; + video->dotClock = mTimingCurrentTime(timing) - cyclesLate; int32_t next = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 6 - (video->p->memory.io[REG_SCX] & 7); video->mode = 3; video->modeEvent.callback = _endMode3;
M src/gba/gba.csrc/gba/gba.c

@@ -527,6 +527,9 @@ ELFClose(elf);

return isGBA; } #endif + if (!vf) { + return false; + } if (vf->seek(vf, GBA_ROM_MAGIC_OFFSET, SEEK_SET) < 0) { return false; }
M src/platform/3ds/main.csrc/platform/3ds/main.c

@@ -27,6 +27,9 @@

#include <3ds.h> #include <3ds/gpu/gx.h> +mLOG_DECLARE_CATEGORY(GUI_3DS); +mLOG_DEFINE_CATEGORY(GUI_3DS, "3DS", "gui.3ds"); + static enum ScreenMode { SM_PA_BOTTOM, SM_AF_BOTTOM,

@@ -58,12 +61,21 @@ #define AUDIO_SAMPLES 384

#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16) #define DSP_BUFFERS 4 -static struct GBA3DSRotationSource { +static struct m3DSRotationSource { struct mRotationSource d; accelVector accel; angularRate gyro; } rotation; +static struct m3DSImageSource { + struct mImageSource d; + Handle handles[2]; + u32 bufferSize; + u32 transferSize; + void* buffer; + unsigned cam; +} camera; + static enum { NO_SOUND, DSP_SUPPORTED,

@@ -128,6 +140,7 @@ if (hasSound == DSP_SUPPORTED) {

ndspExit(); } + camExit(); csndExit(); ptmuExit(); }

@@ -234,6 +247,19 @@ static void _guiFinish(void) {

ctrFlushBatch(); } +static void _resetCamera(struct m3DSImageSource* imageSource) { + if (!imageSource->cam) { + return; + } + CAMU_SetSize(imageSource->cam, SIZE_QCIF, CONTEXT_A); + CAMU_SetOutputFormat(imageSource->cam, OUTPUT_RGB_565, CONTEXT_A); + CAMU_SetFrameRate(imageSource->cam, FRAME_RATE_30); + + CAMU_SetNoiseFilter(imageSource->cam, true); + CAMU_SetAutoExposure(imageSource->cam, false); + CAMU_SetAutoWhiteBalance(imageSource->cam, false); +} + static void _setup(struct mGUIRunner* runner) { bool isNew3DS = false; APT_CheckNew3DS(&isNew3DS);

@@ -243,6 +269,7 @@ mCoreLoadForeignConfig(runner->core, &runner->config);

} runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d); + runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d); if (hasSound != NO_SOUND) { runner->core->setAVStream(runner->core, &stream); }

@@ -284,6 +311,7 @@

static void _gameLoaded(struct mGUIRunner* runner) { switch (runner->core->platform(runner->core)) { #ifdef M_CORE_GBA + // TODO: Move these to callbacks case PLATFORM_GBA: if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) { HIDUSER_EnableAccelerometer();

@@ -334,6 +362,27 @@ }

if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) { darkenMode = mode; } + if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) { + switch (mode) { + case 0: + default: + mode = SELECT_NONE; + break; + case 1: + mode = SELECT_IN1; + break; + case 2: + mode = SELECT_OUT1; + break; + } + if (mode != camera.cam) { + camera.cam = mode; + if (camera.buffer) { + _resetCamera(&camera); + CAMU_Activate(camera.cam); + } + } + } } static void _gameUnloaded(struct mGUIRunner* runner) {

@@ -347,6 +396,7 @@ frameLimiter = true;

switch (runner->core->platform(runner->core)) { #ifdef M_CORE_GBA + // TODO: Move these to callbacks case PLATFORM_GBA: if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) { HIDUSER_DisableAccelerometer();

@@ -600,27 +650,97 @@ return GUI_CURSOR_DOWN;

} static void _sampleRotation(struct mRotationSource* source) { - struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source; + struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source; // Work around ctrulib getting the entries wrong rotation->accel = *(accelVector*) &hidSharedMem[0x48]; rotation->gyro = *(angularRate*) &hidSharedMem[0x5C]; } static int32_t _readTiltX(struct mRotationSource* source) { - struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source; + struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source; return rotation->accel.x << 18L; } static int32_t _readTiltY(struct mRotationSource* source) { - struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source; + struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source; return rotation->accel.y << 18L; } static int32_t _readGyroZ(struct mRotationSource* source) { - struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source; + struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source; return rotation->gyro.y << 18L; // Yes, y } +static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) { + UNUSED(colorFormats); + struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source; + + _resetCamera(imageSource); + + CAMU_SetTrimming(PORT_CAM1, true); + CAMU_SetTrimmingParamsCenter(PORT_CAM1, w, h, 176, 144); + CAMU_GetBufferErrorInterruptEvent(&imageSource->handles[1], PORT_CAM1); + + if (imageSource->bufferSize != w * h * 2 && imageSource->buffer) { + free(imageSource->buffer); + imageSource->buffer = NULL; + } + imageSource->bufferSize = w * h * 2; + if (!imageSource->buffer) { + imageSource->buffer = malloc(imageSource->bufferSize); + } + CAMU_GetMaxBytes(&imageSource->transferSize, w, h); + CAMU_SetTransferBytes(PORT_CAM1, imageSource->transferSize, w, h); + CAMU_Activate(imageSource->cam); + CAMU_ClearBuffer(PORT_CAM1); + CAMU_StartCapture(PORT_CAM1); + CAMU_PlayShutterSound(SHUTTER_SOUND_TYPE_MOVIE); + + if (imageSource->cam) { + CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize); + } +} + +static void _stopRequestImage(struct mImageSource* source) { + struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source; + + free(imageSource->buffer); + imageSource->buffer = NULL; + svcCloseHandle(imageSource->handles[0]); + svcCloseHandle(imageSource->handles[1]); + + CAMU_StopCapture(PORT_CAM1); + CAMU_Activate(SELECT_NONE); +} + + +static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) { + struct m3DSImageSource* imageSource = (struct m3DSImageSource*) source; + + if (!imageSource->cam) { + memset(imageSource->buffer, 0, imageSource->bufferSize); + *buffer = imageSource->buffer; + *stride = 128; + *colorFormat = mCOLOR_RGB565; + return; + } + + s32 i; + svcWaitSynchronizationN(&i, imageSource->handles, 2, false, U64_MAX); + + if (i == 0) { + *buffer = imageSource->buffer; + *stride = 128; + *colorFormat = mCOLOR_RGB565; + } else { + CAMU_ClearBuffer(PORT_CAM1); + CAMU_StartCapture(PORT_CAM1); + } + + svcCloseHandle(imageSource->handles[0]); + CAMU_SetReceiving(&imageSource->handles[0], imageSource->buffer, PORT_CAM1, imageSource->bufferSize, imageSource->transferSize); +} + static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) { UNUSED(stream); if (hasSound == CSND_SUPPORTED) {

@@ -670,6 +790,13 @@ stream.postVideoFrame = 0;

stream.postAudioFrame = 0; stream.postAudioBuffer = _postAudioBuffer; + camera.d.startRequestImage = _startRequestImage; + camera.d.stopRequestImage = _stopRequestImage; + camera.d.requestImage = _requestImage; + camera.buffer = NULL; + camera.bufferSize = 0; + camera.cam = SELECT_IN1; + if (!allocateRomBuffer()) { return 1; }

@@ -677,6 +804,8 @@

aptHook(&cookie, _aptHook, 0); ptmuInit(); + camInit(); + hasSound = NO_SOUND; if (!ndspInit()) { hasSound = DSP_SUPPORTED;

@@ -819,9 +948,21 @@ "Very dark",

"Grayed", }, .nStates = 4 + }, + { + .title = "Camera", + .data = "camera", + .submenu = 0, + .state = 1, + .validStates = (const char*[]) { + "None", + "Inner", + "Outer", + }, + .nStates = 3 } }, - .nConfigExtra = 3, + .nConfigExtra = 4, .setup = _setup, .teardown = 0, .gameLoaded = _gameLoaded,
M src/platform/psp2/CMakeLists.txtsrc/platform/psp2/CMakeLists.txt

@@ -13,7 +13,7 @@

list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(CORE_VFS_SRC ${CORE_VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceAppMgr_stub -lSceCtrl_stub -lScePgf_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceCommonDialog_stub -lSceMotion_stub -lScePhotoExport_stub -lScePower_stub -lSceSysmodule_stub -lSceTouch_stub -l${M_LIBRARY}) +set(OS_LIB -lvita2d -lSceAppMgr_stub -lSceCtrl_stub -lSceAudio_stub -lSceCamera_stub -lSceCommonDialog_stub -lSceDisplay_stub -lSceGxm_stub -lSceMotion_stub -lScePgf_stub -lScePhotoExport_stub -lScePower_stub -lSceSysmodule_stub -lSceTouch_stub -l${M_LIBRARY}) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c)
M src/platform/psp2/main.csrc/platform/psp2/main.c

@@ -105,6 +105,18 @@ "Stretched",

"Fit Aspect Ratio", }, .nStates = 4 + }, + { + .title = "Camera", + .data = "camera", + .submenu = 0, + .state = 1, + .validStates = (const char*[]) { + "None", + "Front", + "Back", + }, + .nStates = 3 } }, .keySources = (struct GUIInputKeys[]) {

@@ -133,7 +145,7 @@ .nKeys = 16

}, { .id = 0 } }, - .nConfigExtra = 1, + .nConfigExtra = 2, .setup = mPSP2Setup, .teardown = mPSP2Teardown, .gameLoaded = mPSP2LoadROM,
M src/platform/psp2/psp2-context.csrc/platform/psp2/psp2-context.c

@@ -27,6 +27,7 @@ #include <mgba-util/vfs.h>

#include <mgba-util/platform/psp2/sce-vfs.h> #include <psp2/audioout.h> +#include <psp2/camera.h> #include <psp2/ctrl.h> #include <psp2/display.h> #include <psp2/gxm.h>

@@ -36,6 +37,7 @@

#include <vita2d.h> #define RUMBLE_PWM 8 +#define CDRAM_ALIGN 0x40000 static enum ScreenMode { SM_BACKDROP,

@@ -49,15 +51,26 @@ static void* outputBuffer;

static vita2d_texture* tex; static vita2d_texture* screenshot; static Thread audioThread; + static struct mSceRotationSource { struct mRotationSource d; struct SceMotionSensorState state; } rotation; + static struct mSceRumble { struct mRumble d; struct CircleBuffer history; int current; } rumble; + +static struct mSceImageSource { + struct mImageSource d; + SceUID memblock; + void* buffer; + unsigned cam; + size_t bufferOffset; +} camera; + bool frameLimiter = true; extern const uint8_t _binary_backdrop_png_start[];

@@ -143,6 +156,79 @@ };

sceCtrlSetActuator(1, &state); } +static void _resetCamera(struct mSceImageSource* imageSource) { + if (!imageSource->cam) { + return; + } + + sceCameraOpen(imageSource->cam - 1, &(SceCameraInfo) { + .size = sizeof(SceCameraInfo), + .format = 5, // SCE_CAMERA_FORMAT_ABGR + .resolution = SCE_CAMERA_RESOLUTION_176_144, + .framerate = SCE_CAMERA_FRAMERATE_30_FPS, + .sizeIBase = 176 * 144 * 4, + .pitch = 0, + .pIBase = imageSource->buffer, + }); + sceCameraStart(imageSource->cam - 1); +} + +static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) { + UNUSED(colorFormats); + struct mSceImageSource* imageSource = (struct mSceImageSource*) source; + + if (!imageSource->buffer) { + imageSource->memblock = sceKernelAllocMemBlock("camera", SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, CDRAM_ALIGN, NULL); + sceKernelGetMemBlockBase(imageSource->memblock, &imageSource->buffer); + } + + if (!imageSource->cam) { + return; + } + + _resetCamera(imageSource); + imageSource->bufferOffset = (176 - w) / 2 + (144 - h) * 176 / 2; + + SceCameraRead read = { + sizeof(SceCameraRead), + 1 + }; + sceCameraRead(imageSource->cam - 1, &read); +} + +static void _stopRequestImage(struct mImageSource* source) { + struct mSceImageSource* imageSource = (struct mSceImageSource*) source; + if (imageSource->cam) { + sceCameraStop(imageSource->cam - 1); + sceCameraClose(imageSource->cam - 1); + } + sceKernelFreeMemBlock(imageSource->memblock); + imageSource->buffer = NULL; +} + + +static void _requestImage(struct mImageSource* source, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) { + struct mSceImageSource* imageSource = (struct mSceImageSource*) source; + + if (!imageSource->cam) { + memset(imageSource->buffer, 0, 176 * 144 * 4); + *buffer = (uint32_t*) imageSource->buffer; + *stride = 176; + *colorFormat = mCOLOR_XBGR8; + return; + } + + *buffer = (uint32_t*) imageSource->buffer + imageSource->bufferOffset; + *stride = 176; + *colorFormat = mCOLOR_XBGR8; + + SceCameraRead read = { + sizeof(SceCameraRead), + 1 + }; + sceCameraRead(imageSource->cam - 1, &read); +} + uint16_t mPSP2PollInput(struct mGUIRunner* runner) { SceCtrlData pad; sceCtrlPeekBufferPositive(0, &pad, 1);

@@ -210,12 +296,22 @@ rumble.d.setRumble = _setRumble;

CircleBufferInit(&rumble.history, RUMBLE_PWM); runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d); + camera.d.startRequestImage = _startRequestImage; + camera.d.stopRequestImage = _stopRequestImage; + camera.d.requestImage = _requestImage; + camera.buffer = NULL; + camera.cam = 1; + runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d); + frameLimiter = true; backdrop = vita2d_load_PNG_buffer(_binary_backdrop_png_start); unsigned mode; if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) { screenMode = mode; + } + if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) { + camera.cam = mode; } }

@@ -312,6 +408,19 @@ void mPSP2Unpaused(struct mGUIRunner* runner) {

unsigned mode; if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode != screenMode) { screenMode = mode; + } + + if (mCoreConfigGetUIntValue(&runner->config, "camera", &mode)) { + if (mode != camera.cam) { + if (camera.buffer) { + sceCameraStop(camera.cam - 1); + sceCameraClose(camera.cam - 1); + } + camera.cam = mode; + if (camera.buffer) { + _resetCamera(&camera); + } + } } }
M src/platform/python/engine.csrc/platform/python/engine.c

@@ -105,6 +105,7 @@ if (!debugger) {

return; } + mPythonSetDebugger(debugger); mPythonDebuggerEntered(reason, info); } #endif
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -67,6 +67,7 @@ AudioProcessor.cpp

CheatsModel.cpp CheatsView.cpp ConfigController.cpp + ColorPicker.cpp CoreManager.cpp CoreController.cpp Display.cpp

@@ -90,6 +91,7 @@ MultiplayerController.cpp

ObjView.cpp OverrideView.cpp PaletteView.cpp + PrinterView.cpp ROMInfo.cpp SavestateButton.cpp SensorView.cpp

@@ -124,6 +126,7 @@ MemoryView.ui

ObjView.ui OverrideView.ui PaletteView.ui + PrinterView.ui ROMInfo.ui SensorView.ui SettingsView.ui

@@ -159,8 +162,11 @@ if(Qt5Multimedia_FOUND)

list(APPEND AUDIO_SRC AudioProcessorQt.cpp AudioDevice.cpp) + list(APPEND SOURCE_FILES + VideoDumper.cpp) if (WIN32 AND QT_STATIC) - list(APPEND QT_LIBRARIES qtaudio_windows strmiids winmm) + list(APPEND QT_LIBRARIES Qt5::QWindowsAudioPlugin Qt5::DSServicePlugin + strmiids winmm mfuuid mfplat mf ksguid dxva2 evr d3d9) endif() list(APPEND QT_LIBRARIES Qt5::Multimedia) list(APPEND QT_DEFINES BUILD_QT_MULTIMEDIA)

@@ -283,10 +289,12 @@ if(APPLE)

if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") get_target_property(QTCOCOA Qt5::QCocoaIntegrationPlugin LOCATION) get_target_property(COREAUDIO Qt5::CoreAudioPlugin LOCATION) + get_target_property(QTAVFSERVICE Qt5::AVFServicePlugin LOCATION) set(BUNDLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.app) target_sources(${BINARY_NAME}-qt PRIVATE "${PLUGINS}") set_source_files_properties("${QTCOCOA}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) + set_source_files_properties("${QTAVFSERVICE}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) install(CODE " include(BundleUtilities) set(BU_CHMOD_BUNDLE_ITEMS ON)

@@ -294,7 +302,7 @@ file(GLOB_RECURSE PLUGINS \"${BUNDLE_PATH}/Contents/PlugIns/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")

fixup_bundle(\"${BUNDLE_PATH}\" \"${PLUGINS}\" \"\") " COMPONENT ${BINARY_NAME}-qt) else() - set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib) + set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib,mediaservice/libqavfcamera.dylib) if(NOT CMAKE_INSTALL_NAME_TOOL EQUAL "install_name_tool") set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -I "${CMAKE_INSTALL_NAME_TOOL}") endif()
A src/platform/qt/ColorPicker.cpp

@@ -0,0 +1,56 @@

+/* 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 "ColorPicker.h" + +#include <QColorDialog> +#include <QEvent> + +using namespace QGBA; + +ColorPicker::ColorPicker() { +} + +ColorPicker::ColorPicker(QWidget* parent, const QColor& defaultColor) + : m_parent(parent) +{ + QPalette palette = parent->palette(); + palette.setColor(parent->backgroundRole(), defaultColor); + parent->setPalette(palette); + parent->installEventFilter(this); +} + +ColorPicker& ColorPicker::operator=(const ColorPicker& other) { + if (m_parent) { + m_parent->removeEventFilter(this); + } + m_parent = other.m_parent; + m_parent->installEventFilter(this); + + return *this; +} + +bool ColorPicker::eventFilter(QObject* obj, QEvent* event) { + if (event->type() != QEvent::MouseButtonRelease) { + return false; + } + int colorId; + if (obj != m_parent) { + return false; + } + + QWidget* swatch = static_cast<QWidget*>(obj); + + QColorDialog* colorPicker = new QColorDialog; + colorPicker->setAttribute(Qt::WA_DeleteOnClose); + colorPicker->open(); + connect(colorPicker, &QColorDialog::colorSelected, [this, swatch](const QColor& color) { + QPalette palette = swatch->palette(); + palette.setColor(swatch->backgroundRole(), color); + swatch->setPalette(palette); + emit colorChanged(color); + }); + return true; +}
A src/platform/qt/ColorPicker.h

@@ -0,0 +1,37 @@

+/* Copyright (c) 2013-2015 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 QGBA_COLOR_PICKER +#define QGBA_COLOR_PICKER + +#include <QObject> + +class QColor; +class QWidget; + +namespace QGBA { + +class ColorPicker : public QObject { +Q_OBJECT + +public: + ColorPicker(); + ColorPicker(QWidget* parent, const QColor& defaultColor); + + ColorPicker& operator=(const ColorPicker&); + +signals: + void colorChanged(const QColor&); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + +private: + QWidget* m_parent = nullptr; +}; + +} + +#endif
M src/platform/qt/CoreController.cppsrc/platform/qt/CoreController.cpp

@@ -47,8 +47,6 @@ m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width());

m_threadContext.startCallback = [](mCoreThread* context) { 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)) { #ifdef M_CORE_GBA

@@ -286,6 +284,9 @@

void CoreController::setInputController(InputController* inputController) { m_inputController = inputController; m_inputController->setPlatform(platform()); + 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) {

@@ -409,7 +410,7 @@ CoreController* controller = static_cast<CoreController*>(context->userData);

if (!controller->m_backupLoadState.isOpen()) { controller->m_backupLoadState = VFileMemChunk(nullptr, 0); } - mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); + mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) { emit controller->frameAvailable(); emit controller->stateLoaded();

@@ -570,6 +571,59 @@ }

Interrupter interrupter(this); GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf); vf->close(vf); +#endif +} + +void CoreController::attachPrinter() { +#ifdef M_CORE_GB + if (platform() != PLATFORM_GB) { + return; + } + GB* gb = static_cast<GB*>(m_threadContext.core->board); + clearMultiplayerController(); + GBPrinterCreate(&m_printer.d); + m_printer.parent = this; + m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) { + QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer); + QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8); + QVector<QRgb> colors; + colors.append(qRgb(0xF8, 0xF8, 0xF8)); + colors.append(qRgb(0xA8, 0xA8, 0xA8)); + colors.append(qRgb(0x50, 0x50, 0x50)); + colors.append(qRgb(0x00, 0x00, 0x00)); + image.setColorTable(colors); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) { + uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4]; + image.setPixel(x + 0, y, (byte & 0xC0) >> 6); + image.setPixel(x + 1, y, (byte & 0x30) >> 4); + image.setPixel(x + 2, y, (byte & 0x0C) >> 2); + image.setPixel(x + 3, y, (byte & 0x03) >> 0); + } + } + QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image)); + }; + GBSIOSetDriver(&gb->sio, &m_printer.d.d); +#endif +} + +void CoreController::detachPrinter() { +#ifdef M_CORE_GB + if (platform() != PLATFORM_GB) { + return; + } + GB* gb = static_cast<GB*>(m_threadContext.core->board); + GBPrinterDonePrinting(&m_printer.d); + GBSIOSetDriver(&gb->sio, nullptr); +#endif +} + +void CoreController::endPrint() { +#ifdef M_CORE_GB + if (platform() != PLATFORM_GB) { + return; + } + GBPrinterDonePrinting(&m_printer.d); #endif }
M src/platform/qt/CoreController.hsrc/platform/qt/CoreController.h

@@ -22,6 +22,10 @@ #include <mgba/core/interface.h>

#include <mgba/core/thread.h> #include <mgba/core/tile-cache.h> +#ifdef M_CORE_GB +#include <mgba/internal/gb/sio/printer.h> +#endif + struct mCore; namespace QGBA {

@@ -119,6 +123,10 @@

void importSharkport(const QString& path); void exportSharkport(const QString& path); + void attachPrinter(); + void detachPrinter(); + void endPrint(); + void setAVStream(mAVStream*); void clearAVStream();

@@ -144,6 +152,8 @@

void unimplementedBiosCall(int); void statusPosted(const QString& message); void logPosted(int level, int category, const QString& log); + + void imagePrinted(const QImage&); private: void updateKeys();

@@ -186,6 +196,13 @@ MultiplayerController* m_multiplayer = nullptr;

mVideoLogContext* m_vl = nullptr; VFile* m_vlVf = nullptr; + +#ifdef M_CORE_GB + struct QGBPrinter { + GBPrinter d; + CoreController* parent; + } m_printer; +#endif }; }
M src/platform/qt/LogController.cppsrc/platform/qt/LogController.cpp

@@ -14,6 +14,7 @@ : QObject(parent)

{ mLogFilterInit(&m_filter); mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB); + mLogFilterSet(&m_filter, "core.status", mLOG_ALL & ~mLOG_DEBUG); m_filter.defaultLevels = levels; if (this != &s_global) {
M src/platform/qt/LogController.hsrc/platform/qt/LogController.h

@@ -63,7 +63,7 @@

static LogController s_global; }; -#define LOG(C, L) (*LogController::global())(_mLOG_CAT_ ## C (), mLOG_ ## L) +#define LOG(C, L) (*LogController::global())(mLOG_ ## L, _mLOG_CAT_ ## C ()) }
M src/platform/qt/MultiplayerController.cppsrc/platform/qt/MultiplayerController.cpp

@@ -233,6 +233,9 @@ return false;

} void MultiplayerController::detachGame(CoreController* controller) { + if (m_players.empty()) { + return; + } mCoreThread* thread = controller->thread(); if (!thread) { return;
M src/platform/qt/OverrideView.cppsrc/platform/qt/OverrideView.cpp

@@ -5,7 +5,6 @@ * 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 "OverrideView.h" -#include <QColorDialog> #include <QPushButton> #include "ConfigController.h"

@@ -44,6 +43,8 @@ s_mbcList.append(GB_MBC3_RTC);

s_mbcList.append(GB_MBC5); s_mbcList.append(GB_MBC5_RUMBLE); s_mbcList.append(GB_MBC7); + s_mbcList.append(GB_POCKETCAM); + s_mbcList.append(GB_TAMA5); s_mbcList.append(GB_HuC3); } if (s_gbModelList.isEmpty()) {

@@ -76,20 +77,16 @@

connect(m_ui.gbModel, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); connect(m_ui.mbc, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); - QPalette palette = m_ui.color0->palette(); - palette.setColor(backgroundRole(), QColor(0xF8, 0xF8, 0xF8)); - m_ui.color0->setPalette(palette); - palette.setColor(backgroundRole(), QColor(0xA8, 0xA8, 0xA8)); - m_ui.color1->setPalette(palette); - palette.setColor(backgroundRole(), QColor(0x50, 0x50, 0x50)); - m_ui.color2->setPalette(palette); - palette.setColor(backgroundRole(), QColor(0x00, 0x00, 0x00)); - m_ui.color3->setPalette(palette); - - m_ui.color0->installEventFilter(this); - m_ui.color1->installEventFilter(this); - m_ui.color2->installEventFilter(this); - m_ui.color3->installEventFilter(this); + m_colorPickers[0] = ColorPicker(m_ui.color0, QColor(0xF8, 0xF8, 0xF8)); + m_colorPickers[1] = ColorPicker(m_ui.color1, QColor(0xA8, 0xA8, 0xA8)); + m_colorPickers[2] = ColorPicker(m_ui.color2, QColor(0x50, 0x50, 0x50)); + m_colorPickers[3] = ColorPicker(m_ui.color3, QColor(0x00, 0x00, 0x00)); + for (int colorId = 0; colorId < 4; ++colorId) { + connect(&m_colorPickers[colorId], &ColorPicker::colorChanged, this, [this, colorId](const QColor& color) { + m_gbColors[colorId] = color.rgb(); + updateOverrides(); + }); + } connect(m_ui.tabWidget, &QTabWidget::currentChanged, this, &OverrideView::updateOverrides); #ifndef M_CORE_GBA

@@ -113,42 +110,6 @@ m_controller->setOverride(std::move(m_override));

} else { m_controller->clearOverride(); } -} - -bool OverrideView::eventFilter(QObject* obj, QEvent* event) { -#ifdef M_CORE_GB - if (event->type() != QEvent::MouseButtonRelease) { - return false; - } - int colorId; - if (obj == m_ui.color0) { - colorId = 0; - } else if (obj == m_ui.color1) { - colorId = 1; - } else if (obj == m_ui.color2) { - colorId = 2; - } else if (obj == m_ui.color3) { - colorId = 3; - } else { - return false; - } - - QWidget* swatch = static_cast<QWidget*>(obj); - - QColorDialog* colorPicker = new QColorDialog; - colorPicker->setAttribute(Qt::WA_DeleteOnClose); - colorPicker->open(); - connect(colorPicker, &QColorDialog::colorSelected, [this, swatch, colorId](const QColor& color) { - QPalette palette = swatch->palette(); - palette.setColor(backgroundRole(), color); - swatch->setPalette(palette); - m_gbColors[colorId] = color.rgb(); - updateOverrides(); - }); - return true; -#else - return false; -#endif } void OverrideView::saveOverride() {
M src/platform/qt/OverrideView.hsrc/platform/qt/OverrideView.h

@@ -14,6 +14,7 @@ #ifdef M_CORE_GB

#include <mgba/gb/interface.h> #endif +#include "ColorPicker.h" #include "Override.h" #include "ui_OverrideView.h"

@@ -42,9 +43,6 @@ void updateOverrides();

void gameStarted(); void gameStopped(); -protected: - bool eventFilter(QObject* obj, QEvent* event) override; - private: Ui::OverrideView m_ui;

@@ -54,6 +52,7 @@ ConfigController* m_config;

#ifdef M_CORE_GB uint32_t m_gbColors[4]{}; + ColorPicker m_colorPickers[4]; static QList<enum GBModel> s_gbModelList; static QList<enum GBMemoryBankControllerType> s_mbcList;
M src/platform/qt/OverrideView.uisrc/platform/qt/OverrideView.ui

@@ -321,6 +321,16 @@ </property>

</item> <item> <property name="text"> + <string>Pocket Cam</string> + </property> + </item> + <item> + <property name="text"> + <string>TAMA5</string> + </property> + </item> + <item> + <property name="text"> <string>HuC-3</string> </property> </item>
A src/platform/qt/PrinterView.cpp

@@ -0,0 +1,74 @@

+/* 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 "PrinterView.h" + +#include "CoreController.h" +#include "GBAApp.h" + +#include <QPainter> + +using namespace QGBA; + +PrinterView::PrinterView(std::shared_ptr<CoreController> controller, QWidget* parent) + : QDialog(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + connect(controller.get(), &CoreController::imagePrinted, this, &PrinterView::printImage); + connect(&m_timer, &QTimer::timeout, this, &PrinterView::printLine); + connect(m_ui.hurry, &QAbstractButton::clicked, this, &PrinterView::printAll); + connect(m_ui.tear, &QAbstractButton::clicked, this, &PrinterView::clear); + connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &PrinterView::save); + m_timer.setInterval(80); + clear(); +} + +PrinterView::~PrinterView() { + m_controller->detachPrinter(); +} + +void PrinterView::save() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Save Printout"), tr("Portable Network Graphics (*.png)")); + if (filename.isNull()) { + return; + } + m_image.save(filename); +} + +void PrinterView::clear() { + m_ui.image->setFixedHeight(0); + m_image = QPixmap(); + m_ui.image->clear(); + m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); +} + +void PrinterView::printImage(const QImage& image) { + QPixmap pixmap(image.width(), image.height() + m_image.height()); + QPainter painter(&pixmap); + painter.drawPixmap(0, 0, m_image); + painter.drawImage(0, m_image.height(), image); + m_image = pixmap; + m_ui.image->setPixmap(m_image); + m_timer.start(); + m_ui.hurry->setEnabled(true); +} + +void PrinterView::printLine() { + m_ui.image->setFixedHeight(m_ui.image->height() + 1); + m_ui.scrollArea->ensureVisible(0, m_ui.image->height(), 0, 0); + if (m_ui.image->height() >= m_image.height()) { + printAll(); + } +} + +void PrinterView::printAll() { + m_timer.stop(); + m_ui.image->setFixedHeight(m_image.height()); + m_controller->endPrint(); + m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true); + m_ui.hurry->setEnabled(false); +}
A src/platform/qt/PrinterView.h

@@ -0,0 +1,50 @@

+/* 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/. */ +#ifndef QGBA_PRINTER_VIEW +#define QGBA_PRINTER_VIEW + +#include <QDialog> +#include <QPixmap> +#include <QTimer> + +#include <memory> + +#include "ui_PrinterView.h" + +namespace QGBA { + +class CoreController; + +class PrinterView : public QDialog { +Q_OBJECT + +public: + PrinterView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); + ~PrinterView(); + +signals: + void donePrinting(); + +public slots: + void save(); + void clear(); + +private slots: + void printImage(const QImage&); + void printLine(); + void printAll(); + +private: + Ui::PrinterView m_ui; + QPixmap m_image; + QTimer m_timer; + + std::shared_ptr<CoreController> m_controller; +}; + +} + +#endif
A src/platform/qt/PrinterView.ui

@@ -0,0 +1,137 @@

+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PrinterView</class> + <widget class="QWidget" name="PrinterView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>241</width> + <height>311</height> + </rect> + </property> + <property name="windowTitle"> + <string>Game Boy Printer</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item alignment="Qt::AlignHCenter"> + <widget class="QScrollArea" name="scrollArea"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="image"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>160</width> + <height>1</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>160</width> + <height>16777215</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter|Qt::AlignTop</set> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="hurry"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hurry up!</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="tear"> + <property name="text"> + <string>Tear off</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Close|QDialogButtonBox::Save</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>PrinterView</receiver> + <slot>close()</slot> + <hints> + <hint type="sourcelabel"> + <x>112</x> + <y>226</y> + </hint> + <hint type="destinationlabel"> + <x>112</x> + <y>123</y> + </hint> + </hints> + </connection> + </connections> +</ui>
M src/platform/qt/SensorView.cppsrc/platform/qt/SensorView.cpp

@@ -68,6 +68,7 @@ });

connect(m_ui.timeFakeEpoch, &QRadioButton::clicked, [controller, this] () { controller->setFakeEpoch(m_ui.time->dateTime()); }); + m_ui.timeButtons->checkedButton()->clicked(); connect(controller.get(), &CoreController::stopping, [this]() { m_controller.reset();
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -130,15 +130,68 @@ m_ui.displayDriver->setCurrentIndex(m_ui.displayDriver->count() - 1);

} #endif + // TODO: Move to reloadConfig() + QVariant cameraDriver = m_controller->getQtOption("cameraDriver"); + m_ui.cameraDriver->addItem(tr("None (Still Image)"), static_cast<int>(InputController::CameraDriver::NONE)); + if (cameraDriver.isNull() || cameraDriver.toInt() == static_cast<int>(InputController::CameraDriver::NONE)) { + m_ui.cameraDriver->setCurrentIndex(m_ui.cameraDriver->count() - 1); + } + +#ifdef BUILD_QT_MULTIMEDIA + m_ui.cameraDriver->addItem(tr("Qt Multimedia"), static_cast<int>(InputController::CameraDriver::QT_MULTIMEDIA)); + if (!cameraDriver.isNull() && cameraDriver.toInt() == static_cast<int>(InputController::CameraDriver::QT_MULTIMEDIA)) { + m_ui.cameraDriver->setCurrentIndex(m_ui.cameraDriver->count() - 1); + } +#endif + +#ifdef M_CORE_GBA connect(m_ui.gbaBiosBrowse, &QPushButton::clicked, [this]() { selectBios(m_ui.gbaBios); }); +#else + m_ui.gbaBiosBrowse->hide(); +#endif + +#ifdef M_CORE_GB connect(m_ui.gbBiosBrowse, &QPushButton::clicked, [this]() { selectBios(m_ui.gbBios); }); connect(m_ui.gbcBiosBrowse, &QPushButton::clicked, [this]() { selectBios(m_ui.gbcBios); }); + + QList<QColor> defaultColors; + defaultColors.append(QColor(0xF8, 0xF8, 0xF8)); + defaultColors.append(QColor(0xA8, 0xA8, 0xA8)); + defaultColors.append(QColor(0x50, 0x50, 0x50)); + defaultColors.append(QColor(0x00, 0x00, 0x00)); + bool ok; + if (m_controller->getOption("gb.pal[0]").toUInt(&ok) || ok) { + defaultColors[0] = QColor::fromRgb(m_controller->getOption("gb.pal[0]").toUInt()); + } + if (m_controller->getOption("gb.pal[1]").toUInt(&ok) || ok) { + defaultColors[1] = QColor::fromRgb(m_controller->getOption("gb.pal[1]").toUInt()); + } + if (m_controller->getOption("gb.pal[2]").toUInt(&ok) || ok) { + defaultColors[2] = QColor::fromRgb(m_controller->getOption("gb.pal[2]").toUInt()); + } + if (m_controller->getOption("gb.pal[3]").toUInt(&ok) || ok) { + defaultColors[3] = QColor::fromRgb(m_controller->getOption("gb.pal[3]").toUInt()); + } + m_colorPickers[0] = ColorPicker(m_ui.color0, defaultColors[0]); + m_colorPickers[1] = ColorPicker(m_ui.color1, defaultColors[1]); + m_colorPickers[2] = ColorPicker(m_ui.color2, defaultColors[2]); + m_colorPickers[3] = ColorPicker(m_ui.color3, defaultColors[3]); + for (int colorId = 0; colorId < 4; ++colorId) { + connect(&m_colorPickers[colorId], &ColorPicker::colorChanged, this, [this, colorId](const QColor& color) { + m_gbColors[colorId] = color.rgb(); + }); + } +#else + m_ui.gbBiosBrowse->hide(); + m_ui.gbcBiosBrowse->hide(); + m_ui.gb->hide(); +#endif connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &SettingsView::updateConfig); connect(m_ui.buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) {

@@ -280,10 +333,23 @@ setShaderSelector(nullptr);

emit displayDriverChanged(); } + QVariant cameraDriver = m_ui.cameraDriver->itemData(m_ui.cameraDriver->currentIndex()); + if (cameraDriver != m_controller->getQtOption("cameraDriver")) { + m_controller->setQtOption("cameraDriver", cameraDriver); + emit cameraDriverChanged(); + } + QLocale language = m_ui.languages->itemData(m_ui.languages->currentIndex()).toLocale(); if (language != m_controller->getQtOption("language").toLocale() && !(language.bcp47Name() == QLocale::system().bcp47Name() && m_controller->getQtOption("language").isNull())) { m_controller->setQtOption("language", language.bcp47Name()); emit languageChanged(); + } + + if (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]) { + m_controller->setOption("gb.pal[0]", m_gbColors[0]); + m_controller->setOption("gb.pal[1]", m_gbColors[1]); + m_controller->setOption("gb.pal[2]", m_gbColors[2]); + m_controller->setOption("gb.pal[3]", m_gbColors[3]); } m_controller->write();
M src/platform/qt/SettingsView.hsrc/platform/qt/SettingsView.h

@@ -8,6 +8,8 @@ #define QGBA_SETTINGS_VIEW

#include <QDialog> +#include "ColorPicker.h" + #include <mgba/core/core.h> #include "ui_SettingsView.h"

@@ -33,6 +35,7 @@ signals:

void biosLoaded(int platform, const QString&); void audioDriverChanged(); void displayDriverChanged(); + void cameraDriverChanged(); void pathsChanged(); void languageChanged(); void libraryCleared();

@@ -50,6 +53,8 @@ InputController* m_input;

ShortcutView* m_shortcutView; ShortcutView* m_keyView; ShaderSelector* m_shader = nullptr; + uint32_t m_gbColors[4]{}; + ColorPicker m_colorPickers[4]; void saveSetting(const char* key, const QAbstractButton*); void saveSetting(const char* key, const QComboBox*);
M src/platform/qt/SettingsView.uisrc/platform/qt/SettingsView.ui

@@ -65,6 +65,11 @@ <property name="text">

<string>Paths</string> </property> </item> + <item> + <property name="text"> + <string>Game Boy</string> + </property> + </item> </widget> </item> <item row="2" column="0" colspan="2">

@@ -1005,6 +1010,158 @@ <item row="11" column="1">

<widget class="QCheckBox" name="patchSameDir"> <property name="text"> <string>Same directory as the ROM</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="gb"> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_29"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Default model</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="gbModel"> + <property name="enabled"> + <bool>false</bool> + </property> + <item> + <property name="text"> + <string>Autodetect</string> + </property> + </item> + <item> + <property name="text"> + <string>Game Boy (DMG)</string> + </property> + </item> + <item> + <property name="text"> + <string>Game Boy Color (CGB)</string> + </property> + </item> + <item> + <property name="text"> + <string>Game Boy Advance (AGB)</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_28"> + <property name="text"> + <string>Default colors</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <widget class="QFrame" name="color0"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="color1"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="color2"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QFrame" name="color3"> + <property name="minimumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0" colspan="2"> + <widget class="Line" name="line_11"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string>Camera driver</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="cameraDriver"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> </widget> </item>
M src/platform/qt/VFileDevice.cppsrc/platform/qt/VFileDevice.cpp

@@ -20,6 +20,9 @@ }

} void VFileDevice::close() { + if (!m_vf) { + return; + } QIODevice::close(); m_vf->close(m_vf); m_vf = nullptr;
A src/platform/qt/VideoDumper.cpp

@@ -0,0 +1,67 @@

+/* 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 "VideoDumper.h" + +#include <QImage> + +using namespace QGBA; + +VideoDumper::VideoDumper(QObject* parent) + : QAbstractVideoSurface(parent) +{ +} + +bool VideoDumper::present(const QVideoFrame& frame) { + QVideoFrame mappedFrame(frame); + if (!mappedFrame.map(QAbstractVideoBuffer::ReadOnly)) { + return false; + } + QVideoFrame::PixelFormat vFormat = mappedFrame.pixelFormat(); + QImage::Format format = QVideoFrame::imageFormatFromPixelFormat(vFormat); + bool swap = false; + if (format == QImage::Format_Invalid) { + vFormat = static_cast<QVideoFrame::PixelFormat>(vFormat - QVideoFrame::Format_BGRA32 + QVideoFrame::Format_ARGB32); + format = QVideoFrame::imageFormatFromPixelFormat(vFormat); + if (format == QImage::Format_ARGB32) { + format = QImage::Format_RGBA8888; + } else if (format == QImage::Format_ARGB32_Premultiplied) { + format = QImage::Format_RGBA8888_Premultiplied; + } + swap = true; + } + uchar* bits = mappedFrame.bits(); + QImage image(bits, mappedFrame.width(), mappedFrame.height(), mappedFrame.bytesPerLine(), format); + if (swap) { + image = image.rgbSwapped(); + } else { +#ifdef Q_OS_WIN + // Qt's DirectShow plug-in is pretty dang buggy + image = image.mirrored(true); +#else + image = image.copy(); // Create a deep copy of the bits +#endif + } + mappedFrame.unmap(); + emit imageAvailable(image); + return true; +} + +QList<QVideoFrame::PixelFormat> VideoDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType) const { + QList<QVideoFrame::PixelFormat> list; + list.append(QVideoFrame::Format_RGB32); + list.append(QVideoFrame::Format_ARGB32); + list.append(QVideoFrame::Format_RGB24); + list.append(QVideoFrame::Format_ARGB32_Premultiplied); + list.append(QVideoFrame::Format_RGB565); + list.append(QVideoFrame::Format_RGB555); + list.append(QVideoFrame::Format_BGR32); + list.append(QVideoFrame::Format_BGRA32); + list.append(QVideoFrame::Format_BGR24); + list.append(QVideoFrame::Format_BGRA32_Premultiplied); + list.append(QVideoFrame::Format_BGR565); + list.append(QVideoFrame::Format_BGR555); + return list; +}
A src/platform/qt/VideoDumper.h

@@ -0,0 +1,27 @@

+/* 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/. */ +#ifndef QGBA_VIDEO_DUMPER +#define QGBA_VIDEO_DUMPER +#include <QAbstractVideoSurface> + +namespace QGBA { + +class VideoDumper : public QAbstractVideoSurface { +Q_OBJECT + +public: + VideoDumper(QObject* parent = nullptr); + + bool present(const QVideoFrame& frame) override; + QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override; + +signals: + void imageAvailable(const QImage& image); +}; + +} + +#endif
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -42,6 +42,7 @@ #include "MemoryView.h"

#include "OverrideView.h" #include "ObjView.h" #include "PaletteView.h" +#include "PrinterView.h" #include "ROMInfo.h" #include "SensorView.h" #include "SettingsView.h"

@@ -153,7 +154,6 @@ }

Window::~Window() { delete m_logView; - delete m_overrideView; #ifdef USE_FFMPEG delete m_videoView;

@@ -378,6 +378,13 @@ widget->setAttribute(Qt::WA_DeleteOnClose);

widget->show(); } +void Window::loadCamImage() { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select image"), tr("Image file (*.png *.gif *.jpg *.jpeg);;All files (*)")); + if (!filename.isEmpty()) { + m_inputController.loadCamImage(filename); + } +} + void Window::importSharkport() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), tr("GameShark saves (*.sps *.xps)")); if (!filename.isEmpty()) {

@@ -401,6 +408,7 @@ }

#endif connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::reloadDisplayDriver); connect(settingsWindow, &SettingsView::audioDriverChanged, this, &Window::reloadAudioDriver); + connect(settingsWindow, &SettingsView::cameraDriverChanged, this, &Window::mustRestart); connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart); connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig); connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear);

@@ -1036,6 +1044,11 @@ m_nonMpActions.append(quickSave);

addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } + fileMenu->addSeparator(); + QAction* camImage = new QAction(tr("Load camera image..."), fileMenu); + connect(camImage, &QAction::triggered, this, &Window::loadCamImage); + addControlledAction(fileMenu, camImage, "loadCamImage"); + #ifdef M_CORE_GBA fileMenu->addSeparator(); QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu);

@@ -1134,6 +1147,7 @@ connect(turbo, &QAction::triggered, [this](bool value) {

m_controller->forceFastForward(value); }); addControlledAction(emulationMenu, turbo, "fastForward"); + m_gameActions.append(turbo); QMenu* ffspeedMenu = emulationMenu->addMenu(tr("Fast forward speed")); ConfigOption* ffspeed = m_config->addOption("fastForwardRatio");

@@ -1149,9 +1163,13 @@ }

m_config->updateOption("fastForwardRatio"); m_inputController.inputIndex()->addItem(qMakePair([this]() { - m_controller->setRewinding(true); + if (m_controller) { + m_controller->setRewinding(true); + } }, [this]() { - m_controller->setRewinding(false); + if (m_controller) { + m_controller->setRewinding(false); + } }), tr("Rewind (held)"), "holdRewind", emulationMenu)->setShortcut(QKeySequence("`")[0]); QAction* rewind = new QAction(tr("Re&wind"), emulationMenu);

@@ -1309,9 +1327,7 @@ fpsTargetOption->addValue(tr("120"), 120, target);

fpsTargetOption->addValue(tr("240"), 240, target); m_config->updateOption("fpsTarget"); -#if defined(USE_PNG) || defined(USE_FFMPEG) || defined(USE_MAGICK) avMenu->addSeparator(); -#endif #ifdef USE_PNG QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);

@@ -1348,6 +1364,18 @@ });

addControlledAction(avMenu, stopVL, "stopVL"); m_gameActions.append(stopVL); +#ifdef M_CORE_GB + QAction* gbPrint = new QAction(tr("Game Boy Printer..."), avMenu); + connect(gbPrint, &QAction::triggered, [this]() { + PrinterView* view = new PrinterView(m_controller); + openView(view); + m_controller->attachPrinter(); + + }); + addControlledAction(avMenu, gbPrint, "gbPrint"); + m_gameActions.append(gbPrint); +#endif + avMenu->addSeparator(); m_videoLayers = avMenu->addMenu(tr("Video layers")); m_audioChannels = avMenu->addMenu(tr("Audio channels"));

@@ -1360,18 +1388,27 @@

QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu); connect(overrides, &QAction::triggered, [this]() { if (!m_overrideView) { - m_overrideView = new OverrideView(m_config); + m_overrideView = std::move(std::make_unique<OverrideView>(m_config)); if (m_controller) { m_overrideView->setController(m_controller); } - connect(this, &Window::shutdown, m_overrideView, &QWidget::close); + connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close); } m_overrideView->show(); }); addControlledAction(toolsMenu, overrides, "overrideWindow"); QAction* sensors = new QAction(tr("Game &Pak sensors..."), toolsMenu); - connect(sensors, &QAction::triggered, openTView<SensorView, InputController*>(&m_inputController)); + connect(sensors, &QAction::triggered, [this]() { + if (!m_sensorView) { + m_sensorView = std::move(std::make_unique<SensorView>(&m_inputController)); + if (m_controller) { + m_sensorView->setController(m_controller); + } + connect(this, &Window::shutdown, m_sensorView.get(), &QWidget::close); + } + m_sensorView->show(); + }); addControlledAction(toolsMenu, sensors, "sensorWindow"); QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu);

@@ -1608,11 +1645,8 @@ connect(m_controller.get(), &CoreController::started, this, &Window::gameStarted);

connect(m_controller.get(), &CoreController::started, &m_inputController, &InputController::suspendScreensaver); connect(m_controller.get(), &CoreController::stopping, this, &Window::gameStopped); { - std::shared_ptr<CoreController> controller(m_controller); - connect(m_controller.get(), &CoreController::stopping, [this, controller]() { - if (m_controller == controller) { - m_controller.reset(); - } + connect(m_controller.get(), &CoreController::stopping, [this]() { + m_controller.reset(); }); } connect(m_controller.get(), &CoreController::stopping, &m_inputController, &InputController::resumeScreensaver);

@@ -1657,15 +1691,27 @@

if (m_gdbController) { m_gdbController->setController(m_controller); } + if (m_console) { m_console->setController(m_controller); } + +#ifdef USE_MAGICK if (m_gifView) { m_gifView->setController(m_controller); } +#endif + +#ifdef USE_FFMPEG if (m_videoView) { m_videoView->setController(m_controller); } +#endif + + if (m_sensorView) { + m_sensorView->setController(m_controller); + } + if (m_overrideView) { m_overrideView->setController(m_controller); }

@@ -1676,6 +1722,7 @@ m_pendingPatch = QString();

} m_controller->start(); + m_controller->loadConfig(m_config); } WindowBackground::WindowBackground(QWidget* parent)
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -34,6 +34,7 @@ class GIFView;

class LibraryController; class LogView; class OverrideView; +class SensorView; class ShaderSelector; class VideoView; class WindowBackground;

@@ -74,6 +75,8 @@ void toggleFullScreen();

void loadConfig(); void reloadConfig(); void saveConfig(); + + void loadCamImage(); void replaceROM();

@@ -194,7 +197,8 @@ QString m_pendingPatch;

bool m_hitUnimplementedBiosCall; - OverrideView* m_overrideView = nullptr; + std::unique_ptr<OverrideView> m_overrideView; + std::unique_ptr<SensorView> m_sensorView; #ifdef USE_FFMPEG VideoView* m_videoView = nullptr;
M src/platform/qt/input/InputController.cppsrc/platform/qt/input/InputController.cpp

@@ -12,12 +12,17 @@ #include "GamepadButtonEvent.h"

#include "InputItem.h" #include "InputModel.h" #include "InputProfile.h" +#include "LogController.h" #include <QApplication> #include <QKeyEvent> #include <QMenu> #include <QTimer> #include <QWidget> +#ifdef BUILD_QT_MULTIMEDIA +#include <QCamera> +#include <QVideoSurfaceFormat> +#endif #include <mgba/core/interface.h> #include <mgba-util/configuration.h>

@@ -57,6 +62,10 @@ });

#endif m_gamepadTimer.setInterval(50); m_gamepadTimer.start(); + +#ifdef BUILD_QT_MULTIMEDIA + connect(&m_videoDumper, &VideoDumper::imageAvailable, this, &InputController::setCamImage); +#endif static QList<QPair<QString, int>> defaultBindings({ qMakePair(QLatin1String("A"), Qt::Key_Z),

@@ -159,6 +168,52 @@ return lux->value;

}; setLuminanceLevel(0); #endif + + m_image.p = this; + m_image.startRequestImage = [](mImageSource* context, unsigned w, unsigned h, int) { + InputControllerImage* image = static_cast<InputControllerImage*>(context); + image->w = w; + image->h = h; + if (image->image.isNull()) { + image->image.load(":/res/no-cam.png"); + } +#ifdef BUILD_QT_MULTIMEDIA + if (image->p->m_config->getQtOption("cameraDriver").toInt() == static_cast<int>(CameraDriver::QT_MULTIMEDIA)) { + QMetaObject::invokeMethod(image->p, "setupCam"); + } +#endif + }; + + m_image.stopRequestImage = [](mImageSource* context) { + InputControllerImage* image = static_cast<InputControllerImage*>(context); +#ifdef BUILD_QT_MULTIMEDIA + QMetaObject::invokeMethod(image->p, "teardownCam"); +#endif + }; + + m_image.requestImage = [](mImageSource* context, const void** buffer, size_t* stride, mColorFormat* format) { + InputControllerImage* image = static_cast<InputControllerImage*>(context); + QSize size; + { + QMutexLocker locker(&image->mutex); + if (image->outOfDate) { + image->resizedImage = image->image.scaled(image->w, image->h, Qt::KeepAspectRatioByExpanding); + image->resizedImage = image->resizedImage.convertToFormat(QImage::Format_RGB16); + image->outOfDate = false; + } + } + size = image->resizedImage.size(); + const uint16_t* bits = reinterpret_cast<const uint16_t*>(image->resizedImage.constBits()); + if (size.width() > image->w) { + bits += (size.width() - image->w) / 2; + } + if (size.height() > image->h) { + bits += ((size.height() - image->h) / 2) * size.width(); + } + *buffer = bits; + *stride = image->resizedImage.bytesPerLine() / sizeof(*bits); + *format = mCOLOR_RGB565; + }; } InputController::~InputController() {

@@ -882,6 +937,20 @@ bindAxis(SDL_BINDING_BUTTON, item->axis(), item->direction(), key);

#endif } +void InputController::loadCamImage(const QString& path) { + QMutexLocker locker(&m_image.mutex); + m_image.image.load(path); + m_image.resizedImage = QImage(); + m_image.outOfDate = true; +} + +void InputController::setCamImage(const QImage& image) { + QMutexLocker locker(&m_image.mutex); + m_image.image = image; + m_image.resizedImage = QImage(); + m_image.outOfDate = true; +} + void InputController::increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); }

@@ -912,3 +981,53 @@ }

emit luminanceValueChanged(m_luxValue); } +void InputController::setupCam() { +#ifdef BUILD_QT_MULTIMEDIA + if (!m_camera) { + m_camera = std::make_unique<QCamera>(); + } + QVideoFrame::PixelFormat format(QVideoFrame::Format_RGB32); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + m_camera->load(); + QCameraViewfinderSettings settings; + QSize size(1920, 1080); + auto cameraRes = m_camera->supportedViewfinderResolutions(settings); + for (auto& cameraSize : cameraRes) { + if (cameraSize.width() < m_image.w || cameraSize.height() < m_image.h) { + continue; + } + if (cameraSize.width() <= size.width() && cameraSize.height() <= size.height()) { + size = cameraSize; + } + } + settings.setResolution(size); + auto cameraFormats = m_camera->supportedViewfinderPixelFormats(settings); + auto goodFormats = m_videoDumper.supportedPixelFormats(); + bool goodFormatFound = false; + for (auto& goodFormat : goodFormats) { + if (cameraFormats.contains(goodFormat)) { + settings.setPixelFormat(goodFormat); + format = goodFormat; + goodFormatFound = true; + break; + } + } + if (!goodFormatFound) { + LOG(QT, WARN) << "Could not find a valid camera format!"; + } + m_camera->setViewfinderSettings(settings); +#endif + m_camera->setCaptureMode(QCamera::CaptureVideo); + m_camera->setViewfinder(&m_videoDumper); + m_camera->start(); +#endif +} + +void InputController::teardownCam() { +#ifdef BUILD_QT_MULTIMEDIA + if (m_camera) { + m_camera->stop(); + m_camera.reset(); + } +#endif +}
M src/platform/qt/input/InputController.hsrc/platform/qt/input/InputController.h

@@ -12,7 +12,9 @@ #include "InputIndex.h"

#include <memory> +#include <QImage> #include <QMap> +#include <QMutex> #include <QObject> #include <QSet> #include <QTimer>

@@ -27,10 +29,15 @@ #ifdef BUILD_SDL

#include "platform/sdl/sdl-events.h" #endif -class QMenu; +#ifdef BUILD_QT_MULTIMEDIA +#include "VideoDumper.h" +#endif struct mRotationSource; struct mRumble; + +class QCamera; +class QMenu; namespace QGBA {

@@ -42,6 +49,13 @@ class InputController : public QObject {

Q_OBJECT public: + enum class CameraDriver : int { + NONE = 0, +#ifdef BUILD_QT_MULTIMEDIA + QT_MULTIMEDIA = 1, +#endif + }; + static const uint32_t KEYBOARD = 0x51545F4B; InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr);

@@ -100,6 +114,7 @@ void releaseFocus(QWidget* focus);

mRumble* rumble(); mRotationSource* rotationSource(); + mImageSource* imageSource() { return &m_image; } GBALuminanceSource* luminance() { return &m_lux; } signals:

@@ -123,8 +138,15 @@ void decreaseLuminanceLevel();

void setLuminanceLevel(int level); void setLuminanceValue(uint8_t value); + void loadCamImage(const QString& path); + void setCamImage(const QImage& image); + protected: bool eventFilter(QObject*, QEvent*) override; + +private slots: + void setupCam(); + void teardownCam(); private: void postPendingEvent(int key);

@@ -146,6 +168,20 @@ uint8_t value;

} m_lux; uint8_t m_luxValue; int m_luxLevel; + + struct InputControllerImage : mImageSource { + InputController* p; + QImage image; + QImage resizedImage; + bool outOfDate; + QMutex mutex; + unsigned w, h; + } m_image; + +#ifdef BUILD_QT_MULTIMEDIA + std::unique_ptr<QCamera> m_camera; + VideoDumper m_videoDumper; +#endif mInputMap m_inputMap; int m_activeKeys;
M src/platform/qt/main.cppsrc/platform/qt/main.cpp

@@ -23,6 +23,7 @@ #ifdef _WIN32

Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #ifdef BUILD_QT_MULTIMEDIA Q_IMPORT_PLUGIN(QWindowsAudioPlugin); +Q_IMPORT_PLUGIN(DSServicePlugin); #endif #endif #endif
M src/platform/qt/resources.qrcsrc/platform/qt/resources.qrc

@@ -3,6 +3,7 @@ <qresource>

<file>../../../res/mgba-1024.png</file> <file>../../../res/keymap.qpic</file> <file>../../../res/patrons.txt</file> + <file>../../../res/no-cam.png</file> <file>input/default-profiles.ini</file> </qresource> </RCC>
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -34,7 +34,7 @@

if(WIN32) list(APPEND SDL_LIBRARY imm32 version winmm) elseif(APPLE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework AppKit -framework AudioUnit -framework Carbon -framework CoreAudio -framework ForceFeedback -framework IOKit") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework AppKit -framework AudioUnit -framework Carbon -framework CoreAudio -framework AudioToolbox -framework ForceFeedback -framework IOKit") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE) endif()