all repos — mgba @ f4a61f91d46b8a90a61ca0f9ef04f431f0a4fd9f

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