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