all repos — mgba @ 13f1ab5ab84c83129e96ae80a9d261806b91289f

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