src/platform/qt/CoreController.cpp (view raw)
1/* Copyright (c) 2013-2017 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 "CoreController.h"
7
8#include "ConfigController.h"
9#include "InputController.h"
10#include "LogController.h"
11#include "MultiplayerController.h"
12#include "Override.h"
13
14#include <QDateTime>
15#include <QMutexLocker>
16
17#include <mgba/core/serialize.h>
18#include <mgba/feature/video-logger.h>
19#ifdef M_CORE_GBA
20#include <mgba/internal/gba/gba.h>
21#include <mgba/internal/gba/renderers/cache-set.h>
22#include <mgba/internal/gba/sharkport.h>
23#endif
24#ifdef M_CORE_GB
25#include <mgba/internal/gb/gb.h>
26#include <mgba/internal/gb/renderers/cache-set.h>
27#endif
28#include "feature/sqlite3/no-intro.h"
29#include <mgba-util/math.h>
30#include <mgba-util/vfs.h>
31
32#define AUTOSAVE_GRANULARITY 600
33
34using namespace QGBA;
35
36CoreController::CoreController(mCore* core, QObject* parent)
37 : QObject(parent)
38 , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC)
39 , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC)
40{
41 m_threadContext.core = core;
42 m_threadContext.userData = this;
43 updateROMInfo();
44
45#ifdef M_CORE_GBA
46 GBASIODolphinCreate(&m_dolphin);
47#endif
48
49 m_resetActions.append([this]() {
50 if (m_autoload) {
51 mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags);
52 }
53 });
54
55 m_threadContext.startCallback = [](mCoreThread* context) {
56 CoreController* controller = static_cast<CoreController*>(context->userData);
57
58 switch (context->core->platform(context->core)) {
59#ifdef M_CORE_GBA
60 case mPLATFORM_GBA:
61 context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, controller->m_inputController->luminance());
62 break;
63#endif
64 default:
65 break;
66 }
67
68 controller->updateFastForward();
69
70 if (controller->m_multiplayer) {
71 controller->m_multiplayer->attachGame(controller);
72 }
73
74 QMetaObject::invokeMethod(controller, "started");
75 };
76
77 m_threadContext.resetCallback = [](mCoreThread* context) {
78 CoreController* controller = static_cast<CoreController*>(context->userData);
79 for (auto action : controller->m_resetActions) {
80 action();
81 }
82
83 if (controller->m_override) {
84 controller->m_override->identify(context->core);
85 controller->m_override->apply(context->core);
86 }
87
88 controller->m_resetActions.clear();
89
90 if (!controller->m_hwaccel) {
91 context->core->setVideoBuffer(context->core, reinterpret_cast<color_t*>(controller->m_activeBuffer.data()), controller->screenDimensions().width());
92 }
93
94 QMetaObject::invokeMethod(controller, "didReset");
95 controller->finishFrame();
96 };
97
98 m_threadContext.frameCallback = [](mCoreThread* context) {
99 CoreController* controller = static_cast<CoreController*>(context->userData);
100
101 if (controller->m_autosaveCounter == AUTOSAVE_GRANULARITY) {
102 if (controller->m_autosave) {
103 mCoreSaveState(context->core, 0, controller->m_saveStateFlags);
104 }
105 controller->m_autosaveCounter = 0;
106 }
107 ++controller->m_autosaveCounter;
108
109 controller->finishFrame();
110 };
111
112 m_threadContext.cleanCallback = [](mCoreThread* context) {
113 CoreController* controller = static_cast<CoreController*>(context->userData);
114
115 if (controller->m_autosave) {
116 mCoreSaveState(context->core, 0, controller->m_saveStateFlags);
117 }
118
119 controller->clearMultiplayerController();
120#ifdef M_CORE_GBA
121 controller->detachDolphin();
122#endif
123 QMetaObject::invokeMethod(controller, "stopping");
124 };
125
126 m_threadContext.pauseCallback = [](mCoreThread* context) {
127 CoreController* controller = static_cast<CoreController*>(context->userData);
128
129 QMetaObject::invokeMethod(controller, "paused");
130 };
131
132 m_threadContext.unpauseCallback = [](mCoreThread* context) {
133 CoreController* controller = static_cast<CoreController*>(context->userData);
134
135 QMetaObject::invokeMethod(controller, "unpaused");
136 };
137
138 m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
139 mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger);
140 mCoreThread* context = logContext->p;
141
142 static const char* savestateMessage = "State %i saved";
143 static const char* loadstateMessage = "State %i loaded";
144 static const char* savestateFailedMessage = "State %i failed to load";
145 static int biosCat = -1;
146 static int statusCat = -1;
147 if (!context) {
148 return;
149 }
150 CoreController* controller = static_cast<CoreController*>(context->userData);
151 QString message;
152 if (biosCat < 0) {
153 biosCat = mLogCategoryById("gba.bios");
154 }
155 if (statusCat < 0) {
156 statusCat = mLogCategoryById("core.status");
157 }
158#ifdef M_CORE_GBA
159 if (level == mLOG_STUB && category == biosCat) {
160 va_list argc;
161 va_copy(argc, args);
162 int immediate = va_arg(argc, int);
163 va_end(argc);
164 QMetaObject::invokeMethod(controller, "unimplementedBiosCall", Q_ARG(int, immediate));
165 } else
166#endif
167 if (category == statusCat) {
168 // Slot 0 is reserved for suspend points
169 if (strncmp(loadstateMessage, format, strlen(loadstateMessage)) == 0) {
170 va_list argc;
171 va_copy(argc, args);
172 int slot = va_arg(argc, int);
173 va_end(argc);
174 if (slot == 0) {
175 format = "Loaded suspend state";
176 }
177 } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0 || strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) {
178 va_list argc;
179 va_copy(argc, args);
180 int slot = va_arg(argc, int);
181 va_end(argc);
182 if (slot == 0) {
183 return;
184 }
185 }
186 message = QString().vsprintf(format, args);
187 QMetaObject::invokeMethod(controller, "statusPosted", Q_ARG(const QString&, message));
188 }
189 message = QString().vsprintf(format, args);
190 QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message));
191 if (level == mLOG_FATAL) {
192 QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, QString().vsprintf(format, args)));
193 }
194 };
195}
196
197CoreController::~CoreController() {
198 endVideoLog();
199 stop();
200 disconnect();
201
202 mCoreThreadJoin(&m_threadContext);
203
204 if (m_cacheSet) {
205 mCacheSetDeinit(m_cacheSet.get());
206 m_cacheSet.reset();
207 }
208
209 mCoreConfigDeinit(&m_threadContext.core->config);
210 m_threadContext.core->deinit(m_threadContext.core);
211}
212
213const color_t* CoreController::drawContext() {
214 if (m_hwaccel) {
215 return nullptr;
216 }
217 QMutexLocker locker(&m_bufferMutex);
218 return reinterpret_cast<const color_t*>(m_completeBuffer.constData());
219}
220
221QImage CoreController::getPixels() {
222 QByteArray buffer;
223 QSize size = screenDimensions();
224 size_t stride = size.width() * BYTES_PER_PIXEL;
225
226 if (!m_hwaccel) {
227 buffer = m_completeBuffer;
228 } else {
229 Interrupter interrupter(this);
230 const void* pixels;
231 m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride);
232 stride *= BYTES_PER_PIXEL;
233 buffer = QByteArray::fromRawData(static_cast<const char*>(pixels), stride * size.height());
234 }
235
236 QImage image(reinterpret_cast<const uchar*>(buffer.constData()),
237 size.width(), size.height(), stride, QImage::Format_RGBX8888);
238 image.bits(); // Cause QImage to detach
239 return image;
240}
241
242bool CoreController::isPaused() {
243 return mCoreThreadIsPaused(&m_threadContext);
244}
245
246bool CoreController::hasStarted() {
247 return mCoreThreadHasStarted(&m_threadContext);
248}
249
250mPlatform CoreController::platform() const {
251 return m_threadContext.core->platform(m_threadContext.core);
252}
253
254QSize CoreController::screenDimensions() const {
255 unsigned width, height;
256 m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
257
258 return QSize(width, height);
259}
260
261void CoreController::loadConfig(ConfigController* config) {
262 Interrupter interrupter(this);
263 m_loadStateFlags = config->getOption("loadStateExtdata", m_loadStateFlags).toInt();
264 m_saveStateFlags = config->getOption("saveStateExtdata", m_saveStateFlags).toInt();
265 m_fastForwardRatio = config->getOption("fastForwardRatio", m_fastForwardRatio).toFloat();
266 m_fastForwardHeldRatio = config->getOption("fastForwardHeldRatio", m_fastForwardRatio).toFloat();
267 m_videoSync = config->getOption("videoSync", m_videoSync).toInt();
268 m_audioSync = config->getOption("audioSync", m_audioSync).toInt();
269 m_fpsTarget = config->getOption("fpsTarget").toFloat();
270 m_autosave = config->getOption("autosave", false).toInt();
271 m_autoload = config->getOption("autoload", true).toInt();
272 m_autofireThreshold = config->getOption("autofireThreshold", m_autofireThreshold).toInt();
273 m_fastForwardVolume = config->getOption("fastForwardVolume", -1).toInt();
274 m_fastForwardMute = config->getOption("fastForwardMute", -1).toInt();
275 mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume");
276 mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
277
278 QSize sizeBefore = screenDimensions();
279 m_activeBuffer.resize(256 * 224 * sizeof(color_t));
280 m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeBefore.width());
281
282 mCoreLoadForeignConfig(m_threadContext.core, config->config());
283
284 QSize sizeAfter = screenDimensions();
285 m_activeBuffer.resize(sizeAfter.width() * sizeAfter.height() * sizeof(color_t));
286 m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeAfter.width());
287
288 if (hasStarted()) {
289 updateFastForward();
290 mCoreThreadRewindParamsChanged(&m_threadContext);
291 }
292 if (sizeBefore != sizeAfter) {
293#ifdef M_CORE_GB
294 mCoreConfigSetIntValue(&m_threadContext.core->config, "sgb.borders", 0);
295 m_threadContext.core->reloadConfigOption(m_threadContext.core, "sgb.borders", nullptr);
296 mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "sgb.borders");
297 m_threadContext.core->reloadConfigOption(m_threadContext.core, "sgb.borders", nullptr);
298#endif
299 }
300}
301
302#ifdef USE_DEBUGGERS
303void CoreController::setDebugger(mDebugger* debugger) {
304 Interrupter interrupter(this);
305 if (debugger) {
306 mDebuggerAttach(debugger, m_threadContext.core);
307 mDebuggerEnter(debugger, DEBUGGER_ENTER_ATTACHED, 0);
308 } else {
309 m_threadContext.core->detachDebugger(m_threadContext.core);
310 }
311}
312#endif
313
314void CoreController::setMultiplayerController(MultiplayerController* controller) {
315 if (controller == m_multiplayer) {
316 return;
317 }
318 clearMultiplayerController();
319 m_multiplayer = controller;
320 if (!mCoreThreadHasStarted(&m_threadContext)) {
321 return;
322 }
323 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
324 CoreController* controller = static_cast<CoreController*>(thread->userData);
325 controller->m_multiplayer->attachGame(controller);
326 });
327}
328
329void CoreController::clearMultiplayerController() {
330 if (!m_multiplayer) {
331 return;
332 }
333 m_multiplayer->detachGame(this);
334 m_multiplayer = nullptr;
335}
336
337mCacheSet* CoreController::graphicCaches() {
338 if (m_cacheSet) {
339 return m_cacheSet.get();
340 }
341 Interrupter interrupter(this);
342 switch (platform()) {
343#ifdef M_CORE_GBA
344 case mPLATFORM_GBA: {
345 GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
346 m_cacheSet = std::make_unique<mCacheSet>();
347 GBAVideoCacheInit(m_cacheSet.get());
348 GBAVideoCacheAssociate(m_cacheSet.get(), &gba->video);
349 break;
350 }
351#endif
352#ifdef M_CORE_GB
353 case mPLATFORM_GB: {
354 GB* gb = static_cast<GB*>(m_threadContext.core->board);
355 m_cacheSet = std::make_unique<mCacheSet>();
356 GBVideoCacheInit(m_cacheSet.get());
357 GBVideoCacheAssociate(m_cacheSet.get(), &gb->video);
358 break;
359 }
360#endif
361 default:
362 return nullptr;
363 }
364 return m_cacheSet.get();
365}
366
367#ifdef M_CORE_GBA
368bool CoreController::attachDolphin(const Address& address) {
369 if (platform() != mPLATFORM_GBA) {
370 return false;
371 }
372 if (GBASIODolphinConnect(&m_dolphin, &address, 0, 0)) {
373 GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
374 GBASIOSetDriver(&gba->sio, &m_dolphin.d, SIO_JOYBUS);
375 return true;
376 }
377 return false;
378}
379
380void CoreController::detachDolphin() {
381 if (platform() == mPLATFORM_GBA) {
382 GBA* gba = static_cast<GBA*>(m_threadContext.core->board);
383 GBASIOSetDriver(&gba->sio, nullptr, SIO_JOYBUS);
384 }
385 GBASIODolphinDestroy(&m_dolphin);
386}
387#endif
388
389void CoreController::setOverride(std::unique_ptr<Override> override) {
390 Interrupter interrupter(this);
391 m_override = std::move(override);
392 m_override->identify(m_threadContext.core);
393}
394
395void CoreController::setInputController(InputController* inputController) {
396 m_inputController = inputController;
397 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_ROTATION, m_inputController->rotationSource());
398 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_RUMBLE, m_inputController->rumble());
399 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_IMAGE_SOURCE, m_inputController->imageSource());
400}
401
402void CoreController::setLogger(LogController* logger) {
403 disconnect(m_log);
404 m_log = logger;
405 m_threadContext.logger.d.filter = logger->filter();
406 connect(this, &CoreController::logPosted, m_log, &LogController::postLog);
407}
408
409void CoreController::start() {
410 QSize size(screenDimensions());
411 m_activeBuffer.resize(size.width() * size.height() * sizeof(color_t));
412 m_activeBuffer.fill(0xFF);
413 m_completeBuffer = m_activeBuffer;
414
415 m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), size.width());
416
417 if (!m_patched) {
418 mCoreAutoloadPatch(m_threadContext.core);
419 }
420 if (!mCoreThreadStart(&m_threadContext)) {
421 emit failed();
422 emit stopping();
423 }
424}
425
426void CoreController::stop() {
427 setSync(false);
428#ifdef USE_DEBUGGERS
429 setDebugger(nullptr);
430#endif
431 setPaused(false);
432 mCoreThreadEnd(&m_threadContext);
433}
434
435void CoreController::reset() {
436 mCoreThreadReset(&m_threadContext);
437}
438
439void CoreController::setPaused(bool paused) {
440 QMutexLocker locker(&m_actionMutex);
441 if (paused) {
442 if (m_moreFrames < 0) {
443 m_moreFrames = 1;
444 }
445 } else {
446 m_moreFrames = -1;
447 if (isPaused()) {
448 mCoreThreadUnpause(&m_threadContext);
449 }
450 }
451}
452
453void CoreController::frameAdvance() {
454 QMutexLocker locker(&m_actionMutex);
455 m_moreFrames = 1;
456 if (isPaused()) {
457 mCoreThreadUnpause(&m_threadContext);
458 }
459}
460
461void CoreController::addFrameAction(std::function<void ()> action) {
462 QMutexLocker locker(&m_actionMutex);
463 m_frameActions.append(action);
464}
465
466void CoreController::setSync(bool sync) {
467 if (sync) {
468 m_threadContext.impl->sync.audioWait = m_audioSync;
469 m_threadContext.impl->sync.videoFrameWait = m_videoSync;
470 } else {
471 m_threadContext.impl->sync.audioWait = false;
472 m_threadContext.impl->sync.videoFrameWait = false;
473 }
474}
475
476void CoreController::setRewinding(bool rewind) {
477 if (!m_threadContext.core->opts.rewindEnable) {
478 return;
479 }
480 if (rewind && m_multiplayer && m_multiplayer->attached() > 1) {
481 return;
482 }
483
484 if (rewind && isPaused()) {
485 setPaused(false);
486 // TODO: restore autopausing
487 }
488 mCoreThreadSetRewinding(&m_threadContext, rewind);
489}
490
491void CoreController::rewind(int states) {
492 {
493 Interrupter interrupter(this);
494 if (!states) {
495 states = INT_MAX;
496 }
497 for (int i = 0; i < states; ++i) {
498 if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) {
499 break;
500 }
501 }
502 }
503 emit frameAvailable();
504 emit rewound();
505}
506
507void CoreController::setFastForward(bool enable) {
508 if (m_fastForward == enable) {
509 return;
510 }
511 m_fastForward = enable;
512 updateFastForward();
513 emit fastForwardChanged(enable);
514}
515
516void CoreController::forceFastForward(bool enable) {
517 if (m_fastForwardForced == enable) {
518 return;
519 }
520 m_fastForwardForced = enable;
521 updateFastForward();
522 emit fastForwardChanged(enable || m_fastForward);
523}
524
525void CoreController::loadState(int slot) {
526 if (slot > 0 && slot != m_stateSlot) {
527 m_stateSlot = slot;
528 m_backupSaveState.clear();
529 }
530 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
531 CoreController* controller = static_cast<CoreController*>(context->userData);
532 if (!controller->m_backupLoadState.isOpen()) {
533 controller->m_backupLoadState = VFileDevice::openMemory();
534 }
535 mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
536 if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
537 emit controller->frameAvailable();
538 emit controller->stateLoaded();
539 }
540 });
541}
542
543void CoreController::loadState(const QString& path, int flags) {
544 m_statePath = path;
545 int savedFlags = m_loadStateFlags;
546 if (flags != -1) {
547 m_loadStateFlags = flags;
548 }
549 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
550 CoreController* controller = static_cast<CoreController*>(context->userData);
551 VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
552 if (!vf) {
553 return;
554 }
555 if (!controller->m_backupLoadState.isOpen()) {
556 controller->m_backupLoadState = VFileDevice::openMemory();
557 }
558 mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
559 if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
560 emit controller->frameAvailable();
561 emit controller->stateLoaded();
562 }
563 vf->close(vf);
564 });
565 m_loadStateFlags = savedFlags;
566}
567
568void CoreController::loadState(QIODevice* iodev, int flags) {
569 m_stateVf = VFileDevice::wrap(iodev, QIODevice::ReadOnly);
570 if (!m_stateVf) {
571 return;
572 }
573 int savedFlags = m_loadStateFlags;
574 if (flags != -1) {
575 m_loadStateFlags = flags;
576 }
577 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
578 CoreController* controller = static_cast<CoreController*>(context->userData);
579 VFile* vf = controller->m_stateVf;
580 if (!vf) {
581 return;
582 }
583 if (!controller->m_backupLoadState.isOpen()) {
584 controller->m_backupLoadState = VFileDevice::openMemory();
585 }
586 mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
587 if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
588 emit controller->frameAvailable();
589 emit controller->stateLoaded();
590 }
591 vf->close(vf);
592 });
593 m_loadStateFlags = savedFlags;
594}
595
596void CoreController::saveState(int slot) {
597 if (slot > 0) {
598 m_stateSlot = slot;
599 }
600 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
601 CoreController* controller = static_cast<CoreController*>(context->userData);
602 VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, false);
603 if (vf) {
604 controller->m_backupSaveState.resize(vf->size(vf));
605 vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
606 vf->close(vf);
607 }
608 mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags);
609 });
610}
611
612void CoreController::saveState(const QString& path, int flags) {
613 m_statePath = path;
614 int savedFlags = m_saveStateFlags;
615 if (flags != -1) {
616 m_saveStateFlags = flags;
617 }
618 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
619 CoreController* controller = static_cast<CoreController*>(context->userData);
620 VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
621 if (vf) {
622 controller->m_backupSaveState.resize(vf->size(vf));
623 vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
624 vf->close(vf);
625 }
626 vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC);
627 if (!vf) {
628 return;
629 }
630 mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
631 vf->close(vf);
632 });
633 m_saveStateFlags = savedFlags;
634}
635
636void CoreController::saveState(QIODevice* iodev, int flags) {
637 m_stateVf = VFileDevice::wrap(iodev, QIODevice::WriteOnly | QIODevice::Truncate);
638 if (!m_stateVf) {
639 return;
640 }
641 int savedFlags = m_saveStateFlags;
642 if (flags != -1) {
643 m_saveStateFlags = flags;
644 }
645 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
646 CoreController* controller = static_cast<CoreController*>(context->userData);
647 VFile* vf = controller->m_stateVf;
648 if (!vf) {
649 return;
650 }
651 mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
652 vf->close(vf);
653 });
654 m_saveStateFlags = savedFlags;
655}
656
657void CoreController::loadBackupState() {
658 if (!m_backupLoadState.isOpen()) {
659 return;
660 }
661
662 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
663 CoreController* controller = static_cast<CoreController*>(context->userData);
664 controller->m_backupLoadState.seek(0);
665 if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) {
666 mLOG(STATUS, INFO, "Undid state load");
667 controller->frameAvailable();
668 controller->stateLoaded();
669 }
670 controller->m_backupLoadState.close();
671 });
672}
673
674void CoreController::saveBackupState() {
675 if (m_backupSaveState.isEmpty()) {
676 return;
677 }
678
679 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
680 CoreController* controller = static_cast<CoreController*>(context->userData);
681 VFile* vf = mCoreGetState(context->core, controller->m_stateSlot, true);
682 if (vf) {
683 vf->write(vf, controller->m_backupSaveState.constData(), controller->m_backupSaveState.size());
684 vf->close(vf);
685 mLOG(STATUS, INFO, "Undid state save");
686 }
687 controller->m_backupSaveState.clear();
688 });
689}
690
691void CoreController::loadSave(const QString& path, bool temporary) {
692 m_resetActions.append([this, path, temporary]() {
693 VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR);
694 if (!vf) {
695 LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path);
696 return;
697 }
698
699 if (temporary) {
700 m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
701 } else {
702 m_threadContext.core->loadSave(m_threadContext.core, vf);
703 }
704 });
705 reset();
706}
707
708void CoreController::loadPatch(const QString& patchPath) {
709 Interrupter interrupter(this);
710 VFile* patch = VFileDevice::open(patchPath, O_RDONLY);
711 if (patch) {
712 m_threadContext.core->loadPatch(m_threadContext.core, patch);
713 m_patched = true;
714 patch->close(patch);
715 updateROMInfo();
716 }
717 if (mCoreThreadHasStarted(&m_threadContext)) {
718 interrupter.resume();
719 reset();
720 }
721}
722
723void CoreController::replaceGame(const QString& path) {
724 QFileInfo info(path);
725 if (!info.isReadable()) {
726 LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
727 return;
728 }
729 QString fname = info.canonicalFilePath();
730 Interrupter interrupter(this);
731 mDirectorySetDetachBase(&m_threadContext.core->dirs);
732 mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData());
733 updateROMInfo();
734}
735
736void CoreController::yankPak() {
737 Interrupter interrupter(this);
738
739 switch (platform()) {
740#ifdef M_CORE_GBA
741 case mPLATFORM_GBA:
742 GBAYankROM(static_cast<GBA*>(m_threadContext.core->board));
743 break;
744#endif
745#ifdef M_CORE_GB
746 case mPLATFORM_GB:
747 GBYankROM(static_cast<GB*>(m_threadContext.core->board));
748 break;
749#endif
750 case mPLATFORM_NONE:
751 LOG(QT, ERROR) << tr("Can't yank pack in unexpected platform!");
752 break;
753 }
754}
755
756void CoreController::addKey(int key) {
757 m_activeKeys |= 1 << key;
758}
759
760void CoreController::clearKey(int key) {
761 m_activeKeys &= ~(1 << key);
762}
763
764void CoreController::setAutofire(int key, bool enable) {
765 if (key >= 32 || key < 0) {
766 return;
767 }
768
769 m_autofire[key] = enable;
770 m_autofireStatus[key] = 0;
771}
772
773#ifdef USE_PNG
774void CoreController::screenshot() {
775 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
776 mCoreTakeScreenshot(context->core);
777 });
778}
779#endif
780
781void CoreController::setRealTime() {
782 m_threadContext.core->rtc.override = RTC_NO_OVERRIDE;
783}
784
785void CoreController::setFixedTime(const QDateTime& time) {
786 m_threadContext.core->rtc.override = RTC_FIXED;
787 m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
788}
789
790void CoreController::setFakeEpoch(const QDateTime& time) {
791 m_threadContext.core->rtc.override = RTC_FAKE_EPOCH;
792 m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
793}
794
795void CoreController::scanCard(const QString& path) {
796#ifdef M_CORE_GBA
797 QImage image(path);
798 if (image.isNull()) {
799 QFile file(path);
800 file.open(QIODevice::ReadOnly);
801 m_eReaderData = file.read(2912);
802 } else if (image.size() == QSize(989, 44)) {
803 const uchar* bits = image.constBits();
804 size_t size;
805#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
806 size = image.sizeInBytes();
807#else
808 size = image.byteCount();
809#endif
810 m_eReaderData.setRawData(reinterpret_cast<const char*>(bits), size);
811 }
812
813 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* thread) {
814 CoreController* controller = static_cast<CoreController*>(thread->userData);
815 GBAEReaderQueueCard(static_cast<GBA*>(thread->core->board), controller->m_eReaderData.constData(), controller->m_eReaderData.size());
816 });
817#endif
818}
819
820
821void CoreController::importSharkport(const QString& path) {
822#ifdef M_CORE_GBA
823 if (platform() != mPLATFORM_GBA) {
824 return;
825 }
826 VFile* vf = VFileDevice::open(path, O_RDONLY);
827 if (!vf) {
828 LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
829 return;
830 }
831 Interrupter interrupter(this);
832 GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
833 vf->close(vf);
834#endif
835}
836
837void CoreController::exportSharkport(const QString& path) {
838#ifdef M_CORE_GBA
839 if (platform() != mPLATFORM_GBA) {
840 return;
841 }
842 VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
843 if (!vf) {
844 LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
845 return;
846 }
847 Interrupter interrupter(this);
848 GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf);
849 vf->close(vf);
850#endif
851}
852
853#ifdef M_CORE_GB
854void CoreController::attachPrinter() {
855 if (platform() != mPLATFORM_GB) {
856 return;
857 }
858 GB* gb = static_cast<GB*>(m_threadContext.core->board);
859 clearMultiplayerController();
860 GBPrinterCreate(&m_printer.d);
861 m_printer.parent = this;
862 m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) {
863 QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
864 QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
865 QVector<QRgb> colors;
866 colors.append(qRgb(0xF8, 0xF8, 0xF8));
867 colors.append(qRgb(0xA8, 0xA8, 0xA8));
868 colors.append(qRgb(0x50, 0x50, 0x50));
869 colors.append(qRgb(0x00, 0x00, 0x00));
870 image.setColorTable(colors);
871 for (int y = 0; y < height; ++y) {
872 for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
873 uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4];
874 image.setPixel(x + 0, y, (byte & 0xC0) >> 6);
875 image.setPixel(x + 1, y, (byte & 0x30) >> 4);
876 image.setPixel(x + 2, y, (byte & 0x0C) >> 2);
877 image.setPixel(x + 3, y, (byte & 0x03) >> 0);
878 }
879 }
880 QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
881 };
882 Interrupter interrupter(this);
883 GBSIOSetDriver(&gb->sio, &m_printer.d.d);
884}
885
886void CoreController::detachPrinter() {
887 if (platform() != mPLATFORM_GB) {
888 return;
889 }
890 Interrupter interrupter(this);
891 GB* gb = static_cast<GB*>(m_threadContext.core->board);
892 GBPrinterDonePrinting(&m_printer.d);
893 GBSIOSetDriver(&gb->sio, nullptr);
894}
895
896void CoreController::endPrint() {
897 if (platform() != mPLATFORM_GB) {
898 return;
899 }
900 Interrupter interrupter(this);
901 GBPrinterDonePrinting(&m_printer.d);
902}
903#endif
904
905#ifdef M_CORE_GBA
906void CoreController::attachBattleChipGate() {
907 if (platform() != mPLATFORM_GBA) {
908 return;
909 }
910 Interrupter interrupter(this);
911 clearMultiplayerController();
912 GBASIOBattlechipGateCreate(&m_battlechip);
913 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, &m_battlechip);
914}
915
916void CoreController::detachBattleChipGate() {
917 if (platform() != mPLATFORM_GBA) {
918 return;
919 }
920 Interrupter interrupter(this);
921 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, nullptr);
922}
923
924void CoreController::setBattleChipId(uint16_t id) {
925 if (platform() != mPLATFORM_GBA) {
926 return;
927 }
928 Interrupter interrupter(this);
929 m_battlechip.chipId = id;
930}
931
932void CoreController::setBattleChipFlavor(int flavor) {
933 if (platform() != mPLATFORM_GBA) {
934 return;
935 }
936 Interrupter interrupter(this);
937 m_battlechip.flavor = flavor;
938}
939#endif
940
941void CoreController::setAVStream(mAVStream* stream) {
942 Interrupter interrupter(this);
943 m_threadContext.core->setAVStream(m_threadContext.core, stream);
944}
945
946void CoreController::clearAVStream() {
947 Interrupter interrupter(this);
948 m_threadContext.core->setAVStream(m_threadContext.core, nullptr);
949}
950
951void CoreController::clearOverride() {
952 m_override.reset();
953}
954
955void CoreController::startVideoLog(const QString& path, bool compression) {
956 if (m_vl) {
957 return;
958 }
959
960 VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
961 if (!vf) {
962 return;
963 }
964 startVideoLog(vf, compression);
965}
966
967void CoreController::startVideoLog(VFile* vf, bool compression) {
968 if (m_vl || !vf) {
969 return;
970 }
971
972 Interrupter interrupter(this);
973 m_vl = mVideoLogContextCreate(m_threadContext.core);
974 m_vlVf = vf;
975 mVideoLogContextSetOutput(m_vl, m_vlVf);
976 mVideoLogContextSetCompression(m_vl, compression);
977 mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
978}
979
980void CoreController::endVideoLog(bool closeVf) {
981 if (!m_vl) {
982 return;
983 }
984
985 Interrupter interrupter(this);
986 mVideoLogContextDestroy(m_threadContext.core, m_vl, closeVf);
987 if (closeVf) {
988 m_vlVf = nullptr;
989 }
990 m_vl = nullptr;
991}
992
993void CoreController::setFramebufferHandle(int fb) {
994 Interrupter interrupter(this);
995 if (fb < 0) {
996 if (!m_hwaccel) {
997 return;
998 }
999 mCoreConfigSetIntValue(&m_threadContext.core->config, "hwaccelVideo", 0);
1000 m_threadContext.core->setVideoGLTex(m_threadContext.core, -1);
1001 m_hwaccel = false;
1002 } else {
1003 mCoreConfigSetIntValue(&m_threadContext.core->config, "hwaccelVideo", 1);
1004 m_threadContext.core->setVideoGLTex(m_threadContext.core, fb);
1005 if (m_hwaccel) {
1006 return;
1007 }
1008 m_hwaccel = true;
1009 }
1010 if (hasStarted()) {
1011 m_threadContext.core->reloadConfigOption(m_threadContext.core, "hwaccelVideo", NULL);
1012 if (!m_hwaccel) {
1013 m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), screenDimensions().width());
1014 }
1015 }
1016}
1017
1018void CoreController::updateKeys() {
1019 int activeKeys = m_activeKeys | updateAutofire() | m_inputController->pollEvents();
1020 m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
1021}
1022
1023int CoreController::updateAutofire() {
1024 int active = 0;
1025 for (int k = 0; k < 32; ++k) {
1026 if (!m_autofire[k]) {
1027 continue;
1028 }
1029 ++m_autofireStatus[k];
1030 if (m_autofireStatus[k] >= 2 * m_autofireThreshold) {
1031 m_autofireStatus[k] = 0;
1032 } else if (m_autofireStatus[k] >= m_autofireThreshold) {
1033 active |= 1 << k;
1034 }
1035 }
1036 return active;
1037}
1038
1039void CoreController::finishFrame() {
1040 if (!m_hwaccel) {
1041 unsigned width, height;
1042 m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
1043
1044 QMutexLocker locker(&m_bufferMutex);
1045 memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), width * height * BYTES_PER_PIXEL);
1046 }
1047
1048 {
1049 QMutexLocker locker(&m_actionMutex);
1050 QList<std::function<void ()>> frameActions(m_frameActions);
1051 m_frameActions.clear();
1052 for (auto& action : frameActions) {
1053 action();
1054 }
1055 if (m_moreFrames > 0) {
1056 --m_moreFrames;
1057 if (!m_moreFrames) {
1058 mCoreThreadPauseFromThread(&m_threadContext);
1059 }
1060 }
1061 }
1062 updateKeys();
1063
1064 QMetaObject::invokeMethod(this, "frameAvailable");
1065}
1066
1067void CoreController::updateFastForward() {
1068 // If we have "Fast forward" checked in the menu (m_fastForwardForced)
1069 // or are holding the fast forward button (m_fastForward):
1070 if (m_fastForward || m_fastForwardForced) {
1071 if (m_fastForwardVolume >= 0) {
1072 m_threadContext.core->opts.volume = m_fastForwardVolume;
1073 }
1074 if (m_fastForwardMute >= 0) {
1075 m_threadContext.core->opts.mute = m_fastForwardMute;
1076 }
1077
1078 // If we aren't holding the fast forward button
1079 // then use the non "(held)" ratio
1080 if(!m_fastForward) {
1081 if (m_fastForwardRatio > 0) {
1082 m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio;
1083 setSync(true);
1084 } else {
1085 setSync(false);
1086 }
1087 } else {
1088 // If we are holding the fast forward button,
1089 // then use the held ratio
1090 if (m_fastForwardHeldRatio > 0) {
1091 m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardHeldRatio;
1092 setSync(true);
1093 } else {
1094 setSync(false);
1095 }
1096 }
1097 } else {
1098 if (!mCoreConfigGetIntValue(&m_threadContext.core->config, "volume", &m_threadContext.core->opts.volume)) {
1099 m_threadContext.core->opts.volume = 0x100;
1100 }
1101 int fakeBool = 0;
1102 mCoreConfigGetIntValue(&m_threadContext.core->config, "mute", &fakeBool);
1103 m_threadContext.core->opts.mute = fakeBool;
1104 m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
1105 setSync(true);
1106 }
1107
1108 m_threadContext.core->reloadConfigOption(m_threadContext.core, NULL, NULL);
1109}
1110
1111void CoreController::updateROMInfo() {
1112 const NoIntroDB* db = GBAApp::app()->gameDB();
1113 NoIntroGame game{};
1114 m_crc32 = 0;
1115 mCore* core = m_threadContext.core;
1116 core->checksum(core, &m_crc32, mCHECKSUM_CRC32);
1117
1118 char gameTitle[17] = { '\0' };
1119 core->getGameTitle(core, gameTitle);
1120 m_internalTitle = QLatin1String(gameTitle);
1121
1122#ifdef USE_SQLITE3
1123 if (db && m_crc32 && NoIntroDBLookupGameByCRC(db, m_crc32, &game)) {
1124 m_dbTitle = QString::fromUtf8(game.name);
1125 }
1126#endif
1127}
1128
1129CoreController::Interrupter::Interrupter()
1130 : m_parent(nullptr)
1131{
1132}
1133
1134CoreController::Interrupter::Interrupter(CoreController* parent)
1135 : m_parent(parent)
1136{
1137 interrupt();
1138}
1139
1140CoreController::Interrupter::Interrupter(std::shared_ptr<CoreController> parent)
1141 : m_parent(parent.get())
1142{
1143 interrupt();
1144}
1145
1146CoreController::Interrupter::Interrupter(const Interrupter& other)
1147 : m_parent(other.m_parent)
1148{
1149 interrupt();
1150}
1151
1152CoreController::Interrupter::~Interrupter() {
1153 resume();
1154}
1155
1156CoreController::Interrupter& CoreController::Interrupter::operator=(const Interrupter& other)
1157{
1158 interrupt(other.m_parent);
1159 return *this;
1160}
1161
1162void CoreController::Interrupter::interrupt(CoreController* controller) {
1163 if (m_parent != controller) {
1164 CoreController* old = m_parent;
1165 m_parent = controller;
1166 interrupt();
1167 resume(old);
1168 }
1169}
1170
1171void CoreController::Interrupter::interrupt(std::shared_ptr<CoreController> controller) {
1172 interrupt(controller.get());
1173}
1174
1175void CoreController::Interrupter::interrupt() {
1176 if (!m_parent || !m_parent->thread()->impl) {
1177 return;
1178 }
1179
1180 if (mCoreThreadGet() != m_parent->thread()) {
1181 mCoreThreadInterrupt(m_parent->thread());
1182 } else {
1183 mCoreThreadInterruptFromThread(m_parent->thread());
1184 }
1185}
1186
1187void CoreController::Interrupter::resume() {
1188 resume(m_parent);
1189 m_parent = nullptr;
1190}
1191
1192void CoreController::Interrupter::resume(CoreController* controller) {
1193 if (!controller || !controller->thread()->impl) {
1194 return;
1195 }
1196
1197 mCoreThreadContinue(controller->thread());
1198}