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