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 "MultiplayerController.h"
11#include "VFileDevice.h"
12
13#include <QDateTime>
14#include <QThread>
15
16#include <ctime>
17
18extern "C" {
19#include "gba/audio.h"
20#include "gba/gba.h"
21#include "gba/serialize.h"
22#include "gba/sharkport.h"
23#include "gba/renderers/video-software.h"
24#include "gba/supervisor/config.h"
25#include "util/vfs.h"
26}
27
28using namespace QGBA;
29using namespace std;
30
31const int GameController::LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 };
32
33GameController::GameController(QObject* parent)
34 : QObject(parent)
35 , m_drawContext(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_inputController(nullptr)
52 , m_multiplayer(nullptr)
53 , m_stateSlot(1)
54{
55 m_renderer = new GBAVideoSoftwareRenderer;
56 GBAVideoSoftwareRendererCreate(m_renderer);
57 m_renderer->outputBuffer = (color_t*) m_drawContext;
58 m_renderer->outputBufferStride = 256;
59
60 GBACheatDeviceCreate(&m_cheatDevice);
61
62 m_threadContext.state = THREAD_INITIALIZED;
63 m_threadContext.debugger = 0;
64 m_threadContext.frameskip = 0;
65 m_threadContext.bios = 0;
66 m_threadContext.renderer = &m_renderer->d;
67 m_threadContext.userData = this;
68 m_threadContext.rewindBufferCapacity = 0;
69 m_threadContext.cheats = &m_cheatDevice;
70 m_threadContext.logLevel = GBA_LOG_ALL;
71
72 m_lux.p = this;
73 m_lux.sample = [](GBALuminanceSource* context) {
74 GameControllerLux* lux = static_cast<GameControllerLux*>(context);
75 lux->value = 0xFF - lux->p->m_luxValue;
76 };
77
78 m_lux.readLuminance = [](GBALuminanceSource* context) {
79 GameControllerLux* lux = static_cast<GameControllerLux*>(context);
80 return lux->value;
81 };
82 setLuminanceLevel(0);
83
84 m_threadContext.startCallback = [](GBAThread* context) {
85 GameController* controller = static_cast<GameController*>(context->userData);
86 controller->m_audioProcessor->setInput(context);
87 context->gba->luminanceSource = &controller->m_lux;
88 GBARTCGenericSourceInit(&controller->m_rtc, context->gba);
89 context->gba->rtcSource = &controller->m_rtc.d;
90 context->gba->rumble = controller->m_inputController->rumble();
91 context->gba->rotationSource = controller->m_inputController->rotationSource();
92 controller->m_fpsTarget = context->fpsTarget;
93 controller->gameStarted(context);
94 };
95
96 m_threadContext.cleanCallback = [](GBAThread* context) {
97 GameController* controller = static_cast<GameController*>(context->userData);
98 controller->gameStopped(context);
99 };
100
101 m_threadContext.frameCallback = [](GBAThread* context) {
102 GameController* controller = static_cast<GameController*>(context->userData);
103 if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) {
104 GBAThreadPauseFromThread(context);
105 controller->gamePaused(&controller->m_threadContext);
106 }
107 if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) {
108 controller->frameAvailable(controller->m_drawContext);
109 } else {
110 controller->frameAvailable(nullptr);
111 }
112 };
113
114 m_threadContext.logHandler = [](GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
115 static const char* stubMessage = "Stub software interrupt";
116 if (!context) {
117 return;
118 }
119 GameController* controller = static_cast<GameController*>(context->userData);
120 if (level == GBA_LOG_STUB && strncmp(stubMessage, format, strlen(stubMessage)) == 0) {
121 va_list argc;
122 va_copy(argc, args);
123 int immediate = va_arg(argc, int);
124 va_end(argc);
125 controller->unimplementedBiosCall(immediate);
126 }
127 if (level == GBA_LOG_FATAL) {
128 QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
129 } else if (!(controller->m_logLevels & level)) {
130 return;
131 }
132 QString message(QString().vsprintf(format, args));
133 if (level == GBA_LOG_STATUS) {
134 controller->statusPosted(message);
135 }
136 controller->postLog(level, message);
137 };
138
139 connect(&m_rewindTimer, &QTimer::timeout, [this]() {
140 GBARewind(&m_threadContext, 1);
141 emit frameAvailable(m_drawContext);
142 emit rewound(&m_threadContext);
143 });
144 m_rewindTimer.setInterval(100);
145
146 m_audioThread->setObjectName("Audio Thread");
147 m_audioThread->start(QThread::TimeCriticalPriority);
148 m_audioProcessor->moveToThread(m_audioThread);
149 connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start()));
150 connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause()));
151 connect(this, SIGNAL(gamePaused(GBAThread*)), m_audioProcessor, SLOT(pause()));
152 connect(this, SIGNAL(gameUnpaused(GBAThread*)), m_audioProcessor, SLOT(start()));
153 connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(pollEvents()));
154}
155
156GameController::~GameController() {
157 m_audioThread->quit();
158 m_audioThread->wait();
159 disconnect();
160 clearMultiplayerController();
161 closeGame();
162 GBACheatDeviceDestroy(&m_cheatDevice);
163 delete m_renderer;
164 delete[] m_drawContext;
165}
166
167void GameController::setMultiplayerController(MultiplayerController* controller) {
168 if (controller == m_multiplayer) {
169 return;
170 }
171 clearMultiplayerController();
172 m_multiplayer = controller;
173 controller->attachGame(this);
174}
175
176void GameController::clearMultiplayerController() {
177 if (!m_multiplayer) {
178 return;
179 }
180 m_multiplayer->detachGame(this);
181 m_multiplayer = nullptr;
182}
183
184void GameController::setOverride(const GBACartridgeOverride& override) {
185 m_threadContext.override = override;
186 m_threadContext.hasOverride = true;
187}
188
189void GameController::setOptions(const GBAOptions* opts) {
190 setFrameskip(opts->frameskip);
191 setAudioSync(opts->audioSync);
192 setVideoSync(opts->videoSync);
193 setSkipBIOS(opts->skipBios);
194 setUseBIOS(opts->useBios);
195 setRewind(opts->rewindEnable, opts->rewindBufferCapacity, opts->rewindBufferInterval);
196 setVolume(opts->volume);
197 setMute(opts->mute);
198
199 threadInterrupt();
200 m_threadContext.idleOptimization = opts->idleOptimization;
201 threadContinue();
202}
203
204#ifdef USE_GDB_STUB
205ARMDebugger* GameController::debugger() {
206 return m_threadContext.debugger;
207}
208
209void GameController::setDebugger(ARMDebugger* debugger) {
210 threadInterrupt();
211 if (m_threadContext.debugger && GBAThreadIsActive(&m_threadContext)) {
212 GBADetachDebugger(m_threadContext.gba);
213 }
214 m_threadContext.debugger = debugger;
215 if (m_threadContext.debugger && GBAThreadIsActive(&m_threadContext)) {
216 GBAAttachDebugger(m_threadContext.gba, m_threadContext.debugger);
217 }
218 threadContinue();
219}
220#endif
221
222void GameController::loadGame(const QString& path, bool dirmode) {
223 closeGame();
224 if (!dirmode) {
225 QFile file(path);
226 if (!file.open(QIODevice::ReadOnly)) {
227 postLog(GBA_LOG_ERROR, tr("Failed to open game file: %1").arg(path));
228 return;
229 }
230 file.close();
231 }
232
233 m_fname = path;
234 m_dirmode = dirmode;
235 openGame();
236}
237
238void GameController::bootBIOS() {
239 closeGame();
240 m_fname = QString();
241 m_dirmode = false;
242 openGame(true);
243}
244
245void GameController::openGame(bool biosOnly) {
246 if (biosOnly && (!m_useBios || m_bios.isNull())) {
247 return;
248 }
249
250 m_gameOpen = true;
251
252 m_pauseAfterFrame = false;
253
254 if (m_turbo) {
255 m_threadContext.sync.videoFrameWait = false;
256 m_threadContext.sync.audioWait = false;
257 } else {
258 m_threadContext.sync.videoFrameWait = m_videoSync;
259 m_threadContext.sync.audioWait = m_audioSync;
260 }
261
262 m_threadContext.gameDir = 0;
263 m_threadContext.bootBios = biosOnly;
264 if (biosOnly) {
265 m_threadContext.fname = nullptr;
266 } else {
267 m_threadContext.fname = strdup(m_fname.toUtf8().constData());
268 if (m_dirmode) {
269 m_threadContext.gameDir = VDirOpen(m_threadContext.fname);
270 m_threadContext.stateDir = m_threadContext.gameDir;
271 } else {
272 GBAThreadLoadROM(&m_threadContext, m_threadContext.fname);
273 }
274 }
275
276 if (!m_bios.isNull() && m_useBios) {
277 m_threadContext.bios = VFileDevice::open(m_bios, O_RDONLY);
278 } else {
279 m_threadContext.bios = nullptr;
280 }
281
282 if (!m_patch.isNull()) {
283 m_threadContext.patch = VFileDevice::open(m_patch, O_RDONLY);
284 }
285
286 m_inputController->recalibrateAxes();
287
288 if (!GBAThreadStart(&m_threadContext)) {
289 m_gameOpen = false;
290 emit gameFailed();
291 }
292}
293
294void GameController::loadBIOS(const QString& path) {
295 if (m_bios == path) {
296 return;
297 }
298 m_bios = path;
299 if (m_gameOpen) {
300 closeGame();
301 openGame();
302 }
303}
304
305void GameController::yankPak() {
306 if (!m_gameOpen) {
307 return;
308 }
309 threadInterrupt();
310 GBAYankROM(m_threadContext.gba);
311 threadContinue();
312}
313
314void GameController::replaceGame(const QString& path) {
315 if (!m_gameOpen) {
316 return;
317 }
318
319 m_fname = path;
320 threadInterrupt();
321 m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData());
322 GBAThreadReplaceROM(&m_threadContext, m_threadContext.fname);
323 threadContinue();
324}
325
326void GameController::loadPatch(const QString& path) {
327 if (m_gameOpen) {
328 closeGame();
329 m_patch = path;
330 openGame();
331 } else {
332 m_patch = path;
333 }
334}
335
336void GameController::importSharkport(const QString& path) {
337 if (!m_gameOpen) {
338 return;
339 }
340 VFile* vf = VFileDevice::open(path, O_RDONLY);
341 if (!vf) {
342 postLog(GBA_LOG_ERROR, tr("Failed to open snapshot file for reading: %1").arg(path));
343 return;
344 }
345 threadInterrupt();
346 GBASavedataImportSharkPort(m_threadContext.gba, vf, false);
347 threadContinue();
348 vf->close(vf);
349}
350
351void GameController::exportSharkport(const QString& path) {
352 if (!m_gameOpen) {
353 return;
354 }
355 VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
356 if (!vf) {
357 postLog(GBA_LOG_ERROR, tr("Failed to open snapshot file for writing: %1").arg(path));
358 return;
359 }
360 threadInterrupt();
361 GBASavedataExportSharkPort(m_threadContext.gba, vf);
362 threadContinue();
363 vf->close(vf);
364}
365
366void GameController::closeGame() {
367 if (!m_gameOpen) {
368 return;
369 }
370 m_rewindTimer.stop();
371 if (GBAThreadIsPaused(&m_threadContext)) {
372 GBAThreadUnpause(&m_threadContext);
373 }
374 GBAThreadEnd(&m_threadContext);
375 GBAThreadJoin(&m_threadContext);
376 if (m_threadContext.fname) {
377 free(const_cast<char*>(m_threadContext.fname));
378 m_threadContext.fname = nullptr;
379 }
380
381 m_patch = QString();
382
383 for (size_t i = 0; i < GBACheatSetsSize(&m_cheatDevice.cheats); ++i) {
384 GBACheatSet* set = *GBACheatSetsGetPointer(&m_cheatDevice.cheats, i);
385 GBACheatSetDeinit(set);
386 delete set;
387 }
388 GBACheatSetsClear(&m_cheatDevice.cheats);
389
390 m_gameOpen = false;
391 emit gameStopped(&m_threadContext);
392}
393
394void GameController::crashGame(const QString& crashMessage) {
395 closeGame();
396 emit gameCrashed(crashMessage);
397 emit gameStopped(&m_threadContext);
398}
399
400bool GameController::isPaused() {
401 if (!m_gameOpen) {
402 return false;
403 }
404 return GBAThreadIsPaused(&m_threadContext);
405}
406
407void GameController::setPaused(bool paused) {
408 if (!m_gameOpen || m_rewindTimer.isActive() || paused == GBAThreadIsPaused(&m_threadContext)) {
409 return;
410 }
411 if (paused) {
412 GBAThreadPause(&m_threadContext);
413 emit gamePaused(&m_threadContext);
414 } else {
415 GBAThreadUnpause(&m_threadContext);
416 emit gameUnpaused(&m_threadContext);
417 }
418}
419
420void GameController::reset() {
421 GBAThreadReset(&m_threadContext);
422}
423
424void GameController::threadInterrupt() {
425 if (m_gameOpen) {
426 GBAThreadInterrupt(&m_threadContext);
427 }
428}
429
430void GameController::threadContinue() {
431 if (m_gameOpen) {
432 GBAThreadContinue(&m_threadContext);
433 }
434}
435
436void GameController::frameAdvance() {
437 if (m_rewindTimer.isActive()) {
438 return;
439 }
440 if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) {
441 setPaused(false);
442 }
443}
444
445void GameController::setRewind(bool enable, int capacity, int interval) {
446 if (m_gameOpen) {
447 threadInterrupt();
448 GBARewindSettingsChanged(&m_threadContext, enable ? capacity : 0, enable ? interval : 0);
449 threadContinue();
450 } else {
451 if (enable) {
452 m_threadContext.rewindBufferInterval = interval;
453 m_threadContext.rewindBufferCapacity = capacity;
454 } else {
455 m_threadContext.rewindBufferInterval = 0;
456 m_threadContext.rewindBufferCapacity = 0;
457 }
458 }
459}
460
461void GameController::rewind(int states) {
462 threadInterrupt();
463 if (!states) {
464 GBARewindAll(&m_threadContext);
465 } else {
466 GBARewind(&m_threadContext, states);
467 }
468 threadContinue();
469 emit frameAvailable(m_drawContext);
470 emit rewound(&m_threadContext);
471}
472
473void GameController::startRewinding() {
474 if (!m_gameOpen || m_rewindTimer.isActive()) {
475 return;
476 }
477 m_wasPaused = isPaused();
478 setPaused(true);
479 m_rewindTimer.start();
480}
481
482void GameController::stopRewinding() {
483 if (!m_rewindTimer.isActive()) {
484 return;
485 }
486 m_rewindTimer.stop();
487 setPaused(m_wasPaused);
488}
489
490void GameController::keyPressed(int key) {
491 int mappedKey = 1 << key;
492 m_activeKeys |= mappedKey;
493 if (!m_inputController->allowOpposing()) {
494 if ((m_activeKeys & 0x30) == 0x30) {
495 m_inactiveKeys |= mappedKey ^ 0x30;
496 m_activeKeys ^= mappedKey ^ 0x30;
497 }
498 if ((m_activeKeys & 0xC0) == 0xC0) {
499 m_inactiveKeys |= mappedKey ^ 0xC0;
500 m_activeKeys ^= mappedKey ^ 0xC0;
501 }
502 }
503 updateKeys();
504}
505
506void GameController::keyReleased(int key) {
507 int mappedKey = 1 << key;
508 m_activeKeys &= ~mappedKey;
509 if (!m_inputController->allowOpposing()) {
510 if (mappedKey & 0x30) {
511 m_activeKeys |= m_inactiveKeys & (0x30 ^ mappedKey);
512 m_inactiveKeys &= ~0x30;
513 }
514 if (mappedKey & 0xC0) {
515 m_activeKeys |= m_inactiveKeys & (0xC0 ^ mappedKey);
516 m_inactiveKeys &= ~0xC0;
517 }
518 }
519 updateKeys();
520}
521
522void GameController::clearKeys() {
523 m_activeKeys = 0;
524 m_inactiveKeys = 0;
525 updateKeys();
526}
527
528void GameController::setAudioBufferSamples(int samples) {
529 threadInterrupt();
530 redoSamples(samples);
531 threadContinue();
532 QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples));
533}
534
535void GameController::setFPSTarget(float fps) {
536 threadInterrupt();
537 m_fpsTarget = fps;
538 m_threadContext.fpsTarget = fps;
539 if (m_turbo && m_turboSpeed > 0) {
540 m_threadContext.fpsTarget *= m_turboSpeed;
541 }
542 redoSamples(m_audioProcessor->getBufferSamples());
543 threadContinue();
544}
545
546void GameController::setSkipBIOS(bool set) {
547 threadInterrupt();
548 m_threadContext.skipBios = set;
549 threadContinue();
550}
551
552void GameController::setUseBIOS(bool use) {
553 threadInterrupt();
554 m_useBios = use;
555 threadContinue();
556}
557
558void GameController::loadState(int slot) {
559 if (slot > 0) {
560 m_stateSlot = slot;
561 }
562 GBARunOnThread(&m_threadContext, [](GBAThread* context) {
563 GameController* controller = static_cast<GameController*>(context->userData);
564 if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) {
565 controller->frameAvailable(controller->m_drawContext);
566 controller->stateLoaded(context);
567 }
568 });
569}
570
571void GameController::saveState(int slot) {
572 if (slot > 0) {
573 m_stateSlot = slot;
574 }
575 GBARunOnThread(&m_threadContext, [](GBAThread* context) {
576 GameController* controller = static_cast<GameController*>(context->userData);
577 GBASaveState(context, context->stateDir, controller->m_stateSlot, true);
578 });
579}
580
581void GameController::setVideoSync(bool set) {
582 m_videoSync = set;
583 if (!m_turbo) {
584 threadInterrupt();
585 m_threadContext.sync.videoFrameWait = set;
586 threadContinue();
587 }
588}
589
590void GameController::setAudioSync(bool set) {
591 m_audioSync = set;
592 if (!m_turbo) {
593 threadInterrupt();
594 m_threadContext.sync.audioWait = set;
595 threadContinue();
596 }
597}
598
599void GameController::setFrameskip(int skip) {
600 m_threadContext.frameskip = skip;
601}
602
603void GameController::setVolume(int volume) {
604 threadInterrupt();
605 m_threadContext.volume = volume;
606 if (m_gameOpen) {
607 m_threadContext.gba->audio.masterVolume = volume;
608 }
609 threadContinue();
610}
611
612void GameController::setMute(bool mute) {
613 threadInterrupt();
614 m_threadContext.mute = mute;
615 if (m_gameOpen) {
616 m_threadContext.gba->audio.masterVolume = mute ? 0 : m_threadContext.volume;
617 }
618 threadContinue();
619}
620
621void GameController::setTurbo(bool set, bool forced) {
622 if (m_turboForced && !forced) {
623 return;
624 }
625 if (m_turbo == set && m_turboForced == forced) {
626 // Don't interrupt the thread if we don't need to
627 return;
628 }
629 m_turbo = set;
630 m_turboForced = set && forced;
631 enableTurbo();
632}
633
634void GameController::setTurboSpeed(float ratio) {
635 m_turboSpeed = ratio;
636 enableTurbo();
637}
638
639void GameController::enableTurbo() {
640 threadInterrupt();
641 if (!m_turbo) {
642 m_threadContext.fpsTarget = m_fpsTarget;
643 m_threadContext.sync.audioWait = m_audioSync;
644 m_threadContext.sync.videoFrameWait = m_videoSync;
645 } else if (m_turboSpeed <= 0) {
646 m_threadContext.fpsTarget = m_fpsTarget;
647 m_threadContext.sync.audioWait = false;
648 m_threadContext.sync.videoFrameWait = false;
649 } else {
650 m_threadContext.fpsTarget = m_fpsTarget * m_turboSpeed;
651 m_threadContext.sync.audioWait = true;
652 m_threadContext.sync.videoFrameWait = false;
653 }
654 redoSamples(m_audioProcessor->getBufferSamples());
655 threadContinue();
656}
657
658void GameController::setAVStream(GBAAVStream* stream) {
659 threadInterrupt();
660 m_threadContext.stream = stream;
661 if (m_gameOpen) {
662 m_threadContext.gba->stream = stream;
663 }
664 threadContinue();
665}
666
667void GameController::clearAVStream() {
668 threadInterrupt();
669 m_threadContext.stream = nullptr;
670 if (m_gameOpen) {
671 m_threadContext.gba->stream = nullptr;
672 }
673 threadContinue();
674}
675
676#ifdef USE_PNG
677void GameController::screenshot() {
678 GBARunOnThread(&m_threadContext, GBAThreadTakeScreenshot);
679}
680#endif
681
682void GameController::reloadAudioDriver() {
683 QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection);
684 int samples = m_audioProcessor->getBufferSamples();
685 delete m_audioProcessor;
686 m_audioProcessor = AudioProcessor::create();
687 m_audioProcessor->setBufferSamples(samples);
688 m_audioProcessor->moveToThread(m_audioThread);
689 connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start()));
690 connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause()));
691 connect(this, SIGNAL(gamePaused(GBAThread*)), m_audioProcessor, SLOT(pause()));
692 connect(this, SIGNAL(gameUnpaused(GBAThread*)), m_audioProcessor, SLOT(start()));
693 if (isLoaded()) {
694 m_audioProcessor->setInput(&m_threadContext);
695 QMetaObject::invokeMethod(m_audioProcessor, "start");
696 }
697}
698
699void GameController::setLuminanceValue(uint8_t value) {
700 m_luxValue = value;
701 value = std::max<int>(value - 0x16, 0);
702 m_luxLevel = 10;
703 for (int i = 0; i < 10; ++i) {
704 if (value < LUX_LEVELS[i]) {
705 m_luxLevel = i;
706 break;
707 }
708 }
709 emit luminanceValueChanged(m_luxValue);
710}
711
712void GameController::setLuminanceLevel(int level) {
713 int value = 0x16;
714 level = std::max(0, std::min(10, level));
715 if (level > 0) {
716 value += LUX_LEVELS[level - 1];
717 }
718 setLuminanceValue(value);
719}
720
721void GameController::setRealTime() {
722 m_rtc.override = GBARTCGenericSource::RTC_NO_OVERRIDE;
723}
724
725void GameController::setFixedTime(const QDateTime& time) {
726 m_rtc.override = GBARTCGenericSource::RTC_FIXED;
727 m_rtc.value = time.toMSecsSinceEpoch() / 1000;
728}
729
730void GameController::setFakeEpoch(const QDateTime& time) {
731 m_rtc.override = GBARTCGenericSource::RTC_FAKE_EPOCH;
732 m_rtc.value = time.toMSecsSinceEpoch() / 1000;
733}
734
735void GameController::updateKeys() {
736 int activeKeys = m_activeKeys;
737 activeKeys |= m_activeButtons;
738 activeKeys &= ~m_inactiveKeys;
739 m_threadContext.activeKeys = activeKeys;
740}
741
742void GameController::redoSamples(int samples) {
743#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF
744 float sampleRate = 0x8000;
745 float ratio;
746 if (m_threadContext.gba) {
747 sampleRate = m_threadContext.gba->audio.sampleRate;
748 }
749 ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, 44100);
750 m_threadContext.audioBuffers = ceil(samples / ratio);
751#else
752 m_threadContext.audioBuffers = samples;
753#endif
754 if (m_threadContext.gba) {
755 GBAAudioResizeBuffer(&m_threadContext.gba->audio, m_threadContext.audioBuffers);
756 }
757 QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged");
758}
759
760void GameController::setLogLevel(int levels) {
761 threadInterrupt();
762 m_logLevels = levels;
763 threadContinue();
764}
765
766void GameController::enableLogLevel(int levels) {
767 threadInterrupt();
768 m_logLevels |= levels;
769 threadContinue();
770}
771
772void GameController::disableLogLevel(int levels) {
773 threadInterrupt();
774 m_logLevels &= ~levels;
775 threadContinue();
776}
777
778void GameController::pollEvents() {
779 if (!m_inputController) {
780 return;
781 }
782
783 m_activeButtons = m_inputController->pollEvents();
784 updateKeys();
785}