all repos — mgba @ 5122c399f5dbe67a5aad3f2bf0be3012b768022d

mGBA Game Boy Advance Emulator

Merge branch 'master' into feature/input-revamp
Vicki Pfau vi@endrift.com
Sun, 23 Jul 2017 16:06:48 -0700
commit

5122c399f5dbe67a5aad3f2bf0be3012b768022d

parent

298d5e968905c47be7748b6869add035db708dc5

100 files changed, 2684 insertions(+), 2254 deletions(-)

jump to
M .travis-deps.sh.travis-deps.sh

@@ -3,15 +3,22 @@ if [ $TRAVIS_OS_NAME = "osx" ]; then

brew update brew install qt5 ffmpeg imagemagick sdl2 libzip libpng if [ "$CC" == "gcc" ]; then - brew install gcc@4.9 - export CC=gcc-4.9 - export CXX=g++-4.9 + brew install gcc@5 + export CC=gcc-5 + export CXX=g++-5 fi else sudo apt-get clean + sudo add-apt-repository -y ppa:george-edison55/cmake-3.x + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test sudo apt-get update sudo apt-get install -y -q cmake libedit-dev libmagickwand-dev \ libpng-dev libsdl2-dev libzip-dev qtbase5-dev \ libqt5opengl5-dev qtmultimedia5-dev libavcodec-dev \ libavutil-dev libavformat-dev libavresample-dev libswscale-dev + if [ "$CC" == "gcc" ]; then + sudo apt-get install -y -q gcc-5 g++-5 + export CC=gcc-5 + export CXX=g++-5 + fi fi
M .travis.yml.travis.yml

@@ -4,9 +4,6 @@ matrix:

include: - os: linux dist: trusty - compiler: clang - - os: linux - dist: trusty compiler: gcc - os: osx compiler: clang

@@ -16,4 +13,4 @@

before_install: - source ./.travis-deps.sh -script: mkdir build && cd build && cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 .. && make +script: mkdir build && cd build && cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 .. && make -j2
M CHANGESCHANGES

@@ -1,12 +1,19 @@

0.7.0: (Future) +Features: + - ELF 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 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 -0.6.0: (Future) +0.6.0: (2017-07-16) Features: - Library view - Sprite viewer

@@ -181,6 +188,8 @@ - Qt: Fix shader selector on Ubuntu (fixes mgba.io/i/767)

- Core: Fix rewinding getting out of sync (fixes mgba.io/i/791) - Qt: Fix GL-less build - Qt: Fix Software renderer not handling alpha bits properly + - Qt: Fix screen background improperly stretching + - SDL: Fix cheats not loading Misc: - GB Serialize: Add MBC state serialization - GBA Memory: Call crash callbacks regardless of if hard crash is enabled
M CMakeLists.txtCMakeLists.txt

@@ -1,4 +1,4 @@

-cmake_minimum_required(VERSION 2.8.11) +cmake_minimum_required(VERSION 3.1) project(mGBA) set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries") if(NOT MSVC)

@@ -16,6 +16,7 @@ set(USE_PNG ON CACHE BOOL "Whether or not to enable PNG support")

set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable LIBZIP support") set(USE_MAGICK ON CACHE BOOL "Whether or not to enable ImageMagick support") set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support") +set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support") set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core") set(M_CORE_GB ON CACHE BOOL "Build Game Boy core") set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")

@@ -256,7 +257,7 @@ set(USE_DEBUGGERS OFF)

set(USE_SQLITE3 OFF) endif() -if(DEFINED 3DS OR DEFINED WII) +if(DEFINED 3DS) add_definitions(-DFIXED_ROM_BUFFER) endif()

@@ -397,6 +398,7 @@ find_feature(USE_MAGICK "MagickWand")

find_feature(USE_EPOXY "epoxy") find_feature(USE_CMOCKA "cmocka") find_feature(USE_SQLITE3 "sqlite3") +find_feature(USE_ELF "libelf") find_feature(ENABLE_PYTHON "PythonLibs") # Features

@@ -602,6 +604,17 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsqlite3-0")

list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/sqlite3/no-intro.c") endif() +if(USE_ELF) + list(APPEND FEATURES ELF) + include_directories(AFTER ${LIBELF_INCLUDE_DIRS}) + find_file(ELF_REPL_H elf_repl.h PATHS ${LIBELF_INCLUDE_DIRS} NO_CMAKE_FIND_ROOT_PATH) + if (ELF_REPL_H) + add_definitions(-DUSE_ELF_REPL) + endif() + list(APPEND DEPENDENCY_LIB ${LIBELF_LIBRARIES}) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libelfg0") +endif() + if(ENABLE_SCRIPTING) list(APPEND ENABLES SCRIPTING)

@@ -935,6 +948,7 @@ message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")

message(STATUS " ZIP support: ${SUMMARY_ZIP}") message(STATUS " 7-Zip support: ${USE_LZMA}") message(STATUS " SQLite3 game database: ${USE_SQLITE3}") + message(STATUS " ELF loading support: ${USE_ELF}") message(STATUS " OpenGL support: ${SUMMARY_GL}") message(STATUS "Frontends:") message(STATUS " Qt: ${BUILD_QT}")
M README.mdREADME.md

@@ -142,6 +142,7 @@ - ffmpeg or libav: for video recording.

- libzip or zlib: for loading ROMs stored in zip files. - ImageMagick: for GIF recording. - SQLite3: for game databases. +- libelf: for ELF loading. SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first.
A include/mgba-util/elf-read.h

@@ -0,0 +1,51 @@

+/* 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 ELF_READ_H +#define ELF_READ_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#ifdef USE_ELF + +#include <libelf.h> + +#if USE_ELF_REPL +#include <elf_repl.h> +#else +#include <elf.h> +#endif + +#include <mgba-util/vector.h> + +struct ELF; +struct VFile; + +DECLARE_VECTOR(ELFProgramHeaders, Elf32_Phdr); +DECLARE_VECTOR(ELFSectionHeaders, Elf32_Shdr); + +struct ELF* ELFOpen(struct VFile*); +void ELFClose(struct ELF*); + +void* ELFBytes(struct ELF*, size_t* size); + +uint16_t ELFMachine(struct ELF*); +uint32_t ELFEntry(struct ELF*); + +void ELFGetProgramHeaders(struct ELF*, struct ELFProgramHeaders*); + +size_t ELFFindSection(struct ELF*, const char* name); +void ELFGetSectionHeaders(struct ELF*, struct ELFSectionHeaders*); +Elf32_Shdr* ELFGetSectionHeader(struct ELF*, size_t index); + +const char* ELFGetString(struct ELF*, size_t section, size_t string); + +#endif + +CXX_GUARD_END + +#endif
M include/mgba/core/core.hinclude/mgba/core/core.h

@@ -190,6 +190,14 @@ void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config);

void mCoreSetRTC(struct mCore* core, struct mRTCSource* rtc); +void* mCoreGetMemoryBlock(struct mCore* core, uint32_t start, size_t* size); + +#ifdef USE_ELF +struct ELF; +bool mCoreLoadELF(struct mCore* core, struct ELF* elf); +void mCoreLoadELFSymbols(struct mDebuggerSymbols* symbols, struct ELF*); +#endif + CXX_GUARD_END #endif
M include/mgba/core/scripting.hinclude/mgba/core/scripting.h

@@ -24,6 +24,7 @@ void (*deinit)(struct mScriptEngine*);

bool (*isScript)(struct mScriptEngine*, const char* name, struct VFile* vf); bool (*loadScript)(struct mScriptEngine*, const char* name, struct VFile* vf); void (*run)(struct mScriptEngine*); + bool (*lookupSymbol)(struct mScriptEngine*, const char* name, int32_t* out); #ifdef USE_DEBUGGERS void (*debuggerEntered)(struct mScriptEngine*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);

@@ -43,6 +44,8 @@ #endif

void mScriptBridgeRun(struct mScriptBridge*); bool mScriptBridgeLoadScript(struct mScriptBridge*, const char* name); + +bool mScriptBridgeLookupSymbol(struct mScriptBridge*, const char* name, int32_t* out); CXX_GUARD_END
M include/mgba/core/thread.hinclude/mgba/core/thread.h

@@ -103,6 +103,7 @@ void mCoreThreadWaitFromThread(struct mCoreThread* threadContext);

void mCoreThreadStopWaiting(struct mCoreThread* threadContext); void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool); +void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext); struct mCoreThread* mCoreThreadGet(void); struct mLogger* mCoreThreadLogger(void);
M include/mgba/debugger/debugger.hinclude/mgba/debugger/debugger.h

@@ -91,6 +91,7 @@ struct mDebugger {

struct mCPUComponent d; struct mDebuggerPlatform* platform; enum mDebuggerState state; + enum mDebuggerType type; struct mCore* core; struct mScriptBridge* bridge;
M include/mgba/internal/gba/gba.hinclude/mgba/internal/gba/gba.h

@@ -168,6 +168,7 @@ void GBALoadBIOS(struct GBA* gba, struct VFile* vf);

void GBAApplyPatch(struct GBA* gba, struct Patch* patch); bool GBALoadMB(struct GBA* gba, struct VFile* vf); +bool GBALoadNull(struct GBA* gba); bool GBAIsROM(struct VFile* vf); bool GBAIsMB(struct VFile* vf);
M src/core/core.csrc/core/core.c

@@ -8,6 +8,11 @@

#include <mgba/core/log.h> #include <mgba/core/serialize.h> #include <mgba-util/vfs.h> +#include <mgba/internal/debugger/symbols.h> + +#ifdef USE_ELF +#include <mgba-util/elf-read.h> +#endif #ifdef M_CORE_GB #include <mgba/gb/core.h>

@@ -273,3 +278,67 @@ void mCoreSetRTC(struct mCore* core, struct mRTCSource* rtc) {

core->rtc.custom = rtc; core->rtc.override = RTC_CUSTOM_START; } + +void* mCoreGetMemoryBlock(struct mCore* core, uint32_t start, size_t* size) { + const struct mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + size_t i; + for (i = 0; i < nBlocks; ++i) { + if (!(blocks[i].flags & mCORE_MEMORY_MAPPED)) { + continue; + } + if (start < blocks[i].start) { + continue; + } + if (start >= blocks[i].start + blocks[i].size) { + continue; + } + uint8_t* out = core->getMemoryBlock(core, blocks[i].id, size); + out += start - blocks[i].start; + *size -= start - blocks[i].start; + return out; + } + return NULL; +} + +#ifdef USE_ELF +bool mCoreLoadELF(struct mCore* core, struct ELF* elf) { + struct ELFProgramHeaders ph; + ELFProgramHeadersInit(&ph, 0); + ELFGetProgramHeaders(elf, &ph); + size_t i; + for (i = 0; i < ELFProgramHeadersSize(&ph); ++i) { + size_t bsize, esize; + Elf32_Phdr* phdr = ELFProgramHeadersGetPointer(&ph, i); + void* block = mCoreGetMemoryBlock(core, phdr->p_paddr, &bsize); + char* bytes = ELFBytes(elf, &esize); + if (block && bsize >= phdr->p_filesz && esize >= phdr->p_filesz + phdr->p_offset) { + memcpy(block, &bytes[phdr->p_offset], phdr->p_filesz); + } else { + return false; + } + } + return true; +} + +void mCoreLoadELFSymbols(struct mDebuggerSymbols* symbols, struct ELF* elf) { + size_t symIndex = ELFFindSection(elf, ".symtab"); + size_t names = ELFFindSection(elf, ".strtab"); + Elf32_Shdr* symHeader = ELFGetSectionHeader(elf, symIndex); + char* bytes = ELFBytes(elf, NULL); + + Elf32_Sym* syms = (Elf32_Sym*) &bytes[symHeader->sh_offset]; + size_t i; + for (i = 0; i * sizeof(*syms) < symHeader->sh_size; ++i) { + if (!syms[i].st_name || ELF32_ST_TYPE(syms[i].st_info) == STT_FILE) { + continue; + } + const char* name = ELFGetString(elf, names, syms[i].st_name); + if (name[0] == '$') { + continue; + } + mDebuggerSymbolAdd(symbols, name, syms[i].st_value, -1); + } +} + +#endif
M src/core/rewind.csrc/core/rewind.c

@@ -19,6 +19,9 @@ THREAD_ENTRY _rewindThread(void* context);

#endif void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, bool onThread) { + if (context->currentState) { + return; + } mCoreRewindPatchesInit(&context->patchMemory, entries); size_t e; for (e = 0; e < entries; ++e) {

@@ -42,6 +45,9 @@ #endif

} void mCoreRewindContextDeinit(struct mCoreRewindContext* context) { + if (!context->currentState) { + return; + } #ifndef DISABLE_THREADING if (context->onThread) { MutexLock(&context->mutex);

@@ -55,6 +61,8 @@ }

#endif context->previousState->close(context->previousState); context->currentState->close(context->currentState); + context->previousState = NULL; + context->currentState = NULL; size_t s; for (s = 0; s < mCoreRewindPatchesSize(&context->patchMemory); ++s) { deinitPatchFast(mCoreRewindPatchesGetPointer(&context->patchMemory, s));
M src/core/scripting.csrc/core/scripting.c

@@ -19,6 +19,12 @@ struct VFile* vf;

bool success; }; +struct mScriptSymbol { + const char* name; + int32_t* out; + bool success; +}; + static void _seDeinit(void* value) { struct mScriptEngine* se = value; se->deinit(se);

@@ -30,6 +36,15 @@ struct mScriptEngine* se = value;

struct mScriptInfo* si = user; if (!si->success && se->isScript(se, si->name, si->vf)) { si->success = se->loadScript(se, si->name, si->vf); + } +} + +static void _seLookupSymbol(const char* key, void* value, void* user) { + UNUSED(key); + struct mScriptEngine* se = value; + struct mScriptSymbol* si = user; + if (!si->success) { + si->success = se->lookupSymbol(se, si->name, si->out); } }

@@ -111,3 +126,13 @@ HashTableEnumerate(&sb->engines, _seTryLoad, &info);

vf->close(vf); return info.success; } + +bool mScriptBridgeLookupSymbol(struct mScriptBridge* sb, const char* name, int32_t* out) { + struct mScriptSymbol info = { + .name = name, + .out = out, + .success = false + }; + HashTableEnumerate(&sb->engines, _seLookupSymbol, &info); + return info.success; +}
M src/core/thread.csrc/core/thread.c

@@ -167,10 +167,7 @@ mLogFilterInit(threadContext->logger.d.filter);

mLogFilterLoad(threadContext->logger.d.filter, &core->config); } - if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) { - mCoreRewindContextInit(&threadContext->impl->rewind, core->opts.rewindBufferCapacity, true); - threadContext->impl->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0; - } + mCoreThreadRewindParamsChanged(threadContext); _changeState(threadContext->impl, THREAD_RUNNING, true);

@@ -252,7 +249,7 @@ return 0;

} bool mCoreThreadStart(struct mCoreThread* threadContext) { - threadContext->impl = malloc(sizeof(*threadContext->impl)); + threadContext->impl = calloc(sizeof(*threadContext->impl), 1); threadContext->impl->state = THREAD_INITIALIZED; threadContext->logger.p = threadContext; if (!threadContext->logger.d.log) {

@@ -545,6 +542,16 @@ if (!rewinding && threadContext->impl->state == THREAD_REWINDING) {

threadContext->impl->state = THREAD_RUNNING; } MutexUnlock(&threadContext->impl->stateMutex); +} + +void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext) { + struct mCore* core = threadContext->core; + if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) { + mCoreRewindContextInit(&threadContext->impl->rewind, core->opts.rewindBufferCapacity, true); + threadContext->impl->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0; + } else { + mCoreRewindContextDeinit(&threadContext->impl->rewind); + } } void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
M src/debugger/cli-debugger.csrc/debugger/cli-debugger.c

@@ -551,6 +551,11 @@ static void _lookupIdentifier(struct mDebugger* debugger, const char* name, struct CLIDebugVector* dv) {

struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->system) { uint32_t value; +#ifdef ENABLE_SCRIPTING + if (debugger->bridge && mScriptBridgeLookupSymbol(debugger->bridge, name, &dv->intValue)) { + return; + } +#endif if (debugger->core->symbolTable && mDebuggerSymbolLookup(debugger->core->symbolTable, name, &dv->intValue, &dv->segmentValue)) { return; }

@@ -832,6 +837,7 @@ debugger->d.deinit = _cliDebuggerDeinit;

debugger->d.custom = _cliDebuggerCustom; debugger->d.paused = _commandLine; debugger->d.entered = _reportEntry; + debugger->d.type = DEBUGGER_CLI; debugger->system = NULL; debugger->backend = NULL;
M src/debugger/gdb-stub.csrc/debugger/gdb-stub.c

@@ -658,6 +658,7 @@ stub->d.deinit = _gdbStubDeinit;

stub->d.paused = _gdbStubWait; stub->d.entered = _gdbStubEntered; stub->d.custom = _gdbStubPoll; + stub->d.type = DEBUGGER_GDB; stub->untilPoll = GDB_STUB_INTERVAL; stub->lineAck = GDB_ACK_PENDING; stub->shouldBlock = false;
M src/gba/core.csrc/gba/core.c

@@ -8,6 +8,7 @@

#include <mgba/core/core.h> #include <mgba/core/log.h> #include <mgba/internal/arm/debugger/debugger.h> +#include <mgba/internal/debugger/symbols.h> #include <mgba/internal/gba/cheats.h> #include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/io.h>

@@ -20,6 +21,9 @@ #include <mgba/internal/gba/renderers/proxy.h>

#include <mgba/internal/gba/renderers/video-software.h> #include <mgba/internal/gba/savedata.h> #include <mgba/internal/gba/serialize.h> +#ifdef USE_ELF +#include <mgba-util/elf-read.h> +#endif #include <mgba-util/memory.h> #include <mgba-util/patch.h> #include <mgba-util/vfs.h>

@@ -315,6 +319,15 @@ }

} static bool _GBACoreLoadROM(struct mCore* core, struct VFile* vf) { +#ifdef USE_ELF + struct ELF* elf = ELFOpen(vf); + if (elf) { + GBALoadNull(core->board); + bool success = mCoreLoadELF(core, elf); + ELFClose(elf); + return success; + } +#endif if (GBAIsMB(vf)) { return GBALoadMB(core->board, vf); }

@@ -704,7 +717,27 @@ core->debugger = NULL;

} static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) { - // TODO +#ifdef USE_ELF + bool closeAfter = false; + core->symbolTable = mDebuggerSymbolTableCreate(); +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 + if (!vf) { + closeAfter = true; + vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".elf", O_RDONLY); + } +#endif + if (!vf) { + return; + } + struct ELF* elf = ELFOpen(vf); + if (elf) { + mCoreLoadELFSymbols(core->symbolTable, elf); + ELFClose(elf); + } + if (closeAfter) { + vf->close(vf); + } +#endif } #endif
M src/gba/gba.csrc/gba/gba.c

@@ -21,6 +21,10 @@ #include <mgba-util/math.h>

#include <mgba-util/memory.h> #include <mgba-util/vfs.h> +#ifdef USE_ELF +#include <mgba-util/elf-read.h> +#endif + mLOG_DEFINE_CATEGORY(GBA, "GBA", "gba"); mLOG_DEFINE_CATEGORY(GBA_DEBUG, "GBA Debug", "gba.debug");

@@ -203,6 +207,10 @@ gba->idleDetectionFailures = 0;

gba->debug = false; memset(gba->debugString, 0, sizeof(gba->debugString)); + + if (!gba->romVf && gba->memory.rom) { + GBASkipBIOS(gba); + } } void GBASkipBIOS(struct GBA* gba) {

@@ -288,6 +296,29 @@ gba->debugger = NULL;

} #endif +bool GBALoadNull(struct GBA* gba) { + GBAUnloadROM(gba); + gba->romVf = NULL; + gba->pristineRomSize = 0; + gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); +#ifndef FIXED_ROM_BUFFER + gba->memory.rom = anonymousMemoryMap(SIZE_CART0); +#else + gba->memory.rom = romBuffer; +#endif + gba->isPristine = false; + gba->yankedRomSize = 0; + gba->memory.romSize = SIZE_CART0; + gba->memory.romMask = SIZE_CART0 - 1; + gba->memory.mirroring = false; + gba->romCrc32 = 0; + + if (gba->cpu) { + gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); + } + return true; +} + bool GBALoadMB(struct GBA* gba, struct VFile* vf) { GBAUnloadROM(gba); gba->romVf = vf;

@@ -308,6 +339,9 @@ gba->yankedRomSize = 0;

gba->memory.romSize = 0; gba->memory.romMask = 0; gba->romCrc32 = doCrc32(gba->memory.wram, gba->pristineRomSize); + if (gba->cpu && gba->memory.activeRegion == REGION_WORKING_RAM) { + gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); + } return true; }

@@ -351,6 +385,9 @@ gba->memory.rom = newRom;

#endif gba->memory.romSize = SIZE_CART0; gba->isPristine = false; + } + if (gba->cpu && gba->memory.activeRegion >= REGION_CART0) { + gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); } // TODO: error check return true;

@@ -479,6 +516,17 @@ gba->debugFlags = GBADebugFlagsClearSend(gba->debugFlags);

} bool GBAIsROM(struct VFile* vf) { +#ifdef USE_ELF + struct ELF* elf = ELFOpen(vf); + if (elf) { + uint32_t entry = ELFEntry(elf); + bool isGBA = true; + isGBA = isGBA && ELFMachine(elf) == EM_ARM; + isGBA = isGBA && (entry == BASE_CART0 || entry == BASE_WORKING_RAM); + ELFClose(elf); + return isGBA; + } +#endif if (vf->seek(vf, GBA_ROM_MAGIC_OFFSET, SEEK_SET) < 0) { return false; }

@@ -496,6 +544,14 @@ bool GBAIsMB(struct VFile* vf) {

if (!GBAIsROM(vf)) { return false; } +#ifdef USE_ELF + struct ELF* elf = ELFOpen(vf); + if (elf) { + bool isMB = ELFEntry(elf) == BASE_WORKING_RAM; + ELFClose(elf); + return isMB; + } +#endif if (vf->size(vf) > SIZE_WORKING_RAM) { return false; }
M src/gba/memory.csrc/gba/memory.c

@@ -80,6 +80,8 @@ cpu->memory.activeNonseqCycles16 = 0;

gba->memory.biosPrefetch = 0; gba->memory.mirroring = false; + gba->memory.iwram = anonymousMemoryMap(SIZE_WORKING_IRAM); + GBADMAInit(gba); GBAVFameInit(&gba->memory.vfame); }

@@ -107,9 +109,8 @@ gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM);

} if (gba->memory.iwram) { - mappedMemoryFree(gba->memory.iwram, SIZE_WORKING_IRAM); + memset(gba->memory.iwram, 0, SIZE_WORKING_IRAM); } - gba->memory.iwram = anonymousMemoryMap(SIZE_WORKING_IRAM); memset(gba->memory.io, 0, sizeof(gba->memory.io));
M src/platform/python/CMakeLists.txtsrc/platform/python/CMakeLists.txt

@@ -30,7 +30,6 @@ set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/lib.c PROPERTIES GENERATED ON)

file(GLOB PYTHON_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.c) add_library(${BINARY_NAME}-pylib STATIC ${CMAKE_CURRENT_BINARY_DIR}/lib.c ${PYTHON_SRC}) -add_dependencies(${BINARY_NAME}-pylib ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py) set_target_properties(${BINARY_NAME}-pylib PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR};${INCLUDE_DIRECTORIES}") set_target_properties(${BINARY_NAME}-pylib PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") set(PYTHON_LIBRARY ${BINARY_NAME}-pylib PARENT_SCOPE)
M src/platform/python/_builder.hsrc/platform/python/_builder.h

@@ -58,4 +58,5 @@ #include <mgba/internal/gb/renderers/tile-cache.h>

#endif #ifdef USE_DEBUGGERS #include <mgba/debugger/debugger.h> +#include <mgba/internal/debugger/cli-debugger.h> #endif
M src/platform/python/_builder.pysrc/platform/python/_builder.py

@@ -28,6 +28,7 @@ #include <mgba/core/tile-cache.h>

#include <mgba/core/version.h> #include <mgba/debugger/debugger.h> #include <mgba/internal/arm/arm.h> +#include <mgba/internal/debugger/cli-debugger.h> #include <mgba/internal/gba/gba.h> #include <mgba/internal/gba/input.h> #include <mgba/internal/gba/renderers/tile-cache.h>

@@ -70,17 +71,27 @@ lines.append(line)

ffi.embedding_api('\n'.join(lines)) ffi.embedding_init_code(""" - from mgba._pylib import ffi - debugger = None + from mgba._pylib import ffi, lib + symbols = {} + globalSyms = { + 'symbols': symbols + } pendingCode = [] @ffi.def_extern() - def mPythonSetDebugger(_debugger): - from mgba.debugger import NativeDebugger - global debugger - if debugger and debugger._native == _debugger: + def mPythonSetDebugger(debugger): + from mgba.debugger import NativeDebugger, CLIDebugger + oldDebugger = globalSyms.get('debugger') + if oldDebugger and oldDebugger._native == debugger: + return + if oldDebugger and not debugger: + del globalSyms['debugger'] return - debugger = _debugger and NativeDebugger(_debugger) + if debugger.type == lib.DEBUGGER_CLI: + debugger = CLIDebugger(debugger) + else: + debugger = NativeDebugger(debugger) + globalSyms['debugger'] = debugger @ffi.def_extern() def mPythonLoadScript(name, vf):

@@ -99,18 +110,40 @@ @ffi.def_extern()

def mPythonRunPending(): global pendingCode for code in pendingCode: - exec(code) + exec(code, globalSyms, {}) pendingCode = [] @ffi.def_extern() def mPythonDebuggerEntered(reason, info): - global debugger + debugger = globalSyms['debugger'] if not debugger: return if info == ffi.NULL: info = None for cb in debugger._cbs: cb(reason, info) + + @ffi.def_extern() + def mPythonLookupSymbol(name, outptr): + name = ffi.string(name).decode('utf-8') + if name not in symbols: + return False + sym = symbols[name] + val = None + try: + val = int(sym) + except: + try: + val = sym() + except: + pass + if val is None: + return False + try: + outptr[0] = ffi.cast('int32_t', val) + return True + except: + return False """) if __name__ == "__main__":
M src/platform/python/engine.csrc/platform/python/engine.c

@@ -21,6 +21,7 @@ static void mPythonScriptEngineDeinit(struct mScriptEngine*);

static bool mPythonScriptEngineIsScript(struct mScriptEngine*, const char* name, struct VFile* vf); static bool mPythonScriptEngineLoadScript(struct mScriptEngine*, const char* name, struct VFile* vf); static void mPythonScriptEngineRun(struct mScriptEngine*); +static bool mPythonScriptEngineLookupSymbol(struct mScriptEngine*, const char* name, int32_t* out); #ifdef USE_DEBUGGERS static void mPythonScriptDebuggerEntered(struct mScriptEngine*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);

@@ -39,6 +40,7 @@ engine->d.deinit = mPythonScriptEngineDeinit;

engine->d.isScript = mPythonScriptEngineIsScript; engine->d.loadScript = mPythonScriptEngineLoadScript; engine->d.run = mPythonScriptEngineRun; + engine->d.lookupSymbol = mPythonScriptEngineLookupSymbol; #ifdef USE_DEBUGGERS engine->d.debuggerEntered = mPythonScriptDebuggerEntered; #endif

@@ -87,6 +89,11 @@ mPythonSetDebugger(debugger);

} mPythonRunPending(); +} + +bool mPythonScriptEngineLookupSymbol(struct mScriptEngine* se, const char* name, int32_t* out) { + struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se; + return mPythonLookupSymbol(name, out); } #ifdef USE_DEBUGGERS
M src/platform/python/lib.hsrc/platform/python/lib.h

@@ -4,6 +4,7 @@ struct VFile;

extern bool mPythonLoadScript(const char*, struct VFile*); extern void mPythonRunPending(); +extern bool mPythonLookupSymbol(const char* name, int32_t* out); #ifdef USE_DEBUGGERS extern void mPythonSetDebugger(struct mDebugger*);
M src/platform/python/mgba/core.pysrc/platform/python/mgba/core.py

@@ -91,6 +91,12 @@ for cb in self.sleep:

cb() class Core(object): + if hasattr(lib, 'PLATFORM_GBA'): + PLATFORM_GBA = lib.PLATFORM_GBA + + if hasattr(lib, 'PLATFORM_GB'): + PLATFORM_GB = lib.PLATFORM_GB + def __init__(self, native): self._core = native self._wasReset = False

@@ -117,8 +123,10 @@

@classmethod def _detect(cls, core): if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA: + from .gba import GBA return GBA(core) if hasattr(cls, 'PLATFORM_GB') and core.platform(core) == cls.PLATFORM_GB: + from .gb import GB return GB(core) return Core(core)

@@ -253,12 +261,3 @@ raise NotImplementedError

def isPaused(self): raise NotImplementedError - -if hasattr(lib, 'PLATFORM_GBA'): - from .gba import GBA - Core.PLATFORM_GBA = lib.PLATFORM_GBA - -if hasattr(lib, 'PLATFORM_GB'): - from .gb import GB - from .lr35902 import LR35902Core - Core.PLATFORM_GB = lib.PLATFORM_GB
M src/platform/python/mgba/debugger.pysrc/platform/python/mgba/debugger.py

@@ -5,6 +5,8 @@ # 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/. from ._pylib import ffi, lib from .core import IRunner, ICoreOwner, Core +import io +import sys class DebuggerCoreOwner(ICoreOwner): def __init__(self, debugger):

@@ -78,3 +80,22 @@ self._native.platform.clearWatchpoint(self._native.platform, address)

def addCallback(self, cb): self._cbs.append(cb) + +class CLIBackend(object): + def __init__(self, backend): + self.backend = backend + + def write(self, string): + self.backend.printf(string) + +class CLIDebugger(NativeDebugger): + def __init__(self, native): + super(CLIDebugger, self).__init__(native) + self._cli = ffi.cast("struct CLIDebugger*", native) + + def printf(self, message, *args, **kwargs): + message = message.format(*args, **kwargs) + self._cli.backend.printf(ffi.new("char []", b"%s"), ffi.new("char []", message.encode('utf-8'))) + + def installPrint(self): + sys.stdout = CLIBackend(self)
M src/platform/qt/AssetTile.cppsrc/platform/qt/AssetTile.cpp

@@ -5,6 +5,7 @@ * 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 "AssetTile.h" +#include "CoreController.h" #include "GBAApp.h" #include <QFontDatabase>

@@ -39,7 +40,7 @@ m_ui.g->setFont(font);

m_ui.b->setFont(font); } -void AssetTile::setController(GameController* controller) { +void AssetTile::setController(std::shared_ptr<CoreController> controller) { m_tileCache = controller->tileCache(); switch (controller->platform()) { #ifdef M_CORE_GBA

@@ -83,7 +84,7 @@ void AssetTile::selectIndex(int index) {

m_index = index; const uint16_t* data; - mTileCacheSetPalette(m_tileCache.get(), m_paletteSet); + mTileCacheSetPalette(m_tileCache, m_paletteSet); unsigned bpp = 8 << m_tileCache->bpp; int dispIndex = index; int paletteId = m_paletteId;

@@ -98,7 +99,7 @@ }

#endif dispIndex -= m_boundary; } - data = mTileCacheGetTile(m_tileCache.get(), index, paletteId); + data = mTileCacheGetTile(m_tileCache, index, paletteId); m_ui.tileId->setText(QString::number(dispIndex * (1 + m_paletteSet))); m_ui.address->setText(tr("%0%1%2") .arg(m_addressWidth == 4 ? index >= m_boundary : 0)

@@ -112,7 +113,7 @@ }

void AssetTile::selectColor(int index) { const uint16_t* data; - mTileCacheSetPalette(m_tileCache.get(), m_paletteSet); + mTileCacheSetPalette(m_tileCache, m_paletteSet); unsigned bpp = 8 << m_tileCache->bpp; int paletteId = m_paletteId; // XXX: Do this better

@@ -121,7 +122,7 @@ if (m_index >= m_boundary && m_boundaryBase == (BASE_VRAM | 0x10000)) {

paletteId += m_tileCache->count / 2; } #endif - data = mTileCacheGetTile(m_tileCache.get(), m_index, m_paletteId); + data = mTileCacheGetTile(m_tileCache, m_index, m_paletteId); uint16_t color = data[index]; m_ui.color->setColor(0, color); m_ui.color->update();
M src/platform/qt/AssetTile.hsrc/platform/qt/AssetTile.h

@@ -6,20 +6,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_ASSET_TILE #define QGBA_ASSET_TILE -#include "GameController.h" - #include "ui_AssetTile.h" + +#include <memory> #include <mgba/core/tile-cache.h> namespace QGBA { +class CoreController; + class AssetTile : public QGroupBox { Q_OBJECT public: AssetTile(QWidget* parent = nullptr); - void setController(GameController*); + void setController(std::shared_ptr<CoreController>); public slots: void setPalette(int);

@@ -30,7 +32,7 @@

private: Ui::AssetTile m_ui; - std::shared_ptr<mTileCache> m_tileCache; + mTileCache* m_tileCache; int m_paletteId = 0; int m_paletteSet = 0; int m_index = 0;
M src/platform/qt/AssetView.cppsrc/platform/qt/AssetView.cpp

@@ -5,32 +5,32 @@ * 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 "AssetView.h" -#include <QTimer> +#include "CoreController.h" -#include <mgba/core/tile-cache.h> +#include <QTimer> using namespace QGBA; -AssetView::AssetView(GameController* controller, QWidget* parent) +AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent) : QWidget(parent) , m_tileCache(controller->tileCache()) , m_controller(controller) { m_updateTimer.setSingleShot(true); m_updateTimer.setInterval(1); - connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles())); + connect(&m_updateTimer, &QTimer::timeout, this, static_cast<void(AssetView::*)()>(&AssetView::updateTiles)); - connect(m_controller, &GameController::frameAvailable, &m_updateTimer, + connect(controller.get(), &CoreController::frameAvailable, &m_updateTimer, static_cast<void(QTimer::*)()>(&QTimer::start)); - connect(m_controller, &GameController::gameStopped, this, &AssetView::close); - connect(m_controller, &GameController::gameStopped, &m_updateTimer, &QTimer::stop); + connect(controller.get(), &CoreController::stopping, this, &AssetView::close); + connect(controller.get(), &CoreController::stopping, &m_updateTimer, &QTimer::stop); } -void AssetView::updateTiles(bool force) { - if (!m_controller->isLoaded()) { - return; - } +void AssetView::updateTiles() { + updateTiles(false); +} +void AssetView::updateTiles(bool force) { switch (m_controller->platform()) { #ifdef M_CORE_GBA case PLATFORM_GBA:

@@ -56,7 +56,7 @@ updateTiles(true);

} void AssetView::compositeTile(unsigned tileId, void* buffer, size_t stride, size_t x, size_t y, int depth) { - const uint8_t* tile = mTileCacheGetRawTile(m_tileCache.get(), tileId); + const uint8_t* tile = mTileCacheGetRawTile(m_tileCache, tileId); uint8_t* pixels = static_cast<uint8_t*>(buffer); size_t base = stride * y + x; switch (depth) {
M src/platform/qt/AssetView.hsrc/platform/qt/AssetView.h

@@ -6,22 +6,28 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_ASSET_VIEW #define QGBA_ASSET_VIEW +#include <QTimer> #include <QWidget> -#include "GameController.h" +#include <mgba/core/tile-cache.h> + +#include <memory> namespace QGBA { + +class CoreController; class AssetView : public QWidget { Q_OBJECT public: - AssetView(GameController* controller, QWidget* parent = nullptr); + AssetView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); void compositeTile(unsigned tileId, void* image, size_t stride, size_t x, size_t y, int depth = 8); protected slots: - void updateTiles(bool force = false); + void updateTiles(); + void updateTiles(bool force); protected: #ifdef M_CORE_GBA

@@ -34,10 +40,10 @@

void resizeEvent(QResizeEvent*) override; void showEvent(QShowEvent*) override; - const std::shared_ptr<mTileCache> m_tileCache; + mTileCache* const m_tileCache; private: - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; QTimer m_updateTimer; };
M src/platform/qt/AudioProcessor.cppsrc/platform/qt/AudioProcessor.cpp

@@ -47,8 +47,16 @@ : QObject(parent)

{ } -void AudioProcessor::setInput(mCoreThread* input) { +AudioProcessor::~AudioProcessor() { + stop(); +} + +void AudioProcessor::setInput(std::shared_ptr<CoreController> input) { m_context = input; +} + +void AudioProcessor::stop() { + m_context.reset(); } void AudioProcessor::setBufferSamples(int samples) {
M src/platform/qt/AudioProcessor.hsrc/platform/qt/AudioProcessor.h

@@ -7,6 +7,10 @@ #ifndef QGBA_AUDIO_PROCESSOR

#define QGBA_AUDIO_PROCESSOR #include <QObject> +#include <memory> + +#include "CoreController.h" + struct mCoreThread; namespace QGBA {

@@ -28,12 +32,14 @@ static AudioProcessor* create();

static void setDriver(Driver driver) { s_driver = driver; } AudioProcessor(QObject* parent = nullptr); + ~AudioProcessor(); int getBufferSamples() const { return m_samples; } virtual unsigned sampleRate() const = 0; public slots: - virtual void setInput(mCoreThread* input); + virtual void setInput(std::shared_ptr<CoreController>); + virtual void stop(); virtual bool start() = 0; virtual void pause() = 0;

@@ -44,10 +50,10 @@

virtual void requestSampleRate(unsigned) = 0; protected: - mCoreThread* input() { return m_context; } + mCoreThread* input() { return m_context->thread(); } private: - mCoreThread* m_context = nullptr; + std::shared_ptr<CoreController> m_context; int m_samples = 2048; static Driver s_driver; };
M src/platform/qt/AudioProcessorQt.cppsrc/platform/qt/AudioProcessorQt.cpp

@@ -20,16 +20,24 @@ : AudioProcessor(parent)

{ } -void AudioProcessorQt::setInput(mCoreThread* input) { - AudioProcessor::setInput(input); +void AudioProcessorQt::setInput(std::shared_ptr<CoreController> controller) { + AudioProcessor::setInput(controller); if (m_device) { - m_device->setInput(input); + m_device->setInput(input()); if (m_audioOutput) { m_device->setFormat(m_audioOutput->format()); } } } +void AudioProcessorQt::stop() { + if (m_device) { + m_device.reset(); + } + pause(); + AudioProcessor::stop(); +} + bool AudioProcessorQt::start() { if (!input()) { LOG(QT, WARN) << tr("Can't start an audio processor without input");

@@ -37,7 +45,7 @@ return false;

} if (!m_device) { - m_device = new AudioDevice(this); + m_device = std::make_unique<AudioDevice>(this); } if (!m_audioOutput) {

@@ -56,7 +64,7 @@

m_device->setInput(input()); m_device->setFormat(m_audioOutput->format()); - m_audioOutput->start(m_device); + m_audioOutput->start(m_device.get()); return m_audioOutput->state() == QAudio::ActiveState; }
M src/platform/qt/AudioProcessorQt.hsrc/platform/qt/AudioProcessorQt.h

@@ -22,7 +22,8 @@

virtual unsigned sampleRate() const override; public slots: - virtual void setInput(mCoreThread* input) override; + virtual void setInput(std::shared_ptr<CoreController> input) override; + virtual void stop() override; virtual bool start() override; virtual void pause() override;

@@ -33,7 +34,7 @@ virtual void requestSampleRate(unsigned) override;

private: QAudioOutput* m_audioOutput = nullptr; - AudioDevice* m_device = nullptr; + std::unique_ptr<AudioDevice> m_device; unsigned m_sampleRate = 44100; };
M src/platform/qt/AudioProcessorSDL.cppsrc/platform/qt/AudioProcessorSDL.cpp

@@ -16,16 +16,17 @@ : AudioProcessor(parent)

{ } -AudioProcessorSDL::~AudioProcessorSDL() { - mSDLDeinitAudio(&m_audio); +void AudioProcessorSDL::setInput(std::shared_ptr<CoreController> controller) { + AudioProcessor::setInput(controller); + if (m_audio.core && input()->core != m_audio.core) { + mSDLDeinitAudio(&m_audio); + mSDLInitAudio(&m_audio, input()); + } } -void AudioProcessorSDL::setInput(mCoreThread* input) { - AudioProcessor::setInput(input); - if (m_audio.core && input->core != m_audio.core) { - mSDLDeinitAudio(&m_audio); - mSDLInitAudio(&m_audio, input); - } +void AudioProcessorSDL::stop() { + mSDLDeinitAudio(&m_audio); + AudioProcessor::stop(); } bool AudioProcessorSDL::start() {

@@ -51,10 +52,12 @@ }

void AudioProcessorSDL::setBufferSamples(int samples) { AudioProcessor::setBufferSamples(samples); - m_audio.samples = samples; - if (m_audio.core) { - mSDLDeinitAudio(&m_audio); - mSDLInitAudio(&m_audio, input()); + if (m_audio.samples != samples) { + m_audio.samples = samples; + if (m_audio.core) { + mSDLDeinitAudio(&m_audio); + mSDLInitAudio(&m_audio, input()); + } } }

@@ -62,10 +65,12 @@ void AudioProcessorSDL::inputParametersChanged() {

} void AudioProcessorSDL::requestSampleRate(unsigned rate) { - m_audio.sampleRate = rate; - if (m_audio.core) { - mSDLDeinitAudio(&m_audio); - mSDLInitAudio(&m_audio, input()); + if (m_audio.sampleRate != rate) { + m_audio.sampleRate = rate; + if (m_audio.core) { + mSDLDeinitAudio(&m_audio); + mSDLInitAudio(&m_audio, input()); + } } }
M src/platform/qt/AudioProcessorSDL.hsrc/platform/qt/AudioProcessorSDL.h

@@ -18,12 +18,12 @@ Q_OBJECT

public: AudioProcessorSDL(QObject* parent = nullptr); - ~AudioProcessorSDL(); virtual unsigned sampleRate() const override; public slots: - virtual void setInput(mCoreThread* input) override; + virtual void setInput(std::shared_ptr<CoreController> input) override; + virtual void stop() override; virtual bool start() override; virtual void pause() override;
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -1,6 +1,6 @@

-if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -endif() +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7")

@@ -67,12 +67,13 @@ AudioProcessor.cpp

CheatsModel.cpp CheatsView.cpp ConfigController.cpp + CoreManager.cpp + CoreController.cpp Display.cpp DisplayGL.cpp DisplayQt.cpp GBAApp.cpp GIFView.cpp - GameController.cpp GamepadAxisEvent.cpp GamepadButtonEvent.cpp GamepadHatEvent.cpp

@@ -282,7 +283,7 @@ 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(BUNDLE_PATH ${BINARY_NAME}-qt 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)
M src/platform/qt/CheatsView.cppsrc/platform/qt/CheatsView.cpp

@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "CheatsView.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include <QClipboard> #include <QPushButton>

@@ -21,7 +21,7 @@ #endif

using namespace QGBA; -CheatsView::CheatsView(GameController* controller, QWidget* parent) +CheatsView::CheatsView(std::shared_ptr<CoreController> controller, QWidget* parent) : QWidget(parent) , m_controller(controller) , m_model(controller->cheatDevice())

@@ -35,8 +35,8 @@ connect(m_ui.load, &QPushButton::clicked, this, &CheatsView::load);

connect(m_ui.save, &QPushButton::clicked, this, &CheatsView::save); connect(m_ui.addSet, &QPushButton::clicked, this, &CheatsView::addSet); connect(m_ui.remove, &QPushButton::clicked, this, &CheatsView::removeSet); - connect(controller, &GameController::gameStopped, this, &CheatsView::close); - connect(controller, &GameController::stateLoaded, &m_model, &CheatsModel::invalidated); + connect(controller.get(), &CoreController::stopping, this, &CheatsView::close); + connect(controller.get(), &CoreController::stateLoaded, &m_model, &CheatsModel::invalidated); QPushButton* add; switch (controller->platform()) {

@@ -123,7 +123,7 @@ }

} void CheatsView::addSet() { - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); mCheatSet* set = m_controller->cheatDevice()->createSet(m_controller->cheatDevice(), nullptr); m_model.addSet(set); }

@@ -134,7 +134,7 @@ QModelIndexList selection = m_ui.cheatList->selectionModel()->selectedIndexes();

if (selection.count() < 1) { return; } - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); for (const QModelIndex& index : selection) { m_model.removeAt(selection[0]); }

@@ -154,7 +154,7 @@

if (!set) { return; } - m_controller->threadInterrupt(); + CoreController::Interrupter interrupter(m_controller); if (selection.count() == 0) { m_model.addSet(set); index = m_model.index(m_model.rowCount() - 1, 0, QModelIndex());

@@ -167,6 +167,5 @@ mCheatAddLine(set, string.toUtf8().constData(), codeType);

m_model.endAppendRow(); } set->refresh(set, m_controller->cheatDevice()); - m_controller->threadContinue(); m_ui.codeEntry->clear(); }
M src/platform/qt/CheatsView.hsrc/platform/qt/CheatsView.h

@@ -9,6 +9,7 @@

#include <QWidget> #include <functional> +#include <memory> #include "CheatsModel.h"

@@ -18,13 +19,13 @@ struct mCheatDevice;

namespace QGBA { -class GameController; +class CoreController; class CheatsView : public QWidget { Q_OBJECT public: - CheatsView(GameController* controller, QWidget* parent = nullptr); + CheatsView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); virtual bool eventFilter(QObject*, QEvent*) override;

@@ -38,7 +39,7 @@ private:

void enterCheat(int codeType); Ui::CheatsView m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; CheatsModel m_model; };
M src/platform/qt/ConfigController.cppsrc/platform/qt/ConfigController.cpp

@@ -5,7 +5,7 @@ * 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 "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #include <QAction> #include <QDir>

@@ -98,8 +98,8 @@ m_settings = new QSettings(fileName, QSettings::IniFormat, this);

mCoreConfigInit(&m_config, PORT); - m_opts.audioSync = GameController::AUDIO_SYNC; - m_opts.videoSync = GameController::VIDEO_SYNC; + m_opts.audioSync = CoreController::AUDIO_SYNC; + m_opts.videoSync = CoreController::VIDEO_SYNC; m_opts.fpsTarget = 60; m_opts.audioBuffers = 1536; m_opts.sampleRate = 44100;
A src/platform/qt/CoreController.cpp

@@ -0,0 +1,694 @@

+/* 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 "CoreController.h" + +#include "ConfigController.h" +#include "InputController.h" +#include "LogController.h" +#include "MultiplayerController.h" +#include "Override.h" + +#include <QDateTime> +#include <QMutexLocker> + +#include <mgba/core/serialize.h> +#include <mgba/feature/video-logger.h> +#ifdef M_CORE_GBA +#include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/renderers/tile-cache.h> +#include <mgba/internal/gba/sharkport.h> +#endif +#ifdef M_CORE_GB +#include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/renderers/tile-cache.h> +#endif +#include <mgba-util/vfs.h> + +using namespace QGBA; + + +CoreController::CoreController(mCore* core, QObject* parent) + : QObject(parent) + , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC) + , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) +{ + m_threadContext.core = core; + m_threadContext.userData = this; + + QSize size = screenDimensions(); + m_buffers[0].resize(size.width() * size.height() * sizeof(color_t)); + m_buffers[1].resize(size.width() * size.height() * sizeof(color_t)); + m_activeBuffer = &m_buffers[0]; + + 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 + case PLATFORM_GBA: + context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, controller->m_inputController->luminance()); + break; +#endif + default: + break; + } + + if (controller->m_override) { + controller->m_override->identify(context->core); + controller->m_override->apply(context->core); + } + + if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) { + mCoreDeleteState(context->core, 0); + } + + if (controller->m_multiplayer) { + controller->m_multiplayer->attachGame(controller); + } + + QMetaObject::invokeMethod(controller, "started"); + }; + + m_threadContext.resetCallback = [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + for (auto action : controller->m_resetActions) { + action(); + } + controller->m_resetActions.clear(); + + controller->m_activeBuffer->fill(0xFF); + controller->finishFrame(); + }; + + m_threadContext.frameCallback = [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + + controller->finishFrame(); + }; + + m_threadContext.cleanCallback = [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + + controller->clearMultiplayerController(); + QMetaObject::invokeMethod(controller, "stopping"); + }; + + m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) { + mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger); + mCoreThread* context = logContext->p; + + static const char* savestateMessage = "State %i loaded"; + static const char* savestateFailedMessage = "State %i failed to load"; + static int biosCat = -1; + static int statusCat = -1; + if (!context) { + return; + } + CoreController* controller = static_cast<CoreController*>(context->userData); + QString message; + if (biosCat < 0) { + biosCat = mLogCategoryById("gba.bios"); + } + if (statusCat < 0) { + statusCat = mLogCategoryById("core.status"); + } +#ifdef M_CORE_GBA + if (level == mLOG_STUB && category == biosCat) { + va_list argc; + va_copy(argc, args); + int immediate = va_arg(argc, int); + va_end(argc); + QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate)); + } else +#endif + if (category == statusCat) { + // Slot 0 is reserved for suspend points + if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { + va_list argc; + va_copy(argc, args); + int slot = va_arg(argc, int); + va_end(argc); + if (slot == 0) { + format = "Loaded suspend state"; + } + } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) { + va_list argc; + va_copy(argc, args); + int slot = va_arg(argc, int); + va_end(argc); + if (slot == 0) { + return; + } + } + message = QString().vsprintf(format, args); + QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message)); + } + if (level == mLOG_FATAL) { + mCoreThreadMarkCrashed(controller->thread()); + QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, QString().vsprintf(format, args))); + } + message = QString().vsprintf(format, args); + QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message)); + }; +} + +CoreController::~CoreController() { + endVideoLog(); + stop(); + disconnect(); + + if (m_tileCache) { + mTileCacheDeinit(m_tileCache.get()); + m_tileCache.reset(); + } + + mCoreThreadJoin(&m_threadContext); + + mCoreConfigDeinit(&m_threadContext.core->config); + m_threadContext.core->deinit(m_threadContext.core); +} + +color_t* CoreController::drawContext() { + QMutexLocker locker(&m_mutex); + if (!m_completeBuffer) { + return nullptr; + } + return reinterpret_cast<color_t*>(m_completeBuffer->data()); +} + +bool CoreController::isPaused() { + return mCoreThreadIsPaused(&m_threadContext); +} + +mPlatform CoreController::platform() const { + return m_threadContext.core->platform(m_threadContext.core); +} + +QSize CoreController::screenDimensions() const { + unsigned width, height; + m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + + return QSize(width, height); +} + +void CoreController::loadConfig(ConfigController* config) { + Interrupter interrupter(this); + m_loadStateFlags = config->getOption("loadStateExtdata").toInt(); + m_saveStateFlags = config->getOption("saveStateExtdata").toInt(); + m_fastForwardRatio = config->getOption("fastForwardRatio").toFloat(); + m_videoSync = config->getOption("videoSync").toInt(); + m_audioSync = config->getOption("audioSync").toInt(); + m_fpsTarget = config->getOption("fpsTarget").toFloat(); + updateFastForward(); + mCoreLoadForeignConfig(m_threadContext.core, config->config()); + mCoreThreadRewindParamsChanged(&m_threadContext); +} + +#ifdef USE_DEBUGGERS +void CoreController::setDebugger(mDebugger* debugger) { + Interrupter interrupter(this); + if (debugger) { + mDebuggerAttach(debugger, m_threadContext.core); + } else { + m_threadContext.core->detachDebugger(m_threadContext.core); + } +} +#endif + +void CoreController::setMultiplayerController(MultiplayerController* controller) { + if (controller == m_multiplayer) { + return; + } + clearMultiplayerController(); + m_multiplayer = controller; + if (!mCoreThreadHasStarted(&m_threadContext)) { + return; + } + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) { + CoreController* controller = static_cast<CoreController*>(thread->userData); + controller->m_multiplayer->attachGame(controller); + }); +} + +void CoreController::clearMultiplayerController() { + if (!m_multiplayer) { + return; + } + m_multiplayer->detachGame(this); + m_multiplayer = nullptr; +} + +mTileCache* CoreController::tileCache() { + if (m_tileCache) { + return m_tileCache.get(); + } + Interrupter interrupter(this); + switch (platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: { + GBA* gba = static_cast<GBA*>(m_threadContext.core->board); + m_tileCache = std::make_unique<mTileCache>(); + GBAVideoTileCacheInit(m_tileCache.get()); + GBAVideoTileCacheAssociate(m_tileCache.get(), &gba->video); + mTileCacheSetPalette(m_tileCache.get(), 0); + break; + } +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: { + GB* gb = static_cast<GB*>(m_threadContext.core->board); + m_tileCache = std::make_unique<mTileCache>(); + GBVideoTileCacheInit(m_tileCache.get()); + GBVideoTileCacheAssociate(m_tileCache.get(), &gb->video); + mTileCacheSetPalette(m_tileCache.get(), 0); + break; + } +#endif + default: + return nullptr; + } + return m_tileCache.get(); +} + +void CoreController::setOverride(std::unique_ptr<Override> override) { + Interrupter interrupter(this); + m_override = std::move(override); + m_override->identify(m_threadContext.core); +} + +void CoreController::setInputController(InputController* inputController) { + m_inputController = inputController; + m_inputController->setPlatform(platform()); +} + +void CoreController::setLogger(LogController* logger) { + disconnect(m_log); + m_log = logger; + m_threadContext.logger.d.filter = logger->filter(); + connect(this, &CoreController::logPosted, m_log, &LogController::postLog); +} + +void CoreController::start() { + if (!m_patched) { + mCoreAutoloadPatch(m_threadContext.core); + } + if (!mCoreThreadStart(&m_threadContext)) { + emit failed(); + emit stopping(); + } +} + +void CoreController::stop() { +#ifdef USE_DEBUGGERS + setDebugger(nullptr); +#endif + setPaused(false); + mCoreThreadEnd(&m_threadContext); + emit stopping(); +} + +void CoreController::reset() { + bool wasPaused = isPaused(); + setPaused(false); + Interrupter interrupter(this); + mCoreThreadReset(&m_threadContext); + if (wasPaused) { + setPaused(true); + } +} + +void CoreController::setPaused(bool paused) { + if (paused == isPaused()) { + return; + } + if (paused) { + QMutexLocker locker(&m_mutex); + m_frameActions.append([this]() { + mCoreThreadPauseFromThread(&m_threadContext); + QMetaObject::invokeMethod(this, "paused"); + }); + } else { + mCoreThreadUnpause(&m_threadContext); + emit unpaused(); + } +} + +void CoreController::frameAdvance() { + QMutexLocker locker(&m_mutex); + m_frameActions.append([this]() { + mCoreThreadPauseFromThread(&m_threadContext); + }); + setPaused(false); +} + +void CoreController::setSync(bool sync) { + if (sync) { + m_threadContext.impl->sync.audioWait = m_audioSync; + m_threadContext.impl->sync.videoFrameWait = m_videoSync; + } else { + m_threadContext.impl->sync.audioWait = false; + m_threadContext.impl->sync.videoFrameWait = false; + } +} + +void CoreController::setRewinding(bool rewind) { + if (!m_threadContext.core->opts.rewindEnable) { + return; + } + if (rewind && m_multiplayer && m_multiplayer->attached() > 1) { + return; + } + + if (rewind && isPaused()) { + setPaused(false); + // TODO: restore autopausing + } + mCoreThreadSetRewinding(&m_threadContext, rewind); +} + +void CoreController::rewind(int states) { + { + Interrupter interrupter(this); + if (!states) { + states = INT_MAX; + } + for (int i = 0; i < states; ++i) { + if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) { + break; + } + } + } + emit frameAvailable(); + emit rewound(); +} + +void CoreController::setFastForward(bool enable) { + m_fastForward = enable; + updateFastForward(); +} + +void CoreController::forceFastForward(bool enable) { + m_fastForwardForced = enable; + updateFastForward(); +} + +void CoreController::loadState(int slot) { + if (slot > 0 && slot != m_stateSlot) { + m_stateSlot = slot; + m_backupSaveState.clear(); + } + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + 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); + if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) { + emit controller->frameAvailable(); + emit controller->stateLoaded(); + } + }); +} + +void CoreController::saveState(int slot) { + if (slot > 0) { + m_stateSlot = slot; + } + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false); + if (vf) { + controller->m_backupSaveState.resize(vf->size(vf)); + vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); + vf->close(vf); + } + mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags); + }); +} + +void CoreController::loadBackupState() { + if (!m_backupLoadState.isOpen()) { + return; + } + + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + controller->m_backupLoadState.seek(0); + if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) { + mLOG(STATUS, INFO, "Undid state load"); + controller->frameAvailable(); + controller->stateLoaded(); + } + controller->m_backupLoadState.close(); + }); +} + +void CoreController::saveBackupState() { + if (m_backupSaveState.isEmpty()) { + return; + } + + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + CoreController* controller = static_cast<CoreController*>(context->userData); + VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true); + if (vf) { + vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size()); + vf->close(vf); + mLOG(STATUS, INFO, "Undid state save"); + } + controller->m_backupSaveState.clear(); + }); +} + +void CoreController::loadSave(const QString& path, bool temporary) { + m_resetActions.append([this, path, temporary]() { + VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path); + return; + } + + if (temporary) { + m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + } else { + m_threadContext.core->loadSave(m_threadContext.core, vf); + } + }); + reset(); +} + +void CoreController::loadPatch(const QString& patchPath) { + Interrupter interrupter(this); + VFile* patch = VFileDevice::open(patchPath, O_RDONLY); + if (patch) { + m_threadContext.core->loadPatch(m_threadContext.core, patch); + m_patched = true; + } + patch->close(patch); + if (mCoreThreadHasStarted(&m_threadContext)) { + reset(); + } +} + +void CoreController::replaceGame(const QString& path) { + QFileInfo info(path); + if (!info.isReadable()) { + LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + return; + } + QString fname = info.canonicalFilePath(); + Interrupter interrupter(this); + mDirectorySetDetachBase(&m_threadContext.core->dirs); + mCoreLoadFile(m_threadContext.core, fname.toLocal8Bit().constData()); +} + +void CoreController::yankPak() { +#ifdef M_CORE_GBA + if (platform() != PLATFORM_GBA) { + return; + } + Interrupter interrupter(this); + GBAYankROM(static_cast<GBA*>(m_threadContext.core->board)); +#endif +} + +#ifdef USE_PNG +void CoreController::screenshot() { + mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { + mCoreTakeScreenshot(context->core); + }); +} +#endif + +void CoreController::setRealTime() { + m_threadContext.core->rtc.override = RTC_NO_OVERRIDE; +} + +void CoreController::setFixedTime(const QDateTime& time) { + m_threadContext.core->rtc.override = RTC_FIXED; + m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); +} + +void CoreController::setFakeEpoch(const QDateTime& time) { + m_threadContext.core->rtc.override = RTC_FAKE_EPOCH; + m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); +} + +void CoreController::importSharkport(const QString& path) { +#ifdef M_CORE_GBA + if (platform() != PLATFORM_GBA) { + return; + } + VFile* vf = VFileDevice::open(path, O_RDONLY); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path); + return; + } + Interrupter interrupter(this); + GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false); + vf->close(vf); +#endif +} + +void CoreController::exportSharkport(const QString& path) { +#ifdef M_CORE_GBA + if (platform() != PLATFORM_GBA) { + return; + } + VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path); + return; + } + Interrupter interrupter(this); + GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf); + vf->close(vf); +#endif +} + +void CoreController::setAVStream(mAVStream* stream) { + Interrupter interrupter(this); + m_threadContext.core->setAVStream(m_threadContext.core, stream); +} + +void CoreController::clearAVStream() { + Interrupter interrupter(this); + m_threadContext.core->setAVStream(m_threadContext.core, nullptr); +} + +void CoreController::clearOverride() { + m_override.reset(); +} + +void CoreController::startVideoLog(const QString& path) { + if (m_vl) { + return; + } + + Interrupter interrupter(this); + m_vl = mVideoLogContextCreate(m_threadContext.core); + m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + mVideoLogContextSetOutput(m_vl, m_vlVf); + mVideoLogContextWriteHeader(m_vl, m_threadContext.core); +} + +void CoreController::endVideoLog() { + if (!m_vl) { + return; + } + + Interrupter interrupter(this); + mVideoLogContextDestroy(m_threadContext.core, m_vl); + if (m_vlVf) { + m_vlVf->close(m_vlVf); + m_vlVf = nullptr; + } + m_vl = nullptr; +} + +void CoreController::updateKeys() { + int activeKeys = m_inputController->updateAutofire() | m_inputController->pollEvents(); + m_threadContext.core->setKeys(m_threadContext.core, activeKeys); +} + +void CoreController::finishFrame() { + QMutexLocker locker(&m_mutex); + m_completeBuffer = m_activeBuffer; + + // TODO: Generalize this to triple buffering? + m_activeBuffer = &m_buffers[0]; + if (m_activeBuffer == m_completeBuffer) { + m_activeBuffer = &m_buffers[1]; + } + m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), screenDimensions().width()); + + for (auto& action : m_frameActions) { + action(); + } + m_frameActions.clear(); + updateKeys(); + + QMetaObject::invokeMethod(this, "frameAvailable"); +} + +void CoreController::updateFastForward() { + if (m_fastForward || m_fastForwardForced) { + if (m_fastForwardRatio > 0) { + m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio; + } else { + setSync(false); + } + } else { + m_threadContext.impl->sync.fpsTarget = m_fpsTarget; + setSync(true); + } +} + +CoreController::Interrupter::Interrupter(CoreController* parent, bool fromThread) + : m_parent(parent) +{ + if (!m_parent->thread()->impl) { + return; + } + if (!fromThread) { + mCoreThreadInterrupt(m_parent->thread()); + } else { + mCoreThreadInterruptFromThread(m_parent->thread()); + } +} + +CoreController::Interrupter::Interrupter(std::shared_ptr<CoreController> parent, bool fromThread) + : m_parent(parent.get()) +{ + if (!m_parent->thread()->impl) { + return; + } + if (!fromThread) { + mCoreThreadInterrupt(m_parent->thread()); + } else { + mCoreThreadInterruptFromThread(m_parent->thread()); + } +} + +CoreController::Interrupter::Interrupter(const Interrupter& other) + : m_parent(other.m_parent) +{ + if (!m_parent->thread()->impl) { + return; + } + mCoreThreadInterrupt(m_parent->thread()); +} + +CoreController::Interrupter::~Interrupter() { + if (!m_parent->thread()->impl) { + return; + } + mCoreThreadContinue(m_parent->thread()); +}
A src/platform/qt/CoreController.h

@@ -0,0 +1,193 @@

+/* 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_CORE_CONTROLLER +#define QGBA_CORE_CONTROLLER + +#include <QByteArray> +#include <QList> +#include <QMutex> +#include <QObject> +#include <QSize> + +#include "VFileDevice.h" + +#include <functional> +#include <memory> + +#include <mgba/core/core.h> +#include <mgba/core/interface.h> +#include <mgba/core/thread.h> +#include <mgba/core/tile-cache.h> + +struct mCore; + +namespace QGBA { + +class ConfigController; +class InputController; +class LogController; +class MultiplayerController; +class Override; + +class CoreController : public QObject { +Q_OBJECT + +public: + static const bool VIDEO_SYNC = false; + static const bool AUDIO_SYNC = true; + + class Interrupter { + public: + Interrupter(CoreController*, bool fromThread = false); + Interrupter(std::shared_ptr<CoreController>, bool fromThread = false); + Interrupter(const Interrupter&); + ~Interrupter(); + + private: + CoreController* m_parent; + }; + + CoreController(mCore* core, QObject* parent = nullptr); + ~CoreController(); + + mCoreThread* thread() { return &m_threadContext; } + + color_t* drawContext(); + + bool isPaused(); + + mPlatform platform() const; + QSize screenDimensions() const; + + void loadConfig(ConfigController*); + + mCheatDevice* cheatDevice() { return m_threadContext.core->cheatDevice(m_threadContext.core); } + +#ifdef USE_DEBUGGERS + mDebugger* debugger() { return m_threadContext.core->debugger; } + void setDebugger(mDebugger*); +#endif + + void setMultiplayerController(MultiplayerController*); + void clearMultiplayerController(); + MultiplayerController* multiplayerController() { return m_multiplayer; } + + mTileCache* tileCache(); + int stateSlot() const { return m_stateSlot; } + + void setOverride(std::unique_ptr<Override> override); + Override* override() { return m_override.get(); } + + void setInputController(InputController*); + void setLogger(LogController*); + +public slots: + void start(); + void stop(); + void reset(); + void setPaused(bool paused); + void frameAdvance(); + void setSync(bool enable); + + void setRewinding(bool); + void rewind(int count = 0); + + void setFastForward(bool); + void forceFastForward(bool); + + void loadState(int slot = 0); + void saveState(int slot = 0); + void loadBackupState(); + void saveBackupState(); + + void loadSave(const QString&, bool temporary); + void loadPatch(const QString&); + void replaceGame(const QString&); + void yankPak(); + +#ifdef USE_PNG + void screenshot(); +#endif + + void setRealTime(); + void setFixedTime(const QDateTime& time); + void setFakeEpoch(const QDateTime& time); + + void importSharkport(const QString& path); + void exportSharkport(const QString& path); + + void setAVStream(mAVStream*); + void clearAVStream(); + + void clearOverride(); + + void startVideoLog(const QString& path); + void endVideoLog(); + +signals: + void started(); + void paused(); + void unpaused(); + void stopping(); + void crashed(const QString& errorMessage); + void failed(); + void frameAvailable(); + void stateLoaded(); + void rewound(); + + void rewindChanged(bool); + void fastForwardChanged(bool); + + void unimplementedBiosCall(int); + void statusPosted(const QString& message); + void logPosted(int level, int category, const QString& log); + +private: + void updateKeys(); + void finishFrame(); + + void updateFastForward(); + + mCoreThread m_threadContext{}; + + bool m_patched = false; + + QByteArray m_buffers[2]; + QByteArray* m_activeBuffer; + QByteArray* m_completeBuffer = nullptr; + + std::unique_ptr<mTileCache> m_tileCache; + std::unique_ptr<Override> m_override; + + QList<std::function<void()>> m_resetActions; + QList<std::function<void()>> m_frameActions; + QMutex m_mutex; + + VFileDevice m_backupLoadState; + QByteArray m_backupSaveState{nullptr}; + int m_stateSlot = 1; + int m_loadStateFlags; + int m_saveStateFlags; + + bool m_audioSync = AUDIO_SYNC; + bool m_videoSync = VIDEO_SYNC; + + int m_fastForward = false; + int m_fastForwardForced = false; + float m_fastForwardRatio = -1.f; + float m_fpsTarget; + + InputController* m_inputController = nullptr; + LogController* m_log = nullptr; + MultiplayerController* m_multiplayer = nullptr; + + mVideoLogContext* m_vl = nullptr; + VFile* m_vlVf = nullptr; +}; + +} + +#endif
A src/platform/qt/CoreManager.cpp

@@ -0,0 +1,165 @@

+/* 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 "CoreManager.h" + +#include "CoreController.h" +#include "LogController.h" + +#include <QDir> + +#ifdef M_CORE_GBA +#include <mgba/gba/core.h> +#endif + +#include <mgba/core/core.h> +#include <mgba-util/vfs.h> + +using namespace QGBA; + +void CoreManager::setConfig(const mCoreConfig* config) { + m_config = config; +} + +void CoreManager::setMultiplayerController(MultiplayerController* multiplayer) { + m_multiplayer = multiplayer; +} + +CoreController* CoreManager::loadGame(const QString& path) { + QFileInfo info(path); + if (!info.isReadable()) { + QString fname = info.fileName(); + QString base = info.path(); + if (base.endsWith("/") || base.endsWith(QDir::separator())) { + base.chop(1); + } + VDir* dir = VDirOpenArchive(base.toUtf8().constData()); + if (dir) { + VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY); + if (vf) { + struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); + uint8_t buffer[2048]; + ssize_t read; + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + vfclone->write(vfclone, buffer, read); + } + vf->close(vf); + vf = vfclone; + } + dir->close(dir); + loadGame(vf, fname, base); + } else { + LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + } + return nullptr; + } + VFile* vf = nullptr; + VDir* archive = VDirOpenArchive(path.toUtf8().constData()); + if (archive) { + VFile* vfOriginal = VDirFindFirst(archive, [](VFile* vf) { + return mCoreIsCompatible(vf) != PLATFORM_NONE; + }); + ssize_t size; + if (vfOriginal && (size = vfOriginal->size(vfOriginal)) > 0) { + void* mem = vfOriginal->map(vfOriginal, size, MAP_READ); + vf = VFileMemChunk(mem, size); + vfOriginal->unmap(vfOriginal, mem, (size_t) read); + vfOriginal->close(vfOriginal); + } + } + QDir dir(info.dir()); + if (!vf) { + vf = VFileOpen(info.canonicalFilePath().toUtf8().constData(), O_RDONLY); + } + return loadGame(vf, info.fileName(), dir.canonicalPath()); +} + +CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QString& base) { + if (!vf) { + return nullptr; + } + + mCore* core = mCoreFindVF(vf); + if (!core) { + return nullptr; + } + + core->init(core); + mCoreInitConfig(core, nullptr); + + if (m_config) { + mCoreLoadForeignConfig(core, m_config); + } + + if (m_preload) { + mCorePreloadVF(core, vf); + } else { + core->loadROM(core, vf); + } + + QFileInfo info(base + "/" + path); + QByteArray bytes(info.baseName().toUtf8()); + strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + + bytes = info.dir().canonicalPath().toUtf8(); + mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); + mCoreAutoloadSave(core); + + CoreController* cc = new CoreController(core); + if (m_multiplayer) { + cc->setMultiplayerController(m_multiplayer); + } + emit coreLoaded(cc); + return cc; +} + +CoreController* CoreManager::loadBIOS(int platform, const QString& path) { + QFileInfo info(path); + VFile* vf = VFileOpen(info.canonicalFilePath().toUtf8().constData(), O_RDONLY); + if (!vf) { + return nullptr; + } + + mCore* core = nullptr; + switch (platform) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + core = GBACoreCreate(); + break; +#endif + default: + vf->close(vf); + return nullptr; + } + if (!core) { + vf->close(vf); + return nullptr; + } + + core->init(core); + mCoreInitConfig(core, nullptr); + + if (m_config) { + mCoreLoadForeignConfig(core, m_config); + } + + core->loadBIOS(core, vf, 0); + + mCoreConfigSetOverrideIntValue(&core->config, "useBios", 1); + mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0); + + QByteArray bytes(info.baseName().toUtf8()); + strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + + bytes = info.dir().canonicalPath().toUtf8(); + mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); + + CoreController* cc = new CoreController(core); + if (m_multiplayer) { + cc->setMultiplayerController(m_multiplayer); + } + emit coreLoaded(cc); + return cc; +}
A src/platform/qt/CoreManager.h

@@ -0,0 +1,45 @@

+/* 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_CORE_MANAGER +#define QGBA_CORE_MANAGER + +#include <QFileInfo> +#include <QObject> +#include <QString> + +struct mCoreConfig; +struct VFile; + +namespace QGBA { + +class CoreController; +class MultiplayerController; + +class CoreManager : public QObject { +Q_OBJECT + +public: + void setConfig(const mCoreConfig*); + void setMultiplayerController(MultiplayerController*); + void setPreload(bool preload) { m_preload = preload; } + +public slots: + CoreController* loadGame(const QString& path); + CoreController* loadGame(VFile* vf, const QString& path, const QString& base); + CoreController* loadBIOS(int platform, const QString& path); + +signals: + void coreLoaded(CoreController*); + +private: + const mCoreConfig* m_config = nullptr; + MultiplayerController* m_multiplayer = nullptr; + bool m_preload = false; +}; + +} + +#endif
M src/platform/qt/DebuggerConsoleController.cppsrc/platform/qt/DebuggerConsoleController.cpp

@@ -5,7 +5,7 @@ * 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 "DebuggerConsoleController.h" -#include "GameController.h" +#include "CoreController.h" #include <QMutexLocker>

@@ -13,8 +13,8 @@ #include <mgba/internal/debugger/cli-debugger.h>

using namespace QGBA; -DebuggerConsoleController::DebuggerConsoleController(GameController* controller, QObject* parent) - : DebuggerController(controller, &m_cliDebugger.d, parent) +DebuggerConsoleController::DebuggerConsoleController(QObject* parent) + : DebuggerController(&m_cliDebugger.d, parent) { m_backend.d.printf = printf; m_backend.d.init = init;

@@ -39,8 +39,10 @@ m_cond.wakeOne();

} void DebuggerConsoleController::detach() { - m_lines.append(QString()); - m_cond.wakeOne(); + if (m_cliDebugger.d.state != DEBUGGER_SHUTDOWN) { + m_lines.append(QString()); + m_cond.wakeOne(); + } DebuggerController::detach(); }

@@ -68,14 +70,16 @@

void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { Backend* consoleBe = reinterpret_cast<Backend*>(be); DebuggerConsoleController* self = consoleBe->self; - self->m_lines.append(QString()); - self->m_cond.wakeOne(); + if (be->p->d.state != DEBUGGER_SHUTDOWN) { + self->m_lines.append(QString()); + self->m_cond.wakeOne(); + } } const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, size_t* len) { Backend* consoleBe = reinterpret_cast<Backend*>(be); DebuggerConsoleController* self = consoleBe->self; - GameController::Interrupter interrupter(self->m_gameController, true); + CoreController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); while (self->m_lines.isEmpty()) { self->m_cond.wait(&self->m_mutex);

@@ -99,7 +103,7 @@

const char* DebuggerConsoleController::historyLast(struct CLIDebuggerBackend* be, size_t* len) { Backend* consoleBe = reinterpret_cast<Backend*>(be); DebuggerConsoleController* self = consoleBe->self; - GameController::Interrupter interrupter(self->m_gameController, true); + CoreController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); if (self->m_history.isEmpty()) { return "i";

@@ -111,7 +115,7 @@

void DebuggerConsoleController::historyAppend(struct CLIDebuggerBackend* be, const char* line) { Backend* consoleBe = reinterpret_cast<Backend*>(be); DebuggerConsoleController* self = consoleBe->self; - GameController::Interrupter interrupter(self->m_gameController, true); + CoreController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); self->m_history.append(QString::fromUtf8(line)); }
M src/platform/qt/DebuggerConsoleController.hsrc/platform/qt/DebuggerConsoleController.h

@@ -16,13 +16,13 @@ #include <mgba/internal/debugger/cli-debugger.h>

namespace QGBA { -class GameController; +class CoreController; class DebuggerConsoleController : public DebuggerController { Q_OBJECT public: - DebuggerConsoleController(GameController* controller, QObject* parent = nullptr); + DebuggerConsoleController(QObject* parent = nullptr); signals: void log(const QString&);

@@ -44,7 +44,7 @@ static void lineAppend(struct CLIDebuggerBackend* be, const char* line);

static const char* historyLast(struct CLIDebuggerBackend* be, size_t* len); static void historyAppend(struct CLIDebuggerBackend* be, const char* line); - CLIDebugger m_cliDebugger; + CLIDebugger m_cliDebugger{}; QMutex m_mutex; QWaitCondition m_cond;
M src/platform/qt/DebuggerController.cppsrc/platform/qt/DebuggerController.cpp

@@ -5,32 +5,44 @@ * 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 "GDBController.h" -#include "GameController.h" +#include "CoreController.h" using namespace QGBA; -DebuggerController::DebuggerController(GameController* controller, mDebugger* debugger, QObject* parent) +DebuggerController::DebuggerController(mDebugger* debugger, QObject* parent) : QObject(parent) , m_debugger(debugger) - , m_gameController(controller) { } bool DebuggerController::isAttached() { + if (!m_gameController) { + return false; + } return m_gameController->debugger() == m_debugger; } +void DebuggerController::setController(std::shared_ptr<CoreController> controller) { + if (m_gameController && controller != m_gameController) { + m_gameController->disconnect(this); + detach(); + } + m_gameController = controller; + if (controller) { + connect(m_gameController.get(), &CoreController::stopping, [this]() { + setController(nullptr); + }); + } +} + void DebuggerController::attach() { if (isAttached()) { return; } - if (m_gameController->isLoaded()) { + if (m_gameController) { attachInternal(); m_gameController->setDebugger(m_debugger); mDebuggerEnter(m_debugger, DEBUGGER_ENTER_ATTACHED, 0); - } else { - QObject::disconnect(m_autoattach); - m_autoattach = connect(m_gameController, &GameController::gameStarted, this, &DebuggerController::attach); } }

@@ -39,16 +51,18 @@ QObject::disconnect(m_autoattach);

if (!isAttached()) { return; } - GameController::Interrupter interrupter(m_gameController); - shutdownInternal(); - m_gameController->setDebugger(nullptr); + if (m_gameController) { + CoreController::Interrupter interrupter(m_gameController); + shutdownInternal(); + m_gameController->setDebugger(nullptr); + } } void DebuggerController::breakInto() { if (!isAttached()) { return; } - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); mDebuggerEnter(m_debugger, DEBUGGER_ENTER_MANUAL, 0); }

@@ -57,7 +71,7 @@ QObject::disconnect(m_autoattach);

if (!isAttached()) { return; } - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); shutdownInternal(); }
M src/platform/qt/DebuggerController.hsrc/platform/qt/DebuggerController.h

@@ -8,20 +8,23 @@ #define QGBA_DEBUGGER_CONTROLLER

#include <QObject> +#include <memory> + struct mDebugger; namespace QGBA { -class GameController; +class CoreController; class DebuggerController : public QObject { Q_OBJECT public: - DebuggerController(GameController* controller, mDebugger* debugger, QObject* parent = nullptr); + DebuggerController(mDebugger* debugger, QObject* parent = nullptr); public: bool isAttached(); + void setController(std::shared_ptr<CoreController>); public slots: virtual void attach();

@@ -34,7 +37,7 @@ virtual void attachInternal();

virtual void shutdownInternal(); mDebugger* const m_debugger; - GameController* const m_gameController; + std::shared_ptr<CoreController> m_gameController; private: QMetaObject::Connection m_autoattach;
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -8,15 +8,18 @@ #define QGBA_DISPLAY

#include <mgba-util/common.h> +#include <memory> + #include <QWidget> #include "MessagePainter.h" -struct mCoreThread; struct VDir; struct VideoShader; namespace QGBA { + +class CoreController; class Display : public QWidget { Q_OBJECT

@@ -41,6 +44,7 @@ bool isAspectRatioLocked() const { return m_lockAspectRatio; }

bool isIntegerScalingLocked() const { return m_lockIntegerScaling; } bool isFiltered() const { return m_filter; } + virtual void startDrawing(std::shared_ptr<CoreController>) = 0; virtual bool isDrawing() const = 0; virtual bool supportsShaders() const = 0; virtual VideoShader* shaders() = 0;

@@ -50,7 +54,6 @@ void showCursor();

void hideCursor(); public slots: - virtual void startDrawing(mCoreThread* context) = 0; virtual void stopDrawing() = 0; virtual void pauseDrawing() = 0; virtual void unpauseDrawing() = 0;

@@ -58,7 +61,7 @@ virtual void forceDraw() = 0;

virtual void lockAspectRatio(bool lock); virtual void lockIntegerScaling(bool lock); virtual void filter(bool filter); - virtual void framePosted(const uint32_t*) = 0; + virtual void framePosted() = 0; virtual void setShaders(struct VDir*) = 0; virtual void clearShaders() = 0;
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -7,12 +7,13 @@ #include "DisplayGL.h"

#if defined(BUILD_GL) || defined(BUILD_GLES) +#include "CoreController.h" + #include <QApplication> #include <QResizeEvent> #include <QTimer> #include <mgba/core/core.h> -#include <mgba/core/thread.h> #ifdef BUILD_GL #include "platform/opengl/gl.h" #endif

@@ -52,14 +53,14 @@ }

return shaders; } -void DisplayGL::startDrawing(mCoreThread* thread) { +void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) { if (m_drawThread) { return; } m_isDrawing = true; - m_painter->setContext(thread); + m_painter->setContext(controller); m_painter->setMessagePainter(messagePainter()); - m_context = thread; + m_context = controller; m_painter->resize(size()); m_gl->move(0, 0); m_drawThread = new QThread(this);

@@ -69,7 +70,6 @@ m_gl->context()->moveToThread(m_drawThread);

m_painter->moveToThread(m_drawThread); connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start); m_drawThread->start(); - mCoreSyncSetVideoSync(&m_context->impl->sync, false); lockAspectRatio(isAspectRatioLocked()); lockIntegerScaling(isIntegerScalingLocked());

@@ -85,41 +85,27 @@

void DisplayGL::stopDrawing() { if (m_drawThread) { m_isDrawing = false; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadInterrupt(m_context); - } + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); m_drawThread->exit(); m_drawThread = nullptr; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadContinue(m_context); - } } + m_context.reset(); } void DisplayGL::pauseDrawing() { if (m_drawThread) { m_isDrawing = false; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadInterrupt(m_context); - } + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); - if (mCoreThreadIsActive(m_context)) { - mCoreThreadContinue(m_context); - } } } void DisplayGL::unpauseDrawing() { if (m_drawThread) { m_isDrawing = true; - if (mCoreThreadIsActive(m_context)) { - mCoreThreadInterrupt(m_context); - } + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); - if (mCoreThreadIsActive(m_context)) { - mCoreThreadContinue(m_context); - } } }

@@ -150,9 +136,9 @@ QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));

} } -void DisplayGL::framePosted(const uint32_t* buffer) { - if (m_drawThread && buffer) { - m_painter->enqueue(buffer); +void DisplayGL::framePosted() { + if (m_drawThread) { + m_painter->enqueue(m_context->drawContext()); QMetaObject::invokeMethod(m_painter, "draw"); } }

@@ -183,12 +169,6 @@ }

PainterGL::PainterGL(int majorVersion, QGLWidget* parent) : m_gl(parent) - , m_active(false) - , m_started(false) - , m_context(nullptr) - , m_shader{} - , m_backend(nullptr) - , m_messagePainter(nullptr) { #ifdef BUILD_GL mGLContext* glBackend;

@@ -262,7 +242,7 @@ delete m_backend;

m_backend = nullptr; } -void PainterGL::setContext(mCoreThread* context) { +void PainterGL::setContext(std::shared_ptr<CoreController> context) { m_context = context; if (!context) {

@@ -273,9 +253,8 @@ m_gl->makeCurrent();

#if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent(); #endif - unsigned width, height; - context->core->desiredVideoDimensions(context->core, &width, &height); - m_backend->setDimensions(m_backend, width, height); + QSize size = m_context->screenDimensions(); + m_backend->setDimensions(m_backend, size.width(), size.height()); m_gl->doneCurrent(); }

@@ -329,13 +308,13 @@ m_started = true;

} void PainterGL::draw() { - if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) { + if (m_queue.isEmpty()) { return; } - if (mCoreSyncWaitFrameStart(&m_context->impl->sync) || !m_queue.isEmpty()) { + if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) { dequeue(); - mCoreSyncWaitFrameEnd(&m_context->impl->sync); + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); m_painter.begin(m_gl->context()->device()); performDraw(); m_painter.end();

@@ -349,7 +328,7 @@ }

m_delayTimer.restart(); } } else { - mCoreSyncWaitFrameEnd(&m_context->impl->sync); + mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync); } if (!m_queue.isEmpty()) { QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);

@@ -375,6 +354,7 @@ m_backend->clear(m_backend);

m_backend->swap(m_backend); m_gl->doneCurrent(); m_gl->context()->moveToThread(m_gl->thread()); + m_context.reset(); moveToThread(m_gl->thread()); }

@@ -409,9 +389,8 @@ buffer = m_queue.dequeue();

} else { buffer = m_free.takeLast(); } - unsigned width, height; - m_context->core->desiredVideoDimensions(m_context->core, &width, &height); - memcpy(buffer, backing, width * height * BYTES_PER_PIXEL); + QSize size = m_context->screenDimensions(); + memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); m_queue.enqueue(buffer); m_mutex.unlock(); }
M src/platform/qt/DisplayGL.hsrc/platform/qt/DisplayGL.h

@@ -46,12 +46,12 @@ public:

DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); ~DisplayGL(); + void startDrawing(std::shared_ptr<CoreController>) override; bool isDrawing() const override { return m_isDrawing; } bool supportsShaders() const override; VideoShader* shaders() override; public slots: - void startDrawing(mCoreThread* context) override; void stopDrawing() override; void pauseDrawing() override; void unpauseDrawing() override;

@@ -59,7 +59,7 @@ void forceDraw() override;

void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; void filter(bool filter) override; - void framePosted(const uint32_t*) override; + void framePosted() override; void setShaders(struct VDir*) override; void clearShaders() override;

@@ -74,7 +74,7 @@ bool m_isDrawing = false;

QGLWidget* m_gl; PainterGL* m_painter; QThread* m_drawThread = nullptr; - mCoreThread* m_context = nullptr; + std::shared_ptr<CoreController> m_context; }; class PainterGL : public QObject {

@@ -84,7 +84,7 @@ public:

PainterGL(int majorVersion, QGLWidget* parent); ~PainterGL(); - void setContext(mCoreThread*); + void setContext(std::shared_ptr<CoreController>); void setMessagePainter(MessagePainter*); void enqueue(const uint32_t* backing);

@@ -116,14 +116,14 @@ QQueue<uint32_t*> m_queue;

QPainter m_painter; QMutex m_mutex; QGLWidget* m_gl; - bool m_active; - bool m_started; - mCoreThread* m_context; + bool m_active = false; + bool m_started = false; + std::shared_ptr<CoreController> m_context = nullptr; bool m_supportsShaders; - VideoShader m_shader; - VideoBackend* m_backend; + VideoShader m_shader{}; + VideoBackend* m_backend = nullptr; QSize m_size; - MessagePainter* m_messagePainter; + MessagePainter* m_messagePainter = nullptr; QElapsedTimer m_delayTimer; };
M src/platform/qt/DisplayQt.cppsrc/platform/qt/DisplayQt.cpp

@@ -5,6 +5,8 @@ * 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 "DisplayQt.h" +#include "CoreController.h" + #include <QPainter> #include <mgba/core/core.h>

@@ -17,10 +19,18 @@ : Display(parent)

{ } -void DisplayQt::startDrawing(mCoreThread* context) { - context->core->desiredVideoDimensions(context->core, &m_width, &m_height); +void DisplayQt::startDrawing(std::shared_ptr<CoreController> controller) { + QSize size = controller->screenDimensions(); + m_width = size.width(); + m_height = size.height(); m_backing = std::move(QImage()); m_isDrawing = true; + m_context = controller; +} + +void DisplayQt::stopDrawing() { + m_isDrawing = false; + m_context.reset(); } void DisplayQt::lockAspectRatio(bool lock) {

@@ -38,8 +48,9 @@ Display::filter(filter);

update(); } -void DisplayQt::framePosted(const uint32_t* buffer) { +void DisplayQt::framePosted() { update(); + color_t* buffer = m_context->drawContext(); if (const_cast<const QImage&>(m_backing).bits() == reinterpret_cast<const uchar*>(buffer)) { return; }
M src/platform/qt/DisplayQt.hsrc/platform/qt/DisplayQt.h

@@ -19,20 +19,20 @@

public: DisplayQt(QWidget* parent = nullptr); + void startDrawing(std::shared_ptr<CoreController>) override; bool isDrawing() const override { return m_isDrawing; } bool supportsShaders() const override { return false; } VideoShader* shaders() override { return nullptr; } public slots: - void startDrawing(mCoreThread* context) override; - void stopDrawing() override { m_isDrawing = false; } + void stopDrawing() override; void pauseDrawing() override { m_isDrawing = false; } void unpauseDrawing() override { m_isDrawing = true; } void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; void filter(bool filter) override; - void framePosted(const uint32_t*) override; + void framePosted() override; void setShaders(struct VDir*) override {} void clearShaders() override {}

@@ -44,6 +44,7 @@ bool m_isDrawing = false;

unsigned m_width; unsigned m_height; QImage m_backing{nullptr}; + std::shared_ptr<CoreController> m_context = nullptr; }; }
M src/platform/qt/GBAApp.cppsrc/platform/qt/GBAApp.cpp

@@ -6,9 +6,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "GBAApp.h" #include "AudioProcessor.h" +#include "CoreController.h" +#include "CoreManager.h" #include "ConfigController.h" #include "Display.h" -#include "GameController.h" #include "Window.h" #include "VFileDevice.h"

@@ -57,6 +58,9 @@ }

reloadGameDB(); + m_manager.setConfig(m_configController->config()); + m_manager.setMultiplayerController(&m_multiplayer); + if (!m_configController->getQtOption("audioDriver").isNull()) { AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController->getQtOption("audioDriver").toInt())); }

@@ -71,7 +75,8 @@ }

bool GBAApp::event(QEvent* event) { if (event->type() == QEvent::FileOpen) { - m_windows[0]->controller()->loadGame(static_cast<QFileOpenEvent*>(event)->file()); + CoreController* core = m_manager.loadGame(static_cast<QFileOpenEvent*>(event)->file()); + m_windows[0]->setController(core, static_cast<QFileOpenEvent*>(event)->file()); return true; } return QApplication::event(event);

@@ -81,7 +86,7 @@ Window* GBAApp::newWindow() {

if (m_windows.count() >= MAX_GBAS) { return nullptr; } - Window* w = new Window(m_configController, m_multiplayer.attached()); + Window* w = new Window(&m_manager, m_configController, m_multiplayer.attached()); int windowId = m_multiplayer.attached(); connect(w, &Window::destroyed, [this, w]() { m_windows.removeAll(w);

@@ -93,7 +98,6 @@ m_windows.append(w);

w->setAttribute(Qt::WA_DeleteOnClose); w->loadConfig(); w->show(); - w->controller()->setMultiplayerController(&m_multiplayer); w->multiplayerChanged(); for (Window* w : m_windows) { w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS);

@@ -107,7 +111,7 @@ }

void GBAApp::pauseAll(QList<Window*>* paused) { for (auto& window : m_windows) { - if (!window->controller()->isLoaded() || window->controller()->isPaused()) { + if (!window->controller() || window->controller()->isPaused()) { continue; } window->controller()->setPaused(true);

@@ -117,7 +121,9 @@ }

void GBAApp::continueAll(const QList<Window*>& paused) { for (auto& window : paused) { - window->controller()->setPaused(false); + if (window->controller()) { + window->controller()->setPaused(false); + } } }
M src/platform/qt/GBAApp.hsrc/platform/qt/GBAApp.h

@@ -8,8 +8,12 @@ #define QGBA_APP_H

#include <QApplication> #include <QFileDialog> +#include <QList> +#include <QObject> +#include <QString> #include <QThread> +#include "CoreManager.h" #include "MultiplayerController.h" struct NoIntroDB;

@@ -70,6 +74,7 @@

ConfigController* m_configController; QList<Window*> m_windows; MultiplayerController m_multiplayer; + CoreManager m_manager; NoIntroDB* m_db = nullptr; #ifdef USE_SQLITE3
M src/platform/qt/GDBController.cppsrc/platform/qt/GDBController.cpp

@@ -5,12 +5,12 @@ * 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 "GDBController.h" -#include "GameController.h" +#include "CoreController.h" using namespace QGBA; -GDBController::GDBController(GameController* controller, QObject* parent) - : DebuggerController(controller, &m_gdbStub.d, parent) +GDBController::GDBController(QObject* parent) + : DebuggerController(&m_gdbStub.d, parent) , m_bindAddress({ IPV4, 0 }) { GDBStubCreate(&m_gdbStub);

@@ -21,7 +21,7 @@ return m_port;

} bool GDBController::isAttached() { - return m_gameController->debugger() == &m_gdbStub.d; + return m_gameController && m_gameController->debugger() == &m_gdbStub.d; } void GDBController::setPort(ushort port) {

@@ -34,7 +34,7 @@ m_bindAddress.ipv4 = htonl(bindAddress);

} void GDBController::listen() { - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); if (!isAttached()) { attach(); }
M src/platform/qt/GDBController.hsrc/platform/qt/GDBController.h

@@ -14,13 +14,13 @@ #include <mgba/internal/debugger/gdb-stub.h>

namespace QGBA { -class GameController; +class CoreController; class GDBController : public DebuggerController { Q_OBJECT public: - GDBController(GameController* controller, QObject* parent = nullptr); + GDBController(QObject* parent = nullptr); public: ushort port();

@@ -38,7 +38,7 @@

private: virtual void shutdownInternal() override; - GDBStub m_gdbStub; + GDBStub m_gdbStub{}; ushort m_port = 2345; Address m_bindAddress;
M src/platform/qt/GIFView.cppsrc/platform/qt/GIFView.cpp

@@ -7,6 +7,7 @@ #include "GIFView.h"

#ifdef USE_MAGICK +#include "CoreController.h" #include "GBAApp.h" #include "LogController.h"

@@ -37,6 +38,12 @@ }

GIFView::~GIFView() { stopRecording(); +} + +void GIFView::setController(std::shared_ptr<CoreController> controller) { + connect(controller.get(), &CoreController::stopping, this, &GIFView::stopRecording); + connect(this, &GIFView::recordingStarted, controller.get(), &CoreController::setAVStream); + connect(this, &GIFView::recordingStopped, controller.get(), &CoreController::clearAVStream, Qt::DirectConnection); } void GIFView::startRecording() {
M src/platform/qt/GIFView.hsrc/platform/qt/GIFView.h

@@ -10,11 +10,15 @@ #ifdef USE_MAGICK

#include <QWidget> +#include <memory> + #include "ui_GIFView.h" #include "feature/imagemagick/imagemagick-gif-encoder.h" namespace QGBA { + +class CoreController; class GIFView : public QWidget { Q_OBJECT

@@ -26,6 +30,8 @@

mAVStream* getStream() { return &m_encoder.d; } public slots: + void setController(std::shared_ptr<CoreController>); + void startRecording(); void stopRecording();
D src/platform/qt/GameController.cpp

@@ -1,1288 +0,0 @@

-/* Copyright (c) 2013-2014 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 "GameController.h" - -#include "AudioProcessor.h" -#include "InputController.h" -#include "LogController.h" -#include "MultiplayerController.h" -#include "Override.h" -#include "VFileDevice.h" - -#include <QCoreApplication> -#include <QDateTime> - -#include <ctime> - -#include <mgba/core/config.h> -#include <mgba/core/directories.h> -#include <mgba/core/serialize.h> -#include <mgba/core/tile-cache.h> -#ifdef M_CORE_GBA -#include <mgba/gba/interface.h> -#include <mgba/internal/gba/gba.h> -#include <mgba/gba/core.h> -#include <mgba/internal/gba/renderers/tile-cache.h> -#include <mgba/internal/gba/sharkport.h> -#endif -#ifdef M_CORE_GB -#include <mgba/internal/gb/gb.h> -#include <mgba/internal/gb/renderers/tile-cache.h> -#endif -#include <mgba-util/vfs.h> -#include <mgba/feature/video-logger.h> - -using namespace QGBA; -using namespace std; - -GameController::GameController(QObject* parent) - : QObject(parent) - , m_audioProcessor(AudioProcessor::create()) - , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA) - , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) -{ -#ifdef M_CORE_GBA - m_lux.p = this; - m_lux.sample = [](GBALuminanceSource* context) { - GameControllerLux* lux = static_cast<GameControllerLux*>(context); - lux->value = 0xFF - lux->p->m_luxValue; - }; - - m_lux.readLuminance = [](GBALuminanceSource* context) { - GameControllerLux* lux = static_cast<GameControllerLux*>(context); - return lux->value; - }; - setLuminanceLevel(0); -#endif - - m_threadContext.startCallback = [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - context->core->setPeripheral(context->core, mPERIPH_ROTATION, controller->m_inputController->rotationSource()); - context->core->setPeripheral(context->core, mPERIPH_RUMBLE, controller->m_inputController->rumble()); - - for (size_t i = 0; i < controller->m_audioChannels.size(); ++i) { - context->core->enableAudioChannel(context->core, i, controller->m_audioChannels[i]); - } - for (size_t i = 0; i < controller->m_videoLayers.size(); ++i) { - context->core->enableVideoLayer(context->core, i, controller->m_videoLayers[i]); - } - - switch (context->core->platform(context->core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, &controller->m_lux); - break; -#endif - default: - break; - } - controller->m_fpsTarget = context->impl->sync.fpsTarget; - - if (controller->m_override) { - controller->m_override->identify(context->core); - controller->m_override->apply(context->core); - } - - if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) { - mCoreDeleteState(context->core, 0); - } - - controller->m_gameOpen = true; - if (controller->m_multiplayer) { - controller->m_multiplayer->attachGame(controller); - } - - QString path = controller->m_fname; - if (!controller->m_fsub.isEmpty()) { - path += QDir::separator() + controller->m_fsub; - } - QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, path)); - QMetaObject::invokeMethod(controller, "startAudio"); - }; - - m_threadContext.resetCallback = [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - for (auto action : controller->m_resetActions) { - action(); - } - controller->m_resetActions.clear(); - - unsigned width, height; - controller->m_threadContext.core->desiredVideoDimensions(controller->m_threadContext.core, &width, &height); - memset(controller->m_frontBuffer, 0xFF, width * height * BYTES_PER_PIXEL); - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); - if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { - mCoreThreadPauseFromThread(context); - QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context)); - } - }; - - m_threadContext.cleanCallback = [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - - if (controller->m_multiplayer) { - controller->m_multiplayer->detachGame(controller); - } - controller->clearOverride(); - controller->endVideoLog(); - - QMetaObject::invokeMethod(controller->m_audioProcessor, "pause"); - - QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(mCoreThread*, context)); - QMetaObject::invokeMethod(controller, "cleanGame"); - }; - - m_threadContext.frameCallback = [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - unsigned width, height; - controller->m_threadContext.core->desiredVideoDimensions(controller->m_threadContext.core, &width, &height); - memcpy(controller->m_frontBuffer, controller->m_drawContext, width * height * BYTES_PER_PIXEL); - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); - - // If no one is using the tile cache, disable it - if (controller->m_tileCache && controller->m_tileCache.unique()) { - switch (controller->platform()) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: { - GBA* gba = static_cast<GBA*>(context->core->board); - gba->video.renderer->cache = nullptr; - break; - } -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: { - GB* gb = static_cast<GB*>(context->core->board); - gb->video.renderer->cache = nullptr; - break; - } -#endif - default: - break; - } - mTileCacheDeinit(controller->m_tileCache.get()); - controller->m_tileCache.reset(); - } - - - if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { - mCoreThreadPauseFromThread(context); - QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context)); - } - }; - - m_threadContext.sleepCallback = [](mCoreThread* context) { - if (!context) { - return; - } - GameController* controller = static_cast<GameController*>(context->userData); - if (!mCoreSaveState(context->core, 0, controller->m_saveStateFlags)) { - return; - } - QMetaObject::invokeMethod(controller, "closeGame"); - }; - - m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) { - mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger); - mCoreThread* context = logContext->p; - - static const char* savestateMessage = "State %i loaded"; - static const char* savestateFailedMessage = "State %i failed to load"; - static int biosCat = -1; - static int statusCat = -1; - if (!context) { - return; - } - GameController* controller = static_cast<GameController*>(context->userData); - QString message; - if (biosCat < 0) { - biosCat = mLogCategoryById("gba.bios"); - } - if (statusCat < 0) { - statusCat = mLogCategoryById("core.status"); - } -#ifdef M_CORE_GBA - if (level == mLOG_STUB && category == biosCat) { - va_list argc; - va_copy(argc, args); - int immediate = va_arg(argc, int); - va_end(argc); - QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate)); - } else -#endif - if (category == statusCat) { - // Slot 0 is reserved for suspend points - if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { - va_list argc; - va_copy(argc, args); - int slot = va_arg(argc, int); - va_end(argc); - if (slot == 0) { - format = "Loaded suspend state"; - } - } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) { - va_list argc; - va_copy(argc, args); - int slot = va_arg(argc, int); - va_end(argc); - if (slot == 0) { - return; - } - } - message = QString().vsprintf(format, args); - QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message)); - } - if (level == mLOG_FATAL) { - mCoreThreadMarkCrashed(controller->thread()); - QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args))); - } else if (!(controller->m_logLevels & level)) { - return; - } - message = QString().vsprintf(format, args); - QMetaObject::invokeMethod(controller, "postLog", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message)); - }; - - m_threadContext.userData = this; - - connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause); - connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput); - connect(this, &GameController::frameAvailable, this, &GameController::pollEvents); - connect(this, &GameController::frameAvailable, this, &GameController::updateAutofire); -} - -GameController::~GameController() { - disconnect(); - closeGame(); - clearMultiplayerController(); - delete m_backupLoadState; -} - -void GameController::setMultiplayerController(MultiplayerController* controller) { - if (controller == m_multiplayer) { - return; - } - clearMultiplayerController(); - m_multiplayer = controller; - if (isLoaded()) { - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) { - GameController* controller = static_cast<GameController*>(thread->userData); - controller->m_multiplayer->attachGame(controller); - }); - } -} - -void GameController::clearMultiplayerController() { - if (!m_multiplayer) { - return; - } - m_multiplayer->detachGame(this); - m_multiplayer = nullptr; -} - -void GameController::setOverride(Override* override) { - m_override = override; - if (isLoaded()) { - Interrupter interrupter(this); - m_override->identify(m_threadContext.core); - } -} - -void GameController::clearOverride() { - delete m_override; - m_override = nullptr; -} - -void GameController::setConfig(const mCoreConfig* config) { - m_config = config; - if (isLoaded()) { - Interrupter interrupter(this); - mCoreLoadForeignConfig(m_threadContext.core, config); - m_audioSync = m_threadContext.impl->sync.audioWait; - m_videoSync = m_threadContext.impl->sync.videoFrameWait; - m_audioProcessor->setInput(&m_threadContext); - } -} - -#ifdef USE_DEBUGGERS -mDebugger* GameController::debugger() { - if (!isLoaded()) { - return nullptr; - } - return m_threadContext.core->debugger; -} - -void GameController::setDebugger(mDebugger* debugger) { - Interrupter interrupter(this); - if (debugger) { - mDebuggerAttach(debugger, m_threadContext.core); - } else { - m_threadContext.core->detachDebugger(m_threadContext.core); - } -} -#endif - -void GameController::loadGame(const QString& path) { - closeGame(); - QFileInfo info(path); - if (!info.isReadable()) { - QString fname = info.fileName(); - QString base = info.path(); - if (base.endsWith("/") || base.endsWith(QDir::separator())) { - base.chop(1); - } - VDir* dir = VDirOpenArchive(base.toUtf8().constData()); - if (dir) { - VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY); - if (vf) { - struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); - uint8_t buffer[2048]; - ssize_t read; - while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { - vfclone->write(vfclone, buffer, read); - } - vf->close(vf); - vf = vfclone; - } - dir->close(dir); - loadGame(vf, fname, base); - } else { - LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); - } - return; - } else { - m_fname = info.canonicalFilePath(); - m_fsub = QString(); - } - m_vf = nullptr; - openGame(); -} - -void GameController::loadGame(VFile* vf, const QString& path, const QString& base) { - closeGame(); - QFileInfo info(base); - if (info.isDir()) { - m_fname = QFileInfo(base + '/' + path).canonicalFilePath(); - m_fsub = QString(); - } else { - m_fname = info.canonicalFilePath(); - m_fsub = path; - } - m_vf = vf; - openGame(); -} - -void GameController::bootBIOS() { - closeGame(); - m_fname = QString(); - openGame(true); -} - -void GameController::openGame(bool biosOnly) { - if (m_fname.isEmpty()) { - biosOnly = true; - } - if (isLoaded()) { - // We need to delay if the game is still cleaning up - QTimer::singleShot(10, this, SLOT(openGame())); - return; - } else if(m_gameOpen) { - cleanGame(); - } - - m_threadContext.core = nullptr; - if (!biosOnly) { - if (m_vf) { - m_threadContext.core = mCoreFindVF(m_vf); - } else { - m_threadContext.core = mCoreFind(m_fname.toUtf8().constData()); - } -#ifdef M_CORE_GBA - } else { - m_threadContext.core = GBACoreCreate(); -#endif - } - - if (!m_threadContext.core) { - return; - } - - m_pauseAfterFrame = false; - - m_threadContext.core->init(m_threadContext.core); - mCoreInitConfig(m_threadContext.core, nullptr); - - unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); - m_drawContext = new uint32_t[width * height]; - m_frontBuffer = new uint32_t[width * height]; - - if (m_config) { - mCoreLoadForeignConfig(m_threadContext.core, m_config); - } - - QByteArray bytes; - if (!biosOnly) { - bytes = m_fname.toUtf8(); - if (m_preload) { - if (m_vf) { - mCorePreloadVF(m_threadContext.core, m_vf); - } else { - mCorePreloadFile(m_threadContext.core, bytes.constData()); - mDirectorySetDetachBase(&m_threadContext.core->dirs); - } - } else { - if (m_vf) { - m_threadContext.core->loadROM(m_threadContext.core, m_vf); - } else { - mCoreLoadFile(m_threadContext.core, bytes.constData()); - mDirectorySetDetachBase(&m_threadContext.core->dirs); - } - } - } else { - bytes = m_bios.toUtf8(); - } - if (bytes.isNull()) { - return; - } - - char dirname[PATH_MAX]; - separatePath(bytes.constData(), dirname, m_threadContext.core->dirs.baseName, 0); - mDirectorySetAttachBase(&m_threadContext.core->dirs, VDirOpen(dirname)); - - m_threadContext.core->setVideoBuffer(m_threadContext.core, m_drawContext, width); - - m_inputController->recalibrateAxes(); - memset(m_drawContext, 0xF8, width * height * 4); - - m_threadContext.core->setAVStream(m_threadContext.core, m_stream); - - if (!biosOnly) { - mCoreAutoloadSave(m_threadContext.core); - if (!m_patch.isNull()) { - VFile* patch = VFileDevice::open(m_patch, O_RDONLY); - if (patch) { - m_threadContext.core->loadPatch(m_threadContext.core, patch); - } - patch->close(patch); - m_patch = QString(); - } else { - mCoreAutoloadPatch(m_threadContext.core); - } - } - m_vf = nullptr; - - if (!mCoreThreadStart(&m_threadContext)) { - emit gameFailed(); - } - if (m_turbo) { - m_threadContext.impl->sync.videoFrameWait = false; - m_threadContext.impl->sync.audioWait = false; - } else { - m_threadContext.impl->sync.videoFrameWait = m_videoSync; - m_threadContext.impl->sync.audioWait = m_audioSync; - } -} - -void GameController::loadBIOS(int platform, const QString& path) { - if (m_bios == path) { - return; - } - if (!m_bios.isNull() && m_gameOpen && this->platform() == platform) { - closeGame(); - m_bios = path; - openGame(); - } else if (!m_gameOpen || m_bios.isNull()) { - m_bios = path; - } -} - -void GameController::loadSave(const QString& path, bool temporary) { - if (!isLoaded()) { - return; - } - m_resetActions.append([this, path, temporary]() { - VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path); - return; - } - - if (temporary) { - m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); - } else { - m_threadContext.core->loadSave(m_threadContext.core, vf); - } - }); - reset(); -} - -void GameController::yankPak() { - if (!m_gameOpen) { - return; - } - Interrupter interrupter(this); - GBAYankROM(static_cast<GBA*>(m_threadContext.core->board)); -} - -void GameController::replaceGame(const QString& path) { - if (!m_gameOpen) { - return; - } - - QFileInfo info(path); - if (!info.isReadable()) { - LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); - return; - } - m_fname = info.canonicalFilePath(); - Interrupter interrupter(this); - mDirectorySetDetachBase(&m_threadContext.core->dirs); - mCoreLoadFile(m_threadContext.core, m_fname.toLocal8Bit().constData()); -} - -void GameController::loadPatch(const QString& path) { - m_patch = path; - if (m_gameOpen) { - closeGame(); - openGame(); - } -} - -void GameController::importSharkport(const QString& path) { - if (!isLoaded()) { - return; - } -#ifdef M_CORE_GBA - if (platform() != PLATFORM_GBA) { - return; - } - VFile* vf = VFileDevice::open(path, O_RDONLY); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path); - return; - } - threadInterrupt(); - GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false); - threadContinue(); - vf->close(vf); -#endif -} - -void GameController::exportSharkport(const QString& path) { - if (!isLoaded()) { - return; - } -#ifdef M_CORE_GBA - if (platform() != PLATFORM_GBA) { - return; - } - VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); - if (!vf) { - LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path); - return; - } - threadInterrupt(); - GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf); - threadContinue(); - vf->close(vf); -#endif -} - -void GameController::closeGame() { - if (!m_gameOpen) { - return; - } -#ifdef USE_DEBUGGERS - setDebugger(nullptr); -#endif - if (mCoreThreadIsPaused(&m_threadContext)) { - mCoreThreadUnpause(&m_threadContext); - } - mCoreThreadEnd(&m_threadContext); -} - -void GameController::cleanGame() { - if (!m_gameOpen || mCoreThreadIsActive(&m_threadContext)) { - return; - } - - m_audioProcessor->pause(); - mCoreThreadJoin(&m_threadContext); - - if (m_tileCache) { - mTileCacheDeinit(m_tileCache.get()); - m_tileCache.reset(); - } - - delete[] m_drawContext; - delete[] m_frontBuffer; - - mCoreConfigDeinit(&m_threadContext.core->config); - m_threadContext.core->deinit(m_threadContext.core); - m_threadContext.core = nullptr; - m_gameOpen = false; -} - -void GameController::crashGame(const QString& crashMessage) { - closeGame(); - emit gameCrashed(crashMessage); -} - -bool GameController::isPaused() { - if (!m_gameOpen) { - return false; - } - return mCoreThreadIsPaused(&m_threadContext); -} - -mPlatform GameController::platform() const { - if (!m_gameOpen) { - return PLATFORM_NONE; - } - return m_threadContext.core->platform(m_threadContext.core); -} - -QSize GameController::screenDimensions() const { - if (!m_gameOpen) { - return QSize(); - } - unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); - - return QSize(width, height); -} - -void GameController::setPaused(bool paused) { - if (!isLoaded() || paused == mCoreThreadIsPaused(&m_threadContext)) { - return; - } - m_wasPaused = paused; - if (paused) { - m_pauseAfterFrame.testAndSetRelaxed(false, true); - } else { - mCoreThreadUnpause(&m_threadContext); - startAudio(); - emit gameUnpaused(&m_threadContext); - } -} - -void GameController::reset() { - if (!m_gameOpen) { - return; - } - bool wasPaused = isPaused(); - setPaused(false); - Interrupter interrupter(this); - mCoreThreadReset(&m_threadContext); - if (wasPaused) { - setPaused(true); - } -} - -void GameController::threadInterrupt() { - if (m_gameOpen) { - mCoreThreadInterrupt(&m_threadContext); - } -} - -void GameController::threadContinue() { - if (m_gameOpen) { - mCoreThreadContinue(&m_threadContext); - } -} - -void GameController::frameAdvance() { - if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) { - setPaused(false); - m_wasPaused = true; - } -} - -void GameController::setRewind(bool enable, int capacity, bool rewindSave) { - if (m_gameOpen) { - Interrupter interrupter(this); - if (m_threadContext.core->opts.rewindEnable && m_threadContext.core->opts.rewindBufferCapacity > 0) { - mCoreRewindContextDeinit(&m_threadContext.impl->rewind); - } - m_threadContext.core->opts.rewindEnable = enable; - m_threadContext.core->opts.rewindBufferCapacity = capacity; - m_threadContext.core->opts.rewindSave = rewindSave; - if (enable && capacity > 0) { - mCoreRewindContextInit(&m_threadContext.impl->rewind, capacity, true); - m_threadContext.impl->rewind.stateFlags = rewindSave ? SAVESTATE_SAVEDATA : 0; - } - } -} - -void GameController::rewind(int states) { - threadInterrupt(); - if (!states) { - states = INT_MAX; - } - for (int i = 0; i < states; ++i) { - if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) { - break; - } - } - threadContinue(); - emit frameAvailable(m_drawContext); - emit rewound(&m_threadContext); -} - -void GameController::startRewinding() { - if (!isLoaded()) { - return; - } - if (!m_threadContext.core->opts.rewindEnable) { - return; - } - if (m_multiplayer && m_multiplayer->attached() > 1) { - return; - } - if (m_wasPaused) { - setPaused(false); - m_wasPaused = true; - } - mCoreThreadSetRewinding(&m_threadContext, true); -} - -void GameController::stopRewinding() { - if (!isLoaded()) { - return; - } - mCoreThreadSetRewinding(&m_threadContext, false); - bool signalsBlocked = blockSignals(true); - setPaused(m_wasPaused); - blockSignals(signalsBlocked); -} - -void GameController::keyPressed(int key) { - int mappedKey = 1 << key; - m_activeKeys |= mappedKey; - if (!m_inputController->allowOpposing()) { - if ((m_activeKeys & 0x30) == 0x30) { - m_inactiveKeys |= mappedKey ^ 0x30; - m_activeKeys ^= mappedKey ^ 0x30; - } - if ((m_activeKeys & 0xC0) == 0xC0) { - m_inactiveKeys |= mappedKey ^ 0xC0; - m_activeKeys ^= mappedKey ^ 0xC0; - } - } - updateKeys(); -} - -void GameController::keyReleased(int key) { - int mappedKey = 1 << key; - m_activeKeys &= ~mappedKey; - if (!m_inputController->allowOpposing()) { - if (mappedKey & 0x30) { - m_activeKeys |= m_inactiveKeys & (0x30 ^ mappedKey); - m_inactiveKeys &= ~0x30; - } - if (mappedKey & 0xC0) { - m_activeKeys |= m_inactiveKeys & (0xC0 ^ mappedKey); - m_inactiveKeys &= ~0xC0; - } - } - updateKeys(); -} - -void GameController::clearKeys() { - m_activeKeys = 0; - m_inactiveKeys = 0; - updateKeys(); -} - -void GameController::setAutofire(int key, bool enable) { - if (key >= GBA_KEY_MAX || key < 0) { - return; - } - - if (!enable && m_autofireStatus[key]) { - keyReleased(key); - } - - m_autofire[key] = enable; - m_autofireStatus[key] = 0; -} - -void GameController::setAudioBufferSamples(int samples) { - if (m_audioProcessor) { - threadInterrupt(); - redoSamples(samples); - threadContinue(); - m_audioProcessor->setBufferSamples(samples); - } -} - -void GameController::setAudioSampleRate(unsigned rate) { - if (!rate) { - return; - } - if (m_audioProcessor) { - threadInterrupt(); - redoSamples(m_audioProcessor->getBufferSamples()); - threadContinue(); - m_audioProcessor->requestSampleRate(rate); - } -} - -void GameController::setAudioChannelEnabled(int channel, bool enable) { - if (channel > 5 || channel < 0) { - return; - } - m_audioChannels.reserve(channel + 1); - while (m_audioChannels.size() <= channel) { - m_audioChannels.append(true); - } - m_audioChannels[channel] = enable; - if (isLoaded()) { - m_threadContext.core->enableAudioChannel(m_threadContext.core, channel, enable); - } -} - -void GameController::startAudio() { - if (!m_audioProcessor->start()) { - LOG(QT, ERROR) << tr("Failed to start audio processor"); - // Don't freeze! - m_audioSync = false; - m_videoSync = true; - if (isLoaded()) { - m_threadContext.impl->sync.audioWait = false; - m_threadContext.impl->sync.videoFrameWait = true; - } - } -} - -void GameController::setVideoLayerEnabled(int layer, bool enable) { - if (layer > 4 || layer < 0) { - return; - } - m_videoLayers.reserve(layer + 1); - while (m_videoLayers.size() <= layer) { - m_videoLayers.append(true); - } - m_videoLayers[layer] = enable; - if (isLoaded()) { - m_threadContext.core->enableVideoLayer(m_threadContext.core, layer, enable); - } -} - -void GameController::setFPSTarget(float fps) { - Interrupter interrupter(this); - m_fpsTarget = fps; - if (isLoaded()) { - m_threadContext.impl->sync.fpsTarget = fps; - if (m_turbo && m_turboSpeed > 0) { - m_threadContext.impl->sync.fpsTarget *= m_turboSpeed; - } - } - if (m_audioProcessor) { - redoSamples(m_audioProcessor->getBufferSamples()); - } -} - -void GameController::setUseBIOS(bool use) { - if (use == m_useBios) { - return; - } - m_useBios = use; - if (m_gameOpen) { - closeGame(); - openGame(); - } -} - -void GameController::loadState(int slot) { - if (m_fname.isEmpty()) { - // We're in the BIOS - return; - } - if (slot > 0 && slot != m_stateSlot) { - m_stateSlot = slot; - m_backupSaveState.clear(); - } - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - if (!controller->m_backupLoadState) { - controller->m_backupLoadState = VFileMemChunk(nullptr, 0); - } - mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); - if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) { - emit controller->frameAvailable(controller->m_drawContext); - emit controller->stateLoaded(context); - } - }); -} - -void GameController::saveState(int slot) { - if (m_fname.isEmpty()) { - // We're in the BIOS - return; - } - if (slot > 0) { - m_stateSlot = slot; - } - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false); - if (vf) { - controller->m_backupSaveState.resize(vf->size(vf)); - vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); - vf->close(vf); - } - mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags); - }); -} - -void GameController::loadBackupState() { - if (!m_backupLoadState) { - return; - } - - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - controller->m_backupLoadState->seek(controller->m_backupLoadState, 0, SEEK_SET); - if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) { - mLOG(STATUS, INFO, "Undid state load"); - controller->frameAvailable(controller->m_drawContext); - controller->stateLoaded(context); - } - controller->m_backupLoadState->close(controller->m_backupLoadState); - controller->m_backupLoadState = nullptr; - }); -} - -void GameController::saveBackupState() { - if (m_backupSaveState.isEmpty()) { - return; - } - - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - GameController* controller = static_cast<GameController*>(context->userData); - VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true); - if (vf) { - vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size()); - vf->close(vf); - mLOG(STATUS, INFO, "Undid state save"); - } - controller->m_backupSaveState.clear(); - }); -} - -void GameController::setTurbo(bool set, bool forced) { - if (m_turboForced && !forced) { - return; - } - if (m_turbo == set && m_turboForced == (set && forced)) { - // Don't interrupt the thread if we don't need to - return; - } - if (!m_sync) { - return; - } - m_turbo = set; - m_turboForced = set && forced; - enableTurbo(); -} - -void GameController::setTurboSpeed(float ratio) { - m_turboSpeed = ratio; - enableTurbo(); -} - -void GameController::enableTurbo() { - Interrupter interrupter(this); - if (!isLoaded()) { - return; - } - bool shouldRedoSamples = false; - if (!m_turbo) { - shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget; - m_threadContext.impl->sync.fpsTarget = m_fpsTarget; - m_threadContext.impl->sync.audioWait = m_audioSync; - m_threadContext.impl->sync.videoFrameWait = m_videoSync; - } else if (m_turboSpeed <= 0) { - shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget; - m_threadContext.impl->sync.fpsTarget = m_fpsTarget; - m_threadContext.impl->sync.audioWait = false; - m_threadContext.impl->sync.videoFrameWait = false; - } else { - shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget * m_turboSpeed; - m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_turboSpeed; - m_threadContext.impl->sync.audioWait = true; - m_threadContext.impl->sync.videoFrameWait = false; - } - if (m_audioProcessor && shouldRedoSamples) { - redoSamples(m_audioProcessor->getBufferSamples()); - } -} - -void GameController::setSync(bool enable) { - m_turbo = false; - m_turboForced = false; - if (isLoaded()) { - if (!enable) { - m_threadContext.impl->sync.audioWait = false; - m_threadContext.impl->sync.videoFrameWait = false; - } else { - m_threadContext.impl->sync.audioWait = m_audioSync; - m_threadContext.impl->sync.videoFrameWait = m_videoSync; - } - } - m_sync = enable; -} - -void GameController::setAudioSync(bool enable) { - m_audioSync = enable; - if (isLoaded()) { - m_threadContext.impl->sync.audioWait = enable; - } -} - -void GameController::setVideoSync(bool enable) { - m_videoSync = enable; - if (isLoaded()) { - m_threadContext.impl->sync.videoFrameWait = enable; - } -} - -void GameController::setAVStream(mAVStream* stream) { - Interrupter interrupter(this); - m_stream = stream; - if (isLoaded()) { - m_threadContext.core->setAVStream(m_threadContext.core, stream); - } -} - -void GameController::clearAVStream() { - Interrupter interrupter(this); - m_stream = nullptr; - if (isLoaded()) { - m_threadContext.core->setAVStream(m_threadContext.core, nullptr); - } -} - -#ifdef USE_PNG -void GameController::screenshot() { - mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { - mCoreTakeScreenshot(context->core); - }); -} -#endif - -void GameController::reloadAudioDriver() { - int samples = 0; - unsigned sampleRate = 0; - if (m_audioProcessor) { - m_audioProcessor->pause(); - samples = m_audioProcessor->getBufferSamples(); - sampleRate = m_audioProcessor->sampleRate(); - delete m_audioProcessor; - } - m_audioProcessor = AudioProcessor::create(); - if (samples) { - m_audioProcessor->setBufferSamples(samples); - } - if (sampleRate) { - m_audioProcessor->requestSampleRate(sampleRate); - } - connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause); - connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput); - if (isLoaded()) { - m_audioProcessor->setInput(&m_threadContext); - startAudio(); - } -} - -void GameController::setSaveStateExtdata(int flags) { - m_saveStateFlags = flags; -} - -void GameController::setLoadStateExtdata(int flags) { - m_loadStateFlags = flags; -} - -void GameController::setPreload(bool preload) { - m_preload = preload; -} - -void GameController::setLuminanceValue(uint8_t value) { - m_luxValue = value; - value = std::max<int>(value - 0x16, 0); - m_luxLevel = 10; - for (int i = 0; i < 10; ++i) { - if (value < GBA_LUX_LEVELS[i]) { - m_luxLevel = i; - break; - } - } - emit luminanceValueChanged(m_luxValue); -} - -void GameController::setLuminanceLevel(int level) { - int value = 0x16; - level = std::max(0, std::min(10, level)); - if (level > 0) { - value += GBA_LUX_LEVELS[level - 1]; - } - setLuminanceValue(value); -} - -void GameController::setRealTime() { - if (!isLoaded()) { - return; - } - m_threadContext.core->rtc.override = RTC_NO_OVERRIDE; -} - -void GameController::setFixedTime(const QDateTime& time) { - if (!isLoaded()) { - return; - } - m_threadContext.core->rtc.override = RTC_FIXED; - m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); -} - -void GameController::setFakeEpoch(const QDateTime& time) { - if (!isLoaded()) { - return; - } - m_threadContext.core->rtc.override = RTC_FAKE_EPOCH; - m_threadContext.core->rtc.value = time.toMSecsSinceEpoch(); -} - -void GameController::updateKeys() { - int activeKeys = m_activeKeys; - activeKeys |= m_activeButtons; - activeKeys &= ~m_inactiveKeys; - if (isLoaded()) { - m_threadContext.core->setKeys(m_threadContext.core, activeKeys); - } -} - -void GameController::redoSamples(int samples) { - if (m_gameOpen && m_threadContext.core) { - m_threadContext.core->setAudioBufferSize(m_threadContext.core, samples); - } - m_audioProcessor->inputParametersChanged(); -} - -void GameController::setLogLevel(int levels) { - Interrupter interrupter(this); - m_logLevels = levels; -} - -void GameController::enableLogLevel(int levels) { - Interrupter interrupter(this); - m_logLevels |= levels; -} - -void GameController::disableLogLevel(int levels) { - Interrupter interrupter(this); - m_logLevels &= ~levels; -} - -void GameController::startVideoLog(const QString& path) { - if (!isLoaded() || m_vl) { - return; - } - - Interrupter interrupter(this); - m_vl = mVideoLogContextCreate(m_threadContext.core); - m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); - mVideoLogContextSetOutput(m_vl, m_vlVf); - mVideoLogContextWriteHeader(m_vl, m_threadContext.core); -} - -void GameController::endVideoLog() { - if (!m_vl) { - return; - } - - Interrupter interrupter(this); - mVideoLogContextDestroy(m_threadContext.core, m_vl); - if (m_vlVf) { - m_vlVf->close(m_vlVf); - m_vlVf = nullptr; - } - m_vl = nullptr; -} - -void GameController::pollEvents() { - if (!m_inputController) { - return; - } - - m_activeButtons = m_inputController->pollEvents(); - updateKeys(); -} - -void GameController::updateAutofire() { - // TODO: Move all key events onto the CPU thread...somehow - for (int k = 0; k < GBA_KEY_MAX; ++k) { - if (!m_autofire[k]) { - continue; - } - m_autofireStatus[k] ^= 1; - if (m_autofireStatus[k]) { - keyPressed(k); - } else { - keyReleased(k); - } - } -} - -std::shared_ptr<mTileCache> GameController::tileCache() { - if (m_tileCache) { - return m_tileCache; - } - Interrupter interrupter(this); - switch (platform()) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: { - GBA* gba = static_cast<GBA*>(m_threadContext.core->board); - m_tileCache = std::make_shared<mTileCache>(); - GBAVideoTileCacheInit(m_tileCache.get()); - GBAVideoTileCacheAssociate(m_tileCache.get(), &gba->video); - mTileCacheSetPalette(m_tileCache.get(), 0); - break; - } -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: { - GB* gb = static_cast<GB*>(m_threadContext.core->board); - m_tileCache = std::make_shared<mTileCache>(); - GBVideoTileCacheInit(m_tileCache.get()); - GBVideoTileCacheAssociate(m_tileCache.get(), &gb->video); - mTileCacheSetPalette(m_tileCache.get(), 0); - break; - } -#endif - default: - return nullptr; - } - return m_tileCache; -} - -GameController::Interrupter::Interrupter(GameController* parent, bool fromThread) - : m_parent(parent) - , m_fromThread(fromThread) -{ - if (!m_fromThread) { - m_parent->threadInterrupt(); - } else { - mCoreThreadInterruptFromThread(m_parent->thread()); - } -} - -GameController::Interrupter::~Interrupter() { - if (!m_fromThread) { - m_parent->threadContinue(); - } else { - mCoreThreadContinue(m_parent->thread()); - } -}
D src/platform/qt/GameController.h

@@ -1,262 +0,0 @@

-/* 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_GAME_CONTROLLER -#define QGBA_GAME_CONTROLLER - -#include <QAtomicInt> -#include <QFile> -#include <QImage> -#include <QObject> -#include <QString> -#include <QTimer> - -#include <memory> -#include <functional> - -#include <mgba/core/core.h> -#include <mgba/core/thread.h> -#include <mgba/gba/interface.h> -#include <mgba/internal/gba/input.h> -#ifdef BUILD_SDL -#include "platform/sdl/sdl-events.h" -#endif - -struct Configuration; -struct GBAAudio; -struct mCoreConfig; -struct mDebugger; -struct mTileCache; -struct mVideoLogContext; - -namespace QGBA { - -class AudioProcessor; -class InputController; -class MultiplayerController; -class Override; - -class GameController : public QObject { -Q_OBJECT - -public: - static const bool VIDEO_SYNC = false; - static const bool AUDIO_SYNC = true; - - class Interrupter { - public: - Interrupter(GameController*, bool fromThread = false); - ~Interrupter(); - - private: - GameController* m_parent; - bool m_fromThread; - }; - - GameController(QObject* parent = nullptr); - ~GameController(); - - const uint32_t* drawContext() const { return m_drawContext; } - mCoreThread* thread() { return &m_threadContext; } - mCheatDevice* cheatDevice() { return m_threadContext.core ? m_threadContext.core->cheatDevice(m_threadContext.core) : nullptr; } - - void threadInterrupt(); - void threadContinue(); - - bool isPaused(); - bool isLoaded() { return m_gameOpen && mCoreThreadIsActive(&m_threadContext); } - mPlatform platform() const; - - bool audioSync() const { return m_audioSync; } - bool videoSync() const { return m_videoSync; } - QSize screenDimensions() const; - - void setInputController(InputController* controller) { m_inputController = controller; } - - void setMultiplayerController(MultiplayerController* controller); - MultiplayerController* multiplayerController() { return m_multiplayer; } - void clearMultiplayerController(); - - void setOverride(Override* override); - Override* override() { return m_override; } - void clearOverride(); - - void setConfig(const mCoreConfig*); - - int stateSlot() const { return m_stateSlot; } - -#ifdef USE_DEBUGGERS - mDebugger* debugger(); - void setDebugger(mDebugger*); -#endif - - std::shared_ptr<mTileCache> tileCache(); - -signals: - void frameAvailable(const uint32_t*); - void gameStarted(mCoreThread*, const QString& fname); - void gameStopped(mCoreThread*); - void gamePaused(mCoreThread*); - void gameUnpaused(mCoreThread*); - void gameCrashed(const QString& errorMessage); - void gameFailed(); - void stateLoaded(mCoreThread*); - void rewound(mCoreThread*); - void unimplementedBiosCall(int); - - void luminanceValueChanged(int); - - void statusPosted(const QString& message); - void postLog(int level, int category, const QString& log); - -public slots: - void loadGame(const QString& path); - void loadGame(VFile* vf, const QString& path, const QString& base); - void loadBIOS(int platform, const QString& path); - void loadSave(const QString& path, bool temporary = true); - void yankPak(); - void replaceGame(const QString& path); - void setUseBIOS(bool); - void loadPatch(const QString& path); - void importSharkport(const QString& path); - void exportSharkport(const QString& path); - void bootBIOS(); - void closeGame(); - void setPaused(bool paused); - void reset(); - void frameAdvance(); - void setRewind(bool enable, int capacity, bool rewindSave); - void rewind(int states = 0); - void startRewinding(); - void stopRewinding(); - void keyPressed(int key); - void keyReleased(int key); - void clearKeys(); - void setAutofire(int key, bool enable); - void setAudioBufferSamples(int samples); - void setAudioSampleRate(unsigned rate); - void setAudioChannelEnabled(int channel, bool enable = true); - void startAudio(); - void setVideoLayerEnabled(int layer, bool enable = true); - void setFPSTarget(float fps); - void loadState(int slot = 0); - void saveState(int slot = 0); - void loadBackupState(); - void saveBackupState(); - void setTurbo(bool, bool forced = true); - void setTurboSpeed(float ratio); - void setSync(bool); - void setAudioSync(bool); - void setVideoSync(bool); - void setAVStream(mAVStream*); - void clearAVStream(); - void reloadAudioDriver(); - void setSaveStateExtdata(int flags); - void setLoadStateExtdata(int flags); - void setPreload(bool); - -#ifdef USE_PNG - void screenshot(); -#endif - - void setLuminanceValue(uint8_t value); - uint8_t luminanceValue() const { return m_luxValue; } - void setLuminanceLevel(int level); - void increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); } - void decreaseLuminanceLevel() { setLuminanceLevel(m_luxLevel - 1); } - - void setRealTime(); - void setFixedTime(const QDateTime& time); - void setFakeEpoch(const QDateTime& time); - - void setLogLevel(int); - void enableLogLevel(int); - void disableLogLevel(int); - - void startVideoLog(const QString& path); - void endVideoLog(); - -private slots: - void openGame(bool bios = false); - void crashGame(const QString& crashMessage); - void cleanGame(); - - void pollEvents(); - void updateAutofire(); - -private: - void updateKeys(); - void redoSamples(int samples); - void enableTurbo(); - - uint32_t* m_drawContext = nullptr; - uint32_t* m_frontBuffer = nullptr; - mCoreThread m_threadContext{}; - const mCoreConfig* m_config; - mCheatDevice* m_cheatDevice; - int m_activeKeys = 0; - int m_activeButtons = 0; - int m_inactiveKeys = 0; - int m_logLevels = 0; - - bool m_gameOpen = false; - - QString m_fname; - QString m_fsub; - VFile* m_vf = nullptr; - QString m_bios; - bool m_useBios = false; - QString m_patch; - Override* m_override = nullptr; - - AudioProcessor* m_audioProcessor; - - QAtomicInt m_pauseAfterFrame{false}; - QList<std::function<void ()>> m_resetActions; - - bool m_sync = true; - bool m_videoSync = VIDEO_SYNC; - bool m_audioSync = AUDIO_SYNC; - float m_fpsTarget = -1; - bool m_turbo = false; - bool m_turboForced = false; - float m_turboSpeed = -1; - bool m_wasPaused = false; - - std::shared_ptr<mTileCache> m_tileCache; - - QList<bool> m_audioChannels; - QList<bool> m_videoLayers; - - bool m_autofire[GBA_KEY_MAX] = {}; - int m_autofireStatus[GBA_KEY_MAX] = {}; - - int m_stateSlot = 1; - struct VFile* m_backupLoadState = nullptr; - QByteArray m_backupSaveState{nullptr}; - int m_saveStateFlags; - int m_loadStateFlags; - - bool m_preload = false; - - InputController* m_inputController = nullptr; - MultiplayerController* m_multiplayer = nullptr; - - mAVStream* m_stream = nullptr; - - mVideoLogContext* m_vl = nullptr; - VFile* m_vlVf = nullptr; - - struct GameControllerLux : GBALuminanceSource { - GameController* p; - uint8_t value; - } m_lux; - uint8_t m_luxValue; - int m_luxLevel; -}; - -} - -#endif
M src/platform/qt/IOViewer.cppsrc/platform/qt/IOViewer.cpp

@@ -5,7 +5,7 @@ * 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 "IOViewer.h" -#include "GameController.h" +#include "CoreController.h" #include <QComboBox> #include <QFontDatabase>

@@ -1023,7 +1023,7 @@ });

return s_registers; } -IOViewer::IOViewer(GameController* controller, QWidget* parent) +IOViewer::IOViewer(std::shared_ptr<CoreController> controller, QWidget* parent) : QDialog(parent) , m_controller(controller) {

@@ -1067,16 +1067,17 @@ connect(m_b[i], &QAbstractButton::toggled, this, &IOViewer::bitFlipped);

} selectRegister(0); + + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); } void IOViewer::updateRegister() { m_value = 0; uint16_t value = 0; - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { + { + CoreController::Interrupter interrupter(m_controller); value = GBAView16(static_cast<ARMCore*>(m_controller->thread()->core->cpu), BASE_IO | m_register); } - m_controller->threadContinue(); for (int i = 0; i < 16; ++i) { m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);

@@ -1095,11 +1096,10 @@ emit valueChanged();

} void IOViewer::writeback() { - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { + { + CoreController::Interrupter interrupter(m_controller); GBAIOWrite(static_cast<GBA*>(m_controller->thread()->core->board), m_register, m_value); } - m_controller->threadContinue(); updateRegister(); }
M src/platform/qt/IOViewer.hsrc/platform/qt/IOViewer.h

@@ -9,11 +9,13 @@

#include <QDialog> #include <QList> +#include <memory> + #include "ui_IOViewer.h" namespace QGBA { -class GameController; +class CoreController; class IOViewer : public QDialog { Q_OBJECT

@@ -39,7 +41,7 @@ QStringList items;

}; typedef QList<RegisterItem> RegisterDescription; - IOViewer(GameController* controller, QWidget* parent = nullptr); + IOViewer(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); static const QList<RegisterDescription>& registerDescriptions();

@@ -65,7 +67,7 @@ uint16_t m_value;

QCheckBox* m_b[16]; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; }; }
M src/platform/qt/LoadSaveState.cppsrc/platform/qt/LoadSaveState.cpp

@@ -5,7 +5,7 @@ * 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 "LoadSaveState.h" -#include "GameController.h" +#include "CoreController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" #include "VFileDevice.h"

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

using namespace QGBA; -LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) +LoadSaveState::LoadSaveState(std::shared_ptr<CoreController> controller, QWidget* parent) : QWidget(parent) , m_controller(controller) , m_mode(LoadSave::LOAD)

@@ -61,6 +61,8 @@ connect(escape, &QAction::triggered, this, &QWidget::close);

escape->setShortcut(QKeySequence("Esc")); escape->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(escape); + + connect(m_controller.get(), &CoreController::stopping, this, &QWidget::close); } void LoadSaveState::setMode(LoadSave mode) {
M src/platform/qt/LoadSaveState.hsrc/platform/qt/LoadSaveState.h

@@ -8,11 +8,13 @@ #define QGBA_LOAD_SAVE_STATE

#include <QWidget> +#include <memory> + #include "ui_LoadSaveState.h" namespace QGBA { -class GameController; +class CoreController; class InputController; class SavestateButton;

@@ -27,7 +29,7 @@

public: const static int NUM_SLOTS = 9; - LoadSaveState(GameController* controller, QWidget* parent = nullptr); + LoadSaveState(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); void setInputController(InputController* controller); void setMode(LoadSave mode);

@@ -46,7 +48,7 @@ void loadState(int slot);

void triggerState(int slot); Ui::LoadSaveState m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; SavestateButton* m_slots[NUM_SLOTS]; LoadSave m_mode;
M src/platform/qt/LogController.cppsrc/platform/qt/LogController.cpp

@@ -11,8 +11,11 @@ LogController LogController::s_global(mLOG_ALL);

LogController::LogController(int levels, QObject* parent) : QObject(parent) - , m_logLevel(levels) { + mLogFilterInit(&m_filter); + mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB); + m_filter.defaultLevels = levels; + if (this != &s_global) { connect(&s_global, &LogController::logPosted, this, &LogController::postLog); connect(this, &LogController::levelsSet, &s_global, &LogController::setLevels);

@@ -26,24 +29,24 @@ return Stream(this, category, level);

} void LogController::postLog(int level, int category, const QString& string) { - if (!(m_logLevel & level)) { + if (!mLogFilterTest(&m_filter, category, static_cast<mLogLevel>(level))) { return; } emit logPosted(level, category, string); } void LogController::setLevels(int levels) { - m_logLevel = levels; + m_filter.defaultLevels = levels; emit levelsSet(levels); } void LogController::enableLevels(int levels) { - m_logLevel |= levels; + m_filter.defaultLevels |= levels; emit levelsEnabled(levels); } void LogController::disableLevels(int levels) { - m_logLevel &= ~levels; + m_filter.defaultLevels &= ~levels; emit levelsDisabled(levels); }
M src/platform/qt/LogController.hsrc/platform/qt/LogController.h

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

#include "GBAApp.h" +#include <mgba/core/log.h> + #include <QObject> #include <QStringList>

@@ -35,7 +37,8 @@

public: LogController(int levels, QObject* parent = nullptr); - int levels() const { return m_logLevel; } + int levels() const { return m_filter.defaultLevels; } + mLogFilter* filter() { return &m_filter; } Stream operator()(int category, int level);

@@ -55,7 +58,7 @@ void enableLevels(int levels);

void disableLevels(int levels); private: - int m_logLevel; + mLogFilter m_filter; static LogController s_global; };
M src/platform/qt/MemoryModel.cppsrc/platform/qt/MemoryModel.cpp

@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "MemoryModel.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include "LogController.h" #include "VFileDevice.h"

@@ -91,7 +91,7 @@

setRegion(0, 0x10000000, tr("All")); } -void MemoryModel::setController(GameController* controller) { +void MemoryModel::setController(std::shared_ptr<CoreController> controller) { m_core = controller->thread()->core; }
M src/platform/qt/MemoryModel.hsrc/platform/qt/MemoryModel.h

@@ -11,6 +11,7 @@ #include <QFont>

#include <QSize> #include <QStaticText> #include <QVector> + #include <memory> #include <mgba-util/text-codec.h>

@@ -19,7 +20,7 @@ struct mCore;

namespace QGBA { -class GameController; +class CoreController; class MemoryModel : public QAbstractScrollArea { Q_OBJECT

@@ -27,7 +28,7 @@

public: MemoryModel(QWidget* parent = nullptr); - void setController(GameController* controller); + void setController(std::shared_ptr<CoreController> controller); void setRegion(uint32_t base, uint32_t size, const QString& name = QString(), int segment = -1); void setSegment(int segment);
M src/platform/qt/MemorySearch.cppsrc/platform/qt/MemorySearch.cpp

@@ -8,12 +8,12 @@ #include "MemorySearch.h"

#include <mgba/core/core.h> -#include "GameController.h" +#include "CoreController.h" #include "MemoryView.h" using namespace QGBA; -MemorySearch::MemorySearch(GameController* controller, QWidget* parent) +MemorySearch::MemorySearch(std::shared_ptr<CoreController> controller, QWidget* parent) : QWidget(parent) , m_controller(controller) {

@@ -26,6 +26,8 @@ connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh);

connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh); connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh); connect(m_ui.viewMem, &QPushButton::clicked, this, &MemorySearch::openMemory); + + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); } MemorySearch::~MemorySearch() {

@@ -109,10 +111,7 @@ mCoreMemorySearchResultsClear(&m_results);

mCoreMemorySearchParams params; - GameController::Interrupter interrupter(m_controller); - if (!m_controller->isLoaded()) { - return; - } + CoreController::Interrupter interrupter(m_controller); mCore* core = m_controller->thread()->core; if (createParams(&params)) {

@@ -125,10 +124,7 @@

void MemorySearch::searchWithin() { mCoreMemorySearchParams params; - GameController::Interrupter interrupter(m_controller); - if (!m_controller->isLoaded()) { - return; - } + CoreController::Interrupter interrupter(m_controller); mCore* core = m_controller->thread()->core; if (createParams(&params)) {

@@ -139,10 +135,7 @@ refresh();

} void MemorySearch::refresh() { - GameController::Interrupter interrupter(m_controller); - if (!m_controller->isLoaded()) { - return; - } + CoreController::Interrupter interrupter(m_controller); mCore* core = m_controller->thread()->core; m_ui.results->clearContents();

@@ -220,7 +213,6 @@

MemoryView* memView = new MemoryView(m_controller); memView->jumpToAddress(address); - connect(m_controller, &GameController::gameStopped, memView, &QWidget::close); memView->setAttribute(Qt::WA_DeleteOnClose); memView->show(); }
M src/platform/qt/MemorySearch.hsrc/platform/qt/MemorySearch.h

@@ -6,13 +6,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_MEMORY_SEARCH #define QGBA_MEMORY_SEARCH +#include <memory> + #include "ui_MemorySearch.h" #include <mgba/core/mem-search.h> namespace QGBA { -class GameController; +class CoreController; class MemorySearch : public QWidget { Q_OBJECT

@@ -20,7 +22,7 @@

public: static constexpr size_t LIMIT = 10000; - MemorySearch(GameController* controller, QWidget* parent = nullptr); + MemorySearch(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); ~MemorySearch(); public slots:

@@ -36,7 +38,7 @@ bool createParams(mCoreMemorySearchParams*);

Ui::MemorySearch m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; mCoreMemorySearchResults m_results; QByteArray m_string;
M src/platform/qt/MemoryView.cppsrc/platform/qt/MemoryView.cpp

@@ -6,13 +6,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "MemoryView.h" -#include "GameController.h" +#include "CoreController.h" #include <mgba/core/core.h> using namespace QGBA; -MemoryView::MemoryView(GameController* controller, QWidget* parent) +MemoryView::MemoryView(std::shared_ptr<CoreController> controller, QWidget* parent) : QWidget(parent) , m_controller(controller) {

@@ -45,12 +45,12 @@ connect(m_ui.setAddress, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),

m_ui.hexfield, static_cast<void (MemoryModel::*)(uint32_t)>(&MemoryModel::jumpToAddress)); connect(m_ui.hexfield, &MemoryModel::selectionChanged, this, &MemoryView::updateSelection); - connect(controller, &GameController::gameStopped, this, &QWidget::close); + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); - connect(controller, &GameController::frameAvailable, this, &MemoryView::update); - connect(controller, &GameController::gamePaused, this, &MemoryView::update); - connect(controller, &GameController::stateLoaded, this, &MemoryView::update); - connect(controller, &GameController::rewound, this, &MemoryView::update); + connect(controller.get(), &CoreController::frameAvailable, this, &MemoryView::update); + connect(controller.get(), &CoreController::paused, this, &MemoryView::update); + connect(controller.get(), &CoreController::stateLoaded, this, &MemoryView::update); + connect(controller.get(), &CoreController::rewound, this, &MemoryView::update); connect(m_ui.copy, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::copy); connect(m_ui.save, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::save);

@@ -94,9 +94,6 @@ }

void MemoryView::updateStatus() { int align = m_ui.hexfield->alignment(); - if (!m_controller->isLoaded()) { - return; - } mCore* core = m_controller->thread()->core; QByteArray selection(m_ui.hexfield->serialize()); QString text(m_ui.hexfield->decodeText(selection));
M src/platform/qt/MemoryView.hsrc/platform/qt/MemoryView.h

@@ -12,13 +12,13 @@ #include "ui_MemoryView.h"

namespace QGBA { -class GameController; +class CoreController; class MemoryView : public QWidget { Q_OBJECT public: - MemoryView(GameController* controller, QWidget* parent = nullptr); + MemoryView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); public slots: void update();

@@ -33,7 +33,7 @@

private: Ui::MemoryView m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; QPair<uint32_t, uint32_t> m_selection; };
M src/platform/qt/MultiplayerController.cppsrc/platform/qt/MultiplayerController.cpp

@@ -5,7 +5,7 @@ * 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 "MultiplayerController.h" -#include "GameController.h" +#include "CoreController.h" #ifdef M_CORE_GBA #include <mgba/internal/gba/gba.h>

@@ -153,7 +153,7 @@ controller->m_lock.unlock();

}; } -bool MultiplayerController::attachGame(GameController* controller) { +bool MultiplayerController::attachGame(CoreController* controller) { if (m_lockstep.attached == MAX_GBAS) { return false; }

@@ -232,13 +232,15 @@

return false; } -void MultiplayerController::detachGame(GameController* controller) { +void MultiplayerController::detachGame(CoreController* controller) { mCoreThread* thread = controller->thread(); if (!thread) { return; } + QList<CoreController::Interrupter> interrupters; + for (int i = 0; i < m_players.count(); ++i) { - m_players[i].controller->threadInterrupt(); + interrupters.append(m_players[i].controller); } switch (controller->platform()) { #ifdef M_CORE_GBA

@@ -269,20 +271,16 @@ default:

break; } - controller->threadContinue(); for (int i = 0; i < m_players.count(); ++i) { if (m_players[i].controller == controller) { m_players.removeAt(i); break; } } - for (int i = 0; i < m_players.count(); ++i) { - m_players[i].controller->threadContinue(); - } emit gameDetached(); } -int MultiplayerController::playerId(GameController* controller) { +int MultiplayerController::playerId(CoreController* controller) { for (int i = 0; i < m_players.count(); ++i) { if (m_players[i].controller == controller) { return i;
M src/platform/qt/MultiplayerController.hsrc/platform/qt/MultiplayerController.h

@@ -23,7 +23,7 @@ struct GBASIOLockstepNode;

namespace QGBA { -class GameController; +class CoreController; class MultiplayerController : public QObject { Q_OBJECT

@@ -31,11 +31,11 @@

public: MultiplayerController(); - bool attachGame(GameController*); - void detachGame(GameController*); + bool attachGame(CoreController*); + void detachGame(CoreController*); int attached(); - int playerId(GameController*); + int playerId(CoreController*); signals: void gameAttached();

@@ -43,7 +43,7 @@ void gameDetached();

private: struct Player { - GameController* controller; + CoreController* controller; GBSIOLockstepNode* gbNode; GBASIOLockstepNode* gbaNode; int awake;
M src/platform/qt/ObjView.cppsrc/platform/qt/ObjView.cpp

@@ -5,6 +5,7 @@ * 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 "ObjView.h" +#include "CoreController.h" #include "GBAApp.h" #include <QFontDatabase>

@@ -24,7 +25,7 @@ #include <mgba-util/png-io.h>

using namespace QGBA; -ObjView::ObjView(GameController* controller, QWidget* parent) +ObjView::ObjView(std::shared_ptr<CoreController> controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) {

@@ -119,16 +120,16 @@ newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));

}; m_objInfo = newInfo; m_tileOffset = tile; - mTileCacheSetPalette(m_tileCache.get(), paletteSet); + mTileCacheSetPalette(m_tileCache, paletteSet); int i = 0; for (int y = 0; y < height / 8; ++y) { for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * tileBase], tile, palette); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * tileBase], tile, palette); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), tile, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, tile, palette)); } } tile += newInfo.stride - width / 8;

@@ -215,16 +216,16 @@ m_objInfo = newInfo;

m_tileOffset = tile; int i = 0; - mTileCacheSetPalette(m_tileCache.get(), 0); + mTileCacheSetPalette(m_tileCache, 0); m_ui.tile->setPalette(palette); m_ui.tile->setPaletteSet(0, 512, 1024); for (int y = 0; y < height / 8; ++y, ++i) { unsigned t = tile + i; - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * t], t, palette); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[16 * t], t, palette); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t, palette)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, t, palette)); } }

@@ -247,7 +248,7 @@ #endif

#ifdef USE_PNG void ObjView::exportObj() { - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), tr("Portable Network Graphics (*.png)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);

@@ -256,11 +257,11 @@ LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);

return; } - mTileCacheSetPalette(m_tileCache.get(), m_objInfo.paletteSet); + mTileCacheSetPalette(m_tileCache, m_objInfo.paletteSet); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8); - const uint16_t* rawPalette = mTileCacheGetPalette(m_tileCache.get(), m_objInfo.paletteId); + const uint16_t* rawPalette = mTileCacheGetPalette(m_tileCache, m_objInfo.paletteId); unsigned colors = 1 << m_objInfo.bits; uint32_t palette[256]; for (unsigned c = 0; c < colors && c < 256; ++c) {
M src/platform/qt/ObjView.hsrc/platform/qt/ObjView.h

@@ -7,7 +7,6 @@ #ifndef QGBA_OBJ_VIEW

#define QGBA_OBJ_VIEW #include "AssetView.h" -#include "GameController.h" #include "ui_ObjView.h"

@@ -15,11 +14,13 @@ #include <mgba/core/tile-cache.h>

namespace QGBA { +class CoreController; + class ObjView : public AssetView { Q_OBJECT public: - ObjView(GameController* controller, QWidget* parent = nullptr); + ObjView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); #ifdef USE_PNG public slots:

@@ -40,7 +41,7 @@ #endif

Ui::ObjView m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size int m_objId = 0; struct ObjInfo {
M src/platform/qt/OverrideView.cppsrc/platform/qt/OverrideView.cpp

@@ -9,7 +9,7 @@ #include <QColorDialog>

#include <QPushButton> #include "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #ifdef M_CORE_GBA #include "GBAOverride.h"

@@ -28,9 +28,8 @@ QList<enum GBModel> OverrideView::s_gbModelList;

QList<enum GBMemoryBankControllerType> OverrideView::s_mbcList; #endif -OverrideView::OverrideView(GameController* controller, ConfigController* config, QWidget* parent) +OverrideView::OverrideView(ConfigController* config, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) - , m_controller(controller) , m_config(config) { #ifdef M_CORE_GB

@@ -57,9 +56,6 @@ }

#endif m_ui.setupUi(this); - connect(controller, &GameController::gameStarted, this, &OverrideView::gameStarted); - connect(controller, &GameController::gameStopped, this, &OverrideView::gameStopped); - connect(m_ui.hwAutodetect, &QAbstractButton::toggled, [this] (bool enabled) { m_ui.hwRTC->setEnabled(!enabled); m_ui.hwGyro->setEnabled(!enabled);

@@ -106,9 +102,16 @@

connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &OverrideView::saveOverride); connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); +} - if (controller->isLoaded()) { - gameStarted(controller->thread()); +void OverrideView::setController(std::shared_ptr<CoreController> controller) { + m_controller = controller; + gameStarted(); + connect(controller.get(), &CoreController::stopping, this, &OverrideView::gameStopped); + if (m_override) { + m_controller->setOverride(std::move(m_override)); + } else { + m_controller->clearOverride(); } }

@@ -149,7 +152,7 @@ #endif

} void OverrideView::saveOverride() { - if (!m_config) { + if (!m_config || !m_controller) { return; } m_config->saveOverride(*m_controller->override());

@@ -158,7 +161,7 @@

void OverrideView::updateOverrides() { #ifdef M_CORE_GBA if (m_ui.tabWidget->currentWidget() == m_ui.tabGBA) { - GBAOverride* gba = new GBAOverride; + std::unique_ptr<GBAOverride> gba(new GBAOverride); memset(gba->override.id, 0, 4); gba->override.savetype = static_cast<SavedataType>(m_ui.savetype->currentIndex() - 1); gba->override.hardware = HW_NO_OVERRIDE;

@@ -193,18 +196,18 @@ if (ok) {

gba->override.idleLoop = parsedIdleLoop; } + if (gba->override.savetype != SAVEDATA_AUTODETECT || gba->override.hardware != HW_NO_OVERRIDE || gba->override.idleLoop != IDLE_LOOP_NONE) { - m_controller->setOverride(gba); + m_override = std::move(gba); } else { - m_controller->clearOverride(); - delete gba; + m_override.reset(); } } #endif #ifdef M_CORE_GB if (m_ui.tabWidget->currentWidget() == m_ui.tabGB) { - GBOverride* gb = new GBOverride; + std::unique_ptr<GBOverride> gb(new GBOverride); gb->override.mbc = s_mbcList[m_ui.mbc->currentIndex()]; gb->override.model = s_gbModelList[m_ui.gbModel->currentIndex()]; gb->override.gbColors[0] = m_gbColors[0];

@@ -214,20 +217,17 @@ gb->override.gbColors[3] = m_gbColors[3];

bool hasOverride = gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT; hasOverride = hasOverride || (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]); if (hasOverride) { - m_controller->setOverride(gb); + m_override = std::move(gb); } else { - m_controller->clearOverride(); - delete gb; + m_override.reset(); } } #endif } -void OverrideView::gameStarted(mCoreThread* thread) { - if (!thread->core) { - gameStopped(); - return; - } +void OverrideView::gameStarted() { + CoreController::Interrupter interrupter(m_controller); + mCoreThread* thread = m_controller->thread(); m_ui.tabWidget->setEnabled(false); m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);

@@ -278,6 +278,7 @@ }

} void OverrideView::gameStopped() { + m_controller.reset(); m_ui.tabWidget->setEnabled(true); m_ui.savetype->setCurrentIndex(0); m_ui.idleLoop->clear();
M src/platform/qt/OverrideView.hsrc/platform/qt/OverrideView.h

@@ -8,9 +8,13 @@ #define QGBA_OVERRIDE_VIEW

#include <QDialog> +#include <memory> + #ifdef M_CORE_GB #include <mgba/gb/interface.h> #endif + +#include "Override.h" #include "ui_OverrideView.h"

@@ -19,21 +23,23 @@

namespace QGBA { class ConfigController; -class GameController; +class CoreController; class Override; class OverrideView : public QDialog { Q_OBJECT public: - OverrideView(GameController* controller, ConfigController* config, QWidget* parent = nullptr); + OverrideView(ConfigController* config, QWidget* parent = nullptr); + + void setController(std::shared_ptr<CoreController> controller); public slots: void saveOverride(); private slots: void updateOverrides(); - void gameStarted(mCoreThread*); + void gameStarted(); void gameStopped(); protected:

@@ -42,7 +48,8 @@

private: Ui::OverrideView m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; + std::unique_ptr<Override> m_override; ConfigController* m_config; #ifdef M_CORE_GB
M src/platform/qt/PaletteView.cppsrc/platform/qt/PaletteView.cpp

@@ -5,6 +5,7 @@ * 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 "PaletteView.h" +#include "CoreController.h" #include "GBAApp.h" #include "LogController.h" #include "VFileDevice.h"

@@ -24,13 +25,13 @@ #include <mgba-util/vfs.h>

using namespace QGBA; -PaletteView::PaletteView(GameController* controller, QWidget* parent) +PaletteView::PaletteView(std::shared_ptr<CoreController> controller, QWidget* parent) : QWidget(parent) , m_controller(controller) { m_ui.setupUi(this); - connect(m_controller, &GameController::frameAvailable, this, &PaletteView::updatePalette); + connect(controller.get(), &CoreController::frameAvailable, this, &PaletteView::updatePalette); m_ui.bgGrid->setDimensions(QSize(16, 16)); m_ui.objGrid->setDimensions(QSize(16, 16)); int count = 256;

@@ -61,7 +62,7 @@ connect(m_ui.objGrid, &Swatch::indexPressed, [this, count] (int index) { selectIndex(index + count); });

connect(m_ui.exportBG, &QAbstractButton::clicked, [this, count] () { exportPalette(0, count); }); connect(m_ui.exportOBJ, &QAbstractButton::clicked, [this, count] () { exportPalette(count, count); }); - connect(controller, &GameController::gameStopped, this, &QWidget::close); + connect(controller.get(), &CoreController::stopping, this, &QWidget::close); } void PaletteView::updatePalette() {

@@ -133,7 +134,7 @@ if (start + length > 512) {

length = 512 - start; } - GameController::Interrupter interrupter(m_controller); + CoreController::Interrupter interrupter(m_controller); QString filename = GBAApp::app()->getSaveFileName(this, tr("Export palette"), tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
M src/platform/qt/PaletteView.hsrc/platform/qt/PaletteView.h

@@ -8,20 +8,22 @@ #define QGBA_PALETTE_VIEW

#include <QWidget> -#include "GameController.h" +#include <memory> + #include "Swatch.h" #include "ui_PaletteView.h" namespace QGBA { +class CoreController; class Swatch; class PaletteView : public QWidget { Q_OBJECT public: - PaletteView(GameController* controller, QWidget* parent = nullptr); + PaletteView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); public slots: void updatePalette();

@@ -34,7 +36,7 @@ void exportPalette(int start, int length);

Ui::PaletteView m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; }; }
M src/platform/qt/ROMInfo.cppsrc/platform/qt/ROMInfo.cpp

@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ROMInfo.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include <mgba/core/core.h> #ifdef M_CORE_GB

@@ -21,21 +21,17 @@ #endif

using namespace QGBA; -ROMInfo::ROMInfo(GameController* controller, QWidget* parent) +ROMInfo::ROMInfo(std::shared_ptr<CoreController> controller, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) { m_ui.setupUi(this); - if (!controller->isLoaded()) { - return; - } - #ifdef USE_SQLITE3 const NoIntroDB* db = GBAApp::app()->gameDB(); #endif uint32_t crc32 = 0; - GameController::Interrupter interrupter(controller); + CoreController::Interrupter interrupter(controller); mCore* core = controller->thread()->core; char title[17] = {}; core->getGameTitle(core, title);
M src/platform/qt/ROMInfo.hsrc/platform/qt/ROMInfo.h

@@ -8,17 +8,19 @@ #define QGBA_ROM_INFO

#include <QWidget> +#include <memory> + #include "ui_ROMInfo.h" namespace QGBA { -class GameController; +class CoreController; class ROMInfo : public QDialog { Q_OBJECT public: - ROMInfo(GameController* controller, QWidget* parent = nullptr); + ROMInfo(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); private: Ui::ROMInfo m_ui;
M src/platform/qt/SensorView.cppsrc/platform/qt/SensorView.cpp

@@ -5,7 +5,7 @@ * 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 "SensorView.h" -#include "GameController.h" +#include "CoreController.h" #include "GamepadAxisEvent.h" #include "InputController.h"

@@ -14,9 +14,8 @@ #include <mgba/internal/gba/gba.h>

using namespace QGBA; -SensorView::SensorView(GameController* controller, InputController* input, QWidget* parent) +SensorView::SensorView(InputController* input, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) - , m_controller(controller) , m_input(input) , m_rotation(input->rotationSource()) {

@@ -26,21 +25,12 @@ connect(m_ui.lightSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),

this, &SensorView::setLuminanceValue); connect(m_ui.lightSlide, &QAbstractSlider::valueChanged, this, &SensorView::setLuminanceValue); - connect(m_ui.timeNoOverride, &QAbstractButton::clicked, controller, &GameController::setRealTime); - connect(m_ui.timeFixed, &QRadioButton::clicked, [controller, this] () { - controller->setFixedTime(m_ui.time->dateTime()); - }); - connect(m_ui.timeFakeEpoch, &QRadioButton::clicked, [controller, this] () { - controller->setFakeEpoch(m_ui.time->dateTime()); - }); - connect(m_ui.time, &QDateTimeEdit::dateTimeChanged, [controller, this] (const QDateTime&) { + connect(m_ui.time, &QDateTimeEdit::dateTimeChanged, [this] (const QDateTime&) { m_ui.timeButtons->checkedButton()->clicked(); }); - connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () { + connect(m_ui.timeNow, &QPushButton::clicked, [this] () { m_ui.time->setDateTime(QDateTime::currentDateTime()); }); - - connect(m_controller, &GameController::luminanceValueChanged, this, &SensorView::luminanceValueChanged); m_timer.setInterval(2); connect(&m_timer, &QTimer::timeout, this, &SensorView::updateSensors);

@@ -66,6 +56,22 @@ connect(m_ui.gyroSensitivity, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [this](double value) {

m_input->setGyroSensitivity(value * 1e8f); }); m_input->stealFocus(this); + connect(m_input, &InputController::luminanceValueChanged, this, &SensorView::luminanceValueChanged); +} + +void SensorView::setController(std::shared_ptr<CoreController> controller) { + m_controller = controller; + connect(m_ui.timeNoOverride, &QAbstractButton::clicked, controller.get(), &CoreController::setRealTime); + connect(m_ui.timeFixed, &QRadioButton::clicked, [controller, this] () { + controller->setFixedTime(m_ui.time->dateTime()); + }); + connect(m_ui.timeFakeEpoch, &QRadioButton::clicked, [controller, this] () { + controller->setFakeEpoch(m_ui.time->dateTime()); + }); + + connect(controller.get(), &CoreController::stopping, [this]() { + m_controller.reset(); + }); } void SensorView::jiggerer(QAbstractButton* button, void (InputController::*setter)(int)) {

@@ -107,16 +113,7 @@ return false;

} void SensorView::updateSensors() { - GameController::Interrupter interrupter(m_controller); - if (m_rotation->sample && - (!m_controller->isLoaded() || !(static_cast<GBA*>(m_controller->thread()->core->board)->memory.hw.devices & (HW_GYRO | HW_TILT)))) { - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); - m_rotation->sample(m_rotation); + if (m_rotation->sample && (!m_controller || m_controller->isPaused())) { m_rotation->sample(m_rotation); } if (m_rotation->readTiltX && m_rotation->readTiltY) {

@@ -132,7 +129,9 @@ }

void SensorView::setLuminanceValue(int value) { value = std::max(0, std::min(value, 255)); - m_controller->setLuminanceValue(value); + if (m_input) { + m_input->setLuminanceValue(value); + } } void SensorView::luminanceValueChanged(int value) {
M src/platform/qt/SensorView.hsrc/platform/qt/SensorView.h

@@ -10,6 +10,7 @@ #include <QTimer>

#include <QDialog> #include <functional> +#include <memory> #include "ui_SensorView.h"

@@ -18,7 +19,7 @@

namespace QGBA { class ConfigController; -class GameController; +class CoreController; class GamepadAxisEvent; class InputController;

@@ -26,7 +27,9 @@ class SensorView : public QDialog {

Q_OBJECT public: - SensorView(GameController* controller, InputController* input, QWidget* parent = nullptr); + SensorView(InputController* input, QWidget* parent = nullptr); + + void setController(std::shared_ptr<CoreController>); protected: bool eventFilter(QObject*, QEvent* event) override;

@@ -41,7 +44,7 @@ private:

Ui::SensorView m_ui; std::function<void(int)> m_jiggered; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; InputController* m_input; mRotationSource* m_rotation; QTimer m_timer;
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -174,19 +174,26 @@ }

SettingsView::~SettingsView() { #if defined(BUILD_GL) || defined(BUILD_GLES) - if (m_shader) { - m_ui.stackedWidget->removeWidget(m_shader); - m_shader->setParent(nullptr); - } + setShaderSelector(nullptr); #endif } void SettingsView::setShaderSelector(ShaderSelector* shaderSelector) { #if defined(BUILD_GL) || defined(BUILD_GLES) + if (m_shader) { + auto items = m_ui.tabs->findItems(tr("Shaders"), Qt::MatchFixedString); + for (const auto& item : items) { + m_ui.tabs->removeItemWidget(item); + } + m_ui.stackedWidget->removeWidget(m_shader); + m_shader->setParent(nullptr); + } m_shader = shaderSelector; - m_ui.stackedWidget->addWidget(m_shader); - m_ui.tabs->addItem(tr("Shaders")); - connect(m_ui.buttonBox, &QDialogButtonBox::accepted, m_shader, &ShaderSelector::saved); + if (shaderSelector) { + m_ui.stackedWidget->addWidget(m_shader); + m_ui.tabs->addItem(tr("Shaders")); + connect(m_ui.buttonBox, &QDialogButtonBox::accepted, m_shader, &ShaderSelector::saved); + } #endif }

@@ -269,6 +276,7 @@ QVariant displayDriver = m_ui.displayDriver->itemData(m_ui.displayDriver->currentIndex());

if (displayDriver != m_controller->getQtOption("displayDriver")) { m_controller->setQtOption("displayDriver", displayDriver); Display::setDriver(static_cast<Display::Driver>(displayDriver.toInt())); + setShaderSelector(nullptr); emit displayDriverChanged(); }
M src/platform/qt/TileView.cppsrc/platform/qt/TileView.cpp

@@ -5,6 +5,7 @@ * 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 "TileView.h" +#include "CoreController.h" #include "GBAApp.h" #include <QFontDatabase>

@@ -16,7 +17,7 @@ #endif

using namespace QGBA; -TileView::TileView(GameController* controller, QWidget* parent) +TileView::TileView(std::shared_ptr<CoreController> controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) {

@@ -79,40 +80,40 @@ #ifdef M_CORE_GBA

void TileView::updateTilesGBA(bool force) { if (m_ui.palette256->isChecked()) { m_ui.tiles->setTileCount(1536); - mTileCacheSetPalette(m_tileCache.get(), 1); + mTileCacheSetPalette(m_tileCache, 1); for (int i = 0; i < 1024; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, 0); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, 0); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, 0)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, 0)); } } for (int i = 1024; i < 1536; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, 1); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, 1); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, 1)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, 1)); } } } else { m_ui.tiles->setTileCount(3072); - mTileCacheSetPalette(m_tileCache.get(), 0); + mTileCacheSetPalette(m_tileCache, 0); for (int i = 0; i < 2048; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, m_paletteId); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, m_paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, m_paletteId)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, m_paletteId)); } } for (int i = 2048; i < 3072; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * i], i, m_paletteId + 16); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[32 * i], i, m_paletteId + 16); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, m_paletteId + 16)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, m_paletteId + 16)); } } }

@@ -124,13 +125,13 @@ void TileView::updateTilesGB(bool force) {

const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board); int count = gb->model >= GB_MODEL_CGB ? 1024 : 512; m_ui.tiles->setTileCount(count); - mTileCacheSetPalette(m_tileCache.get(), 0); + mTileCacheSetPalette(m_tileCache, 0); for (int i = 0; i < count; ++i) { - const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * i], i, m_paletteId); + const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache, &m_tileStatus[16 * i], i, m_paletteId); if (data) { m_ui.tiles->setTile(i, data); } else if (force) { - m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), i, m_paletteId)); + m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache, i, m_paletteId)); } } }
M src/platform/qt/TileView.hsrc/platform/qt/TileView.h

@@ -7,7 +7,6 @@ #ifndef QGBA_TILE_VIEW

#define QGBA_TILE_VIEW #include "AssetView.h" -#include "GameController.h" #include "ui_TileView.h"

@@ -15,11 +14,13 @@ #include <mgba/core/tile-cache.h>

namespace QGBA { +class CoreController; + class TileView : public AssetView { Q_OBJECT public: - TileView(GameController* controller, QWidget* parent = nullptr); + TileView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); public slots: void updatePalette(int);

@@ -34,7 +35,7 @@ #endif

Ui::TileView m_ui; - GameController* m_controller; + std::shared_ptr<CoreController> m_controller; mTileCacheEntry m_tileStatus[3072 * 32] = {}; // TODO: Correct size int m_paletteId = 0; };
M src/platform/qt/VFileDevice.cppsrc/platform/qt/VFileDevice.cpp

@@ -13,7 +13,33 @@ VFileDevice::VFileDevice(VFile* vf, QObject* parent)

: QIODevice(parent) , m_vf(vf) { - // Nothing to do + // TODO: Correct mode + if (vf) { + setOpenMode(QIODevice::ReadWrite); + } +} + +void VFileDevice::close() { + QIODevice::close(); + m_vf->close(m_vf); + m_vf = nullptr; +} + +bool VFileDevice::resize(qint64 sz) { + m_vf->truncate(m_vf, sz); + return true; +} + +bool VFileDevice::seek(qint64 pos) { + QIODevice::seek(pos); + return m_vf->seek(m_vf, pos, SEEK_SET) == pos; +} + +VFileDevice& VFileDevice::operator=(VFile* vf) { + close(); + m_vf = vf; + setOpenMode(QIODevice::ReadWrite); + return *this; } qint64 VFileDevice::readData(char* data, qint64 maxSize) {
M src/platform/qt/VFileDevice.hsrc/platform/qt/VFileDevice.h

@@ -17,7 +17,16 @@ class VFileDevice : public QIODevice {

Q_OBJECT public: - VFileDevice(VFile* vf, QObject* parent = nullptr); + VFileDevice(VFile* vf = nullptr, QObject* parent = nullptr); + + virtual void close() override; + virtual bool seek(qint64 pos) override; + virtual qint64 size() const override; + + bool resize(qint64 sz); + + VFileDevice& operator=(VFile*); + operator VFile*() { return m_vf; } static VFile* open(const QString& path, int mode); static VDir* openDir(const QString& path);

@@ -26,7 +35,6 @@

protected: virtual qint64 readData(char* data, qint64 maxSize) override; virtual qint64 writeData(const char* data, qint64 maxSize) override; - virtual qint64 size() const override; private: VFile* m_vf;
M src/platform/qt/VideoView.cppsrc/platform/qt/VideoView.cpp

@@ -195,6 +195,14 @@ free(m_videoCodecCstr);

free(m_containerCstr); } +void VideoView::setController(std::shared_ptr<CoreController> controller) { + connect(controller.get(), &CoreController::stopping, this, &VideoView::stopRecording); + connect(this, &VideoView::recordingStarted, controller.get(), &CoreController::setAVStream); + connect(this, &VideoView::recordingStopped, controller.get(), &CoreController::clearAVStream, Qt::DirectConnection); + + setNativeResolution(controller->screenDimensions()); +} + void VideoView::startRecording() { if (!validateSettings()) { return;
M src/platform/qt/VideoView.hsrc/platform/qt/VideoView.h

@@ -10,12 +10,18 @@ #ifdef USE_FFMPEG

#include <QWidget> +#include <memory> + +#include "CoreController.h" + #include "ui_VideoView.h" #include "feature/ffmpeg/ffmpeg-encoder.h" namespace QGBA { +class CoreController; + class VideoView : public QWidget { Q_OBJECT

@@ -26,6 +32,8 @@

mAVStream* getStream() { return &m_encoder.d; } public slots: + void setController(std::shared_ptr<CoreController>); + void startRecording(); void stopRecording(); void setNativeResolution(const QSize&);
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -1,4 +1,4 @@

-/* Copyright (c) 2013-2016 Jeffrey Pfau +/* 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

@@ -20,12 +20,14 @@ #include "library/LibraryController.h"

#endif #include "AboutScreen.h" +#include "AudioProcessor.h" #include "CheatsView.h" #include "ConfigController.h" +#include "CoreController.h" #include "DebuggerConsole.h" #include "DebuggerConsoleController.h" #include "Display.h" -#include "GameController.h" +#include "CoreController.h" #include "GBAApp.h" #include "GDBController.h" #include "GDBWindow.h"

@@ -64,8 +66,9 @@ #include <mgba-util/vfs.h>

using namespace QGBA; -Window::Window(ConfigController* config, int playerId, QWidget* parent) +Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWidget* parent) : QMainWindow(parent) + , m_manager(manager) , m_logView(new LogView(&m_log)) , m_screenWidget(new WindowBackground()) , m_config(config)

@@ -74,20 +77,12 @@ {

setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); setAttribute(Qt::WA_DeleteOnClose); - m_controller = new GameController(this); - m_controller->setInputController(&m_inputController); updateTitle(); - - m_display = Display::create(this); -#if defined(BUILD_GL) || defined(BUILD_GLES) - m_shaderView = new ShaderSelector(m_display, m_config); -#endif + reloadDisplayDriver(); m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); m_logo = m_logo; // Free memory left over in old pixmap - m_screenWidget->setMinimumSize(m_display->minimumSize()); - m_screenWidget->setSizePolicy(m_display->sizePolicy()); #if defined(M_CORE_GBA) float i = 2; #elif defined(M_CORE_GB)

@@ -103,7 +98,7 @@ m_libraryView = new LibraryController(nullptr, ConfigController::configDir() + "/library.sqlite3", m_config);

ConfigOption* showLibrary = m_config->addOption("showLibrary"); showLibrary->connect([this](const QVariant& value) { if (value.toBool()) { - if (m_controller->isLoaded()) { + if (m_controller) { m_screenWidget->layout()->addWidget(m_libraryView); } else { attachWidget(m_libraryView);

@@ -123,7 +118,7 @@ connect(m_libraryView, &LibraryController::startGame, [this]() {

VFile* output = m_libraryView->selectedVFile(); if (output) { QPair<QString, QString> path = m_libraryView->selectedPath(); - m_controller->loadGame(output, path.second, path.first); + setController(m_manager->loadGame(output, path.second, path.first), path.first + "/" + path.second); } }); #endif

@@ -133,69 +128,14 @@ #elif defined(M_CORE_GB)

resizeFrame(QSize(GB_VIDEO_HORIZONTAL_PIXELS * i, GB_VIDEO_VERTICAL_PIXELS * i)); #endif m_screenWidget->setPixmap(m_logo); - m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); + m_screenWidget->setDimensions(m_logo.width(), m_logo.height()); m_screenWidget->setLockIntegerScaling(false); + m_screenWidget->setLockAspectRatio(true); setCentralWidget(m_screenWidget); - connect(m_controller, &GameController::gameStarted, this, &Window::gameStarted); - connect(m_controller, &GameController::gameStarted, &m_inputController, &InputController::suspendScreensaver); - connect(m_controller, &GameController::gameStopped, m_display, &Display::stopDrawing); - connect(m_controller, &GameController::gameStopped, this, &Window::gameStopped); - connect(m_controller, &GameController::gameStopped, &m_inputController, &InputController::resumeScreensaver); - connect(m_controller, &GameController::stateLoaded, m_display, &Display::forceDraw); - connect(m_controller, &GameController::rewound, m_display, &Display::forceDraw); - connect(m_controller, &GameController::gamePaused, [this](mCoreThread* context) { - unsigned width, height; - context->core->desiredVideoDimensions(context->core, &width, &height); - QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), width, height, - width * BYTES_PER_PIXEL, QImage::Format_RGBX8888); - QPixmap pixmap; - pixmap.convertFromImage(currentImage); - m_screenWidget->setPixmap(pixmap); - m_screenWidget->setLockAspectRatio(width, height); - }); - connect(m_controller, &GameController::gamePaused, m_display, &Display::pauseDrawing); -#ifndef Q_OS_MAC - connect(m_controller, &GameController::gamePaused, menuBar(), &QWidget::show); - connect(m_controller, &GameController::gameUnpaused, [this]() { - if(isFullScreen()) { - menuBar()->hide(); - } - }); -#endif - connect(m_controller, &GameController::gamePaused, &m_inputController, &InputController::resumeScreensaver); - connect(m_controller, &GameController::gameUnpaused, m_display, &Display::unpauseDrawing); - connect(m_controller, &GameController::gameUnpaused, &m_inputController, &InputController::suspendScreensaver); - connect(m_controller, &GameController::postLog, &m_log, &LogController::postLog); - connect(m_controller, &GameController::frameAvailable, this, &Window::recordFrame); - connect(m_controller, &GameController::frameAvailable, m_display, &Display::framePosted); - connect(m_controller, &GameController::gameCrashed, this, &Window::gameCrashed); - connect(m_controller, &GameController::gameFailed, this, &Window::gameFailed); - connect(m_controller, &GameController::unimplementedBiosCall, this, &Window::unimplementedBiosCall); - connect(m_controller, &GameController::statusPosted, m_display, &Display::showMessage); - connect(&m_log, &LogController::levelsSet, m_controller, &GameController::setLogLevel); - connect(&m_log, &LogController::levelsEnabled, m_controller, &GameController::enableLogLevel); - connect(&m_log, &LogController::levelsDisabled, m_controller, &GameController::disableLogLevel); - connect(this, &Window::startDrawing, m_display, &Display::startDrawing, Qt::QueuedConnection); - connect(this, &Window::shutdown, m_display, &Display::stopDrawing); - connect(this, &Window::shutdown, m_controller, &GameController::closeGame); connect(this, &Window::shutdown, m_logView, &QWidget::hide); - connect(this, &Window::audioBufferSamplesChanged, m_controller, &GameController::setAudioBufferSamples); - connect(this, &Window::sampleRateChanged, m_controller, &GameController::setAudioSampleRate); - connect(this, &Window::fpsTargetChanged, m_controller, &GameController::setFPSTarget); - connect(&m_inputController, &InputController::keyPressed, m_controller, &GameController::keyPressed); - connect(&m_inputController, &InputController::keyReleased, m_controller, &GameController::keyReleased); - connect(&m_inputController, &InputController::keyAutofire, m_controller, &GameController::setAutofire); connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck); - connect(m_display, &Display::hideCursor, [this]() { - if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display) { - m_screenWidget->setCursor(Qt::BlankCursor); - } - }); - connect(m_display, &Display::showCursor, [this]() { - m_screenWidget->unsetCursor(); - }); m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);

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

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

@@ -230,18 +171,14 @@

void Window::argumentsPassed(mArguments* args) { loadConfig(); - if (args->patch) { - m_controller->loadPatch(args->patch); - } - if (args->fname) { - m_controller->loadGame(args->fname); + setController(m_manager->loadGame(args->fname), args->fname); } #ifdef USE_GDB_STUB if (args->debuggerType == DEBUGGER_GDB) { if (!m_gdbController) { - m_gdbController = new GDBController(m_controller, this); + m_gdbController = new GDBController(this); m_gdbController->listen(); } }

@@ -264,19 +201,6 @@ void Window::loadConfig() {

const mCoreOptions* opts = m_config->options(); reloadConfig(); - // TODO: Move these to ConfigController - if (opts->fpsTarget) { - emit fpsTargetChanged(opts->fpsTarget); - } - - if (opts->audioBuffers) { - emit audioBufferSamplesChanged(opts->audioBuffers); - } - - if (opts->sampleRate) { - emit sampleRateChanged(opts->sampleRate); - } - if (opts->width && opts->height) { resizeFrame(QSize(opts->width, opts->height)); }

@@ -285,22 +209,10 @@ if (opts->fullscreen) {

enterFullScreen(); } -#if defined(BUILD_GL) || defined(BUILD_GLES) - if (opts->shader) { - struct VDir* shader = VDirOpen(opts->shader); - if (shader) { - m_display->setShaders(shader); - m_shaderView->refreshShaders(); - shader->close(shader); - } - } -#endif - m_mruFiles = m_config->getMRU(); updateMRU(); m_inputController.setConfiguration(m_config); - m_controller->setUseBIOS(opts->useBios); } void Window::reloadConfig() {

@@ -308,7 +220,13 @@ const mCoreOptions* opts = m_config->options();

m_log.setLevels(opts->logLevel); - m_controller->setConfig(m_config->config()); + if (m_controller) { + m_controller->loadConfig(m_config); + if (m_audioProcessor) { + m_audioProcessor->setBufferSamples(opts->audioBuffers); + m_audioProcessor->requestSampleRate(opts->sampleRate); + } + } m_display->lockAspectRatio(opts->lockAspectRatio); m_display->filter(opts->resampleVideo);

@@ -381,7 +299,7 @@

void Window::selectROM() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters()); if (!filename.isEmpty()) { - m_controller->loadGame(filename); + setController(m_manager->loadGame(filename), filename); } }

@@ -396,7 +314,7 @@ connect(archiveInspector, &QDialog::accepted, [this, archiveInspector]() {

VFile* output = archiveInspector->selectedVFile(); QPair<QString, QString> path = archiveInspector->selectedPath(); if (output) { - m_controller->loadGame(output, path.second, path.first); + setController(m_manager->loadGame(output, path.second, path.first), path.first + "/" + path.second); } archiveInspector->close(); });

@@ -430,28 +348,32 @@ }

} void Window::multiplayerChanged() { + if (!m_controller) { + return; + } int attached = 1; MultiplayerController* multiplayer = m_controller->multiplayerController(); if (multiplayer) { attached = multiplayer->attached(); } - if (m_controller->isLoaded()) { - for (QAction* action : m_nonMpActions) { - action->setDisabled(attached > 1); - } + for (QAction* action : m_nonMpActions) { + action->setDisabled(attached > 1); } } void Window::selectPatch() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select patch"), tr("Patches (*.ips *.ups *.bps)")); if (!filename.isEmpty()) { - m_controller->loadPatch(filename); + if (m_controller) { + m_controller->loadPatch(filename); + } else { + m_pendingPatch = filename; + } } } void Window::openView(QWidget* widget) { connect(this, &Window::shutdown, widget, &QWidget::close); - connect(m_controller, &GameController::gameStopped, widget, &QWidget::close); widget->setAttribute(Qt::WA_DeleteOnClose); widget->show(); }

@@ -474,23 +396,17 @@ void Window::openSettingsWindow() {

SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController); #if defined(BUILD_GL) || defined(BUILD_GLES) if (m_display->supportsShaders()) { - settingsWindow->setShaderSelector(m_shaderView); + settingsWindow->setShaderSelector(m_shaderView.get()); } #endif - connect(settingsWindow, &SettingsView::biosLoaded, m_controller, &GameController::loadBIOS); - connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver); - connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart); + connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::reloadDisplayDriver); + connect(settingsWindow, &SettingsView::audioDriverChanged, this, &Window::reloadAudioDriver); connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart); connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig); connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear); openView(settingsWindow); } -void Window::openAboutScreen() { - AboutScreen* about = new AboutScreen(); - openView(about); -} - void Window::startVideoLog() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Select video log"), tr("Video logs (*.mvl)")); if (!filename.isEmpty()) {

@@ -498,18 +414,19 @@ m_controller->startVideoLog(filename);

} } -template <typename T, typename A> -std::function<void()> Window::openTView(A arg) { +template <typename T, typename... A> +std::function<void()> Window::openTView(A... arg) { return [=]() { - T* view = new T(m_controller, arg); + T* view = new T(arg...); openView(view); }; } -template <typename T> -std::function<void()> Window::openTView() { + +template <typename T, typename... A> +std::function<void()> Window::openControllerTView(A... arg) { return [=]() { - T* view = new T(m_controller); + T* view = new T(m_controller, arg...); openView(view); }; }

@@ -518,15 +435,8 @@ #ifdef USE_FFMPEG

void Window::openVideoWindow() { if (!m_videoView) { m_videoView = new VideoView(); - connect(m_videoView, &VideoView::recordingStarted, m_controller, &GameController::setAVStream); - connect(m_videoView, &VideoView::recordingStopped, m_controller, &GameController::clearAVStream, Qt::DirectConnection); - connect(m_controller, &GameController::gameStopped, m_videoView, &VideoView::stopRecording); - connect(m_controller, &GameController::gameStopped, m_videoView, &QWidget::close); - connect(m_controller, &GameController::gameStarted, [this]() { - m_videoView->setNativeResolution(m_controller->screenDimensions()); - }); - if (m_controller->isLoaded()) { - m_videoView->setNativeResolution(m_controller->screenDimensions()); + if (m_controller) { + m_videoView->setController(m_controller); } connect(this, &Window::shutdown, m_videoView, &QWidget::close); }

@@ -538,10 +448,9 @@ #ifdef USE_MAGICK

void Window::openGIFWindow() { if (!m_gifView) { m_gifView = new GIFView(); - connect(m_gifView, &GIFView::recordingStarted, m_controller, &GameController::setAVStream); - connect(m_gifView, &GIFView::recordingStopped, m_controller, &GameController::clearAVStream, Qt::DirectConnection); - connect(m_controller, &GameController::gameStopped, m_gifView, &GIFView::stopRecording); - connect(m_controller, &GameController::gameStopped, m_gifView, &QWidget::close); + if (m_controller) { + m_gifView->setController(m_controller); + } connect(this, &Window::shutdown, m_gifView, &QWidget::close); } m_gifView->show();

@@ -551,9 +460,11 @@

#ifdef USE_GDB_STUB void Window::gdbOpen() { if (!m_gdbController) { - m_gdbController = new GDBController(m_controller, this); + m_gdbController = new GDBController(this); } GDBWindow* window = new GDBWindow(m_gdbController); + m_gdbController->setController(m_controller); + connect(m_controller.get(), &CoreController::stopping, window, &QWidget::close); openView(window); } #endif

@@ -561,9 +472,12 @@

#ifdef USE_DEBUGGERS void Window::consoleOpen() { if (!m_console) { - m_console = new DebuggerConsoleController(m_controller, this); + m_console = new DebuggerConsoleController(this); } DebuggerConsole* window = new DebuggerConsole(m_console); + if (m_controller) { + m_console->setController(m_controller); + } openView(window); } #endif

@@ -576,7 +490,7 @@ }

int factor = 0; QSize size(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - if (m_controller->isLoaded()) { + if (m_controller) { size = m_controller->screenDimensions(); } if (m_screenWidget->width() % size.width() == 0 && m_screenWidget->height() % size.height() == 0 &&

@@ -631,9 +545,6 @@ m_display->forceDraw();

} void Window::focusOutEvent(QFocusEvent*) { - m_controller->setTurbo(false, false); - m_controller->stopRewinding(); - m_controller->clearKeys(); } void Window::dragEnterEvent(QDragEnterEvent* event) {

@@ -655,7 +566,7 @@ // No remote loading

return; } event->accept(); - m_controller->loadGame(url.toLocalFile()); + setController(m_manager->loadGame(url.toLocalFile()), url.toLocalFile()); } void Window::mouseDoubleClickEvent(QMouseEvent* event) {

@@ -675,7 +586,7 @@ return;

} showFullScreen(); #ifndef Q_OS_MAC - if (m_controller->isLoaded() && !m_controller->isPaused()) { + if (m_controller && !m_controller->isPaused()) { menuBar()->hide(); } #endif

@@ -698,34 +609,27 @@ enterFullScreen();

} } -void Window::gameStarted(mCoreThread* context, const QString& fname) { - if (!mCoreThreadIsActive(context)) { - return; - } - emit startDrawing(context); +void Window::gameStarted() { for (QAction* action : m_gameActions) { action->setDisabled(false); } #ifdef M_CORE_GBA for (QAction* action : m_gbaActions) { - action->setDisabled(context->core->platform(context->core) != PLATFORM_GBA); + action->setDisabled(m_controller->platform() != PLATFORM_GBA); } #endif multiplayerChanged(); - if (!fname.isEmpty()) { - setWindowFilePath(fname); - appendMRU(fname); - } updateTitle(); - unsigned width, height; - context->core->desiredVideoDimensions(context->core, &width, &height); - m_display->setMinimumSize(width, height); + QSize size = m_controller->screenDimensions(); + m_display->setMinimumSize(size); m_screenWidget->setMinimumSize(m_display->minimumSize()); + m_screenWidget->setDimensions(size.width(), size.height()); m_config->updateOption("lockIntegerScaling"); + m_config->updateOption("lockAspectRatio"); if (m_savedScale > 0) { - resizeFrame(QSize(width, height) * m_savedScale); + resizeFrame(size * m_savedScale); } - attachWidget(m_display); + attachWidget(m_display.get()); #ifndef Q_OS_MAC if (isFullScreen()) {

@@ -737,40 +641,40 @@ m_hitUnimplementedBiosCall = false;

m_fpsTimer.start(); m_focusCheck.start(); - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { - m_inputController.setPlatform(m_controller->platform()); - - mCore* core = m_controller->thread()->core; - const mCoreChannelInfo* videoLayers; - const mCoreChannelInfo* audioChannels; - size_t nVideo = core->listVideoLayers(core, &videoLayers); - size_t nAudio = core->listAudioChannels(core, &audioChannels); + CoreController::Interrupter interrupter(m_controller, true); + mCore* core = m_controller->thread()->core; + m_videoLayers->clear(); + m_audioChannels->clear(); + const mCoreChannelInfo* videoLayers; + const mCoreChannelInfo* audioChannels; + size_t nVideo = core->listVideoLayers(core, &videoLayers); + size_t nAudio = core->listAudioChannels(core, &audioChannels); - if (nVideo) { - for (size_t i = 0; i < nVideo; ++i) { - QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); - action->setCheckable(true); - action->setChecked(true); - connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { - m_controller->setVideoLayerEnabled(videoLayers[i].id, enable); - }); - m_videoLayers->addAction(action); - } + if (nVideo) { + for (size_t i = 0; i < nVideo; ++i) { + QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { + m_controller->thread()->core->enableVideoLayer(m_controller->thread()->core, videoLayers[i].id, enable); + }); + m_videoLayers->addAction(action); } - if (nAudio) { - for (size_t i = 0; i < nAudio; ++i) { - QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); - action->setCheckable(true); - action->setChecked(true); - connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { - m_controller->setAudioChannelEnabled(audioChannels[i].id, enable); - }); - m_audioChannels->addAction(action); - } + } + if (nAudio) { + for (size_t i = 0; i < nAudio; ++i) { + QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { + m_controller->thread()->core->enableAudioChannel(m_controller->thread()->core, audioChannels[i].id, enable); + }); + m_audioChannels->addAction(action); } } - m_controller->threadContinue(); + m_display->startDrawing(m_controller); + + reloadAudioDriver(); } void Window::gameStopped() {

@@ -784,9 +688,10 @@ action->setDisabled(true);

} setWindowFilePath(QString()); updateTitle(); - detachWidget(m_display); - m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); + detachWidget(m_display.get()); + m_screenWidget->setDimensions(m_logo.width(), m_logo.height()); m_screenWidget->setLockIntegerScaling(false); + m_screenWidget->setLockAspectRatio(true); m_screenWidget->setPixmap(m_logo); m_screenWidget->unsetCursor(); #ifdef M_CORE_GB

@@ -801,6 +706,8 @@ m_audioChannels->clear();

m_fpsTimer.stop(); m_focusCheck.stop(); + + emit paused(false); } void Window::gameCrashed(const QString& errorMessage) {

@@ -809,7 +716,7 @@ tr("The game has crashed with the following error:\n\n%1").arg(errorMessage),

QMessageBox::Ok, this, Qt::Sheet); crash->setAttribute(Qt::WA_DeleteOnClose); crash->show(); - connect(m_controller, &GameController::gameStarted, crash, &QWidget::close); + m_controller->stop(); } void Window::gameFailed() {

@@ -818,7 +725,6 @@ tr("Could not load game. Are you sure it's in the correct format?"),

QMessageBox::Ok, this, Qt::Sheet); fail->setAttribute(Qt::WA_DeleteOnClose); fail->show(); - connect(m_controller, &GameController::gameStarted, fail, &QWidget::close); } void Window::unimplementedBiosCall(int call) {

@@ -835,6 +741,74 @@ fail->setAttribute(Qt::WA_DeleteOnClose);

fail->show(); } +void Window::reloadDisplayDriver() { + if (m_controller) { + m_display->stopDrawing(); + detachWidget(m_display.get()); + } + m_display = std::move(std::unique_ptr<Display>(Display::create(this))); +#if defined(BUILD_GL) || defined(BUILD_GLES) + m_shaderView.reset(); + m_shaderView = std::make_unique<ShaderSelector>(m_display.get(), m_config); +#endif + m_screenWidget->setMinimumSize(m_display->minimumSize()); + m_screenWidget->setSizePolicy(m_display->sizePolicy()); + connect(this, &Window::shutdown, m_display.get(), &Display::stopDrawing); + connect(m_display.get(), &Display::hideCursor, [this]() { + if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display.get()) { + m_screenWidget->setCursor(Qt::BlankCursor); + } + }); + connect(m_display.get(), &Display::showCursor, [this]() { + m_screenWidget->unsetCursor(); + }); + + const mCoreOptions* opts = m_config->options(); + m_display->lockAspectRatio(opts->lockAspectRatio); + m_display->filter(opts->resampleVideo); +#if defined(BUILD_GL) || defined(BUILD_GLES) + if (opts->shader) { + struct VDir* shader = VDirOpen(opts->shader); + if (shader && m_display->supportsShaders()) { + m_display->setShaders(shader); + m_shaderView->refreshShaders(); + shader->close(shader); + } + } +#endif + + if (m_controller) { + connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); + connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing); + connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); + connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); + connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + + attachWidget(m_display.get()); + m_display->startDrawing(m_controller); + } +} + +void Window::reloadAudioDriver() { + if (!m_controller) { + return; + } + if (m_audioProcessor) { + m_audioProcessor->stop(); + m_audioProcessor.reset(); + } + + const mCoreOptions* opts = m_config->options(); + m_audioProcessor = std::move(std::unique_ptr<AudioProcessor>(AudioProcessor::create())); + m_audioProcessor->setInput(m_controller); + m_audioProcessor->setBufferSamples(opts->audioBuffers); + m_audioProcessor->requestSampleRate(opts->sampleRate); + m_audioProcessor->start(); + connect(m_controller.get(), &CoreController::stopping, m_audioProcessor.get(), &AudioProcessor::stop); +} + void Window::tryMakePortable() { QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"), tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"),

@@ -873,8 +847,8 @@

void Window::updateTitle(float fps) { QString title; - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { + if (m_controller) { + CoreController::Interrupter interrupter(m_controller); const NoIntroDB* db = GBAApp::app()->gameDB(); NoIntroGame game{}; uint32_t crc32 = 0;

@@ -890,19 +864,18 @@ if (db && crc32 && NoIntroDBLookupGameByCRC(db, crc32, &game)) {

title = QLatin1String(game.name); } #endif - } - MultiplayerController* multiplayer = m_controller->multiplayerController(); - if (multiplayer && multiplayer->attached() > 1) { - title += tr(" - Player %1 of %2").arg(multiplayer->playerId(m_controller) + 1).arg(multiplayer->attached()); - for (QAction* action : m_nonMpActions) { - action->setDisabled(true); - } - } else if (m_controller->isLoaded()) { - for (QAction* action : m_nonMpActions) { - action->setDisabled(false); + MultiplayerController* multiplayer = m_controller->multiplayerController(); + if (multiplayer && multiplayer->attached() > 1) { + title += tr(" - Player %1 of %2").arg(multiplayer->playerId(m_controller.get()) + 1).arg(multiplayer->attached()); + for (QAction* action : m_nonMpActions) { + action->setDisabled(true); + } + } else { + for (QAction* action : m_nonMpActions) { + action->setDisabled(false); + } } } - m_controller->threadContinue(); if (title.isNull()) { setWindowTitle(tr("%1 - %2").arg(projectName).arg(projectVersion)); } else if (fps < 0) {

@@ -923,7 +896,6 @@ }

bool wasPaused = m_controller->isPaused(); m_stateWindow = new LoadSaveState(m_controller); connect(this, &Window::shutdown, m_stateWindow, &QWidget::close); - connect(m_controller, &GameController::gameStopped, m_stateWindow, &QWidget::close); connect(m_stateWindow, &LoadSaveState::closed, [this]() { detachWidget(m_stateWindow); m_stateWindow = nullptr;

@@ -931,7 +903,11 @@ QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection);

}); if (!wasPaused) { m_controller->setPaused(true); - connect(m_stateWindow, &LoadSaveState::closed, [this]() { m_controller->setPaused(false); }); + connect(m_stateWindow, &LoadSaveState::closed, [this]() { + if (m_controller) { + m_controller->setPaused(false); + } + }); } m_stateWindow->setAttribute(Qt::WA_DeleteOnClose); m_stateWindow->setMode(ls);

@@ -959,17 +935,18 @@ addControlledAction(fileMenu, loadTemporarySave, "loadTemporarySave");

addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); +#ifdef M_CORE_GBA QAction* bootBIOS = new QAction(tr("Boot BIOS"), fileMenu); connect(bootBIOS, &QAction::triggered, [this]() { - m_controller->loadBIOS(PLATFORM_GBA, m_config->getOption("gba.bios")); - m_controller->bootBIOS(); + setController(m_manager->loadBIOS(PLATFORM_GBA, m_config->getOption("gba.bios")), QString()); }); addControlledAction(fileMenu, bootBIOS, "bootBIOS"); +#endif addControlledAction(fileMenu, fileMenu->addAction(tr("Replace ROM..."), this, SLOT(replaceROM())), "replaceROM"); QAction* romInfo = new QAction(tr("ROM &info..."), fileMenu); - connect(romInfo, &QAction::triggered, openTView<ROMInfo>()); + connect(romInfo, &QAction::triggered, openControllerTView<ROMInfo>()); m_gameActions.append(romInfo); addControlledAction(fileMenu, romInfo, "romInfo");

@@ -999,13 +976,17 @@ QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));

QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); - connect(quickLoad, &QAction::triggered, m_controller, &GameController::loadState); + connect(quickLoad, &QAction::triggered, [this] { + m_controller->loadState(); + }); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); - connect(quickSave, &QAction::triggered, m_controller, &GameController::saveState); + connect(quickLoad, &QAction::triggered, [this] { + m_controller->saveState(); + }); m_gameActions.append(quickSave); m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, "quickSave");

@@ -1015,14 +996,18 @@ quickSaveMenu->addSeparator();

QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu); undoLoadState->setShortcut(tr("F11")); - connect(undoLoadState, &QAction::triggered, m_controller, &GameController::loadBackupState); + connect(undoLoadState, &QAction::triggered, [this]() { + m_controller->loadBackupState(); + }); m_gameActions.append(undoLoadState); m_nonMpActions.append(undoLoadState); addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); undoSaveState->setShortcut(tr("Shift+F11")); - connect(undoSaveState, &QAction::triggered, m_controller, &GameController::saveBackupState); + connect(undoSaveState, &QAction::triggered, [this]() { + m_controller->saveBackupState(); + }); m_gameActions.append(undoSaveState); m_nonMpActions.append(undoSaveState); addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState");

@@ -1034,14 +1019,18 @@ int i;

for (i = 1; i < 10; ++i) { quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); quickLoad->setShortcut(tr("F%1").arg(i)); - connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); + connect(quickLoad, &QAction::triggered, [this, i]() { + m_controller->loadState(i); + }); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); quickSave->setShortcut(tr("Shift+F%1").arg(i)); - connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); }); + connect(quickSave, &QAction::triggered, [this, i]() { + m_controller->saveState(i); + }); m_gameActions.append(quickSave); m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i));

@@ -1074,7 +1063,7 @@ fileMenu->addSeparator();

#endif QAction* about = new QAction(tr("About"), fileMenu); - connect(about, &QAction::triggered, this, &Window::openAboutScreen); + connect(about, &QAction::triggered, openTView<AboutScreen>()); fileMenu->addAction(about); #ifndef Q_OS_MAC

@@ -1084,18 +1073,24 @@

QMenu* emulationMenu = menubar->addMenu(tr("&Emulation")); QAction* reset = new QAction(tr("&Reset"), emulationMenu); reset->setShortcut(tr("Ctrl+R")); - connect(reset, &QAction::triggered, m_controller, &GameController::reset); + connect(reset, &QAction::triggered, [this]() { + m_controller->reset(); + }); m_gameActions.append(reset); addControlledAction(emulationMenu, reset, "reset"); QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu); - connect(shutdown, &QAction::triggered, m_controller, &GameController::closeGame); + connect(shutdown, &QAction::triggered, [this]() { + m_controller->stop(); + }); m_gameActions.append(shutdown); addControlledAction(emulationMenu, shutdown, "shutdown"); #ifdef M_CORE_GBA QAction* yank = new QAction(tr("Yank game pak"), emulationMenu); - connect(yank, &QAction::triggered, m_controller, &GameController::yankPak); + connect(yank, &QAction::triggered, [this]() { + m_controller->yankPak(); + }); m_gameActions.append(yank); m_gbaActions.append(yank); addControlledAction(emulationMenu, yank, "yank");

@@ -1106,39 +1101,44 @@ QAction* pause = new QAction(tr("&Pause"), emulationMenu);

pause->setChecked(false); pause->setCheckable(true); pause->setShortcut(tr("Ctrl+P")); - connect(pause, &QAction::triggered, m_controller, &GameController::setPaused); - connect(m_controller, &GameController::gamePaused, [this, pause]() { - pause->setChecked(true); + connect(pause, &QAction::triggered, [this](bool paused) { + m_controller->setPaused(paused); + }); + connect(this, &Window::paused, [pause](bool paused) { + pause->setChecked(paused); }); - connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); }); m_gameActions.append(pause); addControlledAction(emulationMenu, pause, "pause"); QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu); frameAdvance->setShortcut(tr("Ctrl+N")); - connect(frameAdvance, &QAction::triggered, m_controller, &GameController::frameAdvance); + connect(frameAdvance, &QAction::triggered, [this]() { + m_controller->frameAdvance(); + }); m_gameActions.append(frameAdvance); addControlledAction(emulationMenu, frameAdvance, "frameAdvance"); emulationMenu->addSeparator(); m_inputController.inputIndex()->addItem(qMakePair([this]() { - m_controller->setTurbo(true, false); + m_controller->setFastForward(true); }, [this]() { - m_controller->setTurbo(false, false); + m_controller->setFastForward(false); }), tr("Fast forward (held)"), "holdFastForward", emulationMenu)->setShortcut(QKeySequence(Qt::Key_Tab)[0]); QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu); turbo->setCheckable(true); turbo->setChecked(false); turbo->setShortcut(tr("Shift+Tab")); - connect(turbo, SIGNAL(triggered(bool)), m_controller, SLOT(setTurbo(bool))); + connect(turbo, &QAction::triggered, [this](bool value) { + m_controller->forceFastForward(value); + }); addControlledAction(emulationMenu, turbo, "fastForward"); QMenu* ffspeedMenu = emulationMenu->addMenu(tr("Fast forward speed")); ConfigOption* ffspeed = m_config->addOption("fastForwardRatio"); ffspeed->connect([this](const QVariant& value) { - m_controller->setTurboSpeed(value.toFloat()); + reloadConfig(); }, this); ffspeed->addValue(tr("Unbounded"), -1.0f, ffspeedMenu); ffspeed->setValue(QVariant(-1.0f));

@@ -1149,14 +1149,16 @@ }

m_config->updateOption("fastForwardRatio"); m_inputController.inputIndex()->addItem(qMakePair([this]() { - m_controller->startRewinding(); + m_controller->setRewinding(true); }, [this]() { - m_controller->stopRewinding(); + m_controller->setRewinding(false); }), tr("Rewind (held)"), "holdRewind", emulationMenu)->setShortcut(QKeySequence("`")[0]); QAction* rewind = new QAction(tr("Re&wind"), emulationMenu); rewind->setShortcut(tr("~")); - connect(rewind, &QAction::triggered, m_controller, &GameController::rewind); + connect(rewind, &QAction::triggered, [this]() { + m_controller->rewind(); + }); m_gameActions.append(rewind); m_nonMpActions.append(rewind); addControlledAction(emulationMenu, rewind, "rewind");

@@ -1173,14 +1175,14 @@

ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); videoSync->connect([this](const QVariant& value) { - m_controller->setVideoSync(value.toBool()); + reloadConfig(); }, this); m_config->updateOption("videoSync"); ConfigOption* audioSync = m_config->addOption("audioSync"); audioSync->addBoolean(tr("Sync to &audio"), emulationMenu); audioSync->connect([this](const QVariant& value) { - m_controller->setAudioSync(value.toBool()); + reloadConfig(); }, this); m_config->updateOption("audioSync");

@@ -1188,26 +1190,26 @@ emulationMenu->addSeparator();

QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); - connect(solarIncrease, &QAction::triggered, m_controller, &GameController::increaseLuminanceLevel); + connect(solarIncrease, &QAction::triggered, &m_inputController, &InputController::increaseLuminanceLevel); addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu); - connect(solarDecrease, &QAction::triggered, m_controller, &GameController::decreaseLuminanceLevel); + connect(solarDecrease, &QAction::triggered, &m_inputController, &InputController::decreaseLuminanceLevel); addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel"); QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu); - connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); }); + connect(maxSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(10); }); addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel"); QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu); - connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); }); + connect(minSolar, &QAction::triggered, [this]() { m_inputController.setLuminanceLevel(0); }); addControlledAction(solarMenu, minSolar, "minLuminanceLevel"); solarMenu->addSeparator(); for (int i = 0; i <= 10; ++i) { QAction* setSolar = new QAction(tr("Brightness %1").arg(QString::number(i)), solarMenu); connect(setSolar, &QAction::triggered, [this, i]() { - m_controller->setLuminanceLevel(i); + m_inputController.setLuminanceLevel(i); }); addControlledAction(solarMenu, setSolar, QString("luminanceLevel.%1").arg(QString::number(i))); }

@@ -1223,7 +1225,7 @@ }

connect(setSize, &QAction::triggered, [this, i, setSize]() { showNormal(); QSize size(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - if (m_controller->isLoaded()) { + if (m_controller) { size = m_controller->screenDimensions(); } size *= i;

@@ -1249,6 +1251,9 @@ ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio");

lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); lockAspectRatio->connect([this](const QVariant& value) { m_display->lockAspectRatio(value.toBool()); + if (m_controller) { + m_screenWidget->setLockAspectRatio(value.toBool()); + } }, this); m_config->updateOption("lockAspectRatio");

@@ -1256,7 +1261,7 @@ ConfigOption* lockIntegerScaling = m_config->addOption("lockIntegerScaling");

lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu); lockIntegerScaling->connect([this](const QVariant& value) { m_display->lockIntegerScaling(value.toBool()); - if (m_controller->isLoaded()) { + if (m_controller) { m_screenWidget->setLockIntegerScaling(value.toBool()); } }, this);

@@ -1292,7 +1297,7 @@

QMenu* target = avMenu->addMenu(tr("FPS target")); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget"); fpsTargetOption->connect([this](const QVariant& value) { - emit fpsTargetChanged(value.toFloat()); + reloadConfig(); }, this); fpsTargetOption->addValue(tr("15"), 15, target); fpsTargetOption->addValue(tr("30"), 30, target);

@@ -1311,7 +1316,9 @@

#ifdef USE_PNG QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu); screenshot->setShortcut(tr("F12")); - connect(screenshot, &QAction::triggered, m_controller, &GameController::screenshot); + connect(screenshot, &QAction::triggered, [this]() { + m_controller->screenshot(); + }); m_gameActions.append(screenshot); addControlledAction(avMenu, screenshot, "screenshot"); #endif

@@ -1335,7 +1342,9 @@ addControlledAction(avMenu, recordVL, "recordVL");

m_gameActions.append(recordVL); QAction* stopVL = new QAction(tr("Stop video log"), avMenu); - connect(stopVL, &QAction::triggered, m_controller, &GameController::endVideoLog); + connect(stopVL, &QAction::triggered, [this]() { + m_controller->endVideoLog(); + }); addControlledAction(avMenu, stopVL, "stopVL"); m_gameActions.append(stopVL);

@@ -1349,7 +1358,16 @@ connect(viewLogs, &QAction::triggered, m_logView, &QWidget::show);

addControlledAction(toolsMenu, viewLogs, "viewLogs"); QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu); - connect(overrides, &QAction::triggered, openTView<OverrideView, ConfigController*>(m_config)); + connect(overrides, &QAction::triggered, [this]() { + if (!m_overrideView) { + m_overrideView = new OverrideView(m_config); + if (m_controller) { + m_overrideView->setController(m_controller); + } + connect(this, &Window::shutdown, m_overrideView, &QWidget::close); + } + m_overrideView->show(); + }); addControlledAction(toolsMenu, overrides, "overrideWindow"); QAction* sensors = new QAction(tr("Game &Pak sensors..."), toolsMenu);

@@ -1357,7 +1375,7 @@ connect(sensors, &QAction::triggered, openTView<SensorView, InputController*>(&m_inputController));

addControlledAction(toolsMenu, sensors, "sensorWindow"); QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu); - connect(cheats, &QAction::triggered, openTView<CheatsView>()); + connect(cheats, &QAction::triggered, openControllerTView<CheatsView>()); m_gameActions.append(cheats); addControlledAction(toolsMenu, cheats, "cheatsWindow");

@@ -1377,38 +1395,39 @@ #ifdef USE_GDB_STUB

QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); connect(gdbWindow, &QAction::triggered, this, &Window::gdbOpen); m_gbaActions.append(gdbWindow); + m_gameActions.append(gdbWindow); addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif toolsMenu->addSeparator(); QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu); - connect(paletteView, &QAction::triggered, openTView<PaletteView>()); + connect(paletteView, &QAction::triggered, openControllerTView<PaletteView>()); m_gameActions.append(paletteView); addControlledAction(toolsMenu, paletteView, "paletteWindow"); QAction* objView = new QAction(tr("View &sprites..."), toolsMenu); - connect(objView, &QAction::triggered, openTView<ObjView>()); + connect(objView, &QAction::triggered, openControllerTView<ObjView>()); m_gameActions.append(objView); addControlledAction(toolsMenu, objView, "spriteWindow"); QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu); - connect(tileView, &QAction::triggered, openTView<TileView>()); + connect(tileView, &QAction::triggered, openControllerTView<TileView>()); m_gameActions.append(tileView); addControlledAction(toolsMenu, tileView, "tileWindow"); QAction* memoryView = new QAction(tr("View memory..."), toolsMenu); - connect(memoryView, &QAction::triggered, openTView<MemoryView>()); + connect(memoryView, &QAction::triggered, openControllerTView<MemoryView>()); m_gameActions.append(memoryView); addControlledAction(toolsMenu, memoryView, "memoryView"); QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu); - connect(memorySearch, &QAction::triggered, openTView<MemorySearch>()); + connect(memorySearch, &QAction::triggered, openControllerTView<MemorySearch>()); m_gameActions.append(memorySearch); addControlledAction(toolsMenu, memorySearch, "memorySearch"); #ifdef M_CORE_GBA QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu); - connect(ioViewer, &QAction::triggered, openTView<IOViewer>()); + connect(ioViewer, &QAction::triggered, openControllerTView<IOViewer>()); m_gameActions.append(ioViewer); m_gbaActions.append(ioViewer); addControlledAction(toolsMenu, ioViewer, "ioViewer");

@@ -1421,17 +1440,17 @@ }, this);

ConfigOption* useBios = m_config->addOption("useBios"); useBios->connect([this](const QVariant& value) { - m_controller->setUseBIOS(value.toBool()); + reloadConfig(); }, this); ConfigOption* buffers = m_config->addOption("audioBuffers"); buffers->connect([this](const QVariant& value) { - emit audioBufferSamplesChanged(value.toInt()); + reloadConfig(); }, this); ConfigOption* sampleRate = m_config->addOption("sampleRate"); sampleRate->connect([this](const QVariant& value) { - emit sampleRateChanged(value.toUInt()); + reloadConfig(); }, this); ConfigOption* volume = m_config->addOption("volume");

@@ -1441,39 +1460,37 @@ }, this);

ConfigOption* rewindEnable = m_config->addOption("rewindEnable"); rewindEnable->connect([this](const QVariant& value) { - m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindSave").toInt()); + reloadConfig(); }, this); ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity"); rewindBufferCapacity->connect([this](const QVariant& value) { - m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindSave").toInt()); + reloadConfig(); }, this); ConfigOption* rewindSave = m_config->addOption("rewindSave"); rewindBufferCapacity->connect([this](const QVariant& value) { - m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toBool()); + reloadConfig(); }, this); ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections"); allowOpposingDirections->connect([this](const QVariant& value) { - m_inputController.setAllowOpposing(value.toBool()); + reloadConfig(); }, this); ConfigOption* saveStateExtdata = m_config->addOption("saveStateExtdata"); saveStateExtdata->connect([this](const QVariant& value) { - m_controller->setSaveStateExtdata(value.toInt()); + reloadConfig(); }, this); - m_config->updateOption("saveStateExtdata"); ConfigOption* loadStateExtdata = m_config->addOption("loadStateExtdata"); loadStateExtdata->connect([this](const QVariant& value) { - m_controller->setLoadStateExtdata(value.toInt()); + reloadConfig(); }, this); - m_config->updateOption("loadStateExtdata"); ConfigOption* preload = m_config->addOption("preload"); preload->connect([this](const QVariant& value) { - m_controller->setPreload(value.toBool()); + m_manager->setPreload(value.toBool()); }, this); m_config->updateOption("preload");

@@ -1523,7 +1540,9 @@ int i = 0;

for (const QString& file : m_mruFiles) { QAction* item = new QAction(QDir::toNativeSeparators(file).replace("&", "&&"), m_mruMenu); item->setShortcut(QString("Ctrl+%1").arg(i)); - connect(item, &QAction::triggered, [this, file]() { m_controller->loadGame(file); }); + connect(item, &QAction::triggered, [this, file]() { + setController(m_manager->loadGame(file), file); + }); m_mruMenu->addAction(item); ++i; }

@@ -1546,7 +1565,7 @@ return action;

} void Window::focusCheck() { - if (!m_config->getOption("pauseOnFocusLost").toInt()) { + if (!m_config->getOption("pauseOnFocusLost").toInt() || !m_controller) { return; } if (QGuiApplication::focusWindow() && m_autoresume) {

@@ -1558,6 +1577,107 @@ m_controller->setPaused(true);

} } +void Window::setController(CoreController* controller, const QString& fname) { + if (!controller) { + return; + } + if (!fname.isEmpty()) { + setWindowFilePath(fname); + appendMRU(fname); + } + + if (m_controller) { + m_controller->disconnect(this); + m_controller->stop(); + m_controller.reset(); + } + + m_controller = std::shared_ptr<CoreController>(controller); + m_inputController.recalibrateAxes(); + m_controller->setInputController(&m_inputController); + m_controller->setLogger(&m_log); + + connect(this, &Window::shutdown, [this]() { + if (!m_controller) { + return; + } + m_controller->stop(); + }); + + 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, &m_inputController, &InputController::resumeScreensaver); + connect(m_controller.get(), &CoreController::paused, [this]() { + QSize size = m_controller->screenDimensions(); + QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(), + size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888); + QPixmap pixmap; + pixmap.convertFromImage(currentImage); + m_screenWidget->setPixmap(pixmap); + emit paused(true); + }); + +#ifndef Q_OS_MAC + connect(m_controller.get(), &CoreController::paused, menuBar(), &QWidget::show); + connect(m_controller.get(), &CoreController::unpaused, [this]() { + if(isFullScreen()) { + menuBar()->hide(); + } + }); +#endif + + connect(m_controller.get(), &CoreController::paused, &m_inputController, &InputController::resumeScreensaver); + connect(m_controller.get(), &CoreController::unpaused, [this]() { + emit paused(false); + }); + + connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing); + connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw); + connect(m_controller.get(), &CoreController::paused, m_display.get(), &Display::pauseDrawing); + connect(m_controller.get(), &CoreController::unpaused, m_display.get(), &Display::unpauseDrawing); + connect(m_controller.get(), &CoreController::frameAvailable, m_display.get(), &Display::framePosted); + connect(m_controller.get(), &CoreController::statusPosted, m_display.get(), &Display::showMessage); + + connect(m_controller.get(), &CoreController::unpaused, &m_inputController, &InputController::suspendScreensaver); + connect(m_controller.get(), &CoreController::frameAvailable, this, &Window::recordFrame); + connect(m_controller.get(), &CoreController::crashed, this, &Window::gameCrashed); + connect(m_controller.get(), &CoreController::failed, this, &Window::gameFailed); + connect(m_controller.get(), &CoreController::unimplementedBiosCall, this, &Window::unimplementedBiosCall); + + if (m_gdbController) { + m_gdbController->setController(m_controller); + } + if (m_console) { + m_console->setController(m_controller); + } + if (m_gifView) { + m_gifView->setController(m_controller); + } + if (m_videoView) { + m_videoView->setController(m_controller); + } + if (m_overrideView) { + m_overrideView->setController(m_controller); + } + + if (!m_pendingPatch.isEmpty()) { + m_controller->loadPatch(m_pendingPatch); + m_pendingPatch = QString(); + } + + m_controller->start(); +} + WindowBackground::WindowBackground(QWidget* parent) : QLabel(parent) {

@@ -1574,13 +1694,17 @@ QSize WindowBackground::sizeHint() const {

return m_sizeHint; } -void WindowBackground::setLockAspectRatio(int width, int height) { +void WindowBackground::setDimensions(int width, int height) { m_aspectWidth = width; m_aspectHeight = height; } void WindowBackground::setLockIntegerScaling(bool lock) { m_lockIntegerScaling = lock; +} + +void WindowBackground::setLockAspectRatio(bool lock) { + m_lockAspectRatio = lock; } void WindowBackground::paintEvent(QPaintEvent*) {

@@ -1593,10 +1717,12 @@ painter.setRenderHint(QPainter::SmoothPixmapTransform);

painter.fillRect(QRect(QPoint(), size()), Qt::black); QSize s = size(); QSize ds = s; - if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) { - ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight); - } else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) { - ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth); + if (m_lockAspectRatio) { + if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) { + ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight); + } else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) { + ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth); + } } if (m_lockIntegerScaling) { ds.setWidth(ds.width() - ds.width() % m_aspectWidth);
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -1,4 +1,4 @@

-/* Copyright (c) 2013-2016 Jeffrey Pfau +/* 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

@@ -12,6 +12,7 @@ #include <QMainWindow>

#include <QTimer> #include <functional> +#include <memory> #include <mgba/core/thread.h>

@@ -22,14 +23,17 @@ struct mArguments;

namespace QGBA { +class AudioProcessor; class ConfigController; +class CoreController; +class CoreManager; class DebuggerConsoleController; class Display; -class GameController; class GDBController; class GIFView; class LibraryController; class LogView; +class OverrideView; class ShaderSelector; class VideoView; class WindowBackground;

@@ -38,10 +42,10 @@ class Window : public QMainWindow {

Q_OBJECT public: - Window(ConfigController* config, int playerId = 0, QWidget* parent = nullptr); + Window(CoreManager* manager, ConfigController* config, int playerId = 0, QWidget* parent = nullptr); virtual ~Window(); - GameController* controller() { return m_controller; } + std::shared_ptr<CoreController> controller() { return m_controller; } void setConfig(ConfigController*); void argumentsPassed(mArguments*);

@@ -51,13 +55,12 @@

void updateMultiplayerStatus(bool canOpenAnother) { m_multiWindow->setEnabled(canOpenAnother); } signals: - void startDrawing(mCoreThread*); + void startDrawing(); void shutdown(); - void audioBufferSamplesChanged(int samples); - void sampleRateChanged(unsigned samples); - void fpsTargetChanged(float target); + void paused(bool); public slots: + void setController(CoreController* controller, const QString& fname); void selectROM(); #ifdef USE_SQLITE3 void selectROMInArchive();

@@ -80,7 +83,6 @@ void importSharkport();

void exportSharkport(); void openSettingsWindow(); - void openAboutScreen(); void startVideoLog();

@@ -111,12 +113,15 @@ virtual void dropEvent(QDropEvent*) override;

virtual void mouseDoubleClickEvent(QMouseEvent*) override; private slots: - void gameStarted(mCoreThread*, const QString&); + void gameStarted(); void gameStopped(); void gameCrashed(const QString&); void gameFailed(); void unimplementedBiosCall(int); + void reloadAudioDriver(); + void reloadDisplayDriver(); + void tryMakePortable(); void mustRestart();

@@ -139,8 +144,8 @@ void updateMRU();

void openView(QWidget* widget); - template <typename T, typename A> std::function<void()> openTView(A arg); - template <typename T> std::function<void()> openTView(); + template <typename T, typename... A> std::function<void()> openTView(A... arg); + template <typename T, typename... A> std::function<void()> openControllerTView(A... arg); QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); QAction* addHiddenAction(QMenu* menu, QAction* action, const QString& name);

@@ -150,8 +155,11 @@

QString getFilters() const; QString getFiltersArchive() const; - GameController* m_controller; - Display* m_display; + CoreManager* m_manager; + std::shared_ptr<CoreController> m_controller; + std::unique_ptr<AudioProcessor> m_audioProcessor; + + std::unique_ptr<Display> m_display; int m_savedScale; // TODO: Move these to a new class QList<QAction*> m_gameActions;

@@ -177,14 +185,17 @@ QList<QString> m_mruFiles;

QMenu* m_mruMenu = nullptr; QMenu* m_videoLayers; QMenu* m_audioChannels; - ShaderSelector* m_shaderView; + std::unique_ptr<ShaderSelector> m_shaderView; bool m_fullscreenOnStart = false; QTimer m_focusCheck; bool m_autoresume = false; bool m_wasOpened = false; + QString m_pendingPatch; bool m_hitUnimplementedBiosCall; + OverrideView* m_overrideView = nullptr; + #ifdef USE_FFMPEG VideoView* m_videoView = nullptr; #endif

@@ -210,8 +221,9 @@ WindowBackground(QWidget* parent = 0);

void setSizeHint(const QSize& size); virtual QSize sizeHint() const override; - void setLockAspectRatio(int width, int height); + void setDimensions(int width, int height); void setLockIntegerScaling(bool lock); + void setLockAspectRatio(bool lock); protected: virtual void paintEvent(QPaintEvent*) override;

@@ -220,6 +232,7 @@ private:

QSize m_sizeHint; int m_aspectWidth; int m_aspectHeight; + bool m_lockAspectRatio; bool m_lockIntegerScaling; };
M src/platform/qt/input/InputController.cppsrc/platform/qt/input/InputController.cpp

@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "InputController.h" #include "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" #include "InputItem.h"

@@ -85,18 +85,42 @@ if (itemForKey(name)) {

return; } m_keyIndex.addItem(qMakePair([this, name]() { - emit keyPressed(keyId(name)); + m_activeKeys |= 1 << keyId(name); }, [this, name]() { - emit keyReleased(keyId(name)); + m_activeKeys &= ~(1 << keyId(name)); }), name, QString("key%0").arg(name), m_bindings.get()); m_keyIndex.addItem(qMakePair([this, name]() { - emit keyAutofire(keyId(name), true); + setAutofire(keyId(name), true); }, [this, name]() { - emit keyAutofire(keyId(name), false); + setAutofire(keyId(name), false); }), name, QString("autofire%1").arg(name), m_autofire.get()); } +void InputController::setAutofire(int key, bool enable) { + if (key >= 32 || key < 0) { + return; + } + + m_autofireEnabled[key] = enable; + m_autofireStatus[key] = 0; +} + +int InputController::updateAutofire() { + int active = 0; + for (int k = 0; k < 32; ++k) { + if (!m_autofireEnabled[k]) { + continue; + } + ++m_autofireStatus[k]; + if (m_autofireStatus[k]) { + m_autofireStatus[k] = 0; + active |= 1 << k; + } + } + return active; +} + void InputController::addPlatform(mPlatform platform, const mInputPlatformInfo* info) { m_keyInfo[platform] = info; for (size_t i = 0; i < info->nKeys; ++i) {

@@ -121,6 +145,20 @@ #endif

rebuildKeyIndex(); restoreModel(); + +#ifdef M_CORE_GBA + m_lux.p = this; + m_lux.sample = [](GBALuminanceSource* context) { + InputControllerLux* lux = static_cast<InputControllerLux*>(context); + lux->value = 0xFF - lux->p->m_luxValue; + }; + + m_lux.readLuminance = [](GBALuminanceSource* context) { + InputControllerLux* lux = static_cast<InputControllerLux*>(context); + return lux->value; + }; + setLuminanceLevel(0); +#endif } InputController::~InputController() {

@@ -158,7 +196,6 @@ void InputController::setConfiguration(ConfigController* config) {

m_config = config; m_inputIndex.setConfigController(config); m_keyIndex.setConfigController(config); - setAllowOpposing(config->getOption("allowOpposingDirections").toInt()); loadConfiguration(KEYBOARD); loadProfile(KEYBOARD, profileForType(KEYBOARD)); #ifdef BUILD_SDL

@@ -373,7 +410,7 @@ return &m_inputMap;

} int InputController::pollEvents() { - int activeButtons = 0; + int activeButtons = m_activeKeys; #ifdef BUILD_SDL if (m_playerAttached && m_sdlPlayer.joystick) { SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;

@@ -844,3 +881,34 @@ bindKey(SDL_BINDING_BUTTON, item->button(), key);

bindAxis(SDL_BINDING_BUTTON, item->axis(), item->direction(), key); #endif } + +void InputController::increaseLuminanceLevel() { + setLuminanceLevel(m_luxLevel + 1); +} + +void InputController::decreaseLuminanceLevel() { + setLuminanceLevel(m_luxLevel - 1); +} + +void InputController::setLuminanceLevel(int level) { + int value = 0x16; + level = std::max(0, std::min(10, level)); + if (level > 0) { + value += GBA_LUX_LEVELS[level - 1]; + } + setLuminanceValue(value); +} + +void InputController::setLuminanceValue(uint8_t value) { + m_luxValue = value; + value = std::max<int>(value - 0x16, 0); + m_luxLevel = 10; + for (int i = 0; i < 10; ++i) { + if (value < GBA_LUX_LEVELS[i]) { + m_luxLevel = i; + break; + } + } + emit luminanceValueChanged(m_luxValue); +} +
M src/platform/qt/input/InputController.hsrc/platform/qt/input/InputController.h

@@ -21,6 +21,8 @@

#include <mgba/core/core.h> #include <mgba/core/input.h> +#include <mgba/gba/interface.h> + #ifdef BUILD_SDL #include "platform/sdl/sdl-events.h" #endif

@@ -62,8 +64,9 @@ void saveConfiguration(uint32_t type);

void saveProfile(uint32_t type, const QString& profile); const char* profileForType(uint32_t type); - bool allowOpposing() const { return m_allowOpposing; } - void setAllowOpposing(bool allowOpposing) { m_allowOpposing = allowOpposing; } + GBAKey mapKeyboard(int key) const; + + void bindKey(uint32_t type, int key, GBAKey); const mInputMap* map();

@@ -97,22 +100,29 @@ void releaseFocus(QWidget* focus);

mRumble* rumble(); mRotationSource* rotationSource(); + GBALuminanceSource* luminance() { return &m_lux; } signals: void profileLoaded(const QString& profile); - void keyPressed(int); - void keyReleased(int); - void keyAutofire(int, bool enabled); + void luminanceValueChanged(int value); public slots: void testGamepad(int type); void updateJoysticks(); + int updateAutofire(); + + void setAutofire(int key, bool enable); // TODO: Move these to somewhere that makes sense void suspendScreensaver(); void resumeScreensaver(); void setScreensaverSuspendable(bool); + void increaseLuminanceLevel(); + void decreaseLuminanceLevel(); + void setLuminanceLevel(int level); + void setLuminanceValue(uint8_t value); + protected: bool eventFilter(QObject*, QEvent*) override;

@@ -129,10 +139,21 @@ int keyId(const QString& key);

InputIndex m_inputIndex; InputIndex m_keyIndex; + + struct InputControllerLux : GBALuminanceSource { + InputController* p; + uint8_t value; + } m_lux; + uint8_t m_luxValue; + int m_luxLevel; + mInputMap m_inputMap; + int m_activeKeys; + bool m_autofireEnabled[32] = {}; + int m_autofireStatus[32] = {}; + ConfigController* m_config = nullptr; int m_playerId; - bool m_allowOpposing = false; QWidget* m_topLevel; QWidget* m_focusParent; QMap<mPlatform, const mInputPlatformInfo*> m_keyInfo;
M src/platform/qt/library/LibraryController.cppsrc/platform/qt/library/LibraryController.cpp

@@ -83,6 +83,9 @@ }

} void LibraryController::setViewStyle(LibraryStyle newStyle) { + if (m_currentStyle == newStyle) { + return; + } m_currentStyle = newStyle; AbstractGameList* newCurrentList = nullptr;
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -21,6 +21,7 @@ #include "platform/python/engine.h"

#endif #endif +#include <mgba/core/cheats.h> #include <mgba/core/core.h> #include <mgba/core/config.h> #include <mgba/core/input.h>

@@ -102,6 +103,16 @@

if (!renderer.core->init(renderer.core)) { freeArguments(&args); return 1; + } + + struct mCheatDevice* device = NULL; + if (args.cheatsFile && (device = renderer.core->cheatDevice(renderer.core))) { + struct VFile* vf = VFileOpen(args.cheatsFile, O_RDONLY); + if (vf) { + mCheatDeviceClear(device); + mCheatParseFile(device, vf); + vf->close(vf); + } } mInputMapInit(&renderer.core->inputMap, renderer.core->inputInfo);

@@ -148,6 +159,10 @@ ret = mSDLRun(&renderer, &args);

mSDLDetachPlayer(&renderer.events, &renderer.player); mInputMapDeinit(&renderer.core->inputMap); + if (device) { + mCheatDeviceDestroy(device); + } + mSDLDeinit(&renderer); freeArguments(&args);

@@ -184,10 +199,10 @@ }

#endif mDebuggerAttach(debugger, renderer->core); mDebuggerEnter(debugger, DEBUGGER_ENTER_MANUAL, NULL); - } -#ifdef ENABLE_SCRIPTING - mScriptBridgeSetDebugger(bridge, debugger); + #ifdef ENABLE_SCRIPTING + mScriptBridgeSetDebugger(bridge, debugger); #endif + } #endif if (args->patch) {
A src/util/elf-read.c

@@ -0,0 +1,125 @@

+/* 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-util/elf-read.h> + +#ifdef USE_ELF + +#include <mgba-util/vfs.h> + +DEFINE_VECTOR(ELFProgramHeaders, Elf32_Phdr); +DEFINE_VECTOR(ELFSectionHeaders, Elf32_Shdr); + +static bool _elfInit = false; + +struct ELF { + Elf* e; + struct VFile* vf; + size_t size; + char* memory; +}; + +struct ELF* ELFOpen(struct VFile* vf) { + if (!_elfInit) { + _elfInit = elf_version(EV_CURRENT) != EV_NONE; + if (!_elfInit) { + return NULL; + } + } + if (!vf) { + return NULL; + } + size_t size = vf->size(vf); + char* memory = vf->map(vf, size, MAP_READ); + if (!memory) { + return NULL; + } + + Elf* e = elf_memory(memory, size); + if (!e || elf_kind(e) != ELF_K_ELF) { + elf_end(e); + vf->unmap(vf, memory, size); + return false; + } + struct ELF* elf = malloc(sizeof(*elf)); + elf->e = e; + elf->vf = vf; + elf->size = size; + elf->memory = memory; + return elf; +} + +void ELFClose(struct ELF* elf) { + elf_end(elf->e); + elf->vf->unmap(elf->vf, elf->memory, elf->size); + free(elf); +} + +void* ELFBytes(struct ELF* elf, size_t* size) { + if (size) { + *size = elf->size; + } + return elf->memory; +} + +uint16_t ELFMachine(struct ELF* elf) { + Elf32_Ehdr* hdr = elf32_getehdr(elf->e); + if (!hdr) { + return 0; + } + return hdr->e_machine; +} + +uint32_t ELFEntry(struct ELF* elf) { + Elf32_Ehdr* hdr = elf32_getehdr(elf->e); + if (!hdr) { + return 0; + } + return hdr->e_entry; +} + +void ELFGetProgramHeaders(struct ELF* elf, struct ELFProgramHeaders* ph) { + ELFProgramHeadersClear(ph); + Elf32_Ehdr* hdr = elf32_getehdr(elf->e); + Elf32_Phdr* phdr = elf32_getphdr(elf->e); + ELFProgramHeadersResize(ph, hdr->e_phnum); + memcpy(ELFProgramHeadersGetPointer(ph, 0), phdr, sizeof(*phdr) * hdr->e_phnum); +} + +void ELFGetSectionHeaders(struct ELF* elf, struct ELFSectionHeaders* sh) { + ELFSectionHeadersClear(sh); + Elf_Scn* section = elf_getscn(elf->e, 0); + do { + *ELFSectionHeadersAppend(sh) = *elf32_getshdr(section); + } while ((section = elf_nextscn(elf->e, section))); +} + +Elf32_Shdr* ELFGetSectionHeader(struct ELF* elf, size_t index) { + Elf_Scn* section = elf_getscn(elf->e, index); + return elf32_getshdr(section); +} + +size_t ELFFindSection(struct ELF* elf, const char* name) { + Elf32_Ehdr* hdr = elf32_getehdr(elf->e); + size_t shstrtab = hdr->e_shstrndx; + if (strcmp(name, ".shstrtab") == 0) { + return shstrtab; + } + Elf_Scn* section = NULL; + while ((section = elf_nextscn(elf->e, section))) { + Elf32_Shdr* shdr = elf32_getshdr(section); + const char* sname = elf_strptr(elf->e, shstrtab, shdr->sh_name); + if (strcmp(sname, name) == 0) { + return elf_ndxscn(section); + } + } + return 0; +} + +const char* ELFGetString(struct ELF* elf, size_t section, size_t string) { + return elf_strptr(elf->e, section, string); +} + +#endif