all repos — mgba @ ecc4d6ee03fa5df3ad5a3b45b635b684e1d87cc6

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 "Override.h"
  13#include "VFileDevice.h"
  14
  15#include <QCoreApplication>
  16#include <QDateTime>
  17
  18#include <ctime>
  19
  20#include <mgba/core/config.h>
  21#include <mgba/core/directories.h>
  22#include <mgba/core/serialize.h>
  23#include <mgba/core/tile-cache.h>
  24#ifdef M_CORE_GBA
  25#include <mgba/gba/interface.h>
  26#include <mgba/internal/gba/gba.h>
  27#include <mgba/gba/core.h>
  28#include <mgba/internal/gba/renderers/tile-cache.h>
  29#include <mgba/internal/gba/sharkport.h>
  30#endif
  31#ifdef M_CORE_GB
  32#include <mgba/internal/gb/gb.h>
  33#include <mgba/internal/gb/renderers/tile-cache.h>
  34#endif
  35#include <mgba-util/vfs.h>
  36#include <mgba/feature/video-logger.h>
  37
  38using namespace QGBA;
  39using namespace std;
  40
  41GameController::GameController(QObject* parent)
  42	: QObject(parent)
  43	, m_audioProcessor(AudioProcessor::create())
  44	, m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA)
  45	, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
  46{
  47#ifdef M_CORE_GBA
  48	m_lux.p = this;
  49	m_lux.sample = [](GBALuminanceSource* context) {
  50		GameControllerLux* lux = static_cast<GameControllerLux*>(context);
  51		lux->value = 0xFF - lux->p->m_luxValue;
  52	};
  53
  54	m_lux.readLuminance = [](GBALuminanceSource* context) {
  55		GameControllerLux* lux = static_cast<GameControllerLux*>(context);
  56		return lux->value;
  57	};
  58	setLuminanceLevel(0);
  59#endif
  60
  61	m_threadContext.startCallback = [](mCoreThread* context) {
  62		GameController* controller = static_cast<GameController*>(context->userData);
  63		context->core->setPeripheral(context->core, mPERIPH_ROTATION, controller->m_inputController->rotationSource());
  64		context->core->setPeripheral(context->core, mPERIPH_RUMBLE, controller->m_inputController->rumble());
  65
  66		for (size_t i = 0; i < controller->m_audioChannels.size(); ++i) {
  67			context->core->enableAudioChannel(context->core, i, controller->m_audioChannels[i]);
  68		}
  69		for (size_t i = 0; i < controller->m_videoLayers.size(); ++i) {
  70			context->core->enableVideoLayer(context->core, i, controller->m_videoLayers[i]);
  71		}
  72
  73		switch (context->core->platform(context->core)) {
  74#ifdef M_CORE_GBA
  75		case PLATFORM_GBA:
  76			context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, &controller->m_lux);
  77			break;
  78#endif
  79		default:
  80			break;
  81		}
  82		controller->m_fpsTarget = context->sync.fpsTarget;
  83
  84		if (controller->m_override) {
  85			controller->m_override->identify(context->core);
  86			controller->m_override->apply(context->core);
  87		}
  88
  89		if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) {
  90			mCoreDeleteState(context->core, 0);
  91		}
  92
  93		controller->m_gameOpen = true;
  94		if (controller->m_multiplayer) {
  95			controller->m_multiplayer->attachGame(controller);
  96		}
  97
  98		QString path = controller->m_fname;
  99		if (!controller->m_fsub.isEmpty()) {
 100			path += QDir::separator() + controller->m_fsub;
 101		}
 102		QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, path));
 103		QMetaObject::invokeMethod(controller, "startAudio");
 104	};
 105
 106	m_threadContext.resetCallback = [](mCoreThread* context) {
 107		GameController* controller = static_cast<GameController*>(context->userData);
 108		for (auto action : controller->m_resetActions) {
 109			action();
 110		}
 111		controller->m_resetActions.clear();
 112
 113		unsigned width, height;
 114		controller->m_threadContext.core->desiredVideoDimensions(controller->m_threadContext.core, &width, &height);
 115		memset(controller->m_frontBuffer, 0xFF, width * height * BYTES_PER_PIXEL);
 116		QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer));
 117		if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) {
 118			mCoreThreadPauseFromThread(context);
 119			QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context));
 120		}
 121	};
 122
 123	m_threadContext.cleanCallback = [](mCoreThread* context) {
 124		GameController* controller = static_cast<GameController*>(context->userData);
 125
 126		if (controller->m_multiplayer) {
 127			controller->m_multiplayer->detachGame(controller);
 128		}
 129		controller->m_patch = QString();
 130		controller->clearOverride();
 131		controller->endVideoLog();
 132
 133		QMetaObject::invokeMethod(controller->m_audioProcessor, "pause");
 134
 135		QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(mCoreThread*, context));
 136		QMetaObject::invokeMethod(controller, "cleanGame");
 137	};
 138
 139	m_threadContext.frameCallback = [](mCoreThread* context) {
 140		GameController* controller = static_cast<GameController*>(context->userData);
 141		unsigned width, height;
 142		controller->m_threadContext.core->desiredVideoDimensions(controller->m_threadContext.core, &width, &height);
 143		memcpy(controller->m_frontBuffer, controller->m_drawContext, width * height * BYTES_PER_PIXEL);
 144		QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer));
 145
 146		// If no one is using the tile cache, disable it
 147		if (controller->m_tileCache && controller->m_tileCache.unique()) {
 148			switch (controller->platform()) {
 149#ifdef M_CORE_GBA
 150			case PLATFORM_GBA: {
 151				GBA* gba = static_cast<GBA*>(context->core->board);
 152				gba->video.renderer->cache = nullptr;
 153				break;
 154			}
 155#endif
 156#ifdef M_CORE_GB
 157			case PLATFORM_GB: {
 158				GB* gb = static_cast<GB*>(context->core->board);
 159				gb->video.renderer->cache = nullptr;
 160				break;
 161			}
 162#endif
 163			default:
 164				break;
 165			}
 166			mTileCacheDeinit(controller->m_tileCache.get());
 167			controller->m_tileCache.reset();
 168		}
 169
 170
 171		if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) {
 172			mCoreThreadPauseFromThread(context);
 173			QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context));
 174		}
 175	};
 176
 177	m_threadContext.sleepCallback = [](mCoreThread* context) {
 178		if (!context) {
 179			return;
 180		}
 181		GameController* controller = static_cast<GameController*>(context->userData);
 182		if (!mCoreSaveState(context->core, 0, controller->m_saveStateFlags)) {
 183			return;
 184		}
 185		QMetaObject::invokeMethod(controller, "closeGame");
 186	};
 187
 188	m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
 189		mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger);
 190		mCoreThread* context = logContext->p;
 191
 192		static const char* savestateMessage = "State %i loaded";
 193		static const char* savestateFailedMessage = "State %i failed to load";
 194		static int biosCat = -1;
 195		static int statusCat = -1;
 196		if (!context) {
 197			return;
 198		}
 199		GameController* controller = static_cast<GameController*>(context->userData);
 200		QString message;
 201		if (biosCat < 0) {
 202			biosCat = mLogCategoryById("gba.bios");
 203		}
 204		if (statusCat < 0) {
 205			statusCat = mLogCategoryById("core.status");
 206		}
 207#ifdef M_CORE_GBA
 208		if (level == mLOG_STUB && category == biosCat) {
 209			va_list argc;
 210			va_copy(argc, args);
 211			int immediate = va_arg(argc, int);
 212			va_end(argc);
 213			QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate));
 214		} else
 215#endif
 216		if (category == statusCat) {
 217			// Slot 0 is reserved for suspend points
 218			if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
 219				va_list argc;
 220				va_copy(argc, args);
 221				int slot = va_arg(argc, int);
 222				va_end(argc);
 223				if (slot == 0) {
 224					format = "Loaded suspend state";
 225				}
 226			} else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) {
 227				va_list argc;
 228				va_copy(argc, args);
 229				int slot = va_arg(argc, int);
 230				va_end(argc);
 231				if (slot == 0) {
 232					return;
 233				}
 234			}
 235			message = QString().vsprintf(format, args);
 236			QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
 237		}
 238		if (level == mLOG_FATAL) {
 239			mCoreThreadMarkCrashed(controller->thread());
 240			QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
 241		} else if (!(controller->m_logLevels & level)) {
 242			return;
 243		}
 244		message = QString().vsprintf(format, args);
 245		QMetaObject::invokeMethod(controller, "postLog", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message));
 246	};
 247
 248	m_threadContext.userData = this;
 249
 250	connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause);
 251	connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput);
 252	connect(this, &GameController::frameAvailable, this, &GameController::pollEvents);
 253	connect(this, &GameController::frameAvailable, this, &GameController::updateAutofire);
 254}
 255
 256GameController::~GameController() {
 257	disconnect();
 258	closeGame();
 259	clearMultiplayerController();
 260	delete m_backupLoadState;
 261}
 262
 263void GameController::setMultiplayerController(MultiplayerController* controller) {
 264	if (controller == m_multiplayer) {
 265		return;
 266	}
 267	clearMultiplayerController();
 268	m_multiplayer = controller;
 269	if (isLoaded()) {
 270		mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
 271			GameController* controller = static_cast<GameController*>(thread->userData);
 272			controller->m_multiplayer->attachGame(controller);
 273		});
 274	}
 275}
 276
 277void GameController::clearMultiplayerController() {
 278	if (!m_multiplayer) {
 279		return;
 280	}
 281	m_multiplayer->detachGame(this);
 282	m_multiplayer = nullptr;
 283}
 284
 285void GameController::setOverride(Override* override) {
 286	m_override = override;
 287	if (isLoaded()) {
 288		Interrupter interrupter(this);
 289		m_override->identify(m_threadContext.core);
 290	}
 291}
 292
 293void GameController::clearOverride() {
 294	delete m_override;
 295	m_override = nullptr;
 296}
 297
 298void GameController::setConfig(const mCoreConfig* config) {
 299	m_config = config;
 300	if (isLoaded()) {
 301		Interrupter interrupter(this);
 302		mCoreLoadForeignConfig(m_threadContext.core, config);
 303		m_audioSync = m_threadContext.sync.audioWait;
 304		m_videoSync = m_threadContext.sync.videoFrameWait;
 305		m_audioProcessor->setInput(&m_threadContext);
 306	}
 307}
 308
 309#ifdef USE_DEBUGGERS
 310mDebugger* GameController::debugger() {
 311	if (!isLoaded()) {
 312		return nullptr;
 313	}
 314	return m_threadContext.core->debugger;
 315}
 316
 317void GameController::setDebugger(mDebugger* debugger) {
 318	Interrupter interrupter(this);
 319	if (debugger) {
 320		mDebuggerAttach(debugger, m_threadContext.core);
 321	} else {
 322		m_threadContext.core->detachDebugger(m_threadContext.core);
 323	}
 324}
 325#endif
 326
 327void GameController::loadGame(const QString& path) {
 328	closeGame();
 329	QFileInfo info(path);
 330	if (!info.isReadable()) {
 331		QString fname = info.fileName();
 332		QString base = info.path();
 333		if (base.endsWith("/") || base.endsWith(QDir::separator())) {
 334			base.chop(1);
 335		}
 336		VDir* dir = VDirOpenArchive(base.toUtf8().constData());
 337		if (dir) {
 338			VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY);
 339			if (vf) {
 340				struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf));
 341				uint8_t buffer[2048];
 342				ssize_t read;
 343				while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) {
 344					vfclone->write(vfclone, buffer, read);
 345				}
 346				vf->close(vf);
 347				vf = vfclone;
 348			}
 349			dir->close(dir);
 350			loadGame(vf, fname, base);
 351		} else {
 352			LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
 353		}
 354		return;
 355	} else {
 356		m_fname = info.canonicalFilePath();
 357		m_fsub = QString();
 358	}
 359	m_vf = nullptr;
 360	openGame();
 361}
 362
 363void GameController::loadGame(VFile* vf, const QString& path, const QString& base) {
 364	closeGame();
 365	QFileInfo info(base);
 366	if (info.isDir()) {
 367		m_fname = QFileInfo(base + '/' + path).canonicalFilePath();
 368		m_fsub = QString();
 369	} else {
 370		m_fname = info.canonicalFilePath();
 371		m_fsub = path;
 372	}
 373	m_vf = vf;
 374	openGame();
 375}
 376
 377void GameController::bootBIOS() {
 378	closeGame();
 379	m_fname = QString();
 380	openGame(true);
 381}
 382
 383void GameController::openGame(bool biosOnly) {
 384	if (m_fname.isEmpty()) {
 385		biosOnly = true;
 386	}
 387	if (isLoaded()) {
 388		// We need to delay if the game is still cleaning up
 389		QTimer::singleShot(10, this, SLOT(openGame()));
 390		return;
 391	} else if(m_gameOpen) {
 392		cleanGame();
 393	}
 394
 395	m_threadContext.core = nullptr;
 396	if (!biosOnly) {
 397		if (m_vf) {
 398			m_threadContext.core = mCoreFindVF(m_vf);
 399		} else {
 400			m_threadContext.core = mCoreFind(m_fname.toUtf8().constData());
 401		}
 402#ifdef M_CORE_GBA
 403	} else {
 404		m_threadContext.core = GBACoreCreate();
 405#endif
 406	}
 407
 408	if (!m_threadContext.core) {
 409		return;
 410	}
 411
 412	m_pauseAfterFrame = false;
 413
 414	if (m_turbo) {
 415		m_threadContext.sync.videoFrameWait = false;
 416		m_threadContext.sync.audioWait = false;
 417	} else {
 418		m_threadContext.sync.videoFrameWait = m_videoSync;
 419		m_threadContext.sync.audioWait = m_audioSync;
 420	}
 421	m_threadContext.core->init(m_threadContext.core);
 422	mCoreInitConfig(m_threadContext.core, nullptr);
 423
 424	unsigned width, height;
 425	m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
 426	m_drawContext = new uint32_t[width * height];
 427	m_frontBuffer = new uint32_t[width * height];
 428
 429	if (m_config) {
 430		mCoreLoadForeignConfig(m_threadContext.core, m_config);
 431	}
 432
 433	QByteArray bytes;
 434	if (!biosOnly) {
 435		bytes = m_fname.toUtf8();
 436		if (m_preload) {
 437			if (m_vf) {
 438				mCorePreloadVF(m_threadContext.core, m_vf);
 439			} else {
 440				mCorePreloadFile(m_threadContext.core, bytes.constData());
 441				mDirectorySetDetachBase(&m_threadContext.core->dirs);
 442			}
 443		} else {
 444			if (m_vf) {
 445				m_threadContext.core->loadROM(m_threadContext.core, m_vf);
 446			} else {
 447				mCoreLoadFile(m_threadContext.core, bytes.constData());
 448				mDirectorySetDetachBase(&m_threadContext.core->dirs);
 449			}
 450		}
 451	} else {
 452		bytes = m_bios.toUtf8();
 453	}
 454	if (bytes.isNull()) {
 455		return;
 456	}
 457
 458	char dirname[PATH_MAX];
 459	separatePath(bytes.constData(), dirname, m_threadContext.core->dirs.baseName, 0);
 460	mDirectorySetAttachBase(&m_threadContext.core->dirs, VDirOpen(dirname));
 461
 462	m_threadContext.core->setVideoBuffer(m_threadContext.core, m_drawContext, width);
 463
 464	m_inputController->recalibrateAxes();
 465	memset(m_drawContext, 0xF8, width * height * 4);
 466
 467	m_threadContext.core->setAVStream(m_threadContext.core, m_stream);
 468
 469	if (!biosOnly) {
 470		mCoreAutoloadSave(m_threadContext.core);
 471		if (!m_patch.isNull()) {
 472			VFile* patch = VFileDevice::open(m_patch, O_RDONLY);
 473			if (patch) {
 474				m_threadContext.core->loadPatch(m_threadContext.core, patch);
 475			}
 476			patch->close(patch);
 477		} else {
 478			mCoreAutoloadPatch(m_threadContext.core);
 479		}
 480	}
 481	m_vf = nullptr;
 482
 483	if (!mCoreThreadStart(&m_threadContext)) {
 484		emit gameFailed();
 485	}
 486}
 487
 488void GameController::loadBIOS(int platform, const QString& path) {
 489	if (m_bios == path) {
 490		return;
 491	}
 492	if (!m_bios.isNull() && m_gameOpen && this->platform() == platform) {
 493		closeGame();
 494		m_bios = path;
 495		openGame();
 496	} else if (!m_gameOpen || m_bios.isNull()) {
 497		m_bios = path;
 498	}
 499}
 500
 501void GameController::loadSave(const QString& path, bool temporary) {
 502	if (!isLoaded()) {
 503		return;
 504	}
 505	m_resetActions.append([this, path, temporary]() {
 506		VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR);
 507		if (!vf) {
 508			LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path);
 509			return;
 510		}
 511
 512		if (temporary) {
 513			m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
 514		} else {
 515			m_threadContext.core->loadSave(m_threadContext.core, vf);
 516		}
 517	});
 518	reset();
 519}
 520
 521void GameController::yankPak() {
 522	if (!m_gameOpen) {
 523		return;
 524	}
 525	Interrupter interrupter(this);
 526	GBAYankROM(static_cast<GBA*>(m_threadContext.core->board));
 527}
 528
 529void GameController::replaceGame(const QString& path) {
 530	if (!m_gameOpen) {
 531		return;
 532	}
 533
 534	QFileInfo info(path);
 535	if (!info.isReadable()) {
 536		LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
 537		return;
 538	}
 539	m_fname = info.canonicalFilePath();
 540	Interrupter interrupter(this);
 541	mDirectorySetDetachBase(&m_threadContext.core->dirs);
 542	mCoreLoadFile(m_threadContext.core, m_fname.toLocal8Bit().constData());
 543}
 544
 545void GameController::loadPatch(const QString& path) {
 546	if (isLoaded()) {
 547		closeGame();
 548	}
 549	if (m_gameOpen) {
 550		QTimer::singleShot(10, this, [this, path]() {
 551			loadPatch(path);
 552			if (!m_gameOpen) {
 553				openGame();
 554			}
 555		});
 556	} else {
 557		m_patch = path;
 558	}
 559}
 560
 561void GameController::importSharkport(const QString& path) {
 562	if (!isLoaded()) {
 563		return;
 564	}
 565#ifdef M_CORE_GBA
 566	if (platform() != PLATFORM_GBA) {
 567		return;
 568	}
 569	VFile* vf = VFileDevice::open(path, O_RDONLY);
 570	if (!vf) {
 571		LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
 572		return;
 573	}
 574	threadInterrupt();
 575	GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
 576	threadContinue();
 577	vf->close(vf);
 578#endif
 579}
 580
 581void GameController::exportSharkport(const QString& path) {
 582	if (!isLoaded()) {
 583		return;
 584	}
 585#ifdef M_CORE_GBA
 586	if (platform() != PLATFORM_GBA) {
 587		return;
 588	}
 589	VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
 590	if (!vf) {
 591		LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
 592		return;
 593	}
 594	threadInterrupt();
 595	GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf);
 596	threadContinue();
 597	vf->close(vf);
 598#endif
 599}
 600
 601void GameController::closeGame() {
 602	if (!m_gameOpen) {
 603		return;
 604	}
 605#ifdef USE_DEBUGGERS
 606	setDebugger(nullptr);
 607#endif
 608	if (mCoreThreadIsPaused(&m_threadContext)) {
 609		mCoreThreadUnpause(&m_threadContext);
 610	}
 611	mCoreThreadEnd(&m_threadContext);
 612}
 613
 614void GameController::cleanGame() {
 615	if (!m_gameOpen || mCoreThreadIsActive(&m_threadContext)) {
 616		return;
 617	}
 618
 619	m_audioProcessor->pause();
 620	mCoreThreadJoin(&m_threadContext);
 621
 622	if (m_tileCache) {
 623		mTileCacheDeinit(m_tileCache.get());
 624		m_tileCache.reset();
 625	}
 626
 627	delete[] m_drawContext;
 628	delete[] m_frontBuffer;
 629
 630	mCoreConfigDeinit(&m_threadContext.core->config);
 631	m_threadContext.core->deinit(m_threadContext.core);
 632	m_threadContext.core = nullptr;
 633	m_gameOpen = false;
 634}
 635
 636void GameController::crashGame(const QString& crashMessage) {
 637	closeGame();
 638	emit gameCrashed(crashMessage);
 639}
 640
 641bool GameController::isPaused() {
 642	if (!m_gameOpen) {
 643		return false;
 644	}
 645	return mCoreThreadIsPaused(&m_threadContext);
 646}
 647
 648mPlatform GameController::platform() const {
 649	if (!m_gameOpen) {
 650		return PLATFORM_NONE;
 651	}
 652	return m_threadContext.core->platform(m_threadContext.core);
 653}
 654
 655QSize GameController::screenDimensions() const {
 656	if (!m_gameOpen) {
 657		return QSize();
 658	}
 659	unsigned width, height;
 660	m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
 661
 662	return QSize(width, height);
 663}
 664
 665void GameController::setPaused(bool paused) {
 666	if (!isLoaded() || paused == mCoreThreadIsPaused(&m_threadContext)) {
 667		return;
 668	}
 669	m_wasPaused = paused;
 670	if (paused) {
 671		m_pauseAfterFrame.testAndSetRelaxed(false, true);
 672	} else {
 673		mCoreThreadUnpause(&m_threadContext);
 674		startAudio();
 675		emit gameUnpaused(&m_threadContext);
 676	}
 677}
 678
 679void GameController::reset() {
 680	if (!m_gameOpen) {
 681		return;
 682	}
 683	bool wasPaused = isPaused();
 684	setPaused(false);
 685	Interrupter interrupter(this);
 686	mCoreThreadReset(&m_threadContext);
 687	if (wasPaused) {
 688		setPaused(true);
 689	}
 690}
 691
 692void GameController::threadInterrupt() {
 693	if (m_gameOpen) {
 694		mCoreThreadInterrupt(&m_threadContext);
 695	}
 696}
 697
 698void GameController::threadContinue() {
 699	if (m_gameOpen) {
 700		mCoreThreadContinue(&m_threadContext);
 701	}
 702}
 703
 704void GameController::frameAdvance() {
 705	if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) {
 706		setPaused(false);
 707		m_wasPaused = true;
 708	}
 709}
 710
 711void GameController::setRewind(bool enable, int capacity, bool rewindSave) {
 712	if (m_gameOpen) {
 713		Interrupter interrupter(this);
 714		if (m_threadContext.core->opts.rewindEnable && m_threadContext.core->opts.rewindBufferCapacity > 0) {
 715			mCoreRewindContextDeinit(&m_threadContext.rewind);
 716		}
 717		m_threadContext.core->opts.rewindEnable = enable;
 718		m_threadContext.core->opts.rewindBufferCapacity = capacity;
 719		m_threadContext.core->opts.rewindSave = rewindSave;
 720		if (enable && capacity > 0) {
 721			mCoreRewindContextInit(&m_threadContext.rewind, capacity, true);
 722			m_threadContext.rewind.stateFlags = rewindSave ? SAVESTATE_SAVEDATA : 0;
 723		}
 724	}
 725}
 726
 727void GameController::rewind(int states) {
 728	threadInterrupt();
 729	if (!states) {
 730		states = INT_MAX;
 731	}
 732	for (int i = 0; i < states; ++i) {
 733		if (!mCoreRewindRestore(&m_threadContext.rewind, m_threadContext.core)) {
 734			break;
 735		}
 736	}
 737	threadContinue();
 738	emit frameAvailable(m_drawContext);
 739	emit rewound(&m_threadContext);
 740}
 741
 742void GameController::startRewinding() {
 743	if (!isLoaded()) {
 744		return;
 745	}
 746	if (!m_threadContext.core->opts.rewindEnable) {
 747		return;
 748	}
 749	if (m_multiplayer && m_multiplayer->attached() > 1) {
 750		return;
 751	}
 752	if (m_wasPaused) {
 753		setPaused(false);
 754		m_wasPaused = true;
 755	}
 756	mCoreThreadSetRewinding(&m_threadContext, true);
 757}
 758
 759void GameController::stopRewinding() {
 760	if (!isLoaded()) {
 761		return;
 762	}
 763	mCoreThreadSetRewinding(&m_threadContext, false);
 764	bool signalsBlocked = blockSignals(true);
 765	setPaused(m_wasPaused);
 766	blockSignals(signalsBlocked);
 767}
 768
 769void GameController::keyPressed(int key) {
 770	int mappedKey = 1 << key;
 771	m_activeKeys |= mappedKey;
 772	if (!m_inputController->allowOpposing()) {
 773		if ((m_activeKeys & 0x30) == 0x30) {
 774			m_inactiveKeys |= mappedKey ^ 0x30;
 775			m_activeKeys ^= mappedKey ^ 0x30;
 776		}
 777		if ((m_activeKeys & 0xC0) == 0xC0) {
 778			m_inactiveKeys |= mappedKey ^ 0xC0;
 779			m_activeKeys ^= mappedKey ^ 0xC0;
 780		}
 781	}
 782	updateKeys();
 783}
 784
 785void GameController::keyReleased(int key) {
 786	int mappedKey = 1 << key;
 787	m_activeKeys &= ~mappedKey;
 788	if (!m_inputController->allowOpposing()) {
 789		if (mappedKey & 0x30) {
 790			m_activeKeys |= m_inactiveKeys & (0x30 ^ mappedKey);
 791			m_inactiveKeys &= ~0x30;
 792		}
 793		if (mappedKey & 0xC0) {
 794			m_activeKeys |= m_inactiveKeys & (0xC0 ^ mappedKey);
 795			m_inactiveKeys &= ~0xC0;
 796		}
 797	}
 798	updateKeys();
 799}
 800
 801void GameController::clearKeys() {
 802	m_activeKeys = 0;
 803	m_inactiveKeys = 0;
 804	updateKeys();
 805}
 806
 807void GameController::setAutofire(int key, bool enable) {
 808	if (key >= GBA_KEY_MAX || key < 0) {
 809		return;
 810	}
 811
 812	if (!enable && m_autofireStatus[key]) {
 813		keyReleased(key);
 814	}
 815
 816	m_autofire[key] = enable;
 817	m_autofireStatus[key] = 0;
 818}
 819
 820void GameController::setAudioBufferSamples(int samples) {
 821	if (m_audioProcessor) {
 822		threadInterrupt();
 823		redoSamples(samples);
 824		threadContinue();
 825		m_audioProcessor->setBufferSamples(samples);
 826	}
 827}
 828
 829void GameController::setAudioSampleRate(unsigned rate) {
 830	if (!rate) {
 831		return;
 832	}
 833	if (m_audioProcessor) {
 834		threadInterrupt();
 835		redoSamples(m_audioProcessor->getBufferSamples());
 836		threadContinue();
 837		m_audioProcessor->requestSampleRate(rate);
 838	}
 839}
 840
 841void GameController::setAudioChannelEnabled(int channel, bool enable) {
 842	if (channel > 5 || channel < 0) {
 843		return;
 844	}
 845	m_audioChannels.reserve(channel + 1);
 846	while (m_audioChannels.size() <= channel) {
 847		m_audioChannels.append(true);
 848	}
 849	m_audioChannels[channel] = enable;
 850	if (isLoaded()) {
 851		m_threadContext.core->enableAudioChannel(m_threadContext.core, channel, enable);
 852	}
 853}
 854
 855void GameController::startAudio() {
 856	if (!m_audioProcessor->start()) {
 857		LOG(QT, ERROR) << tr("Failed to start audio processor");
 858		// Don't freeze!
 859		m_audioSync = false;
 860		m_videoSync = true;
 861		m_threadContext.sync.audioWait = false;
 862		m_threadContext.sync.videoFrameWait = true;
 863	}
 864}
 865
 866void GameController::setVideoLayerEnabled(int layer, bool enable) {
 867	if (layer > 4 || layer < 0) {
 868		return;
 869	}
 870	m_videoLayers.reserve(layer + 1);
 871	while (m_videoLayers.size() <= layer) {
 872		m_videoLayers.append(true);
 873	}
 874	m_videoLayers[layer] = enable;
 875	if (isLoaded()) {
 876		m_threadContext.core->enableVideoLayer(m_threadContext.core, layer, enable);
 877	}
 878}
 879
 880void GameController::setFPSTarget(float fps) {
 881	Interrupter interrupter(this);
 882	m_fpsTarget = fps;
 883	m_threadContext.sync.fpsTarget = fps;
 884	if (m_turbo && m_turboSpeed > 0) {
 885		m_threadContext.sync.fpsTarget *= m_turboSpeed;
 886	}
 887	if (m_audioProcessor) {
 888		redoSamples(m_audioProcessor->getBufferSamples());
 889	}
 890}
 891
 892void GameController::setUseBIOS(bool use) {
 893	if (use == m_useBios) {
 894		return;
 895	}
 896	m_useBios = use;
 897	if (m_gameOpen) {
 898		closeGame();
 899		openGame();
 900	}
 901}
 902
 903void GameController::loadState(int slot) {
 904	if (m_fname.isEmpty()) {
 905		// We're in the BIOS
 906		return;
 907	}
 908	if (slot > 0 && slot != m_stateSlot) {
 909		m_stateSlot = slot;
 910		m_backupSaveState.clear();
 911	}
 912	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 913		GameController* controller = static_cast<GameController*>(context->userData);
 914		if (!controller->m_backupLoadState) {
 915			controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
 916		}
 917		mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
 918		if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
 919			emit controller->frameAvailable(controller->m_drawContext);
 920			emit controller->stateLoaded(context);
 921		}
 922	});
 923}
 924
 925void GameController::saveState(int slot) {
 926	if (m_fname.isEmpty()) {
 927		// We're in the BIOS
 928		return;
 929	}
 930	if (slot > 0) {
 931		m_stateSlot = slot;
 932	}
 933	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 934		GameController* controller = static_cast<GameController*>(context->userData);
 935		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false);
 936		if (vf) {
 937			controller->m_backupSaveState.resize(vf->size(vf));
 938			vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
 939			vf->close(vf);
 940		}
 941		mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags);
 942	});
 943}
 944
 945void GameController::loadBackupState() {
 946	if (!m_backupLoadState) {
 947		return;
 948	}
 949
 950	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 951		GameController* controller = static_cast<GameController*>(context->userData);
 952		controller->m_backupLoadState->seek(controller->m_backupLoadState, 0, SEEK_SET);
 953		if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
 954			mLOG(STATUS, INFO, "Undid state load");
 955			controller->frameAvailable(controller->m_drawContext);
 956			controller->stateLoaded(context);
 957		}
 958		controller->m_backupLoadState->close(controller->m_backupLoadState);
 959		controller->m_backupLoadState = nullptr;
 960	});
 961}
 962
 963void GameController::saveBackupState() {
 964	if (m_backupSaveState.isEmpty()) {
 965		return;
 966	}
 967
 968	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
 969		GameController* controller = static_cast<GameController*>(context->userData);
 970		VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true);
 971		if (vf) {
 972			vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
 973			vf->close(vf);
 974			mLOG(STATUS, INFO, "Undid state save");
 975		}
 976		controller->m_backupSaveState.clear();
 977	});
 978}
 979
 980void GameController::setTurbo(bool set, bool forced) {
 981	if (m_turboForced && !forced) {
 982		return;
 983	}
 984	if (m_turbo == set && m_turboForced == (set && forced)) {
 985		// Don't interrupt the thread if we don't need to
 986		return;
 987	}
 988	if (!m_sync) {
 989		return;
 990	}
 991	m_turbo = set;
 992	m_turboForced = set && forced;
 993	enableTurbo();
 994}
 995
 996void GameController::setTurboSpeed(float ratio) {
 997	m_turboSpeed = ratio;
 998	enableTurbo();
 999}
1000
1001void GameController::enableTurbo() {
1002	Interrupter interrupter(this);
1003	bool shouldRedoSamples = false;
1004	if (!m_turbo) {
1005		shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
1006		m_threadContext.sync.fpsTarget = m_fpsTarget;
1007		m_threadContext.sync.audioWait = m_audioSync;
1008		m_threadContext.sync.videoFrameWait = m_videoSync;
1009	} else if (m_turboSpeed <= 0) {
1010		shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
1011		m_threadContext.sync.fpsTarget = m_fpsTarget;
1012		m_threadContext.sync.audioWait = false;
1013		m_threadContext.sync.videoFrameWait = false;
1014	} else {
1015		shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget * m_turboSpeed;
1016		m_threadContext.sync.fpsTarget = m_fpsTarget * m_turboSpeed;
1017		m_threadContext.sync.audioWait = true;
1018		m_threadContext.sync.videoFrameWait = false;
1019	}
1020	if (m_audioProcessor && shouldRedoSamples) {
1021		redoSamples(m_audioProcessor->getBufferSamples());
1022	}
1023}
1024
1025void GameController::setSync(bool enable) {
1026	m_turbo = false;
1027	m_turboForced = false;
1028	if (!enable) {
1029		m_threadContext.sync.audioWait = false;
1030		m_threadContext.sync.videoFrameWait = false;
1031	} else {
1032		m_threadContext.sync.audioWait = m_audioSync;
1033		m_threadContext.sync.videoFrameWait = m_videoSync;
1034	}
1035	m_sync = enable;
1036}
1037
1038void GameController::setAudioSync(bool enable) {
1039	m_audioSync = enable;
1040	m_threadContext.sync.audioWait = enable;
1041}
1042
1043void GameController::setVideoSync(bool enable) {
1044	m_videoSync = enable;
1045	m_threadContext.sync.videoFrameWait = enable;
1046}
1047
1048void GameController::setAVStream(mAVStream* stream) {
1049	Interrupter interrupter(this);
1050	m_stream = stream;
1051	if (isLoaded()) {
1052		m_threadContext.core->setAVStream(m_threadContext.core, stream);
1053	}
1054}
1055
1056void GameController::clearAVStream() {
1057	Interrupter interrupter(this);
1058	m_stream = nullptr;
1059	if (isLoaded()) {
1060		m_threadContext.core->setAVStream(m_threadContext.core, nullptr);
1061	}
1062}
1063
1064#ifdef USE_PNG
1065void GameController::screenshot() {
1066	mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
1067		mCoreTakeScreenshot(context->core);
1068	});
1069}
1070#endif
1071
1072void GameController::reloadAudioDriver() {
1073	int samples = 0;
1074	unsigned sampleRate = 0;
1075	if (m_audioProcessor) {
1076		m_audioProcessor->pause();
1077		samples = m_audioProcessor->getBufferSamples();
1078		sampleRate = m_audioProcessor->sampleRate();
1079		delete m_audioProcessor;
1080	}
1081	m_audioProcessor = AudioProcessor::create();
1082	if (samples) {
1083		m_audioProcessor->setBufferSamples(samples);
1084	}
1085	if (sampleRate) {
1086		m_audioProcessor->requestSampleRate(sampleRate);
1087	}
1088	connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause);
1089	connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput);
1090	if (isLoaded()) {
1091		m_audioProcessor->setInput(&m_threadContext);
1092		startAudio();
1093	}
1094}
1095
1096void GameController::setSaveStateExtdata(int flags) {
1097	m_saveStateFlags = flags;
1098}
1099
1100void GameController::setLoadStateExtdata(int flags) {
1101	m_loadStateFlags = flags;
1102}
1103
1104void GameController::setPreload(bool preload) {
1105	m_preload = preload;
1106}
1107
1108void GameController::setLuminanceValue(uint8_t value) {
1109	m_luxValue = value;
1110	value = std::max<int>(value - 0x16, 0);
1111	m_luxLevel = 10;
1112	for (int i = 0; i < 10; ++i) {
1113		if (value < GBA_LUX_LEVELS[i]) {
1114			m_luxLevel = i;
1115			break;
1116		}
1117	}
1118	emit luminanceValueChanged(m_luxValue);
1119}
1120
1121void GameController::setLuminanceLevel(int level) {
1122	int value = 0x16;
1123	level = std::max(0, std::min(10, level));
1124	if (level > 0) {
1125		value += GBA_LUX_LEVELS[level - 1];
1126	}
1127	setLuminanceValue(value);
1128}
1129
1130void GameController::setRealTime() {
1131	if (!isLoaded()) {
1132		return;
1133	}
1134	m_threadContext.core->rtc.override = RTC_NO_OVERRIDE;
1135}
1136
1137void GameController::setFixedTime(const QDateTime& time) {
1138	if (!isLoaded()) {
1139		return;
1140	}
1141	m_threadContext.core->rtc.override = RTC_FIXED;
1142	m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
1143}
1144
1145void GameController::setFakeEpoch(const QDateTime& time) {
1146	if (!isLoaded()) {
1147		return;
1148	}
1149	m_threadContext.core->rtc.override = RTC_FAKE_EPOCH;
1150	m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
1151}
1152
1153void GameController::updateKeys() {
1154	int activeKeys = m_activeKeys;
1155	activeKeys |= m_activeButtons;
1156	activeKeys &= ~m_inactiveKeys;
1157	if (isLoaded()) {
1158		m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
1159	}
1160}
1161
1162void GameController::redoSamples(int samples) {
1163	if (m_gameOpen && m_threadContext.core) {
1164		m_threadContext.core->setAudioBufferSize(m_threadContext.core, samples);
1165	}
1166	m_audioProcessor->inputParametersChanged();
1167}
1168
1169void GameController::setLogLevel(int levels) {
1170	Interrupter interrupter(this);
1171	m_logLevels = levels;
1172}
1173
1174void GameController::enableLogLevel(int levels) {
1175	Interrupter interrupter(this);
1176	m_logLevels |= levels;
1177}
1178
1179void GameController::disableLogLevel(int levels) {
1180	Interrupter interrupter(this);
1181	m_logLevels &= ~levels;
1182}
1183
1184void GameController::startVideoLog(const QString& path) {
1185	if (!isLoaded() || m_vl) {
1186		return;
1187	}
1188
1189	Interrupter interrupter(this);
1190	m_vl = mVideoLogContextCreate(m_threadContext.core);
1191	m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
1192	mVideoLogContextSetOutput(m_vl, m_vlVf);
1193	mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
1194}
1195
1196void GameController::endVideoLog() {
1197	if (!m_vl) {
1198		return;
1199	}
1200
1201	Interrupter interrupter(this);
1202	mVideoLogContextDestroy(m_threadContext.core, m_vl);
1203	if (m_vlVf) {
1204		m_vlVf->close(m_vlVf);
1205		m_vlVf = nullptr;
1206	}
1207	m_vl = nullptr;
1208}
1209
1210void GameController::pollEvents() {
1211	if (!m_inputController) {
1212		return;
1213	}
1214
1215	m_activeButtons = m_inputController->pollEvents();
1216	updateKeys();
1217}
1218
1219void GameController::updateAutofire() {
1220	// TODO: Move all key events onto the CPU thread...somehow
1221	for (int k = 0; k < GBA_KEY_MAX; ++k) {
1222		if (!m_autofire[k]) {
1223			continue;
1224		}
1225		m_autofireStatus[k] ^= 1;
1226		if (m_autofireStatus[k]) {
1227			keyPressed(k);
1228		} else {
1229			keyReleased(k);
1230		}
1231	}
1232}
1233
1234std::shared_ptr<mTileCache> GameController::tileCache() {
1235	if (m_tileCache) {
1236		return m_tileCache;
1237	}
1238	Interrupter interrupter(this);
1239	switch (platform()) {
1240#ifdef M_CORE_GBA
1241	case PLATFORM_GBA: {
1242		GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
1243		m_tileCache = std::make_shared<mTileCache>();
1244		GBAVideoTileCacheInit(m_tileCache.get());
1245		GBAVideoTileCacheAssociate(m_tileCache.get(), &gba->video);
1246		mTileCacheSetPalette(m_tileCache.get(), 0);
1247		break;
1248	}
1249#endif
1250#ifdef M_CORE_GB
1251	case PLATFORM_GB: {
1252		GB* gb = static_cast<GB*>(m_threadContext.core->board);
1253		m_tileCache = std::make_shared<mTileCache>();
1254		GBVideoTileCacheInit(m_tileCache.get());
1255		GBVideoTileCacheAssociate(m_tileCache.get(), &gb->video);
1256		mTileCacheSetPalette(m_tileCache.get(), 0);
1257		break;
1258	}
1259#endif
1260	default:
1261		return nullptr;
1262	}
1263	return m_tileCache;
1264}
1265
1266GameController::Interrupter::Interrupter(GameController* parent, bool fromThread)
1267	: m_parent(parent)
1268	, m_fromThread(fromThread)
1269{
1270	if (!m_fromThread) {
1271		m_parent->threadInterrupt();
1272	} else {
1273		mCoreThreadInterruptFromThread(m_parent->thread());
1274	}
1275}
1276
1277GameController::Interrupter::~Interrupter() {
1278	if (!m_fromThread) {
1279		m_parent->threadContinue();
1280	} else {
1281		mCoreThreadContinue(m_parent->thread());
1282	}
1283}