diff --git a/.travis-deps.sh b/.travis-deps.sh index c76d4c6d6..fe7c49b23 100755 --- a/.travis-deps.sh +++ b/.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 diff --git a/.travis.yml b/.travis.yml index 2c25e739f..3b8970eae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,6 @@ language: c sudo: required matrix: include: - - os: linux - dist: trusty - compiler: clang - os: linux dist: trusty compiler: gcc @@ -16,4 +13,4 @@ matrix: 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 diff --git a/CHANGES b/CHANGES index deb436775..38ce42b1f 100644 --- a/CHANGES +++ b/CHANGES @@ -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 @@ Bugfixes: - 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 25c698881..3ba280ecd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.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 @@ if(DEFINED 3DS OR DEFINED PSP2 OR DEFINED WII) 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 @@ if(USE_SQLITE3) 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 @@ if(NOT QUIET) 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}") diff --git a/README.md b/README.md index 3b52e1573..aaa8fd3fa 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ mGBA has no hard dependencies, however, the following optional dependencies are - 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. diff --git a/include/mgba-util/elf-read.h b/include/mgba-util/elf-read.h new file mode 100644 index 000000000..e3886ac64 --- /dev/null +++ b/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 + +CXX_GUARD_START + +#ifdef USE_ELF + +#include + +#if USE_ELF_REPL +#include +#else +#include +#endif + +#include + +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 diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index 17c9de9f3..c9715b233 100644 --- a/include/mgba/core/core.h +++ b/include/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 diff --git a/include/mgba/core/scripting.h b/include/mgba/core/scripting.h index ae1964186..ebf8f88cf 100644 --- a/include/mgba/core/scripting.h +++ b/include/mgba/core/scripting.h @@ -24,6 +24,7 @@ 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*); @@ -44,6 +45,8 @@ void mScriptBridgeDebuggerEntered(struct mScriptBridge*, enum mDebuggerEntryReas void mScriptBridgeRun(struct mScriptBridge*); bool mScriptBridgeLoadScript(struct mScriptBridge*, const char* name); +bool mScriptBridgeLookupSymbol(struct mScriptBridge*, const char* name, int32_t* out); + CXX_GUARD_END #endif diff --git a/include/mgba/core/thread.h b/include/mgba/core/thread.h index d6e4566a3..420533762 100644 --- a/include/mgba/core/thread.h +++ b/include/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); diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index 34535b9d4..213236425 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/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; diff --git a/include/mgba/internal/gba/gba.h b/include/mgba/internal/gba/gba.h index 5710864e0..e8bff15ee 100644 --- a/include/mgba/internal/gba/gba.h +++ b/include/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); diff --git a/src/core/core.c b/src/core/core.c index d42ce15ce..d27cc48ef 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -8,6 +8,11 @@ #include #include #include +#include + +#ifdef USE_ELF +#include +#endif #ifdef M_CORE_GB #include @@ -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 diff --git a/src/core/rewind.c b/src/core/rewind.c index f75229195..6a4da46c7 100644 --- a/src/core/rewind.c +++ b/src/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 @@ void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, } void mCoreRewindContextDeinit(struct mCoreRewindContext* context) { + if (!context->currentState) { + return; + } #ifndef DISABLE_THREADING if (context->onThread) { MutexLock(&context->mutex); @@ -55,6 +61,8 @@ void mCoreRewindContextDeinit(struct mCoreRewindContext* context) { #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)); diff --git a/src/core/scripting.c b/src/core/scripting.c index e176aa843..9a0ee7698 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -19,6 +19,12 @@ struct mScriptInfo { bool success; }; +struct mScriptSymbol { + const char* name; + int32_t* out; + bool success; +}; + static void _seDeinit(void* value) { struct mScriptEngine* se = value; se->deinit(se); @@ -33,6 +39,15 @@ static void _seTryLoad(const char* key, void* value, void* user) { } } +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); + } +} + static void _seRun(const char* key, void* value, void* user) { UNUSED(key); UNUSED(user); @@ -111,3 +126,13 @@ bool mScriptBridgeLoadScript(struct mScriptBridge* sb, const char* name) { 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; +} diff --git a/src/core/thread.c b/src/core/thread.c index 16b22d25e..782b573df 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -167,10 +167,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { 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 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { } 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) { @@ -547,6 +544,16 @@ void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) 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) { MutexLock(&threadContext->impl->stateMutex); if (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_RUNNING) { diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index c991f08ee..871143436 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -551,6 +551,11 @@ static void _lookupIdentifier(struct mDebugger* debugger, const char* name, stru 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 @@ void CLIDebuggerCreate(struct CLIDebugger* debugger) { debugger->d.custom = _cliDebuggerCustom; debugger->d.paused = _commandLine; debugger->d.entered = _reportEntry; + debugger->d.type = DEBUGGER_CLI; debugger->system = NULL; debugger->backend = NULL; diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 3c149c9fc..6e2d287fa 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -658,6 +658,7 @@ void GDBStubCreate(struct GDBStub* stub) { 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; diff --git a/src/gba/core.c b/src/gba/core.c index 512a351bf..5f2e9ce1a 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,9 @@ #include #include #include +#ifdef USE_ELF +#include +#endif #include #include #include @@ -315,6 +319,15 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) { } 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 @@ static void _GBACoreDetachDebugger(struct mCore* core) { } 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 diff --git a/src/gba/gba.c b/src/gba/gba.c index 06f39f71b..3baaea232 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -21,6 +21,10 @@ #include #include +#ifdef USE_ELF +#include +#endif + mLOG_DEFINE_CATEGORY(GBA, "GBA", "gba"); mLOG_DEFINE_CATEGORY(GBA_DEBUG, "GBA Debug", "gba.debug"); @@ -203,6 +207,10 @@ void GBAReset(struct ARMCore* cpu) { 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 @@ void GBADetachDebugger(struct GBA* gba) { } #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 @@ bool GBALoadMB(struct GBA* gba, struct VFile* vf) { 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; } @@ -352,6 +386,9 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) { 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 @@ void GBADebug(struct GBA* gba, uint16_t flags) { } 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; } diff --git a/src/gba/memory.c b/src/gba/memory.c index 50da6f524..8b407fa09 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -80,6 +80,8 @@ void GBAMemoryInit(struct GBA* gba) { 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 @@ void GBAMemoryReset(struct GBA* gba) { } 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)); diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt index 2632eb081..2c914eb51 100644 --- a/src/platform/python/CMakeLists.txt +++ b/src/platform/python/CMakeLists.txt @@ -30,7 +30,6 @@ set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/lib.c PROPERTIES GENERAT 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) diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index 0af3b4124..19842c402 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -58,4 +58,5 @@ void free(void*); #endif #ifdef USE_DEBUGGERS #include +#include #endif diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index 05cbddb39..4aa2116c1 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -28,6 +28,7 @@ ffi.set_source("mgba._pylib", """ #include #include #include +#include #include #include #include @@ -70,17 +71,27 @@ for line in preprocessed.splitlines(): 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 - debugger = _debugger and NativeDebugger(_debugger) + if oldDebugger and not debugger: + del globalSyms['debugger'] + return + 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.embedding_init_code(""" 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__": diff --git a/src/platform/python/engine.c b/src/platform/python/engine.c index d91db2a8f..19fe76c0c 100644 --- a/src/platform/python/engine.c +++ b/src/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 @@ struct mPythonScriptEngine* mPythonCreateScriptEngine(void) { engine->d.isScript = mPythonScriptEngineIsScript; engine->d.loadScript = mPythonScriptEngineLoadScript; engine->d.run = mPythonScriptEngineRun; + engine->d.lookupSymbol = mPythonScriptEngineLookupSymbol; #ifdef USE_DEBUGGERS engine->d.debuggerEntered = mPythonScriptDebuggerEntered; #endif @@ -89,6 +91,11 @@ void mPythonScriptEngineRun(struct mScriptEngine* se) { 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 void mPythonScriptDebuggerEntered(struct mScriptEngine* se, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se; diff --git a/src/platform/python/lib.h b/src/platform/python/lib.h index d2b49d7e1..9c6046a51 100644 --- a/src/platform/python/lib.h +++ b/src/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*); diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index 9deb4000b..6f9897439 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -91,6 +91,12 @@ class CoreCallbacks(object): 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 @@ class Core(object): @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 @@ class IRunner(object): 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 diff --git a/src/platform/python/mgba/debugger.py b/src/platform/python/mgba/debugger.py index d6bf30e2e..2d597491c 100644 --- a/src/platform/python/mgba/debugger.py +++ b/src/platform/python/mgba/debugger.py @@ -5,6 +5,8 @@ # 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 @@ class NativeDebugger(IRunner): 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) diff --git a/src/platform/qt/AssetTile.cpp b/src/platform/qt/AssetTile.cpp index a97895c4b..c82ffe424 100644 --- a/src/platform/qt/AssetTile.cpp +++ b/src/platform/qt/AssetTile.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AssetTile.h" +#include "CoreController.h" #include "GBAApp.h" #include @@ -39,7 +40,7 @@ AssetTile::AssetTile(QWidget* parent) m_ui.b->setFont(font); } -void AssetTile::setController(GameController* controller) { +void AssetTile::setController(std::shared_ptr 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 @@ void AssetTile::selectIndex(int index) { #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::selectIndex(int index) { 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 @@ void AssetTile::selectColor(int index) { 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(); diff --git a/src/platform/qt/AssetTile.h b/src/platform/qt/AssetTile.h index beb42a21a..7d5f77be6 100644 --- a/src/platform/qt/AssetTile.h +++ b/src/platform/qt/AssetTile.h @@ -6,20 +6,22 @@ #ifndef QGBA_ASSET_TILE #define QGBA_ASSET_TILE -#include "GameController.h" - #include "ui_AssetTile.h" +#include + #include namespace QGBA { +class CoreController; + class AssetTile : public QGroupBox { Q_OBJECT public: AssetTile(QWidget* parent = nullptr); - void setController(GameController*); + void setController(std::shared_ptr); public slots: void setPalette(int); @@ -30,7 +32,7 @@ public slots: private: Ui::AssetTile m_ui; - std::shared_ptr m_tileCache; + mTileCache* m_tileCache; int m_paletteId = 0; int m_paletteSet = 0; int m_index = 0; diff --git a/src/platform/qt/AssetView.cpp b/src/platform/qt/AssetView.cpp index 0077cae8a..5ad124ad4 100644 --- a/src/platform/qt/AssetView.cpp +++ b/src/platform/qt/AssetView.cpp @@ -5,32 +5,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AssetView.h" -#include +#include "CoreController.h" -#include +#include using namespace QGBA; -AssetView::AssetView(GameController* controller, QWidget* parent) +AssetView::AssetView(std::shared_ptr 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(&AssetView::updateTiles)); - connect(m_controller, &GameController::frameAvailable, &m_updateTimer, + connect(controller.get(), &CoreController::frameAvailable, &m_updateTimer, static_cast(&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() { + updateTiles(false); } void AssetView::updateTiles(bool force) { - if (!m_controller->isLoaded()) { - return; - } - switch (m_controller->platform()) { #ifdef M_CORE_GBA case PLATFORM_GBA: @@ -56,7 +56,7 @@ void AssetView::showEvent(QShowEvent*) { } 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(buffer); size_t base = stride * y + x; switch (depth) { diff --git a/src/platform/qt/AssetView.h b/src/platform/qt/AssetView.h index 76dae708b..aabc79ab1 100644 --- a/src/platform/qt/AssetView.h +++ b/src/platform/qt/AssetView.h @@ -6,22 +6,28 @@ #ifndef QGBA_ASSET_VIEW #define QGBA_ASSET_VIEW +#include #include -#include "GameController.h" +#include + +#include namespace QGBA { +class CoreController; + class AssetView : public QWidget { Q_OBJECT public: - AssetView(GameController* controller, QWidget* parent = nullptr); + AssetView(std::shared_ptr 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 @@ protected: void resizeEvent(QResizeEvent*) override; void showEvent(QShowEvent*) override; - const std::shared_ptr m_tileCache; + mTileCache* const m_tileCache; private: - GameController* m_controller; + std::shared_ptr m_controller; QTimer m_updateTimer; }; diff --git a/src/platform/qt/AudioProcessor.cpp b/src/platform/qt/AudioProcessor.cpp index 6f0b49cbe..96f04d58b 100644 --- a/src/platform/qt/AudioProcessor.cpp +++ b/src/platform/qt/AudioProcessor.cpp @@ -47,10 +47,18 @@ AudioProcessor::AudioProcessor(QObject* parent) { } -void AudioProcessor::setInput(mCoreThread* input) { +AudioProcessor::~AudioProcessor() { + stop(); +} + +void AudioProcessor::setInput(std::shared_ptr input) { m_context = input; } +void AudioProcessor::stop() { + m_context.reset(); +} + void AudioProcessor::setBufferSamples(int samples) { m_samples = samples; } diff --git a/src/platform/qt/AudioProcessor.h b/src/platform/qt/AudioProcessor.h index 9636f0bd4..16a9179f9 100644 --- a/src/platform/qt/AudioProcessor.h +++ b/src/platform/qt/AudioProcessor.h @@ -7,6 +7,10 @@ #define QGBA_AUDIO_PROCESSOR #include +#include + +#include "CoreController.h" + struct mCoreThread; namespace QGBA { @@ -28,12 +32,14 @@ public: 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); + virtual void stop(); virtual bool start() = 0; virtual void pause() = 0; @@ -44,10 +50,10 @@ public slots: 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 m_context; int m_samples = 2048; static Driver s_driver; }; diff --git a/src/platform/qt/AudioProcessorQt.cpp b/src/platform/qt/AudioProcessorQt.cpp index b802d825b..8896e2c37 100644 --- a/src/platform/qt/AudioProcessorQt.cpp +++ b/src/platform/qt/AudioProcessorQt.cpp @@ -20,16 +20,24 @@ AudioProcessorQt::AudioProcessorQt(QObject* parent) { } -void AudioProcessorQt::setInput(mCoreThread* input) { - AudioProcessor::setInput(input); +void AudioProcessorQt::setInput(std::shared_ptr 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 @@ bool AudioProcessorQt::start() { } if (!m_device) { - m_device = new AudioDevice(this); + m_device = std::make_unique(this); } if (!m_audioOutput) { @@ -56,7 +64,7 @@ bool AudioProcessorQt::start() { 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; } diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h index ecf517a15..cd2b9bf67 100644 --- a/src/platform/qt/AudioProcessorQt.h +++ b/src/platform/qt/AudioProcessorQt.h @@ -22,7 +22,8 @@ public: virtual unsigned sampleRate() const override; public slots: - virtual void setInput(mCoreThread* input) override; + virtual void setInput(std::shared_ptr input) override; + virtual void stop() override; virtual bool start() override; virtual void pause() override; @@ -33,7 +34,7 @@ public slots: private: QAudioOutput* m_audioOutput = nullptr; - AudioDevice* m_device = nullptr; + std::unique_ptr m_device; unsigned m_sampleRate = 44100; }; diff --git a/src/platform/qt/AudioProcessorSDL.cpp b/src/platform/qt/AudioProcessorSDL.cpp index 3980108e7..8682ded58 100644 --- a/src/platform/qt/AudioProcessorSDL.cpp +++ b/src/platform/qt/AudioProcessorSDL.cpp @@ -16,16 +16,17 @@ AudioProcessorSDL::AudioProcessorSDL(QObject* parent) { } -AudioProcessorSDL::~AudioProcessorSDL() { - mSDLDeinitAudio(&m_audio); +void AudioProcessorSDL::setInput(std::shared_ptr 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::pause() { 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()); + } } } diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h index e0e354729..07de6e90b 100644 --- a/src/platform/qt/AudioProcessorSDL.h +++ b/src/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 input) override; + virtual void stop() override; virtual bool start() override; virtual void pause() override; diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 85fc2c403..ff44c6a43 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/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 @@ set(SOURCE_FILES 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) diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp index 73ffa09ab..80e398b4b 100644 --- a/src/platform/qt/CheatsView.cpp +++ b/src/platform/qt/CheatsView.cpp @@ -6,7 +6,7 @@ #include "CheatsView.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include #include @@ -21,7 +21,7 @@ using namespace QGBA; -CheatsView::CheatsView(GameController* controller, QWidget* parent) +CheatsView::CheatsView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) , m_model(controller->cheatDevice()) @@ -35,8 +35,8 @@ CheatsView::CheatsView(GameController* controller, QWidget* parent) 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::save() { } 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 @@ void CheatsView::removeSet() { 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 @@ void CheatsView::enterCheat(int codeType) { 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 @@ void CheatsView::enterCheat(int codeType) { m_model.endAppendRow(); } set->refresh(set, m_controller->cheatDevice()); - m_controller->threadContinue(); m_ui.codeEntry->clear(); } diff --git a/src/platform/qt/CheatsView.h b/src/platform/qt/CheatsView.h index 69c15c83f..63ea7bd3e 100644 --- a/src/platform/qt/CheatsView.h +++ b/src/platform/qt/CheatsView.h @@ -9,6 +9,7 @@ #include #include +#include #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 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 m_controller; CheatsModel m_model; }; diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 7832b052c..9136f1679 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #include #include @@ -98,8 +98,8 @@ ConfigController::ConfigController(QObject* parent) 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; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp new file mode 100644 index 000000000..31c126317 --- /dev/null +++ b/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 +#include + +#include +#include +#ifdef M_CORE_GBA +#include +#include +#include +#endif +#ifdef M_CORE_GB +#include +#include +#endif +#include + +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(m_activeBuffer->data()), size.width()); + + m_threadContext.startCallback = [](mCoreThread* context) { + CoreController* controller = static_cast(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(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(context->userData); + + controller->finishFrame(); + }; + + m_threadContext.cleanCallback = [](mCoreThread* context) { + CoreController* controller = static_cast(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(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(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(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(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(m_threadContext.core->board); + m_tileCache = std::make_unique(); + 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(m_threadContext.core->board); + m_tileCache = std::make_unique(); + 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) { + 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(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(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(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(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(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(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(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(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 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()); +} diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h new file mode 100644 index 000000000..1ba8f41d8 --- /dev/null +++ b/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 +#include +#include +#include +#include + +#include "VFileDevice.h" + +#include +#include + +#include +#include +#include +#include + +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, 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() { 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 m_tileCache; + std::unique_ptr m_override; + + QList> m_resetActions; + QList> 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 diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp new file mode 100644 index 000000000..e69f0ee27 --- /dev/null +++ b/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 + +#ifdef M_CORE_GBA +#include +#endif + +#include +#include + +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; +} diff --git a/src/platform/qt/CoreManager.h b/src/platform/qt/CoreManager.h new file mode 100644 index 000000000..78800ac41 --- /dev/null +++ b/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 +#include +#include + +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 diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index e7c35144a..08b113f72 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DebuggerConsoleController.h" -#include "GameController.h" +#include "CoreController.h" #include @@ -13,8 +13,8 @@ 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 @@ void DebuggerConsoleController::enterLine(const QString& line) { } 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::init(struct CLIDebuggerBackend* be) { void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { Backend* consoleBe = reinterpret_cast(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(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 @@ void DebuggerConsoleController::lineAppend(struct CLIDebuggerBackend* be, const const char* DebuggerConsoleController::historyLast(struct CLIDebuggerBackend* be, size_t* len) { Backend* consoleBe = reinterpret_cast(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 @@ const char* DebuggerConsoleController::historyLast(struct CLIDebuggerBackend* be void DebuggerConsoleController::historyAppend(struct CLIDebuggerBackend* be, const char* line) { Backend* consoleBe = reinterpret_cast(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)); } diff --git a/src/platform/qt/DebuggerConsoleController.h b/src/platform/qt/DebuggerConsoleController.h index 543dabeae..83e1b53bb 100644 --- a/src/platform/qt/DebuggerConsoleController.h +++ b/src/platform/qt/DebuggerConsoleController.h @@ -16,13 +16,13 @@ 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 @@ private: 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; diff --git a/src/platform/qt/DebuggerController.cpp b/src/platform/qt/DebuggerController.cpp index a1a7a96c5..eb27789a7 100644 --- a/src/platform/qt/DebuggerController.cpp +++ b/src/platform/qt/DebuggerController.cpp @@ -5,32 +5,44 @@ * 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 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 @@ void DebuggerController::detach() { 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 @@ void DebuggerController::shutdown() { if (!isAttached()) { return; } - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); shutdownInternal(); } diff --git a/src/platform/qt/DebuggerController.h b/src/platform/qt/DebuggerController.h index 01603ae02..8677af1c5 100644 --- a/src/platform/qt/DebuggerController.h +++ b/src/platform/qt/DebuggerController.h @@ -8,20 +8,23 @@ #include +#include + 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); public slots: virtual void attach(); @@ -34,7 +37,7 @@ protected: virtual void shutdownInternal(); mDebugger* const m_debugger; - GameController* const m_gameController; + std::shared_ptr m_gameController; private: QMetaObject::Connection m_autoattach; diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index b7e04d263..f413a2b72 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -8,16 +8,19 @@ #include +#include + #include #include "MessagePainter.h" -struct mCoreThread; struct VDir; struct VideoShader; namespace QGBA { +class CoreController; + class Display : public QWidget { Q_OBJECT @@ -41,6 +44,7 @@ public: bool isIntegerScalingLocked() const { return m_lockIntegerScaling; } bool isFiltered() const { return m_filter; } + virtual void startDrawing(std::shared_ptr) = 0; virtual bool isDrawing() const = 0; virtual bool supportsShaders() const = 0; virtual VideoShader* shaders() = 0; @@ -50,7 +54,6 @@ signals: 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 @@ public slots: 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; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index cffce120e..07ee75dc7 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -7,12 +7,13 @@ #if defined(BUILD_GL) || defined(BUILD_GLES) +#include "CoreController.h" + #include #include #include #include -#include #ifdef BUILD_GL #include "platform/opengl/gl.h" #endif @@ -52,14 +53,14 @@ VideoShader* DisplayGL::shaders() { return shaders; } -void DisplayGL::startDrawing(mCoreThread* thread) { +void DisplayGL::startDrawing(std::shared_ptr 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 @@ void DisplayGL::startDrawing(mCoreThread* thread) { 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::startDrawing(mCoreThread* thread) { 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 @@ void DisplayGL::filter(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 @@ void DisplayGL::resizePainter() { 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 @@ PainterGL::~PainterGL() { m_backend = nullptr; } -void PainterGL::setContext(mCoreThread* context) { +void PainterGL::setContext(std::shared_ptr context) { m_context = context; if (!context) { @@ -273,9 +253,8 @@ void PainterGL::setContext(mCoreThread* context) { #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 @@ void PainterGL::start() { } 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 @@ void PainterGL::draw() { 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 @@ void PainterGL::stop() { 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 @@ void PainterGL::enqueue(const uint32_t* backing) { } 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(); } diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index f2f6dbc7c..c4d2916bd 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -46,12 +46,12 @@ public: DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); ~DisplayGL(); + void startDrawing(std::shared_ptr) 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 @@ public slots: 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 @@ private: QGLWidget* m_gl; PainterGL* m_painter; QThread* m_drawThread = nullptr; - mCoreThread* m_context = nullptr; + std::shared_ptr 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); void setMessagePainter(MessagePainter*); void enqueue(const uint32_t* backing); @@ -116,14 +116,14 @@ private: 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 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; }; diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index dbbd7aad9..0c63e8b78 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DisplayQt.h" +#include "CoreController.h" + #include #include @@ -17,10 +19,18 @@ DisplayQt::DisplayQt(QWidget* parent) { } -void DisplayQt::startDrawing(mCoreThread* context) { - context->core->desiredVideoDimensions(context->core, &m_width, &m_height); +void DisplayQt::startDrawing(std::shared_ptr 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 @@ void DisplayQt::filter(bool filter) { update(); } -void DisplayQt::framePosted(const uint32_t* buffer) { +void DisplayQt::framePosted() { update(); + color_t* buffer = m_context->drawContext(); if (const_cast(m_backing).bits() == reinterpret_cast(buffer)) { return; } diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index 6fcab1b7a..ac7e63a86 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -19,20 +19,20 @@ Q_OBJECT public: DisplayQt(QWidget* parent = nullptr); + void startDrawing(std::shared_ptr) 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 @@ private: unsigned m_width; unsigned m_height; QImage m_backing{nullptr}; + std::shared_ptr m_context = nullptr; }; } diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index c92cc402f..9b994047a 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -6,9 +6,10 @@ #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 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config) reloadGameDB(); + m_manager.setConfig(m_configController->config()); + m_manager.setMultiplayerController(&m_multiplayer); + if (!m_configController->getQtOption("audioDriver").isNull()) { AudioProcessor::setDriver(static_cast(m_configController->getQtOption("audioDriver").toInt())); } @@ -71,7 +75,8 @@ GBAApp::~GBAApp() { bool GBAApp::event(QEvent* event) { if (event->type() == QEvent::FileOpen) { - m_windows[0]->controller()->loadGame(static_cast(event)->file()); + CoreController* core = m_manager.loadGame(static_cast(event)->file()); + m_windows[0]->setController(core, static_cast(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 @@ Window* GBAApp::newWindow() { 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 @@ GBAApp* GBAApp::app() { void GBAApp::pauseAll(QList* 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::pauseAll(QList* paused) { void GBAApp::continueAll(const QList& paused) { for (auto& window : paused) { - window->controller()->setPaused(false); + if (window->controller()) { + window->controller()->setPaused(false); + } } } diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index 2b7c2e5cc..99db127a5 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -8,8 +8,12 @@ #include #include +#include +#include +#include #include +#include "CoreManager.h" #include "MultiplayerController.h" struct NoIntroDB; @@ -70,6 +74,7 @@ private: ConfigController* m_configController; QList m_windows; MultiplayerController m_multiplayer; + CoreManager m_manager; NoIntroDB* m_db = nullptr; #ifdef USE_SQLITE3 diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index 5a72420a0..ade67e08c 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/platform/qt/GDBController.cpp @@ -5,12 +5,12 @@ * 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 @@ ushort GDBController::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 @@ void GDBController::setBindAddress(uint32_t bindAddress) { } void GDBController::listen() { - GameController::Interrupter interrupter(m_gameController); + CoreController::Interrupter interrupter(m_gameController); if (!isAttached()) { attach(); } diff --git a/src/platform/qt/GDBController.h b/src/platform/qt/GDBController.h index fc8a6e6a1..25c0a18bf 100644 --- a/src/platform/qt/GDBController.h +++ b/src/platform/qt/GDBController.h @@ -14,13 +14,13 @@ 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 @@ signals: private: virtual void shutdownInternal() override; - GDBStub m_gdbStub; + GDBStub m_gdbStub{}; ushort m_port = 2345; Address m_bindAddress; diff --git a/src/platform/qt/GIFView.cpp b/src/platform/qt/GIFView.cpp index 1b9e0156c..901e6c365 100644 --- a/src/platform/qt/GIFView.cpp +++ b/src/platform/qt/GIFView.cpp @@ -7,6 +7,7 @@ #ifdef USE_MAGICK +#include "CoreController.h" #include "GBAApp.h" #include "LogController.h" @@ -39,6 +40,12 @@ GIFView::~GIFView() { stopRecording(); } +void GIFView::setController(std::shared_ptr 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() { int delayMs = m_ui.delayAuto->isChecked() ? -1 : m_ui.delayMs->value(); ImageMagickGIFEncoderSetParams(&m_encoder, m_ui.frameskip->value(), delayMs); diff --git a/src/platform/qt/GIFView.h b/src/platform/qt/GIFView.h index 6641d8d94..f5433ae3e 100644 --- a/src/platform/qt/GIFView.h +++ b/src/platform/qt/GIFView.h @@ -10,12 +10,16 @@ #include +#include + #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 @@ public: mAVStream* getStream() { return &m_encoder.d; } public slots: + void setController(std::shared_ptr); + void startRecording(); void stopRecording(); diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp deleted file mode 100644 index d4b7f1e3a..000000000 --- a/src/platform/qt/GameController.cpp +++ /dev/null @@ -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 -#include - -#include - -#include -#include -#include -#include -#ifdef M_CORE_GBA -#include -#include -#include -#include -#include -#endif -#ifdef M_CORE_GB -#include -#include -#endif -#include -#include - -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(context); - lux->value = 0xFF - lux->p->m_luxValue; - }; - - m_lux.readLuminance = [](GBALuminanceSource* context) { - GameControllerLux* lux = static_cast(context); - return lux->value; - }; - setLuminanceLevel(0); -#endif - - m_threadContext.startCallback = [](mCoreThread* context) { - GameController* controller = static_cast(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(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(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(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(context->core->board); - gba->video.renderer->cache = nullptr; - break; - } -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: { - GB* gb = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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 GameController::tileCache() { - if (m_tileCache) { - return m_tileCache; - } - Interrupter interrupter(this); - switch (platform()) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: { - GBA* gba = static_cast(m_threadContext.core->board); - m_tileCache = std::make_shared(); - 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(m_threadContext.core->board); - m_tileCache = std::make_shared(); - 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()); - } -} diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h deleted file mode 100644 index 111cbdada..000000000 --- a/src/platform/qt/GameController.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#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 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> 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 m_tileCache; - - QList m_audioChannels; - QList 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 diff --git a/src/platform/qt/IOViewer.cpp b/src/platform/qt/IOViewer.cpp index 030ead19f..76be744ac 100644 --- a/src/platform/qt/IOViewer.cpp +++ b/src/platform/qt/IOViewer.cpp @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "IOViewer.h" -#include "GameController.h" +#include "CoreController.h" #include #include @@ -1023,7 +1023,7 @@ const QList& IOViewer::registerDescriptions() { return s_registers; } -IOViewer::IOViewer(GameController* controller, QWidget* parent) +IOViewer::IOViewer(std::shared_ptr controller, QWidget* parent) : QDialog(parent) , m_controller(controller) { @@ -1067,16 +1067,17 @@ IOViewer::IOViewer(GameController* controller, QWidget* parent) } 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(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 @@ void IOViewer::bitFlipped() { } void IOViewer::writeback() { - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { + { + CoreController::Interrupter interrupter(m_controller); GBAIOWrite(static_cast(m_controller->thread()->core->board), m_register, m_value); } - m_controller->threadContinue(); updateRegister(); } diff --git a/src/platform/qt/IOViewer.h b/src/platform/qt/IOViewer.h index c2451e08d..1e1da3c62 100644 --- a/src/platform/qt/IOViewer.h +++ b/src/platform/qt/IOViewer.h @@ -9,11 +9,13 @@ #include #include +#include + #include "ui_IOViewer.h" namespace QGBA { -class GameController; +class CoreController; class IOViewer : public QDialog { Q_OBJECT @@ -39,7 +41,7 @@ public: }; typedef QList RegisterDescription; - IOViewer(GameController* controller, QWidget* parent = nullptr); + IOViewer(std::shared_ptr controller, QWidget* parent = nullptr); static const QList& registerDescriptions(); @@ -65,7 +67,7 @@ private: QCheckBox* m_b[16]; - GameController* m_controller; + std::shared_ptr m_controller; }; } diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index a381c3d22..538254ff6 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -5,7 +5,7 @@ * 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 @@ using namespace QGBA; -LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) +LoadSaveState::LoadSaveState(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) , m_mode(LoadSave::LOAD) @@ -61,6 +61,8 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) escape->setShortcut(QKeySequence("Esc")); escape->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(escape); + + connect(m_controller.get(), &CoreController::stopping, this, &QWidget::close); } void LoadSaveState::setMode(LoadSave mode) { diff --git a/src/platform/qt/LoadSaveState.h b/src/platform/qt/LoadSaveState.h index b5aa09c59..8da0beef3 100644 --- a/src/platform/qt/LoadSaveState.h +++ b/src/platform/qt/LoadSaveState.h @@ -8,11 +8,13 @@ #include +#include + #include "ui_LoadSaveState.h" namespace QGBA { -class GameController; +class CoreController; class InputController; class SavestateButton; @@ -27,7 +29,7 @@ Q_OBJECT public: const static int NUM_SLOTS = 9; - LoadSaveState(GameController* controller, QWidget* parent = nullptr); + LoadSaveState(std::shared_ptr controller, QWidget* parent = nullptr); void setInputController(InputController* controller); void setMode(LoadSave mode); @@ -46,7 +48,7 @@ private: void triggerState(int slot); Ui::LoadSaveState m_ui; - GameController* m_controller; + std::shared_ptr m_controller; SavestateButton* m_slots[NUM_SLOTS]; LoadSave m_mode; diff --git a/src/platform/qt/LogController.cpp b/src/platform/qt/LogController.cpp index 5de4c74e5..26383ba6d 100644 --- a/src/platform/qt/LogController.cpp +++ b/src/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 @@ LogController::Stream LogController::operator()(int category, int level) { } void LogController::postLog(int level, int category, const QString& string) { - if (!(m_logLevel & level)) { + if (!mLogFilterTest(&m_filter, category, static_cast(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); } diff --git a/src/platform/qt/LogController.h b/src/platform/qt/LogController.h index 0a4be0159..88c24f1b1 100644 --- a/src/platform/qt/LogController.h +++ b/src/platform/qt/LogController.h @@ -8,6 +8,8 @@ #include "GBAApp.h" +#include + #include #include @@ -35,7 +37,8 @@ private: 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 @@ public slots: void disableLevels(int levels); private: - int m_logLevel; + mLogFilter m_filter; static LogController s_global; }; diff --git a/src/platform/qt/MemoryModel.cpp b/src/platform/qt/MemoryModel.cpp index 8b7a08dd7..92fb2aa98 100644 --- a/src/platform/qt/MemoryModel.cpp +++ b/src/platform/qt/MemoryModel.cpp @@ -6,7 +6,7 @@ #include "MemoryModel.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include "LogController.h" #include "VFileDevice.h" @@ -91,7 +91,7 @@ MemoryModel::MemoryModel(QWidget* parent) setRegion(0, 0x10000000, tr("All")); } -void MemoryModel::setController(GameController* controller) { +void MemoryModel::setController(std::shared_ptr controller) { m_core = controller->thread()->core; } diff --git a/src/platform/qt/MemoryModel.h b/src/platform/qt/MemoryModel.h index f74078528..f40b9383a 100644 --- a/src/platform/qt/MemoryModel.h +++ b/src/platform/qt/MemoryModel.h @@ -11,6 +11,7 @@ #include #include #include + #include #include @@ -19,7 +20,7 @@ struct mCore; namespace QGBA { -class GameController; +class CoreController; class MemoryModel : public QAbstractScrollArea { Q_OBJECT @@ -27,7 +28,7 @@ Q_OBJECT public: MemoryModel(QWidget* parent = nullptr); - void setController(GameController* controller); + void setController(std::shared_ptr controller); void setRegion(uint32_t base, uint32_t size, const QString& name = QString(), int segment = -1); void setSegment(int segment); diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 60d4b095b..2230f00aa 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -8,12 +8,12 @@ #include -#include "GameController.h" +#include "CoreController.h" #include "MemoryView.h" using namespace QGBA; -MemorySearch::MemorySearch(GameController* controller, QWidget* parent) +MemorySearch::MemorySearch(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) { @@ -26,6 +26,8 @@ MemorySearch::MemorySearch(GameController* controller, QWidget* parent) 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 @@ void MemorySearch::search() { 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(¶ms)) { @@ -125,10 +124,7 @@ void MemorySearch::search() { 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(¶ms)) { @@ -139,10 +135,7 @@ void MemorySearch::searchWithin() { } 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 @@ void MemorySearch::openMemory() { MemoryView* memView = new MemoryView(m_controller); memView->jumpToAddress(address); - connect(m_controller, &GameController::gameStopped, memView, &QWidget::close); memView->setAttribute(Qt::WA_DeleteOnClose); memView->show(); } diff --git a/src/platform/qt/MemorySearch.h b/src/platform/qt/MemorySearch.h index 65f365f44..8c02aa28d 100644 --- a/src/platform/qt/MemorySearch.h +++ b/src/platform/qt/MemorySearch.h @@ -6,13 +6,15 @@ #ifndef QGBA_MEMORY_SEARCH #define QGBA_MEMORY_SEARCH +#include + #include "ui_MemorySearch.h" #include namespace QGBA { -class GameController; +class CoreController; class MemorySearch : public QWidget { Q_OBJECT @@ -20,7 +22,7 @@ Q_OBJECT public: static constexpr size_t LIMIT = 10000; - MemorySearch(GameController* controller, QWidget* parent = nullptr); + MemorySearch(std::shared_ptr controller, QWidget* parent = nullptr); ~MemorySearch(); public slots: @@ -36,7 +38,7 @@ private: Ui::MemorySearch m_ui; - GameController* m_controller; + std::shared_ptr m_controller; mCoreMemorySearchResults m_results; QByteArray m_string; diff --git a/src/platform/qt/MemoryView.cpp b/src/platform/qt/MemoryView.cpp index 543dbe6bf..9b91d8180 100644 --- a/src/platform/qt/MemoryView.cpp +++ b/src/platform/qt/MemoryView.cpp @@ -6,13 +6,13 @@ #include "MemoryView.h" -#include "GameController.h" +#include "CoreController.h" #include using namespace QGBA; -MemoryView::MemoryView(GameController* controller, QWidget* parent) +MemoryView::MemoryView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) , m_controller(controller) { @@ -45,12 +45,12 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent) m_ui.hexfield, static_cast(&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::updateSelection(uint32_t start, uint32_t end) { 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)); diff --git a/src/platform/qt/MemoryView.h b/src/platform/qt/MemoryView.h index 04a492ca9..049c63aed 100644 --- a/src/platform/qt/MemoryView.h +++ b/src/platform/qt/MemoryView.h @@ -12,13 +12,13 @@ namespace QGBA { -class GameController; +class CoreController; class MemoryView : public QWidget { Q_OBJECT public: - MemoryView(GameController* controller, QWidget* parent = nullptr); + MemoryView(std::shared_ptr controller, QWidget* parent = nullptr); public slots: void update(); @@ -33,7 +33,7 @@ private slots: private: Ui::MemoryView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; QPair m_selection; }; diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index dc5e54ebb..203e32330 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -5,7 +5,7 @@ * 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 @@ -153,7 +153,7 @@ MultiplayerController::MultiplayerController() { }; } -bool MultiplayerController::attachGame(GameController* controller) { +bool MultiplayerController::attachGame(CoreController* controller) { if (m_lockstep.attached == MAX_GBAS) { return false; } @@ -232,13 +232,15 @@ bool MultiplayerController::attachGame(GameController* controller) { return false; } -void MultiplayerController::detachGame(GameController* controller) { +void MultiplayerController::detachGame(CoreController* controller) { mCoreThread* thread = controller->thread(); if (!thread) { return; } + QList 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 @@ void MultiplayerController::detachGame(GameController* controller) { 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; diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 65054fcb2..b7f043140 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/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 @@ Q_OBJECT 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 @@ signals: private: struct Player { - GameController* controller; + CoreController* controller; GBSIOLockstepNode* gbNode; GBASIOLockstepNode* gbaNode; int awake; diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp index 35fb9be0b..871a95cbc 100644 --- a/src/platform/qt/ObjView.cpp +++ b/src/platform/qt/ObjView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ObjView.h" +#include "CoreController.h" #include "GBAApp.h" #include @@ -24,7 +25,7 @@ using namespace QGBA; -ObjView::ObjView(GameController* controller, QWidget* parent) +ObjView::ObjView(std::shared_ptr controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) { @@ -119,16 +120,16 @@ void ObjView::updateTilesGBA(bool force) { }; 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 @@ void ObjView::updateTilesGB(bool force) { 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 @@ void ObjView::updateTilesGB(bool force) { #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 @@ void ObjView::exportObj() { 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) { diff --git a/src/platform/qt/ObjView.h b/src/platform/qt/ObjView.h index 0e03ce29c..1771883a7 100644 --- a/src/platform/qt/ObjView.h +++ b/src/platform/qt/ObjView.h @@ -7,7 +7,6 @@ #define QGBA_OBJ_VIEW #include "AssetView.h" -#include "GameController.h" #include "ui_ObjView.h" @@ -15,11 +14,13 @@ namespace QGBA { +class CoreController; + class ObjView : public AssetView { Q_OBJECT public: - ObjView(GameController* controller, QWidget* parent = nullptr); + ObjView(std::shared_ptr controller, QWidget* parent = nullptr); #ifdef USE_PNG public slots: @@ -40,7 +41,7 @@ private: Ui::ObjView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size int m_objId = 0; struct ObjInfo { diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index 6b7652f81..640f5bcf4 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -9,7 +9,7 @@ #include #include "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #ifdef M_CORE_GBA #include "GBAOverride.h" @@ -28,9 +28,8 @@ QList OverrideView::s_gbModelList; QList 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 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, #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 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, 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 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 @@ bool OverrideView::eventFilter(QObject* obj, QEvent* event) { } void OverrideView::saveOverride() { - if (!m_config) { + if (!m_config || !m_controller) { return; } m_config->saveOverride(*m_controller->override()); @@ -158,7 +161,7 @@ void OverrideView::saveOverride() { void OverrideView::updateOverrides() { #ifdef M_CORE_GBA if (m_ui.tabWidget->currentWidget() == m_ui.tabGBA) { - GBAOverride* gba = new GBAOverride; + std::unique_ptr gba(new GBAOverride); memset(gba->override.id, 0, 4); gba->override.savetype = static_cast(m_ui.savetype->currentIndex() - 1); gba->override.hardware = HW_NO_OVERRIDE; @@ -193,18 +196,18 @@ void OverrideView::updateOverrides() { 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 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 @@ void OverrideView::updateOverrides() { 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::gameStarted(mCoreThread* thread) { } void OverrideView::gameStopped() { + m_controller.reset(); m_ui.tabWidget->setEnabled(true); m_ui.savetype->setCurrentIndex(0); m_ui.idleLoop->clear(); diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h index 3d1c8a3aa..2b971aaae 100644 --- a/src/platform/qt/OverrideView.h +++ b/src/platform/qt/OverrideView.h @@ -8,10 +8,14 @@ #include +#include + #ifdef M_CORE_GB #include #endif +#include "Override.h" + #include "ui_OverrideView.h" struct mCoreThread; @@ -19,21 +23,23 @@ struct mCoreThread; 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 controller); public slots: void saveOverride(); private slots: void updateOverrides(); - void gameStarted(mCoreThread*); + void gameStarted(); void gameStopped(); protected: @@ -42,7 +48,8 @@ protected: private: Ui::OverrideView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; + std::unique_ptr m_override; ConfigController* m_config; #ifdef M_CORE_GB diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp index f669168de..394a54e7e 100644 --- a/src/platform/qt/PaletteView.cpp +++ b/src/platform/qt/PaletteView.cpp @@ -5,6 +5,7 @@ * 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 @@ using namespace QGBA; -PaletteView::PaletteView(GameController* controller, QWidget* parent) +PaletteView::PaletteView(std::shared_ptr 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 @@ PaletteView::PaletteView(GameController* controller, QWidget* parent) 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 @@ void PaletteView::exportPalette(int start, int length) { 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); diff --git a/src/platform/qt/PaletteView.h b/src/platform/qt/PaletteView.h index 057d49a5b..d3bbb6023 100644 --- a/src/platform/qt/PaletteView.h +++ b/src/platform/qt/PaletteView.h @@ -8,20 +8,22 @@ #include -#include "GameController.h" +#include + #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 controller, QWidget* parent = nullptr); public slots: void updatePalette(); @@ -34,7 +36,7 @@ private: Ui::PaletteView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; }; } diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index d657731c0..dc53347eb 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -6,7 +6,7 @@ #include "ROMInfo.h" #include "GBAApp.h" -#include "GameController.h" +#include "CoreController.h" #include #ifdef M_CORE_GB @@ -21,21 +21,17 @@ using namespace QGBA; -ROMInfo::ROMInfo(GameController* controller, QWidget* parent) +ROMInfo::ROMInfo(std::shared_ptr 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); diff --git a/src/platform/qt/ROMInfo.h b/src/platform/qt/ROMInfo.h index 05ecd23a1..b5ce734aa 100644 --- a/src/platform/qt/ROMInfo.h +++ b/src/platform/qt/ROMInfo.h @@ -8,17 +8,19 @@ #include +#include + #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 controller, QWidget* parent = nullptr); private: Ui::ROMInfo m_ui; diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp index 8f107df46..75cc5c3ae 100644 --- a/src/platform/qt/SensorView.cpp +++ b/src/platform/qt/SensorView.cpp @@ -5,7 +5,7 @@ * 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 @@ 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,22 +25,13 @@ SensorView::SensorView(GameController* controller, InputController* input, QWidg 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); if (!m_rotation || !m_rotation->readTiltX || !m_rotation->readTiltY) { @@ -66,6 +56,22 @@ SensorView::SensorView(GameController* controller, InputController* input, QWidg m_input->setGyroSensitivity(value * 1e8f); }); m_input->stealFocus(this); + connect(m_input, &InputController::luminanceValueChanged, this, &SensorView::luminanceValueChanged); +} + +void SensorView::setController(std::shared_ptr 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 @@ bool SensorView::eventFilter(QObject*, QEvent* event) { } void SensorView::updateSensors() { - GameController::Interrupter interrupter(m_controller); - if (m_rotation->sample && - (!m_controller->isLoaded() || !(static_cast(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::updateSensors() { 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) { diff --git a/src/platform/qt/SensorView.h b/src/platform/qt/SensorView.h index 666c37f41..78dc4d82d 100644 --- a/src/platform/qt/SensorView.h +++ b/src/platform/qt/SensorView.h @@ -10,6 +10,7 @@ #include #include +#include #include "ui_SensorView.h" @@ -18,7 +19,7 @@ struct mRotationSource; 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); protected: bool eventFilter(QObject*, QEvent* event) override; @@ -41,7 +44,7 @@ private: Ui::SensorView m_ui; std::function m_jiggered; - GameController* m_controller; + std::shared_ptr m_controller; InputController* m_input; mRotationSource* m_rotation; QTimer m_timer; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 4ee8a22cc..5636534df 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -174,19 +174,26 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC 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 @@ void SettingsView::updateConfig() { if (displayDriver != m_controller->getQtOption("displayDriver")) { m_controller->setQtOption("displayDriver", displayDriver); Display::setDriver(static_cast(displayDriver.toInt())); + setShaderSelector(nullptr); emit displayDriverChanged(); } diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index eb74a57a6..be8df4b15 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TileView.h" +#include "CoreController.h" #include "GBAApp.h" #include @@ -16,7 +17,7 @@ using namespace QGBA; -TileView::TileView(GameController* controller, QWidget* parent) +TileView::TileView(std::shared_ptr controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) { @@ -79,40 +80,40 @@ TileView::TileView(GameController* controller, QWidget* parent) 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(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)); } } } diff --git a/src/platform/qt/TileView.h b/src/platform/qt/TileView.h index 6e35df355..2cdb276c7 100644 --- a/src/platform/qt/TileView.h +++ b/src/platform/qt/TileView.h @@ -7,7 +7,6 @@ #define QGBA_TILE_VIEW #include "AssetView.h" -#include "GameController.h" #include "ui_TileView.h" @@ -15,11 +14,13 @@ namespace QGBA { +class CoreController; + class TileView : public AssetView { Q_OBJECT public: - TileView(GameController* controller, QWidget* parent = nullptr); + TileView(std::shared_ptr controller, QWidget* parent = nullptr); public slots: void updatePalette(int); @@ -34,7 +35,7 @@ private: Ui::TileView m_ui; - GameController* m_controller; + std::shared_ptr m_controller; mTileCacheEntry m_tileStatus[3072 * 32] = {}; // TODO: Correct size int m_paletteId = 0; }; diff --git a/src/platform/qt/VFileDevice.cpp b/src/platform/qt/VFileDevice.cpp index 04da09353..d3b8c8bed 100644 --- a/src/platform/qt/VFileDevice.cpp +++ b/src/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) { diff --git a/src/platform/qt/VFileDevice.h b/src/platform/qt/VFileDevice.h index 71baecd1a..e4e6b17e0 100644 --- a/src/platform/qt/VFileDevice.h +++ b/src/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 @@ public: 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; diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index e5f28458e..ba769e2c7 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -195,6 +195,14 @@ VideoView::~VideoView() { free(m_containerCstr); } +void VideoView::setController(std::shared_ptr 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; diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index 6f3ef3ecb..e84d63fe8 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -10,12 +10,18 @@ #include +#include + +#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 @@ public: mAVStream* getStream() { return &m_encoder.d; } public slots: + void setController(std::shared_ptr); + void startRecording(); void stopRecording(); void setNativeResolution(const QSize&); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 433b2284d..37870080f 100644 --- a/src/platform/qt/Window.cpp +++ b/src/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 @@ #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 @@ 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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) 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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) 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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) VFile* output = m_libraryView->selectedVFile(); if (output) { QPair 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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) 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(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(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(ConfigController* config, int playerId, QWidget* parent) Window::~Window() { delete m_logView; + delete m_overrideView; #ifdef USE_FFMPEG delete m_videoView; @@ -230,18 +171,14 @@ Window::~Window() { 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 @@ void Window::loadConfig() { 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 @@ void Window::reloadConfig() { 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 @@ QString Window::getFiltersArchive() const { 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 @@ void Window::selectROMInArchive() { VFile* output = archiveInspector->selectedVFile(); QPair 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::selectSave(bool temporary) { } 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 @@ void Window::startVideoLog() { } } -template -std::function Window::openTView(A arg) { +template +std::function Window::openTView(A... arg) { return [=]() { - T* view = new T(m_controller, arg); + T* view = new T(arg...); openView(view); }; } -template -std::function Window::openTView() { + +template +std::function Window::openControllerTView(A... arg) { return [=]() { - T* view = new T(m_controller); + T* view = new T(m_controller, arg...); openView(view); }; } @@ -518,15 +435,8 @@ std::function Window::openTView() { 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 @@ void Window::openVideoWindow() { 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 @@ void Window::openGIFWindow() { #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 @@ void Window::gdbOpen() { #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 @@ void Window::resizeEvent(QResizeEvent* event) { 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 @@ void Window::focusInEvent(QFocusEvent*) { } void Window::focusOutEvent(QFocusEvent*) { - m_controller->setTurbo(false, false); - m_controller->stopRewinding(); - m_controller->clearKeys(); } void Window::dragEnterEvent(QDragEnterEvent* event) { @@ -655,7 +566,7 @@ void Window::dropEvent(QDropEvent* event) { 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 @@ void Window::enterFullScreen() { } 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 @@ void Window::toggleFullScreen() { } } -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 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { m_fpsTimer.start(); m_focusCheck.start(); - m_controller->threadInterrupt(); - if (m_controller->isLoaded()) { - m_inputController.setPlatform(m_controller->platform()); + 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); - 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); - - 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 (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 (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); } } - m_controller->threadContinue(); + 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_display->startDrawing(m_controller); + + reloadAudioDriver(); } void Window::gameStopped() { @@ -784,9 +688,10 @@ void Window::gameStopped() { } 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 @@ void Window::gameStopped() { m_fpsTimer.stop(); m_focusCheck.stop(); + + emit paused(false); } void Window::gameCrashed(const QString& errorMessage) { @@ -809,7 +716,7 @@ void Window::gameCrashed(const QString& 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 @@ void Window::gameFailed() { 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 @@ void Window::unimplementedBiosCall(int call) { fail->show(); } +void Window::reloadDisplayDriver() { + if (m_controller) { + m_display->stopDrawing(); + detachWidget(m_display.get()); + } + m_display = std::move(std::unique_ptr(Display::create(this))); +#if defined(BUILD_GL) || defined(BUILD_GLES) + m_shaderView.reset(); + m_shaderView = std::make_unique(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(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::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::showFPS() { 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 @@ void Window::updateTitle(float fps) { 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 @@ void Window::openStateWindow(LoadSave ls) { 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 @@ void Window::openStateWindow(LoadSave ls) { }); 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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()); + connect(romInfo, &QAction::triggered, openControllerTView()); m_gameActions.append(romInfo); addControlledAction(fileMenu, romInfo, "romInfo"); @@ -999,13 +976,17 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { #endif QAction* about = new QAction(tr("About"), fileMenu); - connect(about, &QAction::triggered, this, &Window::openAboutScreen); + connect(about, &QAction::triggered, openTView()); fileMenu->addAction(about); #ifndef Q_OS_MAC @@ -1084,18 +1073,24 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { #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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(toolsMenu, viewLogs, "viewLogs"); QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu); - connect(overrides, &QAction::triggered, openTView(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 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(toolsMenu, sensors, "sensorWindow"); QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu); - connect(cheats, &QAction::triggered, openTView()); + connect(cheats, &QAction::triggered, openControllerTView()); m_gameActions.append(cheats); addControlledAction(toolsMenu, cheats, "cheatsWindow"); @@ -1377,38 +1395,39 @@ void Window::setupMenu(QMenuBar* menubar) { 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()); + connect(paletteView, &QAction::triggered, openControllerTView()); m_gameActions.append(paletteView); addControlledAction(toolsMenu, paletteView, "paletteWindow"); QAction* objView = new QAction(tr("View &sprites..."), toolsMenu); - connect(objView, &QAction::triggered, openTView()); + connect(objView, &QAction::triggered, openControllerTView()); m_gameActions.append(objView); addControlledAction(toolsMenu, objView, "spriteWindow"); QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu); - connect(tileView, &QAction::triggered, openTView()); + connect(tileView, &QAction::triggered, openControllerTView()); m_gameActions.append(tileView); addControlledAction(toolsMenu, tileView, "tileWindow"); QAction* memoryView = new QAction(tr("View memory..."), toolsMenu); - connect(memoryView, &QAction::triggered, openTView()); + connect(memoryView, &QAction::triggered, openControllerTView()); m_gameActions.append(memoryView); addControlledAction(toolsMenu, memoryView, "memoryView"); QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu); - connect(memorySearch, &QAction::triggered, openTView()); + connect(memorySearch, &QAction::triggered, openControllerTView()); 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()); + connect(ioViewer, &QAction::triggered, openControllerTView()); m_gameActions.append(ioViewer); m_gbaActions.append(ioViewer); addControlledAction(toolsMenu, ioViewer, "ioViewer"); @@ -1421,17 +1440,17 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::updateMRU() { 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 @@ QAction* Window::addHiddenAction(QMenu* menu, QAction* action, const QString& na } 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 @@ void Window::focusCheck() { } } +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(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 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(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,7 +1694,7 @@ 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; } @@ -1583,6 +1703,10 @@ void WindowBackground::setLockIntegerScaling(bool lock) { m_lockIntegerScaling = lock; } +void WindowBackground::setLockAspectRatio(bool lock) { + m_lockAspectRatio = lock; +} + void WindowBackground::paintEvent(QPaintEvent*) { const QPixmap* logo = pixmap(); if (!logo) { @@ -1593,10 +1717,12 @@ void WindowBackground::paintEvent(QPaintEvent*) { 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); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index d6727d572..a0fb45c52 100644 --- a/src/platform/qt/Window.h +++ b/src/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 #include +#include #include @@ -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 controller() { return m_controller; } void setConfig(ConfigController*); void argumentsPassed(mArguments*); @@ -51,13 +55,12 @@ public: 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 @@ public slots: void exportSharkport(); void openSettingsWindow(); - void openAboutScreen(); void startVideoLog(); @@ -111,12 +113,15 @@ protected: 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 @@ private: void openView(QWidget* widget); - template std::function openTView(A arg); - template std::function openTView(); + template std::function openTView(A... arg); + template std::function 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 @@ private: QString getFilters() const; QString getFiltersArchive() const; - GameController* m_controller; - Display* m_display; + CoreManager* m_manager; + std::shared_ptr m_controller; + std::unique_ptr m_audioProcessor; + + std::unique_ptr m_display; int m_savedScale; // TODO: Move these to a new class QList m_gameActions; @@ -177,14 +185,17 @@ private: QMenu* m_mruMenu = nullptr; QMenu* m_videoLayers; QMenu* m_audioChannels; - ShaderSelector* m_shaderView; + std::unique_ptr 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 @@ public: 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; }; diff --git a/src/platform/qt/input/InputController.cpp b/src/platform/qt/input/InputController.cpp index cbcd465a7..ab8f172bb 100644 --- a/src/platform/qt/input/InputController.cpp +++ b/src/platform/qt/input/InputController.cpp @@ -6,7 +6,7 @@ #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 @@ void InputController::addKey(const QString& 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 @@ void InputController::setPlatform(mPlatform platform) { rebuildKeyIndex(); restoreModel(); + +#ifdef M_CORE_GBA + m_lux.p = this; + m_lux.sample = [](GBALuminanceSource* context) { + InputControllerLux* lux = static_cast(context); + lux->value = 0xFF - lux->p->m_luxValue; + }; + + m_lux.readLuminance = [](GBALuminanceSource* context) { + InputControllerLux* lux = static_cast(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 @@ const mInputMap* InputController::map() { } 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 @@ void InputController::rebindKey(const QString& 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(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); +} + diff --git a/src/platform/qt/input/InputController.h b/src/platform/qt/input/InputController.h index a6b531d93..2ba0a23f3 100644 --- a/src/platform/qt/input/InputController.h +++ b/src/platform/qt/input/InputController.h @@ -21,6 +21,8 @@ #include #include +#include + #ifdef BUILD_SDL #include "platform/sdl/sdl-events.h" #endif @@ -62,8 +64,9 @@ public: 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 @@ public: 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 @@ private: 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 m_keyInfo; diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index fc558f226..1a7fc8bf4 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -83,6 +83,9 @@ LibraryController::~LibraryController() { } void LibraryController::setViewStyle(LibraryStyle newStyle) { + if (m_currentStyle == newStyle) { + return; + } m_currentStyle = newStyle; AbstractGameList* newCurrentList = nullptr; diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 521028d58..b008ac16d 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -21,6 +21,7 @@ #endif #endif +#include #include #include #include @@ -104,6 +105,16 @@ int main(int argc, char** argv) { 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); mCoreInitConfig(renderer.core, PORT); applyArguments(&args, &subparser, &renderer.core->config); @@ -148,6 +159,10 @@ int main(int argc, char** argv) { mSDLDetachPlayer(&renderer.events, &renderer.player); mInputMapDeinit(&renderer.core->inputMap); + if (device) { + mCheatDeviceDestroy(device); + } + mSDLDeinit(&renderer); freeArguments(&args); @@ -184,10 +199,10 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { #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) { diff --git a/src/util/elf-read.c b/src/util/elf-read.c new file mode 100644 index 000000000..8ac38774d --- /dev/null +++ b/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 + +#ifdef USE_ELF + +#include + +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