all repos — mgba @ 4f43b574e2df578159a38a984ce715ac2060395d

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