all repos — mgba @ d481bd29fed68f15a827a71f0ca23b9b45947738

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