all repos — mgba @ d0771b78e22e89b5523badee256ec6e15467dc54

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