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