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#ifdef M_CORE_GBA
632 if (platform() != PLATFORM_GBA) {
633 return;
634 }
635 Interrupter interrupter(this);
636 GBAYankROM(static_cast<GBA*>(m_threadContext.core->board));
637#endif
638}
639
640void CoreController::addKey(int key) {
641 m_activeKeys |= 1 << key;
642}
643
644void CoreController::clearKey(int key) {
645 m_activeKeys &= ~(1 << key);
646}
647
648void CoreController::setAutofire(int key, bool enable) {
649 if (key >= 32 || key < 0) {
650 return;
651 }
652
653 m_autofire[key] = enable;
654 m_autofireStatus[key] = 0;
655}
656
657#ifdef USE_PNG
658void CoreController::screenshot() {
659 mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
660 mCoreTakeScreenshot(context->core);
661 });
662}
663#endif
664
665void CoreController::setRealTime() {
666 m_threadContext.core->rtc.override = RTC_NO_OVERRIDE;
667}
668
669void CoreController::setFixedTime(const QDateTime& time) {
670 m_threadContext.core->rtc.override = RTC_FIXED;
671 m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
672}
673
674void CoreController::setFakeEpoch(const QDateTime& time) {
675 m_threadContext.core->rtc.override = RTC_FAKE_EPOCH;
676 m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
677}
678
679void CoreController::importSharkport(const QString& path) {
680#ifdef M_CORE_GBA
681 if (platform() != PLATFORM_GBA) {
682 return;
683 }
684 VFile* vf = VFileDevice::open(path, O_RDONLY);
685 if (!vf) {
686 LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
687 return;
688 }
689 Interrupter interrupter(this);
690 GBASavedataImportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf, false);
691 vf->close(vf);
692#endif
693}
694
695void CoreController::exportSharkport(const QString& path) {
696#ifdef M_CORE_GBA
697 if (platform() != PLATFORM_GBA) {
698 return;
699 }
700 VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
701 if (!vf) {
702 LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
703 return;
704 }
705 Interrupter interrupter(this);
706 GBASavedataExportSharkPort(static_cast<GBA*>(m_threadContext.core->board), vf);
707 vf->close(vf);
708#endif
709}
710
711#ifdef M_CORE_GB
712void CoreController::attachPrinter() {
713 if (platform() != PLATFORM_GB) {
714 return;
715 }
716 GB* gb = static_cast<GB*>(m_threadContext.core->board);
717 clearMultiplayerController();
718 GBPrinterCreate(&m_printer.d);
719 m_printer.parent = this;
720 m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) {
721 QGBPrinter* qPrinter = reinterpret_cast<QGBPrinter*>(printer);
722 QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8);
723 QVector<QRgb> colors;
724 colors.append(qRgb(0xF8, 0xF8, 0xF8));
725 colors.append(qRgb(0xA8, 0xA8, 0xA8));
726 colors.append(qRgb(0x50, 0x50, 0x50));
727 colors.append(qRgb(0x00, 0x00, 0x00));
728 image.setColorTable(colors);
729 for (int y = 0; y < height; ++y) {
730 for (int x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) {
731 uint8_t byte = data[(x + y * GB_VIDEO_HORIZONTAL_PIXELS) / 4];
732 image.setPixel(x + 0, y, (byte & 0xC0) >> 6);
733 image.setPixel(x + 1, y, (byte & 0x30) >> 4);
734 image.setPixel(x + 2, y, (byte & 0x0C) >> 2);
735 image.setPixel(x + 3, y, (byte & 0x03) >> 0);
736 }
737 }
738 QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image));
739 };
740 Interrupter interrupter(this);
741 GBSIOSetDriver(&gb->sio, &m_printer.d.d);
742}
743
744void CoreController::detachPrinter() {
745 if (platform() != PLATFORM_GB) {
746 return;
747 }
748 Interrupter interrupter(this);
749 GB* gb = static_cast<GB*>(m_threadContext.core->board);
750 GBPrinterDonePrinting(&m_printer.d);
751 GBSIOSetDriver(&gb->sio, nullptr);
752}
753
754void CoreController::endPrint() {
755 if (platform() != PLATFORM_GB) {
756 return;
757 }
758 Interrupter interrupter(this);
759 GBPrinterDonePrinting(&m_printer.d);
760}
761#endif
762
763#ifdef M_CORE_GBA
764void CoreController::attachBattleChipGate() {
765 if (platform() != PLATFORM_GBA) {
766 return;
767 }
768 Interrupter interrupter(this);
769 clearMultiplayerController();
770 GBASIOBattlechipGateCreate(&m_battlechip);
771 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, &m_battlechip);
772}
773
774void CoreController::detachBattleChipGate() {
775 if (platform() != PLATFORM_GBA) {
776 return;
777 }
778 Interrupter interrupter(this);
779 m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, nullptr);
780}
781
782void CoreController::setBattleChipId(uint16_t id) {
783 if (platform() != PLATFORM_GBA) {
784 return;
785 }
786 Interrupter interrupter(this);
787 m_battlechip.chipId = id;
788}
789
790void CoreController::setBattleChipFlavor(int flavor) {
791 if (platform() != PLATFORM_GBA) {
792 return;
793 }
794 Interrupter interrupter(this);
795 m_battlechip.flavor = flavor;
796}
797#endif
798
799void CoreController::setAVStream(mAVStream* stream) {
800 Interrupter interrupter(this);
801 m_threadContext.core->setAVStream(m_threadContext.core, stream);
802}
803
804void CoreController::clearAVStream() {
805 Interrupter interrupter(this);
806 m_threadContext.core->setAVStream(m_threadContext.core, nullptr);
807}
808
809void CoreController::clearOverride() {
810 m_override.reset();
811}
812
813void CoreController::startVideoLog(const QString& path, bool compression) {
814 if (m_vl) {
815 return;
816 }
817
818 VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
819 if (!vf) {
820 return;
821 }
822 startVideoLog(vf);
823}
824
825void CoreController::startVideoLog(VFile* vf, bool compression) {
826 if (m_vl || !vf) {
827 return;
828 }
829
830 Interrupter interrupter(this);
831 m_vl = mVideoLogContextCreate(m_threadContext.core);
832 m_vlVf = vf;
833 mVideoLogContextSetOutput(m_vl, m_vlVf);
834 mVideoLogContextSetCompression(m_vl, compression);
835 mVideoLogContextWriteHeader(m_vl, m_threadContext.core);
836}
837
838void CoreController::endVideoLog(bool closeVf) {
839 if (!m_vl) {
840 return;
841 }
842
843 Interrupter interrupter(this);
844 mVideoLogContextDestroy(m_threadContext.core, m_vl);
845 if (m_vlVf && closeVf) {
846 m_vlVf->close(m_vlVf);
847 m_vlVf = nullptr;
848 }
849 m_vl = nullptr;
850}
851
852void CoreController::setFramebufferHandle(int fb) {
853 Interrupter interrupter(this);
854 if (fb < 0) {
855 m_hwaccel = false;
856 } else {
857 m_threadContext.core->setVideoGLTex(m_threadContext.core, fb);
858 m_hwaccel = true;
859 }
860}
861
862void CoreController::updateKeys() {
863 int activeKeys = m_activeKeys | updateAutofire() | m_inputController->pollEvents();
864 m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
865}
866
867int CoreController::updateAutofire() {
868 int active = 0;
869 for (int k = 0; k < 32; ++k) {
870 if (!m_autofire[k]) {
871 continue;
872 }
873 ++m_autofireStatus[k];
874 if (m_autofireStatus[k] >= 2 * m_autofireThreshold) {
875 m_autofireStatus[k] = 0;
876 } else if (m_autofireStatus[k] >= m_autofireThreshold) {
877 active |= 1 << k;
878 }
879 }
880 return active;
881}
882
883void CoreController::finishFrame() {
884 if (!m_hwaccel) {
885 unsigned width, height;
886 m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);
887
888 QMutexLocker locker(&m_bufferMutex);
889 memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), 256 * height * BYTES_PER_PIXEL);
890 }
891
892 QMutexLocker locker(&m_actionMutex);
893 QList<std::function<void ()>> frameActions(m_frameActions);
894 m_frameActions.clear();
895 for (auto& action : frameActions) {
896 action();
897 }
898 updateKeys();
899
900 QMetaObject::invokeMethod(this, "frameAvailable");
901}
902
903void CoreController::updateFastForward() {
904 // If we have "Fast forward" checked in the menu (m_fastForwardForced)
905 // or are holding the fast forward button (m_fastForward):
906 if (m_fastForward || m_fastForwardForced) {
907 if (m_fastForwardVolume >= 0) {
908 m_threadContext.core->opts.volume = m_fastForwardVolume;
909 }
910 if (m_fastForwardMute >= 0) {
911 m_threadContext.core->opts.mute = m_fastForwardMute;
912 }
913
914 // If we aren't holding the fast forward button
915 // then use the non "(held)" ratio
916 if(!m_fastForward) {
917 if (m_fastForwardRatio > 0) {
918 m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio;
919 setSync(true);
920 } else {
921 setSync(false);
922 }
923 } else {
924 // If we are holding the fast forward button,
925 // then use the held ratio
926 if (m_fastForwardHeldRatio > 0) {
927 m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardHeldRatio;
928 setSync(true);
929 } else {
930 setSync(false);
931 }
932 }
933 } else {
934 if (!mCoreConfigGetIntValue(&m_threadContext.core->config, "volume", &m_threadContext.core->opts.volume)) {
935 m_threadContext.core->opts.volume = 0x100;
936 }
937 int fakeBool = 0;
938 mCoreConfigGetIntValue(&m_threadContext.core->config, "mute", &fakeBool);
939 m_threadContext.core->opts.mute = fakeBool;
940 m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
941 setSync(true);
942 }
943
944 m_threadContext.core->reloadConfigOption(m_threadContext.core, NULL, NULL);
945}
946
947CoreController::Interrupter::Interrupter(CoreController* parent, bool fromThread)
948 : m_parent(parent)
949{
950 if (!m_parent->thread()->impl) {
951 return;
952 }
953 if (!fromThread) {
954 mCoreThreadInterrupt(m_parent->thread());
955 } else {
956 mCoreThreadInterruptFromThread(m_parent->thread());
957 }
958}
959
960CoreController::Interrupter::Interrupter(std::shared_ptr<CoreController> parent, bool fromThread)
961 : m_parent(parent.get())
962{
963 if (!m_parent->thread()->impl) {
964 return;
965 }
966 if (!fromThread) {
967 mCoreThreadInterrupt(m_parent->thread());
968 } else {
969 mCoreThreadInterruptFromThread(m_parent->thread());
970 }
971}
972
973CoreController::Interrupter::Interrupter(const Interrupter& other)
974 : m_parent(other.m_parent)
975{
976 if (!m_parent->thread()->impl) {
977 return;
978 }
979 mCoreThreadInterrupt(m_parent->thread());
980}
981
982CoreController::Interrupter::~Interrupter() {
983 if (!m_parent->thread()->impl) {
984 return;
985 }
986 mCoreThreadContinue(m_parent->thread());
987}