all repos — mgba @ 394a2e0bb886a779e9479935fc0082afa4b7ad26

mGBA Game Boy Advance Emulator

Merge branch 'feature/input-revamp' into medusa
Vicki Pfau vi@endrift.com
Mon, 24 Jul 2017 07:55:12 -0700
commit

394a2e0bb886a779e9479935fc0082afa4b7ad26

parent

76b8916dfec91edd2966be7ebbb6742e92123d2a

78 files changed, 2199 insertions(+), 694 deletions(-)

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

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

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

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

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

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

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

@@ -23,10 +23,17 @@ - ELF support

Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - Python: Fix importing .gb or .gba before .core +<<<<<<< HEAD +======= + - GBA: Reset active region as needed when loading a ROM + - Qt: Fix command line debugger closing second game +>>>>>>> feature/input-revamp 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: (2017-07-16) Features:
M CMakeLists.txtCMakeLists.txt

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

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

@@ -262,7 +262,7 @@ set(USE_DEBUGGERS OFF)

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

@@ -625,7 +625,7 @@

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}) + 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()
M include/mgba/core/thread.hinclude/mgba/core/thread.h

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

void mCoreThreadStopWaiting(struct mCoreThread* threadContext); void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool); +void mCoreThreadRewindParamsChanged(struct mCoreThread* threadContext); struct mCoreThread* mCoreThreadGet(void); struct mLogger* mCoreThreadLogger(void);
M src/core/rewind.csrc/core/rewind.c

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

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

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

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

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

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

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

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

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

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

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

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

@@ -208,7 +208,7 @@

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

@@ -312,6 +312,10 @@ 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; }

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

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

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

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

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

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

@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AssetTile.h" +#include "CoreController.h" #include "GBAApp.h" #include <QFontDatabase>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,32 +5,32 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AssetView.h" -#include <QTimer> +#include "CoreController.h" -#include <mgba/core/tile-cache.h> +#include <QTimer> using namespace QGBA; -AssetView::AssetView(GameController* controller, QWidget* parent) +AssetView::AssetView(std::shared_ptr<CoreController> controller, QWidget* parent) : QWidget(parent) , m_tileCache(controller->tileCache()) , m_controller(controller) { m_updateTimer.setSingleShot(true); m_updateTimer.setInterval(1); - connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles())); + connect(&m_updateTimer, &QTimer::timeout, this, static_cast<void(AssetView::*)()>(&AssetView::updateTiles)); - connect(m_controller, &GameController::frameAvailable, &m_updateTimer, + connect(controller.get(), &CoreController::frameAvailable, &m_updateTimer, static_cast<void(QTimer::*)()>(&QTimer::start)); - connect(m_controller, &GameController::gameStopped, this, &AssetView::close); - connect(m_controller, &GameController::gameStopped, &m_updateTimer, &QTimer::stop); + connect(controller.get(), &CoreController::stopping, this, &AssetView::close); + connect(controller.get(), &CoreController::stopping, &m_updateTimer, &QTimer::stop); } -void AssetView::updateTiles(bool force) { - if (!m_controller->isLoaded()) { - return; - } +void AssetView::updateTiles() { + updateTiles(false); +} +void AssetView::updateTiles(bool force) { switch (m_controller->platform()) { #ifdef M_CORE_GBA case PLATFORM_GBA:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -282,7 +283,7 @@ if(APPLE)

if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") get_target_property(QTCOCOA Qt5::QCocoaIntegrationPlugin LOCATION) get_target_property(COREAUDIO Qt5::CoreAudioPlugin LOCATION) - get_target_property(BUNDLE_PATH ${BINARY_NAME}-qt LOCATION) + set(BUNDLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.app) target_sources(${BINARY_NAME}-qt PRIVATE "${PLUGINS}") set_source_files_properties("${QTCOCOA}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns)
M src/platform/qt/CheatsView.cppsrc/platform/qt/CheatsView.cpp

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ConfigController.h" -#include "GameController.h" +#include "CoreController.h" #include <QAction> #include <QDir>

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

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

@@ -0,0 +1,698 @@

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

@@ -0,0 +1,194 @@

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

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

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "CoreManager.h" + +#include "CoreController.h" +#include "LogController.h" + +#include <QDir> + +#ifdef M_CORE_GBA +#include <mgba/gba/core.h> +#endif + +#include <mgba/core/core.h> +#include <mgba-util/vfs.h> + +using namespace QGBA; + +void CoreManager::setConfig(const mCoreConfig* config) { + m_config = config; +} + +void CoreManager::setMultiplayerController(MultiplayerController* multiplayer) { + m_multiplayer = multiplayer; +} + +CoreController* CoreManager::loadGame(const QString& path) { + QFileInfo info(path); + if (!info.isReadable()) { + QString fname = info.fileName(); + QString base = info.path(); + if (base.endsWith("/") || base.endsWith(QDir::separator())) { + base.chop(1); + } + VDir* dir = VDirOpenArchive(base.toUtf8().constData()); + if (dir) { + VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY); + if (vf) { + struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); + uint8_t buffer[2048]; + ssize_t read; + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + vfclone->write(vfclone, buffer, read); + } + vf->close(vf); + vf = vfclone; + } + dir->close(dir); + loadGame(vf, fname, base); + } else { + LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + } + return nullptr; + } + VFile* vf = nullptr; + VDir* archive = VDirOpenArchive(path.toUtf8().constData()); + if (archive) { + VFile* vfOriginal = VDirFindFirst(archive, [](VFile* vf) { + return mCoreIsCompatible(vf) != PLATFORM_NONE; + }); + ssize_t size; + if (vfOriginal && (size = vfOriginal->size(vfOriginal)) > 0) { + void* mem = vfOriginal->map(vfOriginal, size, MAP_READ); + vf = VFileMemChunk(mem, size); + vfOriginal->unmap(vfOriginal, mem, (size_t) read); + vfOriginal->close(vfOriginal); + } + } + QDir dir(info.dir()); + if (!vf) { + vf = VFileOpen(info.canonicalFilePath().toUtf8().constData(), O_RDONLY); + } + return loadGame(vf, info.fileName(), dir.canonicalPath()); +} + +CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QString& base) { + if (!vf) { + return nullptr; + } + + mCore* core = mCoreFindVF(vf); + if (!core) { + return nullptr; + } + + core->init(core); + mCoreInitConfig(core, nullptr); + + if (m_config) { + mCoreLoadForeignConfig(core, m_config); + } + + if (m_preload) { + mCorePreloadVF(core, vf); + } else { + core->loadROM(core, vf); + } + + QFileInfo info(base + "/" + path); + QByteArray bytes(info.baseName().toUtf8()); + strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + + bytes = info.dir().canonicalPath().toUtf8(); + mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); + mCoreAutoloadSave(core); + + CoreController* cc = new CoreController(core); + if (m_multiplayer) { + cc->setMultiplayerController(m_multiplayer); + } + emit coreLoaded(cc); + return cc; +} + +CoreController* CoreManager::loadBIOS(int platform, const QString& path) { + QFileInfo info(path); + VFile* vf = VFileOpen(info.canonicalFilePath().toUtf8().constData(), O_RDONLY); + if (!vf) { + return nullptr; + } + + mCore* core = nullptr; + switch (platform) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + core = GBACoreCreate(); + break; +#endif + default: + vf->close(vf); + return nullptr; + } + if (!core) { + vf->close(vf); + return nullptr; + } + + core->init(core); + mCoreInitConfig(core, nullptr); + + if (m_config) { + mCoreLoadForeignConfig(core, m_config); + } + + core->loadBIOS(core, vf, 0); + + mCoreConfigSetOverrideIntValue(&core->config, "useBios", 1); + mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0); + + QByteArray bytes(info.baseName().toUtf8()); + strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + + bytes = info.dir().canonicalPath().toUtf8(); + mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); + + CoreController* cc = new CoreController(core); + if (m_multiplayer) { + cc->setMultiplayerController(m_multiplayer); + } + emit coreLoaded(cc); + return cc; +}
A src/platform/qt/CoreManager.h

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

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_CORE_MANAGER +#define QGBA_CORE_MANAGER + +#include <QFileInfo> +#include <QObject> +#include <QString> + +struct mCoreConfig; +struct VFile; + +namespace QGBA { + +class CoreController; +class MultiplayerController; + +class CoreManager : public QObject { +Q_OBJECT + +public: + void setConfig(const mCoreConfig*); + void setMultiplayerController(MultiplayerController*); + void setPreload(bool preload) { m_preload = preload; } + +public slots: + CoreController* loadGame(const QString& path); + CoreController* loadGame(VFile* vf, const QString& path, const QString& base); + CoreController* loadBIOS(int platform, const QString& path); + +signals: + void coreLoaded(CoreController*); + +private: + const mCoreConfig* m_config = nullptr; + MultiplayerController* m_multiplayer = nullptr; + bool m_preload = false; +}; + +} + +#endif
M src/platform/qt/DebuggerConsoleController.cppsrc/platform/qt/DebuggerConsoleController.cpp

@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DebuggerConsoleController.h" -#include "GameController.h" +#include "CoreController.h" #include <QMutexLocker>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,32 +5,44 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GDBController.h" -#include "GameController.h" +#include "CoreController.h" using namespace QGBA; -DebuggerController::DebuggerController(GameController* controller, mDebugger* debugger, QObject* parent) +DebuggerController::DebuggerController(mDebugger* debugger, QObject* parent) : QObject(parent) , m_debugger(debugger) - , m_gameController(controller) { } bool DebuggerController::isAttached() { + if (!m_gameController) { + return false; + } return m_gameController->debugger() == m_debugger; } +void DebuggerController::setController(std::shared_ptr<CoreController> controller) { + if (m_gameController && controller != m_gameController) { + m_gameController->disconnect(this); + detach(); + } + m_gameController = controller; + if (controller) { + connect(m_gameController.get(), &CoreController::stopping, [this]() { + setController(nullptr); + }); + } +} + void DebuggerController::attach() { if (isAttached()) { return; } - if (m_gameController->isLoaded()) { + if (m_gameController) { attachInternal(); m_gameController->setDebugger(m_debugger); mDebuggerEnter(m_debugger, DEBUGGER_ENTER_ATTACHED, 0); - } else { - QObject::disconnect(m_autoattach); - m_autoattach = connect(m_gameController, &GameController::gameStarted, this, &DebuggerController::attach); } }

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

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

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

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

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

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

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

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

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

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

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

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

@@ -52,7 +56,6 @@ void showCursor();

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

@@ -60,7 +63,7 @@ virtual void forceDraw() = 0;

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

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

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

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

return shaders; } -void DisplayGL::startDrawing(mCoreThread* thread) { +void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) { if (m_drawThread) { return; } + QSize dims = controller->screenDimensions(); + setSystemDimensions(dims.width(), dims.height()); + 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,15 +73,10 @@ m_gl->context()->moveToThread(m_drawThread);

m_painter->moveToThread(m_drawThread); connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start); m_drawThread->start(); - mCoreSyncSetVideoSync(&m_context->impl->sync, false); lockAspectRatio(isAspectRatioLocked()); lockIntegerScaling(isIntegerScalingLocked()); - unsigned width, height; - thread->core->desiredVideoDimensions(thread->core, &width, &height); - setSystemDimensions(width, height); - filter(isFiltered()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());

@@ -90,41 +89,27 @@

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

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

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

@@ -188,12 +173,6 @@ }

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

@@ -267,7 +246,7 @@ delete m_backend;

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

@@ -278,9 +257,8 @@ m_gl->makeCurrent();

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

@@ -334,13 +312,13 @@ m_started = true;

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

@@ -354,7 +332,7 @@ }

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

@@ -380,6 +358,7 @@ m_backend->clear(m_backend);

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

@@ -414,9 +393,8 @@ buffer = m_queue.dequeue();

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

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

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

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

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

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

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

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

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

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

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

@@ -5,6 +5,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DisplayQt.h" +#include "CoreController.h" + #include <QPainter> #include <mgba/core/core.h>

@@ -17,11 +19,19 @@ : Display(parent)

{ } -void DisplayQt::startDrawing(mCoreThread* context) { - context->core->desiredVideoDimensions(context->core, &m_width, &m_height); +void DisplayQt::startDrawing(std::shared_ptr<CoreController> controller) { + QSize size = controller->screenDimensions(); + m_width = size.width(); + m_height = size.height(); setSystemDimensions(m_width, m_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) {

@@ -39,8 +49,9 @@ Display::filter(filter);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,12 +5,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GDBController.h" -#include "GameController.h" +#include "CoreController.h" using namespace QGBA; -GDBController::GDBController(GameController* controller, QObject* parent) - : DebuggerController(controller, &m_gdbStub.d, parent) +GDBController::GDBController(QObject* parent) + : DebuggerController(&m_gdbStub.d, parent) , m_bindAddress({ IPV4, 0 }) { GDBStubCreate(&m_gdbStub);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "IOViewer.h" -#include "GameController.h" +#include "CoreController.h" #include <QComboBox> #include <QFontDatabase>

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LoadSaveState.h" -#include "GameController.h" +#include "CoreController.h" #include "GamepadAxisEvent.h" #include "GamepadButtonEvent.h" #include "VFileDevice.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MultiplayerController.h" -#include "GameController.h" +#include "CoreController.h" #ifdef M_CORE_GBA #include <mgba/internal/gba/gba.h>

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ObjView.h" +#include "CoreController.h" #include "GBAApp.h" #include <QFontDatabase>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PaletteView.h" +#include "CoreController.h" #include "GBAApp.h" #include "LogController.h" #include "VFileDevice.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SensorView.h" -#include "GameController.h" +#include "CoreController.h" #include "GamepadAxisEvent.h" #include "InputController.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -183,19 +183,26 @@ }

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

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

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

@@ -5,6 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TileView.h" +#include "CoreController.h" #include "GBAApp.h" #include <QFontDatabase>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2017 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this

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

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

@@ -85,8 +87,9 @@ #endif

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)

@@ -95,20 +98,12 @@ {

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

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

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

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

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

@@ -157,67 +152,11 @@ m_screenWidget->setPixmap(m_logo);

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

@@ -238,6 +177,7 @@ }

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

@@ -255,18 +195,14 @@

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

@@ -289,19 +225,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)); }

@@ -310,22 +233,10 @@ if (opts->fullscreen) {

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

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

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

@@ -422,7 +339,7 @@

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

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

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

@@ -471,28 +388,32 @@ }

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

@@ -515,23 +436,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()) {

@@ -539,18 +454,19 @@ m_controller->startVideoLog(filename);

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

@@ -559,17 +475,8 @@ #ifdef USE_FFMPEG

void Window::openVideoWindow() { if (!m_videoView) { m_videoView = new VideoView(); - connect(m_videoView, &VideoView::recordingStarted, m_controller, &GameController::setAVStream, Qt::DirectConnection); - 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()); - m_videoView->setNativeFrameRate(m_controller->frameRate()); - }); - if (m_controller->isLoaded()) { - m_videoView->setNativeResolution(m_controller->screenDimensions()); - m_videoView->setNativeFrameRate(m_controller->frameRate()); + if (m_controller) { + m_videoView->setController(m_controller); } connect(this, &Window::shutdown, m_videoView, &QWidget::close); }

@@ -581,10 +488,9 @@ #ifdef USE_MAGICK

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

@@ -594,9 +500,11 @@

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

@@ -604,9 +512,12 @@

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

@@ -619,7 +530,7 @@ }

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

@@ -674,9 +585,6 @@ m_display->forceDraw();

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

@@ -698,11 +606,11 @@ // No remote loading

return; } event->accept(); - m_controller->loadGame(url.toLocalFile()); + setController(m_manager->loadGame(url.toLocalFile()), url.toLocalFile()); } void Window::mouseMoveEvent(QMouseEvent* event) { - if (!m_controller->isLoaded()) { + if (!m_controller) { return; } QPoint pos = event->pos();

@@ -712,7 +620,8 @@ QSize viewportDimensions = m_display->viewportSize();

QSize screenDimensions = m_screenWidget->size(); int x = dimensions.width() * (pos.x() - (screenDimensions.width() - viewportDimensions.width()) / 2) / viewportDimensions.width(); int y = dimensions.height() * (pos.y() - (screenDimensions.height() - viewportDimensions.height()) / 2) / viewportDimensions.height(); - m_controller->cursorLocation(x, y); + mCore* core = m_controller->thread()->core; + core->setCursorLocation(core, x, y); event->accept(); }

@@ -720,22 +629,24 @@ void Window::mousePressEvent(QMouseEvent* event) {

if (event->button() != Qt::LeftButton) { return; } - if (!m_controller->isLoaded()) { + if (!m_controller) { return; } mouseMoveEvent(event); - m_controller->cursorDown(true); + mCore* core = m_controller->thread()->core; + core->setCursorDown(core, true); } void Window::mouseReleaseEvent(QMouseEvent* event) { if (event->button() != Qt::LeftButton) { return; } - if (!m_controller->isLoaded()) { + if (!m_controller) { return; } mouseMoveEvent(event); - m_controller->cursorDown(false); + mCore* core = m_controller->thread()->core; + core->setCursorDown(core, false); } void Window::enterFullScreen() {

@@ -748,7 +659,7 @@ return;

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

@@ -771,11 +682,14 @@ enterFullScreen();

} } -void Window::gameStarted(mCoreThread* context, const QString& fname) { - if (!mCoreThreadIsActive(context)) { - return; +void Window::gameStarted() { + for (QAction* action : m_gameActions) { + action->setDisabled(false); } - int platform = 1 << context->core->platform(context->core); + int platform = 1 << m_controller->platform(); + for (QPair<QAction*, int> action : m_platformActions) { + action.first->setEnabled(action.second & platform); + } #ifdef M_CORE_DS if ((platform & SUPPORT_DS) && (!m_config->getOption("useBios").toInt() || m_config->getOption("ds.bios7").isNull() || m_config->getOption("ds.bios9").isNull() || m_config->getOption("ds.firmware").isNull())) { QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("BIOS required"),

@@ -783,34 +697,22 @@ tr("DS support requires dumps of the BIOS and firmware."),

QMessageBox::Ok, this, Qt::Sheet); fail->setAttribute(Qt::WA_DeleteOnClose); fail->show(); - m_controller->closeGame(); + m_controller->stop(); return; } #endif - emit startDrawing(context); - for (QAction* action : m_gameActions) { - action->setDisabled(false); - } - for (QPair<QAction*, int> action : m_platformActions) { - action.first->setEnabled(action.second & platform); - } 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(width, height); + 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()); setMouseTracking(true); #ifndef Q_OS_MAC

@@ -823,40 +725,40 @@ m_hitUnimplementedBiosCall = false;

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 (nVideo) { + for (size_t i = 0; i < nVideo; ++i) { + QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { + m_controller->thread()->core->enableVideoLayer(m_controller->thread()->core, videoLayers[i].id, enable); + }); + m_videoLayers->addAction(action); } - if (nAudio) { - for (size_t i = 0; i < nAudio; ++i) { - QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); - action->setCheckable(true); - action->setChecked(true); - connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { - m_controller->setAudioChannelEnabled(audioChannels[i].id, enable); - }); - m_audioChannels->addAction(action); - } + } + if (nAudio) { + for (size_t i = 0; i < nAudio; ++i) { + QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { + m_controller->thread()->core->enableAudioChannel(m_controller->thread()->core, audioChannels[i].id, enable); + }); + m_audioChannels->addAction(action); } } - m_controller->threadContinue(); + m_display->startDrawing(m_controller); + + reloadAudioDriver(); } void Window::gameStopped() {

@@ -868,11 +770,10 @@ action->setDisabled(true);

} setWindowFilePath(QString()); updateTitle(); - detachWidget(m_display); + detachWidget(m_display.get()); m_screenWidget->setCenteredAspectRatio(m_logo.width(), m_logo.height()); 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

@@ -888,6 +789,8 @@ m_audioChannels->clear();

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

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

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

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

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

@@ -922,6 +824,74 @@ fail->setAttribute(Qt::WA_DeleteOnClose);

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

@@ -960,8 +930,8 @@

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

@@ -977,19 +947,18 @@ if (db && crc32 && NoIntroDBLookupGameByCRC(db, crc32, &game)) {

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

@@ -1010,7 +979,6 @@ }

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

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

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

@@ -1046,17 +1018,18 @@ addControlledAction(fileMenu, loadTemporarySave, "loadTemporarySave");

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

@@ -1088,14 +1061,18 @@ QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));

QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save")); QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); - connect(quickLoad, &QAction::triggered, m_controller, &GameController::loadState); + connect(quickLoad, &QAction::triggered, [this] { + m_controller->loadState(); + }); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); m_platformActions.append(qMakePair(quickLoad, SUPPORT_GB | SUPPORT_GBA)); 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");

@@ -1106,7 +1083,9 @@ quickSaveMenu->addSeparator();

QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu); undoLoadState->setShortcut(tr("F11")); - connect(undoLoadState, &QAction::triggered, m_controller, &GameController::loadBackupState); + connect(undoLoadState, &QAction::triggered, [this]() { + m_controller->loadBackupState(); + }); m_gameActions.append(undoLoadState); m_nonMpActions.append(undoLoadState); m_platformActions.append(qMakePair(undoLoadState, SUPPORT_GB | SUPPORT_GBA));

@@ -1114,7 +1093,9 @@ 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); m_platformActions.append(qMakePair(undoSaveState, SUPPORT_GB | SUPPORT_GBA));

@@ -1127,7 +1108,9 @@ int i;

for (i = 1; i < 10; ++i) { quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu); quickLoad->setShortcut(tr("F%1").arg(i)); - connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); + connect(quickLoad, &QAction::triggered, [this, i]() { + m_controller->loadState(i); + }); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); m_platformActions.append(qMakePair(quickLoad, SUPPORT_GB | SUPPORT_GBA));

@@ -1135,7 +1118,9 @@ 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); m_platformActions.append(qMakePair(quickSave, SUPPORT_GB | SUPPORT_GBA));

@@ -1169,7 +1154,7 @@ fileMenu->addSeparator();

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

@@ -1179,18 +1164,24 @@

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

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

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

@@ -1244,14 +1240,16 @@ }

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

@@ -1270,14 +1268,14 @@

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

@@ -1285,26 +1283,26 @@ emulationMenu->addSeparator();

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

@@ -1320,7 +1318,7 @@ }

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

@@ -1346,7 +1344,7 @@ ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio");

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

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

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

@@ -1392,7 +1390,7 @@

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

@@ -1411,7 +1409,9 @@

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

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

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

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

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

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

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

@@ -1478,41 +1489,42 @@ #ifdef USE_GDB_STUB

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

@@ -1525,17 +1537,17 @@ }, this);

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

@@ -1545,39 +1557,37 @@ }, this);

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

@@ -1627,7 +1637,9 @@ int i = 0;

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

@@ -1650,7 +1662,7 @@ return action;

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

@@ -1660,6 +1672,107 @@ } else if (!QGuiApplication::focusWindow() && !m_controller->isPaused()) {

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

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

-/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2017 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this

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

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

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

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

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

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

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

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

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

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

@@ -113,11 +115,14 @@ virtual void mousePressEvent(QMouseEvent*) override;

virtual void mouseReleaseEvent(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();

@@ -141,8 +146,8 @@ void updateMRU();

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

@@ -152,8 +157,11 @@

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

@@ -177,13 +185,16 @@ QList<QString> m_mruFiles;

QMenu* m_mruMenu = nullptr; QMenu* m_videoLayers; QMenu* m_audioChannels; - ShaderSelector* m_shaderView; + std::unique_ptr<ShaderSelector> m_shaderView; bool m_fullscreenOnStart = false; QTimer m_focusCheck; bool m_autoresume = false; bool m_wasOpened = false; + QString m_pendingPatch; bool m_hitUnimplementedBiosCall; + + OverrideView* m_overrideView = nullptr; #ifdef USE_FFMPEG VideoView* m_videoView = nullptr;
M src/platform/qt/input/InputController.cppsrc/platform/qt/input/InputController.cpp

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

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

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

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

@@ -130,6 +154,20 @@ #endif

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

@@ -167,7 +205,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

@@ -382,7 +419,7 @@ return &m_inputMap;

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

@@ -853,3 +890,34 @@ bindKey(SDL_BINDING_BUTTON, item->button(), key);

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

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

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

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

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

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

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

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

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

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

} void LibraryController::setViewStyle(LibraryStyle newStyle) { + if (m_currentStyle == newStyle) { + return; + } m_currentStyle = newStyle; AbstractGameList* newCurrentList = nullptr;