all repos — mgba @ 4420054c1a3b13e406e28aaa7ab8f44ab3509438

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 <mgba-util/math.h>
  29#include <mgba-util/vfs.h>
  30
  31#define AUTOSAVE_GRANULARITY 600
  32
  33using namespace QGBA;
  34
  35CoreController::CoreController(mCore* core, QObject* parent)
  36	: QObject(parent)
  37	, m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC)
  38	, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
  39{
  40	m_threadContext.core = core;
  41	m_threadContext.userData = this;
  42
  43	m_resetActions.append([this]() {
  44		if (m_autoload) {
  45			mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);
  46		}
  47	});
  48
  49	m_threadContext.startCallback = [](mCoreThread* context) {
  50		CoreController* controller = static_cast<CoreController*>(context->userData);
  51
  52		switch (context->core->platform(context->core)) {
  53#ifdef M_CORE_GBA
  54		case PLATFORM_GBA:
  55			context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, controller->m_inputController->luminance());
  56			break;
  57#endif
  58		default:
  59			break;
  60		}
  61
  62		controller->updateFastForward();
  63
  64		if (controller->m_multiplayer) {
  65			controller->m_multiplayer->attachGame(controller);
  66		}
  67
  68		QMetaObject::invokeMethod(controller, "started");
  69	};
  70
  71	m_threadContext.resetCallback = [](mCoreThread* context) {
  72		CoreController* controller = static_cast<CoreController*>(context->userData);
  73		for (auto action : controller->m_resetActions) {
  74			action();
  75		}
  76
  77		if (controller->m_override) {
  78			controller->m_override->identify(context->core);
  79			controller->m_override->apply(context->core);
  80		}
  81
  82		controller->m_resetActions.clear();
  83
  84		if (!controller->m_hwaccel) {
  85			controller->m_activeBuffer = &controller->m_buffers[0];
  86			context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer->data()), controller->screenDimensions().width());
  87		}
  88
  89		QMetaObject::invokeMethod(controller, "didReset");
  90		controller->finishFrame();
  91	};
  92
  93	m_threadContext.frameCallback = [](mCoreThread* context) {
  94		CoreController* controller = static_cast<CoreController*>(context->userData);
  95
  96		if (controller->m_autosaveCounter == AUTOSAVE_GRANULARITY) {
  97			if (controller->m_autosave) {
  98				mCoreSaveState(context->core, 0, controller->m_saveStateFlags);
  99			}
 100			controller->m_autosaveCounter = 0;
 101		}
 102		++controller->m_autosaveCounter;
 103
 104		controller->finishFrame();
 105	};
 106
 107	m_threadContext.cleanCallback = [](mCoreThread* context) {
 108		CoreController* controller = static_cast<CoreController*>(context->userData);
 109
 110		if (controller->m_autosave) {
 111			mCoreSaveState(context->core, 0, controller->m_saveStateFlags);
 112		}
 113
 114		controller->clearMultiplayerController();
 115		QMetaObject::invokeMethod(controller, "stopping");
 116	};
 117
 118	m_threadContext.pauseCallback = [](mCoreThread* context) {
 119		CoreController* controller = static_cast<CoreController*>(context->userData);
 120
 121		QMetaObject::invokeMethod(controller, "paused");
 122	};
 123
 124	m_threadContext.unpauseCallback = [](mCoreThread* context) {
 125		CoreController* controller = static_cast<CoreController*>(context->userData);
 126
 127		QMetaObject::invokeMethod(controller, "unpaused");
 128	};
 129
 130	m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
 131		mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger);
 132		mCoreThread* context = logContext->p;
 133
 134		static const char* savestateMessage = "State %i saved";
 135		static const char* loadstateMessage = "State %i loaded";
 136		static const char* savestateFailedMessage = "State %i failed to load";
 137		static int biosCat = -1;
 138		static int statusCat = -1;
 139		if (!context) {
 140			return;
 141		}
 142		CoreController* controller = static_cast<CoreController*>(context->userData);
 143		QString message;
 144		if (biosCat < 0) {
 145			biosCat = mLogCategoryById("gba.bios");
 146		}
 147		if (statusCat < 0) {
 148			statusCat = mLogCategoryById("core.status");
 149		}
 150#ifdef M_CORE_GBA
 151		if (level == mLOG_STUB && category == biosCat) {
 152			va_list argc;
 153			va_copy(argc, args);
 154			int immediate = va_arg(argc, int);
 155			va_end(argc);
 156			QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate));
 157		} else
 158#endif
 159		if (category == statusCat) {
 160			// Slot 0 is reserved for suspend points
 161			if (strncmp(loadstateMessage, format, strlen(loadstateMessage)) == 0) {
 162				va_list argc;
 163				va_copy(argc, args);
 164				int slot = va_arg(argc, int);
 165				va_end(argc);
 166				if (slot == 0) {
 167					format = "Loaded suspend state";
 168				}
 169			} else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0 || strncmp(savestateMessage, format, strlen(savestateMessage)) == 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					return;
 176				}
 177			}
 178			message = QString().vsprintf(format, args);
 179			QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
 180		}
 181		message = QString().vsprintf(format, args);
 182		QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message));
 183		if (level == mLOG_FATAL) {
 184			QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, QString().vsprintf(format, args)));
 185		}
 186	};
 187}
 188
 189CoreController::~CoreController() {
 190	endVideoLog();
 191	stop();
 192	disconnect();
 193
 194	mCoreThreadJoin(&m_threadContext);
 195
 196	if (m_cacheSet) {
 197		mCacheSetDeinit(m_cacheSet.get());
 198		m_cacheSet.reset();
 199	}
 200
 201	mCoreConfigDeinit(&m_threadContext.core->config);
 202	m_threadContext.core->deinit(m_threadContext.core);
 203}
 204
 205const color_t* CoreController::drawContext() {
 206	if (m_hwaccel) {
 207		return nullptr;
 208	}
 209	QMutexLocker locker(&m_bufferMutex);
 210	return reinterpret_cast<const color_t*>(m_completeBuffer.constData());
 211}
 212
 213QImage CoreController::getPixels() {
 214	QByteArray buffer;
 215	QSize size = screenDimensions();
 216	size_t stride = size.width() * BYTES_PER_PIXEL;
 217
 218	if (!m_hwaccel) {
 219		buffer = m_completeBuffer;
 220	} else {
 221		Interrupter interrupter(this);
 222		const void* pixels;
 223		m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride);
 224		stride *= BYTES_PER_PIXEL;
 225		buffer.resize(stride * size.height());
 226		memcpy(buffer.data(), pixels, buffer.size());
 227	}
 228
 229	return QImage(reinterpret_cast<const uchar*>(buffer.constData()),
 230	              size.width(), size.height(), stride, QImage::Format_RGBX8888);
 231}
 232
 233bool CoreController::isPaused() {
 234	return mCoreThreadIsPaused(&m_threadContext);
 235}
 236
 237bool CoreController::hasStarted() {
 238	return mCoreThreadHasStarted(&m_threadContext);
 239}
 240
 241mPlatform CoreController::platform() const {
 242	return m_threadContext.core->platform(m_threadContext.core);
 243}
 244
 245QSize CoreController::screenDimensions() const {
 246	unsigned width, height;
 247	m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
 248
 249	return QSize(width, height);
 250}
 251
 252void CoreController::loadConfig(ConfigController* config) {
 253	Interrupter interrupter(this);
 254	m_loadStateFlags = config->getOption("loadStateExtdata", m_loadStateFlags).toInt();
 255	m_saveStateFlags = config->getOption("saveStateExtdata", m_saveStateFlags).toInt();
 256	m_fastForwardRatio = config->getOption("fastForwardRatio", m_fastForwardRatio).toFloat();
 257	m_videoSync = config->getOption("videoSync", m_videoSync).toInt();
 258	m_audioSync = config->getOption("audioSync", m_audioSync).toInt();
 259	m_fpsTarget = config->getOption("fpsTarget").toFloat();
 260	m_autosave = config->getOption("autosave", false).toInt();
 261	m_autoload = config->getOption("autoload", true).toInt();
 262	m_autofireThreshold = config->getOption("autofireThreshold", m_autofireThreshold).toInt();
 263	m_fastForwardVolume = config->getOption("fastForwardVolume", -1).toInt();
 264	m_fastForwardMute = config->getOption("fastForwardMute", -1).toInt();
 265	mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume");
 266	mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
 267	mCoreLoadForeignConfig(m_threadContext.core, config->config());
 268	if (hasStarted()) {
 269		updateFastForward();
 270		mCoreThreadRewindParamsChanged(&m_threadContext);
 271	}
 272}
 273
 274#ifdef USE_DEBUGGERS
 275void CoreController::setDebugger(mDebugger* debugger) {
 276	Interrupter interrupter(this);
 277	if (debugger) {
 278		mDebuggerAttach(debugger, m_threadContext.core);
 279	} else {
 280		m_threadContext.core->detachDebugger(m_threadContext.core);
 281	}
 282}
 283#endif
 284
 285void CoreController::setMultiplayerController(MultiplayerController* controller) {
 286	if (controller == m_multiplayer) {
 287		return;
 288	}
 289	clearMultiplayerController();
 290	m_multiplayer = controller;
 291	if (!mCoreThreadHasStarted(&m_threadContext)) {
 292		return;
 293	}
 294	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
 295		CoreController* controller = static_cast<CoreController*>(thread->userData);
 296		controller->m_multiplayer->attachGame(controller);
 297	});
 298}
 299
 300void CoreController::clearMultiplayerController() {
 301	if (!m_multiplayer) {
 302		return;
 303	}
 304	m_multiplayer->detachGame(this);
 305	m_multiplayer = nullptr;
 306}
 307
 308mCacheSet* CoreController::graphicCaches() {
 309	if (m_cacheSet) {
 310		return m_cacheSet.get();
 311	}
 312	Interrupter interrupter(this);
 313	switch (platform()) {
 314#ifdef M_CORE_GBA
 315	case PLATFORM_GBA: {
 316		GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
 317		m_cacheSet = std::make_unique<mCacheSet>();
 318		GBAVideoCacheInit(m_cacheSet.get());
 319		GBAVideoCacheAssociate(m_cacheSet.get(), &gba->video);
 320		break;
 321	}
 322#endif
 323#ifdef M_CORE_GB
 324	case PLATFORM_GB: {
 325		GB* gb = static_cast<GB*>(m_threadContext.core->board);
 326		m_cacheSet = std::make_unique<mCacheSet>();
 327		GBVideoCacheInit(m_cacheSet.get());
 328		GBVideoCacheAssociate(m_cacheSet.get(), &gb->video);
 329		break;
 330	}
 331#endif
 332	default:
 333		return nullptr;
 334	}
 335	return m_cacheSet.get();
 336}
 337
 338void CoreController::setOverride(std::unique_ptr<Override> override) {
 339	Interrupter interrupter(this);
 340	m_override = std::move(override);
 341	m_override->identify(m_threadContext.core);
 342}
 343
 344void CoreController::setInputController(InputController* inputController) {
 345	m_inputController = inputController;
 346	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_ROTATION, m_inputController->rotationSource());
 347	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_RUMBLE, m_inputController->rumble());
 348	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_IMAGE_SOURCE, m_inputController->imageSource());
 349}
 350
 351void CoreController::setLogger(LogController* logger) {
 352	disconnect(m_log);
 353	m_log = logger;
 354	m_threadContext.logger.d.filter = logger->filter();
 355	connect(this, &CoreController::logPosted, m_log, &LogController::postLog);
 356}
 357
 358void CoreController::start() {
 359	if (!m_hwaccel) {
 360		QSize size(1024, 2048);
 361		m_buffers[0].resize(size.width() * size.height() * sizeof(color_t));
 362		m_buffers[1].resize(size.width() * size.height() * sizeof(color_t));
 363		m_buffers[0].fill(0xFF);
 364		m_buffers[1].fill(0xFF);
 365		m_activeBuffer = &m_buffers[0];
 366		m_completeBuffer = m_buffers[0];
 367
 368		m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), size.width());
 369	}
 370
 371	if (!m_patched) {
 372		mCoreAutoloadPatch(m_threadContext.core);
 373	}
 374	if (!mCoreThreadStart(&m_threadContext)) {
 375		emit failed();
 376		emit stopping();
 377	}
 378}
 379
 380void CoreController::stop() {
 381	setSync(false);
 382#ifdef USE_DEBUGGERS
 383	setDebugger(nullptr);
 384#endif
 385	setPaused(false);
 386	mCoreThreadEnd(&m_threadContext);
 387}
 388
 389void CoreController::reset() {
 390	bool wasPaused = isPaused();
 391	setPaused(false);
 392	Interrupter interrupter(this);
 393	mCoreThreadReset(&m_threadContext);
 394	if (wasPaused) {
 395		setPaused(true);
 396	}
 397}
 398
 399void CoreController::setPaused(bool paused) {
 400	if (paused == isPaused()) {
 401		return;
 402	}
 403	if (paused) {
 404		addFrameAction([this]() {
 405			mCoreThreadPauseFromThread(&m_threadContext);
 406		});
 407	} else {
 408		mCoreThreadUnpause(&m_threadContext);
 409	}
 410}
 411
 412void CoreController::frameAdvance() {
 413	addFrameAction([this]() {
 414		mCoreThreadPauseFromThread(&m_threadContext);
 415	});
 416	setPaused(false);
 417}
 418
 419void CoreController::addFrameAction(std::function<void ()> action) {
 420	QMutexLocker locker(&m_actionMutex);
 421	m_frameActions.append(action);
 422}
 423
 424void CoreController::setSync(bool sync) {
 425	if (sync) {
 426		m_threadContext.impl->sync.audioWait = m_audioSync;
 427		m_threadContext.impl->sync.videoFrameWait = m_videoSync;
 428	} else {
 429		m_threadContext.impl->sync.audioWait = false;
 430		m_threadContext.impl->sync.videoFrameWait = false;
 431	}
 432}
 433
 434void CoreController::setRewinding(bool rewind) {
 435	if (!m_threadContext.core->opts.rewindEnable) {
 436		return;
 437	}
 438	if (rewind && m_multiplayer && m_multiplayer->attached() > 1) {
 439		return;
 440	}
 441
 442	if (rewind && isPaused()) {
 443		setPaused(false);
 444		// TODO: restore autopausing
 445	}
 446	mCoreThreadSetRewinding(&m_threadContext, rewind);
 447}
 448
 449void CoreController::rewind(int states) {
 450	{
 451		Interrupter interrupter(this);
 452		if (!states) {
 453			states = INT_MAX;
 454		}
 455		for (int i = 0; i < states; ++i) {
 456			if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) {
 457				break;
 458			}
 459		}
 460	}
 461	emit frameAvailable();
 462	emit rewound();
 463}
 464
 465void CoreController::setFastForward(bool enable) {
 466	if (m_fastForward == enable) {
 467		return;
 468	}
 469	m_fastForward = enable;
 470	updateFastForward();
 471	emit fastForwardChanged(enable);
 472}
 473
 474void CoreController::forceFastForward(bool enable) {
 475	if (m_fastForwardForced == enable) {
 476		return;
 477	}
 478	m_fastForwardForced = enable;
 479	updateFastForward();
 480	emit fastForwardChanged(enable || m_fastForward);
 481}
 482
 483void CoreController::loadState(int slot) {
 484	if (slot > 0 && slot != m_stateSlot) {
 485		m_stateSlot = slot;
 486		m_backupSaveState.clear();
 487	}
 488	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 489		CoreController* controller = static_cast<CoreController*>(context->userData);
 490		if (!controller->m_backupLoadState.isOpen()) {
 491			controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
 492		}
 493		mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
 494		if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
 495			emit controller->frameAvailable();
 496			emit controller->stateLoaded();
 497		}
 498	});
 499}
 500
 501void CoreController::loadState(const QString& path) {
 502	m_statePath = path;
 503	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 504		CoreController* controller = static_cast<CoreController*>(context->userData);
 505		VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
 506		if (!vf) {
 507			return;
 508		}
 509		if (!controller->m_backupLoadState.isOpen()) {
 510			controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
 511		}
 512		mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
 513		if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
 514			emit controller->frameAvailable();
 515			emit controller->stateLoaded();
 516		}
 517		vf->close(vf);
 518	});
 519}
 520
 521void CoreController::saveState(int slot) {
 522	if (slot > 0) {
 523		m_stateSlot = slot;
 524	}
 525	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 526		CoreController* controller = static_cast<CoreController*>(context->userData);
 527		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false);
 528		if (vf) {
 529			controller->m_backupSaveState.resize(vf->size(vf));
 530			vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
 531			vf->close(vf);
 532		}
 533		mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags);
 534	});
 535}
 536
 537void CoreController::saveState(const QString& path) {
 538	m_statePath = path;
 539	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 540		CoreController* controller = static_cast<CoreController*>(context->userData);
 541		VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
 542		if (vf) {
 543			controller->m_backupSaveState.resize(vf->size(vf));
 544			vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
 545			vf->close(vf);
 546		}
 547		vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC);
 548		if (!vf) {
 549			return;
 550		}
 551		mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
 552		vf->close(vf);
 553	});
 554}
 555
 556void CoreController::loadBackupState() {
 557	if (!m_backupLoadState.isOpen()) {
 558		return;
 559	}
 560
 561	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 562		CoreController* controller = static_cast<CoreController*>(context->userData);
 563		controller->m_backupLoadState.seek(0);
 564		if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
 565			mLOG(STATUS, INFO, "Undid state load");
 566			controller->frameAvailable();
 567			controller->stateLoaded();
 568		}
 569		controller->m_backupLoadState.close();
 570	});
 571}
 572
 573void CoreController::saveBackupState() {
 574	if (m_backupSaveState.isEmpty()) {
 575		return;
 576	}
 577
 578	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 579		CoreController* controller = static_cast<CoreController*>(context->userData);
 580		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true);
 581		if (vf) {
 582			vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
 583			vf->close(vf);
 584			mLOG(STATUS, INFO, "Undid state save");
 585		}
 586		controller->m_backupSaveState.clear();
 587	});
 588}
 589
 590void CoreController::loadSave(const QString& path, bool temporary) {
 591	m_resetActions.append([this, path, temporary]() {
 592		VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR);
 593		if (!vf) {
 594			LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path);
 595			return;
 596		}
 597
 598		if (temporary) {
 599			m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
 600		} else {
 601			m_threadContext.core->loadSave(m_threadContext.core, vf);
 602		}
 603	});
 604	reset();
 605}
 606
 607void CoreController::loadPatch(const QString& patchPath) {
 608	Interrupter interrupter(this);
 609	VFile* patch = VFileDevice::open(patchPath, O_RDONLY);
 610	if (patch) {
 611		m_threadContext.core->loadPatch(m_threadContext.core, patch);
 612		m_patched = true;
 613		patch->close(patch);
 614	}
 615	if (mCoreThreadHasStarted(&m_threadContext)) {
 616		reset();
 617	}
 618}
 619
 620void CoreController::replaceGame(const QString& path) {
 621	QFileInfo info(path);
 622	if (!info.isReadable()) {
 623		LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
 624		return;
 625	}
 626	QString fname = info.canonicalFilePath();
 627	Interrupter interrupter(this);
 628	mDirectorySetDetachBase(&m_threadContext.core->dirs);
 629	mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData());
 630}
 631
 632void CoreController::yankPak() {
 633#ifdef M_CORE_GBA
 634	if (platform() != PLATFORM_GBA) {
 635		return;
 636	}
 637	Interrupter interrupter(this);
 638	GBAYankROM(static_cast<GBA*>(m_threadContext.core->board));
 639#endif
 640}
 641
 642void CoreController::addKey(int key) {
 643	m_activeKeys |= 1 << key;
 644}
 645
 646void CoreController::clearKey(int key) {
 647	m_activeKeys &= ~(1 << key);
 648}
 649
 650void CoreController::setAutofire(int key, bool enable) {
 651	if (key >= 32 || key < 0) {
 652		return;
 653	}
 654
 655	m_autofire[key] = enable;
 656	m_autofireStatus[key] = 0;
 657}
 658
 659#ifdef USE_PNG
 660void CoreController::screenshot() {
 661	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 662		mCoreTakeScreenshot(context->core);
 663	});
 664}
 665#endif
 666
 667void CoreController::setRealTime() {
 668	m_threadContext.core->rtc.override = RTC_NO_OVERRIDE;
 669}
 670
 671void CoreController::setFixedTime(const QDateTime& time) {
 672	m_threadContext.core->rtc.override = RTC_FIXED;
 673	m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
 674}
 675
 676void CoreController::setFakeEpoch(const QDateTime& time) {
 677	m_threadContext.core->rtc.override = RTC_FAKE_EPOCH;
 678	m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
 679}
 680
 681void CoreController::importSharkport(const QString& path) {
 682#ifdef M_CORE_GBA
 683	if (platform() != PLATFORM_GBA) {
 684		return;
 685	}
 686	VFile* vf = VFileDevice::open(path, O_RDONLY);
 687	if (!vf) {
 688		LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
 689		return;
 690	}
 691	Interrupter interrupter(this);
 692	GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
 693	vf->close(vf);
 694#endif
 695}
 696
 697void CoreController::exportSharkport(const QString& path) {
 698#ifdef M_CORE_GBA
 699	if (platform() != PLATFORM_GBA) {
 700		return;
 701	}
 702	VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
 703	if (!vf) {
 704		LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
 705		return;
 706	}
 707	Interrupter interrupter(this);
 708	GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf);
 709	vf->close(vf);
 710#endif
 711}
 712
 713#ifdef M_CORE_GB
 714void CoreController::attachPrinter() {
 715	if (platform() != PLATFORM_GB) {
 716		return;
 717	}
 718	GB* gb = static_cast<GB*>(m_threadContext.core->board);
 719	clearMultiplayerController();
 720	GBPrinterCreate(&m_printer.d);
 721	m_printer.parent = this;
 722	m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) {
 723		QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
 724		QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
 725		QVector<QRgb> colors;
 726		colors.append(qRgb(0xF8, 0xF8, 0xF8));
 727		colors.append(qRgb(0xA8, 0xA8, 0xA8));
 728		colors.append(qRgb(0x50, 0x50, 0x50));
 729		colors.append(qRgb(0x00, 0x00, 0x00));
 730		image.setColorTable(colors);
 731		for (int y = 0; y < height; ++y) {
 732			for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
 733				uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4];
 734				image.setPixel(x + 0, y, (byte & 0xC0) >> 6);
 735				image.setPixel(x + 1, y, (byte & 0x30) >> 4);
 736				image.setPixel(x + 2, y, (byte & 0x0C) >> 2);
 737				image.setPixel(x + 3, y, (byte & 0x03) >> 0);
 738			}
 739		}
 740		QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
 741	};
 742	Interrupter interrupter(this);
 743	GBSIOSetDriver(&gb->sio, &m_printer.d.d);
 744}
 745
 746void CoreController::detachPrinter() {
 747	if (platform() != PLATFORM_GB) {
 748		return;
 749	}
 750	Interrupter interrupter(this);
 751	GB* gb = static_cast<GB*>(m_threadContext.core->board);
 752	GBPrinterDonePrinting(&m_printer.d);
 753	GBSIOSetDriver(&gb->sio, nullptr);
 754}
 755
 756void CoreController::endPrint() {
 757	if (platform() != PLATFORM_GB) {
 758		return;
 759	}
 760	Interrupter interrupter(this);
 761	GBPrinterDonePrinting(&m_printer.d);
 762}
 763#endif
 764
 765#ifdef M_CORE_GBA
 766void CoreController::attachBattleChipGate() {
 767	if (platform() != PLATFORM_GBA) {
 768		return;
 769	}
 770	Interrupter interrupter(this);
 771	clearMultiplayerController();
 772	GBASIOBattlechipGateCreate(&m_battlechip);
 773	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, &m_battlechip);
 774}
 775
 776void CoreController::detachBattleChipGate() {
 777	if (platform() != PLATFORM_GBA) {
 778		return;
 779	}
 780	Interrupter interrupter(this);
 781	m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, nullptr);
 782}
 783
 784void CoreController::setBattleChipId(uint16_t id) {
 785	if (platform() != PLATFORM_GBA) {
 786		return;
 787	}
 788	Interrupter interrupter(this);
 789	m_battlechip.chipId = id;
 790}
 791
 792void CoreController::setBattleChipFlavor(int flavor) {
 793	if (platform() != PLATFORM_GBA) {
 794		return;
 795	}
 796	Interrupter interrupter(this);
 797	m_battlechip.flavor = flavor;
 798}
 799#endif
 800
 801void CoreController::setAVStream(mAVStream* stream) {
 802	Interrupter interrupter(this);
 803	m_threadContext.core->setAVStream(m_threadContext.core, stream);
 804}
 805
 806void CoreController::clearAVStream() {
 807	Interrupter interrupter(this);
 808	m_threadContext.core->setAVStream(m_threadContext.core, nullptr);
 809}
 810
 811void CoreController::clearOverride() {
 812	m_override.reset();
 813}
 814
 815void CoreController::startVideoLog(const QString& path, bool compression) {
 816	if (m_vl) {
 817		return;
 818	}
 819
 820	VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
 821	if (!vf) {
 822		return;
 823	}
 824	startVideoLog(vf);
 825}
 826
 827void CoreController::startVideoLog(VFile* vf, bool compression) {
 828	if (m_vl || !vf) {
 829		return;
 830	}
 831
 832	Interrupter interrupter(this);
 833	m_vl = mVideoLogContextCreate(m_threadContext.core);
 834	m_vlVf = vf;
 835	mVideoLogContextSetOutput(m_vl, m_vlVf);
 836	mVideoLogContextSetCompression(m_vl, compression);
 837	mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
 838}
 839
 840void CoreController::endVideoLog(bool closeVf) {
 841	if (!m_vl) {
 842		return;
 843	}
 844
 845	Interrupter interrupter(this);
 846	mVideoLogContextDestroy(m_threadContext.core, m_vl);
 847	if (m_vlVf && closeVf) {
 848		m_vlVf->close(m_vlVf);
 849		m_vlVf = nullptr;
 850	}
 851	m_vl = nullptr;
 852}
 853
 854void CoreController::setFramebufferHandle(int fb) {
 855	Interrupter interrupter(this);
 856	if (fb < 0) {
 857		m_hwaccel = false;
 858	} else {
 859		m_threadContext.core->setVideoGLTex(m_threadContext.core, fb);
 860		m_hwaccel = true;
 861	}
 862}
 863
 864void CoreController::updateKeys() {
 865	int activeKeys = m_activeKeys | updateAutofire() | m_inputController->pollEvents();
 866	m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
 867}
 868
 869int CoreController::updateAutofire() {
 870	int active = 0;
 871	for (int k = 0; k < 32; ++k) {
 872		if (!m_autofire[k]) {
 873			continue;
 874		}
 875		++m_autofireStatus[k];
 876		if (m_autofireStatus[k] >= 2 * m_autofireThreshold) {
 877			m_autofireStatus[k] = 0;
 878		} else if (m_autofireStatus[k] >= m_autofireThreshold) {
 879			active |= 1 << k;
 880		}
 881	}
 882	return active;
 883}
 884
 885void CoreController::finishFrame() {
 886	if (!m_hwaccel) {
 887		QMutexLocker locker(&m_bufferMutex);
 888		memcpy(m_completeBuffer.data(), m_activeBuffer->constData(), m_activeBuffer->size());
 889
 890		// TODO: Generalize this to triple buffering?
 891		m_activeBuffer = &m_buffers[0];
 892		if (m_activeBuffer == m_completeBuffer) {
 893			m_activeBuffer = &m_buffers[1];
 894		}
 895		// Copy contents to avoid issues when doing frameskip
 896		memcpy(m_activeBuffer->data(), m_completeBuffer.constData(), m_activeBuffer->size());
 897		m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer->data()), screenDimensions().width());
 898	}
 899
 900	QMutexLocker locker(&m_actionMutex);
 901	QList<std::function<void ()>> frameActions(m_frameActions);
 902	m_frameActions.clear();
 903	for (auto& action : frameActions) {
 904		action();
 905	}
 906	updateKeys();
 907
 908	QMetaObject::invokeMethod(this, "frameAvailable");
 909}
 910
 911void CoreController::updateFastForward() {
 912	if (m_fastForward || m_fastForwardForced) {
 913		if (m_fastForwardVolume >= 0) {
 914			m_threadContext.core->opts.volume = m_fastForwardVolume;
 915		}
 916		if (m_fastForwardMute >= 0) {
 917			m_threadContext.core->opts.mute = m_fastForwardMute;
 918		}
 919		if (m_fastForwardRatio > 0) {
 920			m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio;
 921		} else {
 922			setSync(false);
 923		}
 924	} else {
 925		if (!mCoreConfigGetIntValue(&m_threadContext.core->config, "volume", &m_threadContext.core->opts.volume)) {
 926			m_threadContext.core->opts.volume = 0x100;
 927		}
 928		int fakeBool = 0;
 929		mCoreConfigGetIntValue(&m_threadContext.core->config, "mute", &fakeBool);
 930		m_threadContext.core->opts.mute = fakeBool;
 931		m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
 932		setSync(true);
 933	}
 934	// XXX: Have a way of just updating volume
 935	switch (platform()) {
 936#ifdef M_CORE_GBA
 937	case PLATFORM_GBA: {
 938		GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
 939		if (m_threadContext.core->opts.mute) {
 940			gba->audio.masterVolume = 0;
 941		} else {
 942			gba->audio.masterVolume = m_threadContext.core->opts.volume;
 943		}
 944		break;
 945	}
 946#endif
 947#ifdef M_CORE_GB
 948	case PLATFORM_GB: {
 949		GB* gb = static_cast<GB*>(m_threadContext.core->board);
 950		if (m_threadContext.core->opts.mute) {
 951			gb->audio.masterVolume = 0;
 952		} else {
 953			gb->audio.masterVolume = m_threadContext.core->opts.volume;
 954		}
 955		break;
 956	}
 957#endif
 958	default:
 959		break;
 960	}
 961}
 962
 963CoreController::Interrupter::Interrupter(CoreController* parent, bool fromThread)
 964	: m_parent(parent)
 965{
 966	if (!m_parent->thread()->impl) {
 967		return;
 968	}
 969	if (!fromThread) {
 970		mCoreThreadInterrupt(m_parent->thread());
 971	} else {
 972		mCoreThreadInterruptFromThread(m_parent->thread());
 973	}
 974}
 975
 976CoreController::Interrupter::Interrupter(std::shared_ptr<CoreController> parent, bool fromThread)
 977	: m_parent(parent.get())
 978{
 979	if (!m_parent->thread()->impl) {
 980		return;
 981	}
 982	if (!fromThread) {
 983		mCoreThreadInterrupt(m_parent->thread());
 984	} else {
 985		mCoreThreadInterruptFromThread(m_parent->thread());
 986	}
 987}
 988
 989CoreController::Interrupter::Interrupter(const Interrupter& other)
 990	: m_parent(other.m_parent)
 991{
 992	if (!m_parent->thread()->impl) {
 993		return;
 994	}
 995	mCoreThreadInterrupt(m_parent->thread());
 996}
 997
 998CoreController::Interrupter::~Interrupter() {
 999	if (!m_parent->thread()->impl) {
1000		return;
1001	}
1002	mCoreThreadContinue(m_parent->thread());
1003}