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