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}