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