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