all repos — mgba @ 1f204c8eefe74322cf49c827cbb43a12c960c994

mGBA Game Boy Advance Emulator

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

   1/* Copyright (c) 2013-2014 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 "GameController.h"
   7
   8#include "AudioProcessor.h"
   9#include "InputController.h"
  10#include "LogController.h"
  11#include "MultiplayerController.h"
  12#include "VFileDevice.h"
  13
  14#include <QCoreApplication>
  15#include <QDateTime>
  16#include <QThread>
  17
  18#include <ctime>
  19
  20extern "C" {
  21#include "core/config.h"
  22#include "core/directories.h"
  23#ifdef M_CORE_GBA
  24#include "gba/bios.h"
  25#include "gba/core.h"
  26#include "gba/gba.h"
  27#include "gba/serialize.h"
  28#include "gba/sharkport.h"
  29#endif
  30#ifdef M_CORE_GB
  31#include "gb/gb.h"
  32#endif
  33#include "util/vfs.h"
  34}
  35
  36using namespace QGBA;
  37using namespace std;
  38
  39GameController::GameController(QObject* parent)
  40	: QObject(parent)
  41	, m_drawContext(nullptr)
  42	, m_frontBuffer(nullptr)
  43	, m_threadContext()
  44	, m_activeKeys(0)
  45	, m_inactiveKeys(0)
  46	, m_logLevels(0)
  47	, m_gameOpen(false)
  48	, m_useBios(false)
  49	, m_audioThread(new QThread(this))
  50	, m_audioProcessor(AudioProcessor::create())
  51	, m_pauseAfterFrame(false)
  52	, m_videoSync(VIDEO_SYNC)
  53	, m_audioSync(AUDIO_SYNC)
  54	, m_fpsTarget(-1)
  55	, m_turbo(false)
  56	, m_turboForced(false)
  57	, m_turboSpeed(-1)
  58	, m_wasPaused(false)
  59	, m_audioChannels{ true, true, true, true, true, true }
  60	, m_videoLayers{ true, true, true, true, true }
  61	, m_autofire{}
  62	, m_autofireStatus{}
  63	, m_inputController(nullptr)
  64	, m_multiplayer(nullptr)
  65	, m_stream(nullptr)
  66	, m_stateSlot(1)
  67	, m_backupLoadState(nullptr)
  68	, m_backupSaveState(nullptr)
  69	, m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS)
  70	, m_loadStateFlags(SAVESTATE_SCREENSHOT)
  71{
  72	GBACheatDeviceCreate(&m_cheatDevice);
  73
  74	m_lux.p = this;
  75	m_lux.sample = [](GBALuminanceSource* context) {
  76		GameControllerLux* lux = static_cast<GameControllerLux*>(context);
  77		lux->value = 0xFF - lux->p->m_luxValue;
  78	};
  79
  80	m_lux.readLuminance = [](GBALuminanceSource* context) {
  81		GameControllerLux* lux = static_cast<GameControllerLux*>(context);
  82		return lux->value;
  83	};
  84	setLuminanceLevel(0);
  85
  86	m_threadContext.startCallback = [](mCoreThread* context) {
  87		GameController* controller = static_cast<GameController*>(context->userData);
  88		if (controller->m_audioProcessor) {
  89			controller->m_audioProcessor->setInput(context);
  90		}
  91		mRTCGenericSourceInit(&controller->m_rtc, context->core);
  92		context->core->setRTC(context->core, &controller->m_rtc.d);
  93		context->core->setRotation(context->core, controller->m_inputController->rotationSource());
  94		context->core->setRumble(context->core, controller->m_inputController->rumble());
  95
  96#ifdef M_CORE_GBA
  97		GBA* gba = static_cast<GBA*>(context->core->board);
  98#endif
  99#ifdef M_CORE_GB
 100		GB* gb = static_cast<GB*>(context->core->board);
 101#endif
 102		switch (context->core->platform(context->core)) {
 103#ifdef M_CORE_GBA
 104		case PLATFORM_GBA:
 105			gba->luminanceSource = &controller->m_lux;
 106			gba->audio.psg.forceDisableCh[0] = !controller->m_audioChannels[0];
 107			gba->audio.psg.forceDisableCh[1] = !controller->m_audioChannels[1];
 108			gba->audio.psg.forceDisableCh[2] = !controller->m_audioChannels[2];
 109			gba->audio.psg.forceDisableCh[3] = !controller->m_audioChannels[3];
 110			gba->audio.forceDisableChA = !controller->m_audioChannels[4];
 111			gba->audio.forceDisableChB = !controller->m_audioChannels[5];
 112			gba->video.renderer->disableBG[0] = !controller->m_videoLayers[0];
 113			gba->video.renderer->disableBG[1] = !controller->m_videoLayers[1];
 114			gba->video.renderer->disableBG[2] = !controller->m_videoLayers[2];
 115			gba->video.renderer->disableBG[3] = !controller->m_videoLayers[3];
 116			gba->video.renderer->disableOBJ = !controller->m_videoLayers[4];
 117			break;
 118#endif
 119#ifdef M_CORE_GB
 120		case PLATFORM_GB:
 121			gb->audio.forceDisableCh[0] = !controller->m_audioChannels[0];
 122			gb->audio.forceDisableCh[1] = !controller->m_audioChannels[1];
 123			gb->audio.forceDisableCh[2] = !controller->m_audioChannels[2];
 124			gb->audio.forceDisableCh[3] = !controller->m_audioChannels[3];
 125			break;
 126#endif
 127		default:
 128			break;
 129		}
 130		controller->m_fpsTarget = context->sync.fpsTarget;
 131
 132		if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) {
 133			mCoreDeleteState(context->core, 0);
 134		}
 135		QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, controller->m_fname));
 136	};
 137
 138	m_threadContext.cleanCallback = [](mCoreThread* context) {
 139		GameController* controller = static_cast<GameController*>(context->userData);
 140		QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(mCoreThread*, context));
 141	};
 142
 143	m_threadContext.frameCallback = [](mCoreThread* context) {
 144		GameController* controller = static_cast<GameController*>(context->userData);
 145		unsigned width, height;
 146		controller->m_threadContext.core->desiredVideoDimensions(controller->m_threadContext.core, &width, &height);
 147		memcpy(controller->m_frontBuffer, controller->m_drawContext, width * height * BYTES_PER_PIXEL);
 148		QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer));
 149		if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) {
 150			mCoreThreadPauseFromThread(context);
 151			QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context));
 152		}
 153	};
 154
 155	/*m_threadContext.stopCallback = [](mCoreThread* context) {
 156		if (!context) {
 157			return false;
 158		}
 159		GameController* controller = static_cast<GameController*>(context->userData);
 160		if (!mCoreSaveState(context->core, 0, controller->m_saveStateFlags)) {
 161			return false;
 162		}
 163		QMetaObject::invokeMethod(controller, "closeGame");
 164		return true;
 165	};*/
 166
 167	m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
 168		mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger);
 169		mCoreThread* context = logContext->p;
 170
 171		static const char* savestateMessage = "State %i loaded";
 172		static const char* savestateFailedMessage = "State %i failed to load";
 173		if (!context) {
 174			return;
 175		}
 176		GameController* controller = static_cast<GameController*>(context->userData);
 177		if (level == mLOG_STUB && category == _mLOG_CAT_GBA_BIOS()) {
 178			va_list argc;
 179			va_copy(argc, args);
 180			int immediate = va_arg(argc, int);
 181			va_end(argc);
 182			QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate));
 183		} else if (category == _mLOG_CAT_STATUS()) {
 184			// Slot 0 is reserved for suspend points
 185			if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
 186				va_list argc;
 187				va_copy(argc, args);
 188				int slot = va_arg(argc, int);
 189				va_end(argc);
 190				if (slot == 0) {
 191					format = "Loaded suspend state";
 192				}
 193			} else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) {
 194				va_list argc;
 195				va_copy(argc, args);
 196				int slot = va_arg(argc, int);
 197				va_end(argc);
 198				if (slot == 0) {
 199					return;
 200				}
 201			}
 202		}
 203		if (level == mLOG_FATAL) {
 204			QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
 205		} else if (!(controller->m_logLevels & level)) {
 206			return;
 207		}
 208		QString message(QString().vsprintf(format, args));
 209		if (category == _mLOG_CAT_STATUS()) {
 210			QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
 211		}
 212		QMetaObject::invokeMethod(controller, "postLog", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message));
 213	};
 214
 215	m_threadContext.userData = this;
 216
 217	connect(&m_rewindTimer, &QTimer::timeout, [this]() {
 218		// TODO: Put rewind back
 219		emit frameAvailable(m_drawContext);
 220		emit rewound(&m_threadContext);
 221	});
 222	m_rewindTimer.setInterval(100);
 223
 224	m_audioThread->setObjectName("Audio Thread");
 225	m_audioThread->start(QThread::TimeCriticalPriority);
 226	m_audioProcessor->moveToThread(m_audioThread);
 227	connect(this, SIGNAL(gamePaused(mCoreThread*)), m_audioProcessor, SLOT(pause()));
 228	connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(pollEvents()));
 229	connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateAutofire()));
 230}
 231
 232GameController::~GameController() {
 233	m_audioThread->quit();
 234	m_audioThread->wait();
 235	disconnect();
 236	clearMultiplayerController();
 237	closeGame();
 238	GBACheatDeviceDestroy(&m_cheatDevice);
 239	delete m_backupLoadState;
 240}
 241
 242void GameController::setMultiplayerController(MultiplayerController* controller) {
 243	if (controller == m_multiplayer) {
 244		return;
 245	}
 246	clearMultiplayerController();
 247	m_multiplayer = controller;
 248	controller->attachGame(this);
 249}
 250
 251void GameController::clearMultiplayerController() {
 252	if (!m_multiplayer) {
 253		return;
 254	}
 255	m_multiplayer->detachGame(this);
 256	m_multiplayer = nullptr;
 257}
 258
 259void GameController::setOverride(const GBACartridgeOverride& override) {
 260	// TODO: Put back overrides
 261}
 262
 263void GameController::setConfig(const mCoreConfig* config) {
 264	m_config = config;
 265	if (isLoaded()) {
 266		threadInterrupt();
 267		mCoreLoadForeignConfig(m_threadContext.core, config);
 268		m_audioProcessor->setInput(&m_threadContext);
 269		threadContinue();
 270	}
 271}
 272
 273#ifdef USE_GDB_STUB
 274Debugger* GameController::debugger() {
 275	// TODO: Put back debugger
 276	return nullptr;
 277}
 278
 279void GameController::setDebugger(Debugger* debugger) {
 280	threadInterrupt();
 281	// TODO: Put back debugger
 282	threadContinue();
 283}
 284#endif
 285
 286void GameController::loadGame(const QString& path) {
 287	closeGame();
 288	QFile file(path);
 289	if (!file.open(QIODevice::ReadOnly)) {
 290		LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
 291		return;
 292	}
 293	file.close();
 294
 295	m_fname = path;
 296	openGame();
 297}
 298
 299void GameController::bootBIOS() {
 300	closeGame();
 301	m_fname = QString();
 302	openGame(true);
 303}
 304
 305void GameController::openGame(bool biosOnly) {
 306	if (biosOnly && (!m_useBios || m_bios.isNull())) {
 307		return;
 308	}
 309
 310	if (!biosOnly) {
 311		m_threadContext.core = mCoreFind(m_fname.toUtf8().constData());
 312	} else {
 313		m_threadContext.core = GBACoreCreate();
 314	}
 315
 316	if (!m_threadContext.core) {
 317		return;
 318	}
 319
 320	m_gameOpen = true;
 321
 322	m_pauseAfterFrame = false;
 323
 324	if (m_turbo) {
 325		m_threadContext.sync.videoFrameWait = false;
 326		m_threadContext.sync.audioWait = false;
 327	} else {
 328		m_threadContext.sync.videoFrameWait = m_videoSync;
 329		m_threadContext.sync.audioWait = m_audioSync;
 330	}
 331	m_threadContext.core->init(m_threadContext.core);
 332
 333	unsigned width, height;
 334	m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
 335	m_drawContext = new uint32_t[width * height];
 336	m_frontBuffer = new uint32_t[width * height];
 337
 338	if (!biosOnly) {
 339		mCoreLoadFile(m_threadContext.core, m_fname.toUtf8().constData());
 340	}
 341
 342	m_threadContext.core->setVideoBuffer(m_threadContext.core, m_drawContext, width);
 343
 344	if (!m_bios.isNull() && m_useBios) {
 345		VFile* bios = VFileDevice::open(m_bios, O_RDONLY);
 346		if (bios) {
 347			// TODO: Lifetime issues?
 348			m_threadContext.core->loadBIOS(m_threadContext.core, bios, 0);
 349		}
 350	}
 351
 352	if (!m_patch.isNull()) {
 353		VFile* patch = VFileDevice::open(m_patch, O_RDONLY);
 354		if (patch) {
 355			m_threadContext.core->loadPatch(m_threadContext.core, patch);
 356		}
 357		patch->close(patch);
 358	}
 359
 360	m_inputController->recalibrateAxes();
 361	memset(m_drawContext, 0xF8, width * height * 4);
 362
 363	m_threadContext.core->setAVStream(m_threadContext.core, m_stream);
 364
 365	if (m_config) {
 366		mCoreLoadForeignConfig(m_threadContext.core, m_config);
 367	}
 368
 369	if (!biosOnly) {
 370		mCoreAutoloadSave(m_threadContext.core);
 371	}
 372
 373	if (!mCoreThreadStart(&m_threadContext)) {
 374		m_gameOpen = false;
 375		emit gameFailed();
 376	} else if (m_audioProcessor) {
 377		startAudio();
 378	}
 379}
 380
 381void GameController::loadBIOS(const QString& path) {
 382	if (m_bios == path) {
 383		return;
 384	}
 385	m_bios = path;
 386	if (m_gameOpen) {
 387		closeGame();
 388		openGame();
 389	}
 390}
 391
 392void GameController::yankPak() {
 393	if (!m_gameOpen) {
 394		return;
 395	}
 396	threadInterrupt();
 397	GBAYankROM(static_cast<GBA*>(m_threadContext.core->board));
 398	threadContinue();
 399}
 400
 401void GameController::replaceGame(const QString& path) {
 402	if (!m_gameOpen) {
 403		return;
 404	}
 405
 406	m_fname = path;
 407	threadInterrupt();
 408	mCoreLoadFile(m_threadContext.core, m_fname.toLocal8Bit().constData());
 409	threadContinue();
 410}
 411
 412void GameController::loadPatch(const QString& path) {
 413	if (m_gameOpen) {
 414		closeGame();
 415		m_patch = path;
 416		openGame();
 417	} else {
 418		m_patch = path;
 419	}
 420}
 421
 422void GameController::importSharkport(const QString& path) {
 423	if (!isLoaded()) {
 424		return;
 425	}
 426	VFile* vf = VFileDevice::open(path, O_RDONLY);
 427	if (!vf) {
 428		LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
 429		return;
 430	}
 431	threadInterrupt();
 432	GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
 433	threadContinue();
 434	vf->close(vf);
 435}
 436
 437void GameController::exportSharkport(const QString& path) {
 438	if (!isLoaded()) {
 439		return;
 440	}
 441	VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
 442	if (!vf) {
 443		LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
 444		return;
 445	}
 446	threadInterrupt();
 447	GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf);
 448	threadContinue();
 449	vf->close(vf);
 450}
 451
 452void GameController::closeGame() {
 453	if (!m_gameOpen) {
 454		return;
 455	}
 456	m_gameOpen = false;
 457
 458	m_rewindTimer.stop();
 459	if (mCoreThreadIsPaused(&m_threadContext)) {
 460		mCoreThreadUnpause(&m_threadContext);
 461	}
 462	m_audioProcessor->pause();
 463	mCoreThreadEnd(&m_threadContext);
 464	mCoreThreadJoin(&m_threadContext);
 465	// Make sure the event queue clears out before the thread is reused
 466	QCoreApplication::processEvents();
 467
 468	delete[] m_drawContext;
 469	delete[] m_frontBuffer;
 470
 471	m_patch = QString();
 472
 473	for (size_t i = 0; i < GBACheatSetsSize(&m_cheatDevice.cheats); ++i) {
 474		GBACheatSet* set = *GBACheatSetsGetPointer(&m_cheatDevice.cheats, i);
 475		GBACheatSetDeinit(set);
 476		delete set;
 477	}
 478	GBACheatSetsClear(&m_cheatDevice.cheats);
 479
 480	m_threadContext.core->deinit(m_threadContext.core);
 481}
 482
 483void GameController::crashGame(const QString& crashMessage) {
 484	closeGame();
 485	emit gameCrashed(crashMessage);
 486	emit gameStopped(&m_threadContext);
 487}
 488
 489bool GameController::isPaused() {
 490	if (!m_gameOpen) {
 491		return false;
 492	}
 493	return mCoreThreadIsPaused(&m_threadContext);
 494}
 495
 496mPlatform GameController::platform() const {
 497	if (!m_gameOpen) {
 498		return PLATFORM_NONE;
 499	}
 500	return m_threadContext.core->platform(m_threadContext.core);
 501}
 502
 503QSize GameController::screenDimensions() const {
 504	if (!m_gameOpen) {
 505		return QSize();
 506	}
 507	unsigned width, height;
 508	m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
 509
 510	return QSize(width, height);
 511}
 512
 513void GameController::setPaused(bool paused) {
 514	if (!isLoaded() || m_rewindTimer.isActive() || paused == mCoreThreadIsPaused(&m_threadContext)) {
 515		return;
 516	}
 517	if (paused) {
 518		m_pauseAfterFrame.testAndSetRelaxed(false, true);
 519	} else {
 520		mCoreThreadUnpause(&m_threadContext);
 521		startAudio();
 522		emit gameUnpaused(&m_threadContext);
 523	}
 524}
 525
 526void GameController::reset() {
 527	if (!m_gameOpen) {
 528		return;
 529	}
 530	bool wasPaused = isPaused();
 531	setPaused(false);
 532	mCoreThreadReset(&m_threadContext);
 533	if (wasPaused) {
 534		setPaused(true);
 535	}
 536}
 537
 538void GameController::threadInterrupt() {
 539	if (m_gameOpen) {
 540		mCoreThreadInterrupt(&m_threadContext);
 541	}
 542}
 543
 544void GameController::threadContinue() {
 545	if (m_gameOpen) {
 546		mCoreThreadContinue(&m_threadContext);
 547	}
 548}
 549
 550void GameController::frameAdvance() {
 551	if (m_rewindTimer.isActive()) {
 552		return;
 553	}
 554	if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) {
 555		setPaused(false);
 556	}
 557}
 558
 559void GameController::setRewind(bool enable, int capacity, int interval) {
 560	if (m_gameOpen) {
 561		threadInterrupt();
 562		// TODO: Put back rewind
 563		threadContinue();
 564	} else {
 565		// TODO: Put back rewind
 566	}
 567}
 568
 569void GameController::rewind(int states) {
 570	threadInterrupt();
 571	if (!states) {
 572		// TODO: Put back rewind
 573	} else {
 574		// TODO: Put back rewind
 575	}
 576	threadContinue();
 577	emit frameAvailable(m_drawContext);
 578	emit rewound(&m_threadContext);
 579}
 580
 581void GameController::startRewinding() {
 582	if (!m_gameOpen || m_rewindTimer.isActive()) {
 583		return;
 584	}
 585	if (m_multiplayer && m_multiplayer->attached() > 1) {
 586		return;
 587	}
 588	m_wasPaused = isPaused();
 589	if (!mCoreThreadIsPaused(&m_threadContext)) {
 590		mCoreThreadPause(&m_threadContext);
 591	}
 592	m_rewindTimer.start();
 593}
 594
 595void GameController::stopRewinding() {
 596	if (!m_rewindTimer.isActive()) {
 597		return;
 598	}
 599	m_rewindTimer.stop();
 600	bool signalsBlocked = blockSignals(true);
 601	setPaused(m_wasPaused);
 602	blockSignals(signalsBlocked);
 603}
 604
 605void GameController::keyPressed(int key) {
 606	int mappedKey = 1 << key;
 607	m_activeKeys |= mappedKey;
 608	if (!m_inputController->allowOpposing()) {
 609		if ((m_activeKeys & 0x30) == 0x30) {
 610			m_inactiveKeys |= mappedKey ^ 0x30;
 611			m_activeKeys ^= mappedKey ^ 0x30;
 612		}
 613		if ((m_activeKeys & 0xC0) == 0xC0) {
 614			m_inactiveKeys |= mappedKey ^ 0xC0;
 615			m_activeKeys ^= mappedKey ^ 0xC0;
 616		}
 617	}
 618	updateKeys();
 619}
 620
 621void GameController::keyReleased(int key) {
 622	int mappedKey = 1 << key;
 623	m_activeKeys &= ~mappedKey;
 624	if (!m_inputController->allowOpposing()) {
 625		if (mappedKey & 0x30) {
 626			m_activeKeys |= m_inactiveKeys & (0x30 ^ mappedKey);
 627			m_inactiveKeys &= ~0x30;
 628		}
 629		if (mappedKey & 0xC0) {
 630			m_activeKeys |= m_inactiveKeys & (0xC0 ^ mappedKey);
 631			m_inactiveKeys &= ~0xC0;
 632		}
 633	}
 634	updateKeys();
 635}
 636
 637void GameController::clearKeys() {
 638	m_activeKeys = 0;
 639	m_inactiveKeys = 0;
 640	updateKeys();
 641}
 642
 643void GameController::setAutofire(int key, bool enable) {
 644	if (key >= GBA_KEY_MAX || key < 0) {
 645		return;
 646	}
 647	m_autofire[key] = enable;
 648	m_autofireStatus[key] = 0;
 649}
 650
 651void GameController::setAudioBufferSamples(int samples) {
 652	if (m_audioProcessor) {
 653		threadInterrupt();
 654		redoSamples(samples);
 655		threadContinue();
 656		QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Qt::BlockingQueuedConnection, Q_ARG(int, samples));
 657	}
 658}
 659
 660void GameController::setAudioSampleRate(unsigned rate) {
 661	if (!rate) {
 662		return;
 663	}
 664	if (m_audioProcessor) {
 665		threadInterrupt();
 666		redoSamples(m_audioProcessor->getBufferSamples());
 667		threadContinue();
 668		QMetaObject::invokeMethod(m_audioProcessor, "requestSampleRate", Q_ARG(unsigned, rate));
 669	}
 670}
 671
 672void GameController::setAudioChannelEnabled(int channel, bool enable) {
 673	if (channel > 5 || channel < 0) {
 674		return;
 675	}
 676#ifdef M_CORE_GBA
 677	GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
 678#endif
 679#ifdef M_CORE_GB
 680	GB* gb = static_cast<GB*>(m_threadContext.core->board);
 681#endif
 682	m_audioChannels[channel] = enable;
 683	if (isLoaded()) {
 684		switch (channel) {
 685		case 0:
 686		case 1:
 687		case 2:
 688		case 3:
 689			switch (m_threadContext.core->platform(m_threadContext.core)) {
 690#ifdef M_CORE_GBA
 691			case PLATFORM_GBA:
 692				gba->audio.psg.forceDisableCh[channel] = !enable;
 693				break;
 694#endif
 695#ifdef M_CORE_GB
 696			case PLATFORM_GB:
 697				gb->audio.forceDisableCh[channel] = !enable;
 698				break;
 699#endif
 700			default:
 701				break;
 702			}
 703			break;
 704#ifdef M_CORE_GBA
 705		case 4:
 706			if (m_threadContext.core->platform(m_threadContext.core) == PLATFORM_GBA) {
 707				gba->audio.forceDisableChA = !enable;
 708			}
 709			break;
 710		case 5:
 711			if (m_threadContext.core->platform(m_threadContext.core) == PLATFORM_GBA) {
 712				gba->audio.forceDisableChB = !enable;
 713			}
 714			break;
 715#endif
 716		}
 717	}
 718}
 719
 720void GameController::startAudio() {
 721	bool started = false;
 722	QMetaObject::invokeMethod(m_audioProcessor, "start", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, started));
 723	if (!started) {
 724		LOG(QT, ERROR) << tr("Failed to start audio processor");
 725		// Don't freeze!
 726		m_audioSync = false;
 727		m_videoSync = true;
 728		m_threadContext.sync.audioWait = false;
 729		m_threadContext.sync.videoFrameWait = true;
 730	}
 731}
 732
 733void GameController::setVideoLayerEnabled(int layer, bool enable) {
 734	if (layer > 4 || layer < 0) {
 735		return;
 736	}
 737	m_videoLayers[layer] = enable;
 738	if (isLoaded() && m_threadContext.core->platform(m_threadContext.core) == PLATFORM_GBA) {
 739		GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
 740		switch (layer) {
 741		case 0:
 742		case 1:
 743		case 2:
 744		case 3:
 745			gba->video.renderer->disableBG[layer] = !enable;
 746			break;
 747		case 4:
 748			gba->video.renderer->disableOBJ = !enable;
 749			break;
 750		}
 751	}
 752}
 753
 754void GameController::setFPSTarget(float fps) {
 755	threadInterrupt();
 756	m_fpsTarget = fps;
 757	m_threadContext.sync.fpsTarget = fps;
 758	if (m_turbo && m_turboSpeed > 0) {
 759		m_threadContext.sync.fpsTarget *= m_turboSpeed;
 760	}
 761	if (m_audioProcessor) {
 762		redoSamples(m_audioProcessor->getBufferSamples());
 763	}
 764	threadContinue();
 765}
 766
 767void GameController::setUseBIOS(bool use) {
 768	if (use == m_useBios) {
 769		return;
 770	}
 771	m_useBios = use;
 772	if (m_gameOpen) {
 773		closeGame();
 774		openGame();
 775	}
 776}
 777
 778void GameController::loadState(int slot) {
 779	if (m_fname.isEmpty()) {
 780		// We're in the BIOS
 781		return;
 782	}
 783	if (slot > 0 && slot != m_stateSlot) {
 784		m_stateSlot = slot;
 785		m_backupSaveState.clear();
 786	}
 787	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 788		GameController* controller = static_cast<GameController*>(context->userData);
 789		if (!controller->m_backupLoadState) {
 790			controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
 791		}
 792		context->core->saveState(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
 793		if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
 794			controller->frameAvailable(controller->m_drawContext);
 795			controller->stateLoaded(context);
 796		}
 797	});
 798}
 799
 800void GameController::saveState(int slot) {
 801	if (m_fname.isEmpty()) {
 802		// We're in the BIOS
 803		return;
 804	}
 805	if (slot > 0) {
 806		m_stateSlot = slot;
 807	}
 808	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 809		GameController* controller = static_cast<GameController*>(context->userData);
 810		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false);
 811		if (vf) {
 812			controller->m_backupSaveState.resize(vf->size(vf));
 813			vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
 814			vf->close(vf);
 815		}
 816		mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags);
 817	});
 818}
 819
 820void GameController::loadBackupState() {
 821	if (!m_backupLoadState) {
 822		return;
 823	}
 824
 825	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 826		GameController* controller = static_cast<GameController*>(context->userData);
 827		controller->m_backupLoadState->seek(controller->m_backupLoadState, 0, SEEK_SET);
 828		if (context->core->loadState(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
 829			mLOG(STATUS, INFO, "Undid state load");
 830			controller->frameAvailable(controller->m_drawContext);
 831			controller->stateLoaded(context);
 832		}
 833		controller->m_backupLoadState->close(controller->m_backupLoadState);
 834		controller->m_backupLoadState = nullptr;
 835	});
 836}
 837
 838void GameController::saveBackupState() {
 839	if (m_backupSaveState.isEmpty()) {
 840		return;
 841	}
 842
 843	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 844		GameController* controller = static_cast<GameController*>(context->userData);
 845		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true);
 846		if (vf) {
 847			vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
 848			vf->close(vf);
 849			mLOG(STATUS, INFO, "Undid state save");
 850		}
 851		controller->m_backupSaveState.clear();
 852	});
 853}
 854
 855void GameController::setTurbo(bool set, bool forced) {
 856	if (m_turboForced && !forced) {
 857		return;
 858	}
 859	if (m_turbo == set && m_turboForced == forced) {
 860		// Don't interrupt the thread if we don't need to
 861		return;
 862	}
 863	m_turbo = set;
 864	m_turboForced = set && forced;
 865	enableTurbo();
 866}
 867
 868void GameController::setTurboSpeed(float ratio) {
 869	m_turboSpeed = ratio;
 870	enableTurbo();
 871}
 872
 873void GameController::enableTurbo() {
 874	threadInterrupt();
 875	if (!m_turbo) {
 876		m_threadContext.sync.fpsTarget = m_fpsTarget;
 877		m_threadContext.sync.audioWait = m_audioSync;
 878		m_threadContext.sync.videoFrameWait = m_videoSync;
 879	} else if (m_turboSpeed <= 0) {
 880		m_threadContext.sync.fpsTarget = m_fpsTarget;
 881		m_threadContext.sync.audioWait = false;
 882		m_threadContext.sync.videoFrameWait = false;
 883	} else {
 884		m_threadContext.sync.fpsTarget = m_fpsTarget * m_turboSpeed;
 885		m_threadContext.sync.audioWait = true;
 886		m_threadContext.sync.videoFrameWait = false;
 887	}
 888	if (m_audioProcessor) {
 889		redoSamples(m_audioProcessor->getBufferSamples());
 890	}
 891	threadContinue();
 892}
 893
 894void GameController::setAVStream(mAVStream* stream) {
 895	threadInterrupt();
 896	m_stream = stream;
 897	if (isLoaded()) {
 898		m_threadContext.core->setAVStream(m_threadContext.core, stream);
 899	}
 900	threadContinue();
 901}
 902
 903void GameController::clearAVStream() {
 904	threadInterrupt();
 905	m_stream = nullptr;
 906	if (isLoaded()) {
 907		m_threadContext.core->setAVStream(m_threadContext.core, nullptr);
 908	}
 909	threadContinue();
 910}
 911
 912#ifdef USE_PNG
 913void GameController::screenshot() {
 914	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 915		mCoreTakeScreenshot(context->core);
 916	});
 917}
 918#endif
 919
 920void GameController::reloadAudioDriver() {
 921	int samples = 0;
 922	unsigned sampleRate = 0;
 923	if (m_audioProcessor) {
 924		QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection);
 925		samples = m_audioProcessor->getBufferSamples();
 926		sampleRate = m_audioProcessor->sampleRate();
 927		delete m_audioProcessor;
 928	}
 929	m_audioProcessor = AudioProcessor::create();
 930	if (samples) {
 931		m_audioProcessor->setBufferSamples(samples);
 932	}
 933	if (sampleRate) {
 934		m_audioProcessor->requestSampleRate(sampleRate);
 935	}
 936	m_audioProcessor->moveToThread(m_audioThread);
 937	connect(this, SIGNAL(gamePaused(mCoreThread*)), m_audioProcessor, SLOT(pause()));
 938	if (isLoaded()) {
 939		m_audioProcessor->setInput(&m_threadContext);
 940		startAudio();
 941	}
 942}
 943
 944void GameController::setSaveStateExtdata(int flags) {
 945	m_saveStateFlags = flags;
 946}
 947
 948void GameController::setLoadStateExtdata(int flags) {
 949	m_loadStateFlags = flags;
 950}
 951
 952void GameController::setLuminanceValue(uint8_t value) {
 953	m_luxValue = value;
 954	value = std::max<int>(value - 0x16, 0);
 955	m_luxLevel = 10;
 956	for (int i = 0; i < 10; ++i) {
 957		if (value < GBA_LUX_LEVELS[i]) {
 958			m_luxLevel = i;
 959			break;
 960		}
 961	}
 962	emit luminanceValueChanged(m_luxValue);
 963}
 964
 965void GameController::setLuminanceLevel(int level) {
 966	int value = 0x16;
 967	level = std::max(0, std::min(10, level));
 968	if (level > 0) {
 969		value += GBA_LUX_LEVELS[level - 1];
 970	}
 971	setLuminanceValue(value);
 972}
 973
 974void GameController::setRealTime() {
 975	m_rtc.override = RTC_NO_OVERRIDE;
 976}
 977
 978void GameController::setFixedTime(const QDateTime& time) {
 979	m_rtc.override = RTC_FIXED;
 980	m_rtc.value = time.toMSecsSinceEpoch() / 1000;
 981}
 982
 983void GameController::setFakeEpoch(const QDateTime& time) {
 984	m_rtc.override = RTC_FAKE_EPOCH;
 985	m_rtc.value = time.toMSecsSinceEpoch() / 1000;
 986}
 987
 988void GameController::updateKeys() {
 989	int activeKeys = m_activeKeys;
 990	activeKeys |= m_activeButtons;
 991	activeKeys &= ~m_inactiveKeys;
 992	if (isLoaded()) {
 993		m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
 994	}
 995}
 996
 997void GameController::redoSamples(int samples) {
 998	if (m_threadContext.core) {
 999		m_threadContext.core->setAudioBufferSize(m_threadContext.core, samples);
1000	}
1001	QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged");
1002}
1003
1004void GameController::setLogLevel(int levels) {
1005	threadInterrupt();
1006	m_logLevels = levels;
1007	threadContinue();
1008}
1009
1010void GameController::enableLogLevel(int levels) {
1011	threadInterrupt();
1012	m_logLevels |= levels;
1013	threadContinue();
1014}
1015
1016void GameController::disableLogLevel(int levels) {
1017	threadInterrupt();
1018	m_logLevels &= ~levels;
1019	threadContinue();
1020}
1021
1022void GameController::pollEvents() {
1023	if (!m_inputController) {
1024		return;
1025	}
1026
1027	m_activeButtons = m_inputController->pollEvents();
1028	updateKeys();
1029}
1030
1031void GameController::updateAutofire() {
1032	// TODO: Move all key events onto the CPU thread...somehow
1033	for (int k = 0; k < GBA_KEY_MAX; ++k) {
1034		if (!m_autofire[k]) {
1035			continue;
1036		}
1037		m_autofireStatus[k] ^= 1;
1038		if (m_autofireStatus[k]) {
1039			keyPressed(k);
1040		} else {
1041			keyReleased(k);
1042		}
1043	}
1044}