all repos — mgba @ 2aac9da42b7514ddb3105becf46de8dee5c797f2

mGBA Game Boy Advance Emulator

src/platform/qt/CoreController.cpp (view raw)

   1/* Copyright (c) 2013-2017 Jeffrey Pfau
   2 *
   3 * This Source Code Form is subject to the terms of the Mozilla Public
   4 * License, v. 2.0. If a copy of the MPL was not distributed with this
   5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
   6#include "CoreController.h"
   7
   8#include "ConfigController.h"
   9#include "InputController.h"
  10#include "LogController.h"
  11#include "MultiplayerController.h"
  12#include "Override.h"
  13
  14#include <QDateTime>
  15#include <QMutexLocker>
  16
  17#include <mgba/core/serialize.h>
  18#include <mgba/feature/video-logger.h>
  19#ifdef M_CORE_GBA
  20#include <mgba/internal/gba/gba.h>
  21#include <mgba/internal/gba/renderers/cache-set.h>
  22#include <mgba/internal/gba/sharkport.h>
  23#endif
  24#ifdef M_CORE_GB
  25#include <mgba/internal/gb/gb.h>
  26#include <mgba/internal/gb/renderers/cache-set.h>
  27#endif
  28#include "feature/sqlite3/no-intro.h"
  29#include <mgba-util/math.h>
  30#include <mgba-util/vfs.h>
  31
  32#define AUTOSAVE_GRANULARITY 600
  33
  34using namespace QGBA;
  35
  36CoreController::CoreController(mCore* core, QObject* parent)
  37	: QObject(parent)
  38	, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
  39	, m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC)
  40{
  41	m_threadContext.core = core;
  42	m_threadContext.userData = this;
  43	updateROMInfo();
  44
  45#ifdef M_CORE_GBA
  46	GBASIODolphinCreate(&m_dolphin);
  47#endif
  48
  49	m_resetActions.append([this]() {
  50		if (m_autoload) {
  51			mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);
  52		}
  53	});
  54
  55	m_threadContext.startCallback = [](mCoreThread* context) {
  56		CoreController* controller = static_cast<CoreController*>(context->userData);
  57
  58		switch (context->core->platform(context->core)) {
  59#ifdef M_CORE_GBA
  60		case mPLATFORM_GBA:
  61			context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, controller->m_inputController->luminance());
  62			break;
  63#endif
  64		default:
  65			break;
  66		}
  67
  68		controller->updateFastForward();
  69
  70		if (controller->m_multiplayer) {
  71			controller->m_multiplayer->attachGame(controller);
  72		}
  73
  74		QMetaObject::invokeMethod(controller, "started");
  75	};
  76
  77	m_threadContext.resetCallback = [](mCoreThread* context) {
  78		CoreController* controller = static_cast<CoreController*>(context->userData);
  79		for (auto action : controller->m_resetActions) {
  80			action();
  81		}
  82
  83		if (controller->m_override) {
  84			controller->m_override->identify(context->core);
  85			controller->m_override->apply(context->core);
  86		}
  87
  88		controller->m_resetActions.clear();
  89
  90		if (!controller->m_hwaccel) {
  91			context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width());
  92		}
  93
  94		QMetaObject::invokeMethod(controller, "didReset");
  95		controller->finishFrame();
  96	};
  97
  98	m_threadContext.frameCallback = [](mCoreThread* context) {
  99		CoreController* controller = static_cast<CoreController*>(context->userData);
 100
 101		if (controller->m_autosaveCounter == AUTOSAVE_GRANULARITY) {
 102			if (controller->m_autosave) {
 103				mCoreSaveState(context->core, 0, controller->m_saveStateFlags);
 104			}
 105			controller->m_autosaveCounter = 0;
 106		}
 107		++controller->m_autosaveCounter;
 108
 109		controller->finishFrame();
 110	};
 111
 112	m_threadContext.cleanCallback = [](mCoreThread* context) {
 113		CoreController* controller = static_cast<CoreController*>(context->userData);
 114
 115		if (controller->m_autosave) {
 116			mCoreSaveState(context->core, 0, controller->m_saveStateFlags);
 117		}
 118
 119		controller->clearMultiplayerController();
 120#ifdef M_CORE_GBA
 121		controller->detachDolphin();
 122#endif
 123		QMetaObject::invokeMethod(controller, "stopping");
 124	};
 125
 126	m_threadContext.pauseCallback = [](mCoreThread* context) {
 127		CoreController* controller = static_cast<CoreController*>(context->userData);
 128
 129		QMetaObject::invokeMethod(controller, "paused");
 130	};
 131
 132	m_threadContext.unpauseCallback = [](mCoreThread* context) {
 133		CoreController* controller = static_cast<CoreController*>(context->userData);
 134
 135		QMetaObject::invokeMethod(controller, "unpaused");
 136	};
 137
 138	m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
 139		mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger);
 140		mCoreThread* context = logContext->p;
 141
 142		static const char* savestateMessage = "State %i saved";
 143		static const char* loadstateMessage = "State %i loaded";
 144		static const char* savestateFailedMessage = "State %i failed to load";
 145		static int biosCat = -1;
 146		static int statusCat = -1;
 147		if (!context) {
 148			return;
 149		}
 150		CoreController* controller = static_cast<CoreController*>(context->userData);
 151		QString message;
 152		if (biosCat < 0) {
 153			biosCat = mLogCategoryById("gba.bios");
 154		}
 155		if (statusCat < 0) {
 156			statusCat = mLogCategoryById("core.status");
 157		}
 158#ifdef M_CORE_GBA
 159		if (level == mLOG_STUB && category == biosCat) {
 160			va_list argc;
 161			va_copy(argc, args);
 162			int immediate = va_arg(argc, int);
 163			va_end(argc);
 164			QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate));
 165		} else
 166#endif
 167		if (category == statusCat) {
 168			// Slot 0 is reserved for suspend points
 169			if (strncmp(loadstateMessage, format, strlen(loadstateMessage)) == 0) {
 170				va_list argc;
 171				va_copy(argc, args);
 172				int slot = va_arg(argc, int);
 173				va_end(argc);
 174				if (slot == 0) {
 175					format = "Loaded suspend state";
 176				}
 177			} else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0 || strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
 178				va_list argc;
 179				va_copy(argc, args);
 180				int slot = va_arg(argc, int);
 181				va_end(argc);
 182				if (slot == 0) {
 183					return;
 184				}
 185			}
 186			message = QString().vsprintf(format, args);
 187			QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
 188		}
 189		message = QString().vsprintf(format, args);
 190		QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message));
 191		if (level == mLOG_FATAL) {
 192			QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, QString().vsprintf(format, args)));
 193		}
 194	};
 195}
 196
 197CoreController::~CoreController() {
 198	endVideoLog();
 199	stop();
 200	disconnect();
 201
 202	mCoreThreadJoin(&m_threadContext);
 203
 204	if (m_cacheSet) {
 205		mCacheSetDeinit(m_cacheSet.get());
 206		m_cacheSet.reset();
 207	}
 208
 209	mCoreConfigDeinit(&m_threadContext.core->config);
 210	m_threadContext.core->deinit(m_threadContext.core);
 211}
 212
 213const color_t* CoreController::drawContext() {
 214	if (m_hwaccel) {
 215		return nullptr;
 216	}
 217	QMutexLocker locker(&m_bufferMutex);
 218	return reinterpret_cast<const color_t*>(m_completeBuffer.constData());
 219}
 220
 221QImage CoreController::getPixels() {
 222	QByteArray buffer;
 223	QSize size = screenDimensions();
 224	size_t stride = size.width() * BYTES_PER_PIXEL;
 225
 226	if (!m_hwaccel) {
 227		buffer = m_completeBuffer;
 228	} else {
 229		Interrupter interrupter(this);
 230		const void* pixels;
 231		m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride);
 232		stride *= BYTES_PER_PIXEL;
 233		buffer = QByteArray::fromRawData(static_cast<const char*>(pixels), stride * size.height());
 234	}
 235
 236	QImage image(reinterpret_cast<const uchar*>(buffer.constData()),
 237	             size.width(), size.height(), stride, QImage::Format_RGBX8888);
 238	image.bits(); // Cause QImage to detach
 239	return image;
 240}
 241
 242bool CoreController::isPaused() {
 243	return mCoreThreadIsPaused(&m_threadContext);
 244}
 245
 246bool CoreController::hasStarted() {
 247	return mCoreThreadHasStarted(&m_threadContext);
 248}
 249
 250mPlatform CoreController::platform() const {
 251	return m_threadContext.core->platform(m_threadContext.core);
 252}
 253
 254QSize CoreController::screenDimensions() const {
 255	unsigned width, height;
 256	m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
 257
 258	return QSize(width, height);
 259}
 260
 261void CoreController::loadConfig(ConfigController* config) {
 262	Interrupter interrupter(this);
 263	m_loadStateFlags = config->getOption("loadStateExtdata", m_loadStateFlags).toInt();
 264	m_saveStateFlags = config->getOption("saveStateExtdata", m_saveStateFlags).toInt();
 265	m_fastForwardRatio = config->getOption("fastForwardRatio", m_fastForwardRatio).toFloat();
 266	m_fastForwardHeldRatio = config->getOption("fastForwardHeldRatio", m_fastForwardRatio).toFloat();
 267	m_videoSync = config->getOption("videoSync", m_videoSync).toInt();
 268	m_audioSync = config->getOption("audioSync", m_audioSync).toInt();
 269	m_fpsTarget = config->getOption("fpsTarget").toFloat();
 270	m_autosave = config->getOption("autosave", false).toInt();
 271	m_autoload = config->getOption("autoload", true).toInt();
 272	m_autofireThreshold = config->getOption("autofireThreshold", m_autofireThreshold).toInt();
 273	m_fastForwardVolume = config->getOption("fastForwardVolume", -1).toInt();
 274	m_fastForwardMute = config->getOption("fastForwardMute", -1).toInt();
 275	mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume");
 276	mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
 277
 278	QSize sizeBefore = screenDimensions();
 279	m_activeBuffer.resize(256 * 224 * sizeof(color_t));
 280	m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeBefore.width());
 281
 282	mCoreLoadForeignConfig(m_threadContext.core, config->config());
 283
 284	QSize sizeAfter = screenDimensions();
 285	m_activeBuffer.resize(sizeAfter.width() * sizeAfter.height() * sizeof(color_t));
 286	m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeAfter.width());
 287
 288	if (hasStarted()) {
 289		updateFastForward();
 290		mCoreThreadRewindParamsChanged(&m_threadContext);
 291	}
 292	if (sizeBefore != sizeAfter) {
 293#ifdef M_CORE_GB
 294		mCoreConfigSetIntValue(&m_threadContext.core->config, "sgb.borders", 0);
 295		m_threadContext.core->reloadConfigOption(m_threadContext.core, "sgb.borders", nullptr);
 296		mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "sgb.borders");
 297		m_threadContext.core->reloadConfigOption(m_threadContext.core, "sgb.borders", nullptr);
 298#endif
 299	}
 300}
 301
 302#ifdef USE_DEBUGGERS
 303void CoreController::setDebugger(mDebugger* debugger) {
 304	Interrupter interrupter(this);
 305	if (debugger) {
 306		mDebuggerAttach(debugger, m_threadContext.core);
 307		mDebuggerEnter(debugger, DEBUGGER_ENTER_ATTACHED, 0);
 308	} else {
 309		m_threadContext.core->detachDebugger(m_threadContext.core);
 310	}
 311}
 312#endif
 313
 314void CoreController::setMultiplayerController(MultiplayerController* controller) {
 315	if (controller == m_multiplayer) {
 316		return;
 317	}
 318	clearMultiplayerController();
 319	m_multiplayer = controller;
 320	if (!mCoreThreadHasStarted(&m_threadContext)) {
 321		return;
 322	}
 323	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
 324		CoreController* controller = static_cast<CoreController*>(thread->userData);
 325		controller->m_multiplayer->attachGame(controller);
 326	});
 327}
 328
 329void CoreController::clearMultiplayerController() {
 330	if (!m_multiplayer) {
 331		return;
 332	}
 333	m_multiplayer->detachGame(this);
 334	m_multiplayer = nullptr;
 335}
 336
 337mCacheSet* CoreController::graphicCaches() {
 338	if (m_cacheSet) {
 339		return m_cacheSet.get();
 340	}
 341	Interrupter interrupter(this);
 342	switch (platform()) {
 343#ifdef M_CORE_GBA
 344	case mPLATFORM_GBA: {
 345		GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
 346		m_cacheSet = std::make_unique<mCacheSet>();
 347		GBAVideoCacheInit(m_cacheSet.get());
 348		GBAVideoCacheAssociate(m_cacheSet.get(), &gba->video);
 349		break;
 350	}
 351#endif
 352#ifdef M_CORE_GB
 353	case mPLATFORM_GB: {
 354		GB* gb = static_cast<GB*>(m_threadContext.core->board);
 355		m_cacheSet = std::make_unique<mCacheSet>();
 356		GBVideoCacheInit(m_cacheSet.get());
 357		GBVideoCacheAssociate(m_cacheSet.get(), &gb->video);
 358		break;
 359	}
 360#endif
 361	default:
 362		return nullptr;
 363	}
 364	return m_cacheSet.get();
 365}
 366
 367#ifdef M_CORE_GBA
 368bool CoreController::attachDolphin(const Address& address) {
 369	if (platform() != mPLATFORM_GBA) {
 370		return false;
 371	}
 372	if (GBASIODolphinConnect(&m_dolphin, &address, 0, 0)) {
 373		GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
 374		GBASIOSetDriver(&gba->sio, &m_dolphin.d, SIO_JOYBUS);
 375		return true;
 376	}
 377	return false;
 378}
 379
 380void CoreController::detachDolphin() {
 381	if (platform() == mPLATFORM_GBA) {
 382		GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
 383		GBASIOSetDriver(&gba->sio, nullptr, SIO_JOYBUS);
 384	}
 385	GBASIODolphinDestroy(&m_dolphin);
 386}
 387#endif
 388
 389void CoreController::setOverride(std::unique_ptr<Override> override) {
 390	Interrupter interrupter(this);
 391	m_override = std::move(override);
 392	m_override->identify(m_threadContext.core);
 393}
 394
 395void CoreController::setInputController(InputController* inputController) {
 396	m_inputController = inputController;
 397	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_ROTATION, m_inputController->rotationSource());
 398	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_RUMBLE, m_inputController->rumble());
 399	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_IMAGE_SOURCE, m_inputController->imageSource());
 400}
 401
 402void CoreController::setLogger(LogController* logger) {
 403	disconnect(m_log);
 404	m_log = logger;
 405	m_threadContext.logger.d.filter = logger->filter();
 406	connect(this, &CoreController::logPosted, m_log, &LogController::postLog);
 407}
 408
 409void CoreController::start() {
 410	QSize size(screenDimensions());
 411	m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t));
 412	m_activeBuffer.fill(0xFF);
 413	m_completeBuffer = m_activeBuffer;
 414
 415	m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), size.width());
 416
 417	if (!m_patched) {
 418		mCoreAutoloadPatch(m_threadContext.core);
 419	}
 420	if (!mCoreThreadStart(&m_threadContext)) {
 421		emit failed();
 422		emit stopping();
 423	}
 424}
 425
 426void CoreController::stop() {
 427	setSync(false);
 428#ifdef USE_DEBUGGERS
 429	setDebugger(nullptr);
 430#endif
 431	setPaused(false);
 432	mCoreThreadEnd(&m_threadContext);
 433}
 434
 435void CoreController::reset() {
 436	mCoreThreadReset(&m_threadContext);
 437}
 438
 439void CoreController::setPaused(bool paused) {
 440	QMutexLocker locker(&m_actionMutex);
 441	if (paused) {
 442		if (m_moreFrames < 0) {
 443			m_moreFrames = 1;
 444		}
 445	} else {
 446		m_moreFrames = -1;
 447		if (isPaused()) {
 448			mCoreThreadUnpause(&m_threadContext);
 449		}
 450	}
 451}
 452
 453void CoreController::frameAdvance() {
 454	QMutexLocker locker(&m_actionMutex);
 455	m_moreFrames = 1;
 456	if (isPaused()) {
 457		mCoreThreadUnpause(&m_threadContext);
 458	}
 459}
 460
 461void CoreController::addFrameAction(std::function<void ()> action) {
 462	QMutexLocker locker(&m_actionMutex);
 463	m_frameActions.append(action);
 464}
 465
 466void CoreController::setSync(bool sync) {
 467	if (sync) {
 468		m_threadContext.impl->sync.audioWait = m_audioSync;
 469		m_threadContext.impl->sync.videoFrameWait = m_videoSync;
 470	} else {
 471		m_threadContext.impl->sync.audioWait = false;
 472		m_threadContext.impl->sync.videoFrameWait = false;
 473	}
 474}
 475
 476void CoreController::setRewinding(bool rewind) {
 477	if (!m_threadContext.core->opts.rewindEnable) {
 478		return;
 479	}
 480	if (rewind && m_multiplayer && m_multiplayer->attached() > 1) {
 481		return;
 482	}
 483
 484	if (rewind && isPaused()) {
 485		setPaused(false);
 486		// TODO: restore autopausing
 487	}
 488	mCoreThreadSetRewinding(&m_threadContext, rewind);
 489}
 490
 491void CoreController::rewind(int states) {
 492	{
 493		Interrupter interrupter(this);
 494		if (!states) {
 495			states = INT_MAX;
 496		}
 497		for (int i = 0; i < states; ++i) {
 498			if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) {
 499				break;
 500			}
 501		}
 502	}
 503	emit frameAvailable();
 504	emit rewound();
 505}
 506
 507void CoreController::setFastForward(bool enable) {
 508	if (m_fastForward == enable) {
 509		return;
 510	}
 511	m_fastForward = enable;
 512	updateFastForward();
 513	emit fastForwardChanged(enable);
 514}
 515
 516void CoreController::forceFastForward(bool enable) {
 517	if (m_fastForwardForced == enable) {
 518		return;
 519	}
 520	m_fastForwardForced = enable;
 521	updateFastForward();
 522	emit fastForwardChanged(enable || m_fastForward);
 523}
 524
 525void CoreController::loadState(int slot) {
 526	if (slot > 0 && slot != m_stateSlot) {
 527		m_stateSlot = slot;
 528		m_backupSaveState.clear();
 529	}
 530	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 531		CoreController* controller = static_cast<CoreController*>(context->userData);
 532		if (!controller->m_backupLoadState.isOpen()) {
 533			controller->m_backupLoadState = VFileDevice::openMemory();
 534		}
 535		mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
 536		if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
 537			emit controller->frameAvailable();
 538			emit controller->stateLoaded();
 539		}
 540	});
 541}
 542
 543void CoreController::loadState(const QString& path, int flags) {
 544	m_statePath = path;
 545	int savedFlags = m_loadStateFlags;
 546	if (flags != -1) {
 547		m_loadStateFlags = flags;
 548	}
 549	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 550		CoreController* controller = static_cast<CoreController*>(context->userData);
 551		VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
 552		if (!vf) {
 553			return;
 554		}
 555		if (!controller->m_backupLoadState.isOpen()) {
 556			controller->m_backupLoadState = VFileDevice::openMemory();
 557		}
 558		mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
 559		if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
 560			emit controller->frameAvailable();
 561			emit controller->stateLoaded();
 562		}
 563		vf->close(vf);
 564	});
 565	m_loadStateFlags = savedFlags;
 566}
 567
 568void CoreController::loadState(QIODevice* iodev, int flags) {
 569	m_stateVf = VFileDevice::wrap(iodev, QIODevice::ReadOnly);
 570	if (!m_stateVf) {
 571		return;
 572	}
 573	int savedFlags = m_loadStateFlags;
 574	if (flags != -1) {
 575		m_loadStateFlags = flags;
 576	}
 577	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 578		CoreController* controller = static_cast<CoreController*>(context->userData);
 579		VFile* vf = controller->m_stateVf;
 580		if (!vf) {
 581			return;
 582		}
 583		if (!controller->m_backupLoadState.isOpen()) {
 584			controller->m_backupLoadState = VFileDevice::openMemory();
 585		}
 586		mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
 587		if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
 588			emit controller->frameAvailable();
 589			emit controller->stateLoaded();
 590		}
 591		vf->close(vf);
 592	});
 593	m_loadStateFlags = savedFlags;
 594}
 595
 596void CoreController::saveState(int slot) {
 597	if (slot > 0) {
 598		m_stateSlot = slot;
 599	}
 600	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 601		CoreController* controller = static_cast<CoreController*>(context->userData);
 602		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false);
 603		if (vf) {
 604			controller->m_backupSaveState.resize(vf->size(vf));
 605			vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
 606			vf->close(vf);
 607		}
 608		mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags);
 609	});
 610}
 611
 612void CoreController::saveState(const QString& path, int flags) {
 613	m_statePath = path;
 614	int savedFlags = m_saveStateFlags;
 615	if (flags != -1) {
 616		m_saveStateFlags = flags;
 617	}
 618	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 619		CoreController* controller = static_cast<CoreController*>(context->userData);
 620		VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
 621		if (vf) {
 622			controller->m_backupSaveState.resize(vf->size(vf));
 623			vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
 624			vf->close(vf);
 625		}
 626		vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC);
 627		if (!vf) {
 628			return;
 629		}
 630		mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
 631		vf->close(vf);
 632	});
 633	m_saveStateFlags = savedFlags;
 634}
 635
 636void CoreController::saveState(QIODevice* iodev, int flags) {
 637	m_stateVf = VFileDevice::wrap(iodev, QIODevice::WriteOnly | QIODevice::Truncate);
 638	if (!m_stateVf) {
 639		return;
 640	}
 641	int savedFlags = m_saveStateFlags;
 642	if (flags != -1) {
 643		m_saveStateFlags = flags;
 644	}
 645	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 646		CoreController* controller = static_cast<CoreController*>(context->userData);
 647		VFile* vf = controller->m_stateVf;
 648		if (!vf) {
 649			return;
 650		}
 651		mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
 652		vf->close(vf);
 653	});
 654	m_saveStateFlags = savedFlags;
 655}
 656
 657void CoreController::loadBackupState() {
 658	if (!m_backupLoadState.isOpen()) {
 659		return;
 660	}
 661
 662	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 663		CoreController* controller = static_cast<CoreController*>(context->userData);
 664		controller->m_backupLoadState.seek(0);
 665		if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
 666			mLOG(STATUS, INFO, "Undid state load");
 667			controller->frameAvailable();
 668			controller->stateLoaded();
 669		}
 670		controller->m_backupLoadState.close();
 671	});
 672}
 673
 674void CoreController::saveBackupState() {
 675	if (m_backupSaveState.isEmpty()) {
 676		return;
 677	}
 678
 679	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 680		CoreController* controller = static_cast<CoreController*>(context->userData);
 681		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true);
 682		if (vf) {
 683			vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
 684			vf->close(vf);
 685			mLOG(STATUS, INFO, "Undid state save");
 686		}
 687		controller->m_backupSaveState.clear();
 688	});
 689}
 690
 691void CoreController::loadSave(const QString& path, bool temporary) {
 692	m_resetActions.append([this, path, temporary]() {
 693		VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR);
 694		if (!vf) {
 695			LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path);
 696			return;
 697		}
 698
 699		if (temporary) {
 700			m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
 701		} else {
 702			m_threadContext.core->loadSave(m_threadContext.core, vf);
 703		}
 704	});
 705	reset();
 706}
 707
 708void CoreController::loadPatch(const QString& patchPath) {
 709	Interrupter interrupter(this);
 710	VFile* patch = VFileDevice::open(patchPath, O_RDONLY);
 711	if (patch) {
 712		m_threadContext.core->loadPatch(m_threadContext.core, patch);
 713		m_patched = true;
 714		patch->close(patch);
 715		updateROMInfo();
 716	}
 717	if (mCoreThreadHasStarted(&m_threadContext)) {
 718		interrupter.resume();
 719		reset();
 720	}
 721}
 722
 723void CoreController::replaceGame(const QString& path) {
 724	QFileInfo info(path);
 725	if (!info.isReadable()) {
 726		LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
 727		return;
 728	}
 729	QString fname = info.canonicalFilePath();
 730	Interrupter interrupter(this);
 731	mDirectorySetDetachBase(&m_threadContext.core->dirs);
 732	mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData());
 733	updateROMInfo();
 734}
 735
 736void CoreController::yankPak() {
 737	Interrupter interrupter(this);
 738
 739	switch (platform()) {
 740#ifdef M_CORE_GBA
 741	case mPLATFORM_GBA:
 742		GBAYankROM(static_cast<GBA*>(m_threadContext.core->board));
 743		break;
 744#endif
 745#ifdef M_CORE_GB
 746	case mPLATFORM_GB:
 747		GBYankROM(static_cast<GB*>(m_threadContext.core->board));
 748		break;
 749#endif
 750	case mPLATFORM_NONE:
 751		LOG(QT, ERROR) << tr("Can't yank pack in unexpected platform!");
 752		break;
 753	}
 754}
 755
 756void CoreController::addKey(int key) {
 757	m_activeKeys |= 1 << key;
 758}
 759
 760void CoreController::clearKey(int key) {
 761	m_activeKeys &= ~(1 << key);
 762}
 763
 764void CoreController::setAutofire(int key, bool enable) {
 765	if (key >= 32 || key < 0) {
 766		return;
 767	}
 768
 769	m_autofire[key] = enable;
 770	m_autofireStatus[key] = 0;
 771}
 772
 773#ifdef USE_PNG
 774void CoreController::screenshot() {
 775	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 776		mCoreTakeScreenshot(context->core);
 777	});
 778}
 779#endif
 780
 781void CoreController::setRealTime() {
 782	m_threadContext.core->rtc.override = RTC_NO_OVERRIDE;
 783}
 784
 785void CoreController::setFixedTime(const QDateTime& time) {
 786	m_threadContext.core->rtc.override = RTC_FIXED;
 787	m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
 788}
 789
 790void CoreController::setFakeEpoch(const QDateTime& time) {
 791	m_threadContext.core->rtc.override = RTC_FAKE_EPOCH;
 792	m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
 793}
 794
 795void CoreController::scanCard(const QString& path) {
 796#ifdef M_CORE_GBA
 797	QImage image(path);
 798	if (image.isNull()) {
 799		QFile file(path);
 800		file.open(QIODevice::ReadOnly);
 801		m_eReaderData = file.read(2912);
 802	} else if (image.size() == QSize(989, 44)) {
 803		const uchar* bits = image.constBits();
 804		size_t size;
 805#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
 806		size = image.sizeInBytes();
 807#else
 808		size = image.byteCount();
 809#endif
 810		m_eReaderData.setRawData(reinterpret_cast<const char*>(bits), size);
 811	}
 812
 813	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
 814		CoreController* controller = static_cast<CoreController*>(thread->userData);
 815		GBAEReaderQueueCard(static_cast<GBA*>(thread->core->board), controller->m_eReaderData.constData(), controller->m_eReaderData.size());
 816	});
 817#endif
 818}
 819
 820
 821void CoreController::importSharkport(const QString& path) {
 822#ifdef M_CORE_GBA
 823	if (platform() != mPLATFORM_GBA) {
 824		return;
 825	}
 826	VFile* vf = VFileDevice::open(path, O_RDONLY);
 827	if (!vf) {
 828		LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
 829		return;
 830	}
 831	Interrupter interrupter(this);
 832	GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
 833	vf->close(vf);
 834#endif
 835}
 836
 837void CoreController::exportSharkport(const QString& path) {
 838#ifdef M_CORE_GBA
 839	if (platform() != mPLATFORM_GBA) {
 840		return;
 841	}
 842	VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
 843	if (!vf) {
 844		LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
 845		return;
 846	}
 847	Interrupter interrupter(this);
 848	GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf);
 849	vf->close(vf);
 850#endif
 851}
 852
 853#ifdef M_CORE_GB
 854void CoreController::attachPrinter() {
 855	if (platform() != mPLATFORM_GB) {
 856		return;
 857	}
 858	GB* gb = static_cast<GB*>(m_threadContext.core->board);
 859	clearMultiplayerController();
 860	GBPrinterCreate(&m_printer.d);
 861	m_printer.parent = this;
 862	m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) {
 863		QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
 864		QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
 865		QVector<QRgb> colors;
 866		colors.append(qRgb(0xF8, 0xF8, 0xF8));
 867		colors.append(qRgb(0xA8, 0xA8, 0xA8));
 868		colors.append(qRgb(0x50, 0x50, 0x50));
 869		colors.append(qRgb(0x00, 0x00, 0x00));
 870		image.setColorTable(colors);
 871		for (int y = 0; y < height; ++y) {
 872			for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
 873				uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4];
 874				image.setPixel(x + 0, y, (byte & 0xC0) >> 6);
 875				image.setPixel(x + 1, y, (byte & 0x30) >> 4);
 876				image.setPixel(x + 2, y, (byte & 0x0C) >> 2);
 877				image.setPixel(x + 3, y, (byte & 0x03) >> 0);
 878			}
 879		}
 880		QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
 881	};
 882	Interrupter interrupter(this);
 883	GBSIOSetDriver(&gb->sio, &m_printer.d.d);
 884}
 885
 886void CoreController::detachPrinter() {
 887	if (platform() != mPLATFORM_GB) {
 888		return;
 889	}
 890	Interrupter interrupter(this);
 891	GB* gb = static_cast<GB*>(m_threadContext.core->board);
 892	GBPrinterDonePrinting(&m_printer.d);
 893	GBSIOSetDriver(&gb->sio, nullptr);
 894}
 895
 896void CoreController::endPrint() {
 897	if (platform() != mPLATFORM_GB) {
 898		return;
 899	}
 900	Interrupter interrupter(this);
 901	GBPrinterDonePrinting(&m_printer.d);
 902}
 903#endif
 904
 905#ifdef M_CORE_GBA
 906void CoreController::attachBattleChipGate() {
 907	if (platform() != mPLATFORM_GBA) {
 908		return;
 909	}
 910	Interrupter interrupter(this);
 911	clearMultiplayerController();
 912	GBASIOBattlechipGateCreate(&m_battlechip);
 913	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, &m_battlechip);
 914}
 915
 916void CoreController::detachBattleChipGate() {
 917	if (platform() != mPLATFORM_GBA) {
 918		return;
 919	}
 920	Interrupter interrupter(this);
 921	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, nullptr);
 922}
 923
 924void CoreController::setBattleChipId(uint16_t id) {
 925	if (platform() != mPLATFORM_GBA) {
 926		return;
 927	}
 928	Interrupter interrupter(this);
 929	m_battlechip.chipId = id;
 930}
 931
 932void CoreController::setBattleChipFlavor(int flavor) {
 933	if (platform() != mPLATFORM_GBA) {
 934		return;
 935	}
 936	Interrupter interrupter(this);
 937	m_battlechip.flavor = flavor;
 938}
 939#endif
 940
 941void CoreController::setAVStream(mAVStream* stream) {
 942	Interrupter interrupter(this);
 943	m_threadContext.core->setAVStream(m_threadContext.core, stream);
 944}
 945
 946void CoreController::clearAVStream() {
 947	Interrupter interrupter(this);
 948	m_threadContext.core->setAVStream(m_threadContext.core, nullptr);
 949}
 950
 951void CoreController::clearOverride() {
 952	m_override.reset();
 953}
 954
 955void CoreController::startVideoLog(const QString& path, bool compression) {
 956	if (m_vl) {
 957		return;
 958	}
 959
 960	VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
 961	if (!vf) {
 962		return;
 963	}
 964	startVideoLog(vf, compression);
 965}
 966
 967void CoreController::startVideoLog(VFile* vf, bool compression) {
 968	if (m_vl || !vf) {
 969		return;
 970	}
 971
 972	Interrupter interrupter(this);
 973	m_vl = mVideoLogContextCreate(m_threadContext.core);
 974	m_vlVf = vf;
 975	mVideoLogContextSetOutput(m_vl, m_vlVf);
 976	mVideoLogContextSetCompression(m_vl, compression);
 977	mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
 978}
 979
 980void CoreController::endVideoLog(bool closeVf) {
 981	if (!m_vl) {
 982		return;
 983	}
 984
 985	Interrupter interrupter(this);
 986	mVideoLogContextDestroy(m_threadContext.core, m_vl, closeVf);
 987	if (closeVf) {
 988		m_vlVf = nullptr;
 989	}
 990	m_vl = nullptr;
 991}
 992
 993void CoreController::setFramebufferHandle(int fb) {
 994	Interrupter interrupter(this);
 995	if (fb < 0) {
 996		if (!m_hwaccel) {
 997			return;
 998		}
 999		mCoreConfigSetIntValue(&m_threadContext.core->config, "hwaccelVideo", 0);
1000		m_threadContext.core->setVideoGLTex(m_threadContext.core, -1);
1001		m_hwaccel = false;
1002	} else {
1003		mCoreConfigSetIntValue(&m_threadContext.core->config, "hwaccelVideo", 1);
1004		m_threadContext.core->setVideoGLTex(m_threadContext.core, fb);
1005		if (m_hwaccel) {
1006			return;
1007		}
1008		m_hwaccel = true;
1009	}
1010	if (hasStarted()) {
1011		m_threadContext.core->reloadConfigOption(m_threadContext.core, "hwaccelVideo", NULL);
1012		if (!m_hwaccel) {
1013			m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), screenDimensions().width());
1014		}
1015	}
1016}
1017
1018void CoreController::updateKeys() {
1019	int activeKeys = m_activeKeys | updateAutofire() | m_inputController->pollEvents();
1020	m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
1021}
1022
1023int CoreController::updateAutofire() {
1024	int active = 0;
1025	for (int k = 0; k < 32; ++k) {
1026		if (!m_autofire[k]) {
1027			continue;
1028		}
1029		++m_autofireStatus[k];
1030		if (m_autofireStatus[k] >= 2 * m_autofireThreshold) {
1031			m_autofireStatus[k] = 0;
1032		} else if (m_autofireStatus[k] >= m_autofireThreshold) {
1033			active |= 1 << k;
1034		}
1035	}
1036	return active;
1037}
1038
1039void CoreController::finishFrame() {
1040	if (!m_hwaccel) {
1041		unsigned width, height;
1042		m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
1043
1044		QMutexLocker locker(&m_bufferMutex);
1045		memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), width * height * BYTES_PER_PIXEL);
1046	}
1047
1048	{
1049		QMutexLocker locker(&m_actionMutex);
1050		QList<std::function<void ()>> frameActions(m_frameActions);
1051		m_frameActions.clear();
1052		for (auto& action : frameActions) {
1053			action();
1054		}
1055		if (m_moreFrames > 0) {
1056			--m_moreFrames;
1057			if (!m_moreFrames) {
1058				mCoreThreadPauseFromThread(&m_threadContext);
1059			}
1060		}
1061	}
1062	updateKeys();
1063
1064	QMetaObject::invokeMethod(this, "frameAvailable");
1065}
1066
1067void CoreController::updateFastForward() {
1068	// If we have "Fast forward" checked in the menu (m_fastForwardForced)
1069	// or are holding the fast forward button (m_fastForward):
1070	if (m_fastForward || m_fastForwardForced) {
1071		if (m_fastForwardVolume >= 0) {
1072			m_threadContext.core->opts.volume = m_fastForwardVolume;
1073		}
1074		if (m_fastForwardMute >= 0) {
1075			m_threadContext.core->opts.mute = m_fastForwardMute;
1076		}
1077
1078		// If we aren't holding the fast forward button
1079		// then use the non "(held)" ratio
1080		if(!m_fastForward) {
1081			if (m_fastForwardRatio > 0) {
1082				m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio;
1083				setSync(true);
1084			}	else {
1085				setSync(false);
1086			}
1087		} else {
1088			// If we are holding the fast forward button,
1089			// then use the held ratio
1090			if (m_fastForwardHeldRatio > 0) {
1091				m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardHeldRatio;
1092				setSync(true);
1093			} else {
1094				setSync(false);
1095			}
1096		}
1097	} else {
1098		if (!mCoreConfigGetIntValue(&m_threadContext.core->config, "volume", &m_threadContext.core->opts.volume)) {
1099			m_threadContext.core->opts.volume = 0x100;
1100		}
1101		int fakeBool = 0;
1102		mCoreConfigGetIntValue(&m_threadContext.core->config, "mute", &fakeBool);
1103		m_threadContext.core->opts.mute = fakeBool;
1104		m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
1105		setSync(true);
1106	}
1107
1108	m_threadContext.core->reloadConfigOption(m_threadContext.core, NULL, NULL);
1109}
1110
1111void CoreController::updateROMInfo() {
1112	const NoIntroDB* db = GBAApp::app()->gameDB();
1113	NoIntroGame game{};
1114	m_crc32 = 0;
1115	mCore* core = m_threadContext.core;
1116	core->checksum(core, &m_crc32, mCHECKSUM_CRC32);
1117
1118	char gameTitle[17] = { '\0' };
1119	core->getGameTitle(core, gameTitle);
1120	m_internalTitle = QLatin1String(gameTitle);
1121
1122#ifdef USE_SQLITE3
1123	if (db && m_crc32 && NoIntroDBLookupGameByCRC(db, m_crc32, &game)) {
1124		m_dbTitle = QString::fromUtf8(game.name);
1125	}
1126#endif
1127}
1128
1129CoreController::Interrupter::Interrupter()
1130	: m_parent(nullptr)
1131{
1132}
1133
1134CoreController::Interrupter::Interrupter(CoreController* parent)
1135	: m_parent(parent)
1136{
1137	interrupt();
1138}
1139
1140CoreController::Interrupter::Interrupter(std::shared_ptr<CoreController> parent)
1141	: m_parent(parent.get())
1142{
1143	interrupt();
1144}
1145
1146CoreController::Interrupter::Interrupter(const Interrupter& other)
1147	: m_parent(other.m_parent)
1148{
1149	interrupt();
1150}
1151
1152CoreController::Interrupter::~Interrupter() {
1153	resume();
1154}
1155
1156CoreController::Interrupter& CoreController::Interrupter::operator=(const Interrupter& other)
1157{
1158	interrupt(other.m_parent);
1159	return *this;
1160}
1161
1162void CoreController::Interrupter::interrupt(CoreController* controller) {
1163	if (m_parent != controller) {
1164		CoreController* old = m_parent;
1165		m_parent = controller;
1166		interrupt();
1167		resume(old);
1168	}
1169}
1170
1171void CoreController::Interrupter::interrupt(std::shared_ptr<CoreController> controller) {
1172	interrupt(controller.get());
1173}
1174
1175void CoreController::Interrupter::interrupt() {
1176	if (!m_parent || !m_parent->thread()->impl) {
1177		return;
1178	}
1179
1180	if (mCoreThreadGet() != m_parent->thread()) {
1181		mCoreThreadInterrupt(m_parent->thread());
1182	} else {
1183		mCoreThreadInterruptFromThread(m_parent->thread());
1184	}
1185}
1186
1187void CoreController::Interrupter::resume() {
1188	resume(m_parent);
1189	m_parent = nullptr;
1190}
1191
1192void CoreController::Interrupter::resume(CoreController* controller) {
1193	if (!controller || !controller->thread()->impl) {
1194		return;
1195	}
1196
1197	mCoreThreadContinue(controller->thread());
1198}