aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/qt_sdl/main.cpp
diff options
context:
space:
mode:
authorRSDuck <RSDuck@users.noreply.github.com>2020-05-12 16:07:28 +0200
committerRSDuck <rsduck@users.noreply.github.com>2020-06-16 12:06:42 +0200
commite7d076403df7afd6dc8304196211b49e3ed7f464 (patch)
tree1d5ff1e743839f271de77f8bd312c985033c6a89 /src/frontend/qt_sdl/main.cpp
parent4cff4b52286a7d1a7e40817d52a5d271a937ddc2 (diff)
parentc17f7b100e36edb1c728dbf21c77f9484d1820c6 (diff)
Merge branch 'generic_jit' of https://github.com/Arisotura/melonDS into generic_jit
Diffstat (limited to 'src/frontend/qt_sdl/main.cpp')
-rw-r--r--src/frontend/qt_sdl/main.cpp2091
1 files changed, 2091 insertions, 0 deletions
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
new file mode 100644
index 0000000..fa542ad
--- /dev/null
+++ b/src/frontend/qt_sdl/main.cpp
@@ -0,0 +1,2091 @@
+/*
+ Copyright 2016-2020 Arisotura
+
+ This file is part of melonDS.
+
+ melonDS is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdlib.h>
+#include <time.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <QApplication>
+#include <QMessageBox>
+#include <QMenuBar>
+#include <QFileDialog>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QKeyEvent>
+#include <QMimeData>
+
+#include <SDL2/SDL.h>
+
+#include "main.h"
+#include "Input.h"
+#include "EmuSettingsDialog.h"
+#include "InputConfigDialog.h"
+#include "VideoSettingsDialog.h"
+#include "AudioSettingsDialog.h"
+#include "WifiSettingsDialog.h"
+
+#include "types.h"
+#include "version.h"
+
+#include "FrontendUtil.h"
+#include "OSD.h"
+
+#include "NDS.h"
+#include "GBACart.h"
+#include "OpenGLSupport.h"
+#include "GPU.h"
+#include "SPU.h"
+#include "Wifi.h"
+#include "Platform.h"
+#include "Config.h"
+#include "PlatformConfig.h"
+
+#include "Savestate.h"
+
+#include "main_shaders.h"
+
+
+// TODO: uniform variable spelling
+
+bool RunningSomething;
+
+MainWindow* mainWindow;
+EmuThread* emuThread;
+
+int autoScreenSizing = 0;
+
+int videoRenderer;
+GPU::RenderSettings videoSettings;
+bool videoSettingsDirty;
+
+SDL_AudioDeviceID audioDevice;
+int audioFreq;
+SDL_cond* audioSync;
+SDL_mutex* audioSyncLock;
+
+SDL_AudioDeviceID micDevice;
+s16 micExtBuffer[2048];
+u32 micExtBufferWritePos;
+
+u32 micWavLength;
+s16* micWavBuffer;
+
+
+void audioCallback(void* data, Uint8* stream, int len)
+{
+ len /= (sizeof(s16) * 2);
+
+ // resample incoming audio to match the output sample rate
+
+ int len_in = Frontend::AudioOut_GetNumSamples(len);
+ s16 buf_in[1024*2];
+ int num_in;
+
+ SDL_LockMutex(audioSyncLock);
+ num_in = SPU::ReadOutput(buf_in, len_in);
+ SDL_CondSignal(audioSync);
+ SDL_UnlockMutex(audioSyncLock);
+
+ if (num_in < 1)
+ {
+ memset(stream, 0, len*sizeof(s16)*2);
+ return;
+ }
+
+ int margin = 6;
+ if (num_in < len_in-margin)
+ {
+ int last = num_in-1;
+ if (last < 0) last = 0;
+
+ for (int i = num_in; i < len_in-margin; i++)
+ ((u32*)buf_in)[i] = ((u32*)buf_in)[last];
+
+ num_in = len_in-margin;
+ }
+
+ Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume);
+}
+
+
+void micLoadWav(const char* name)
+{
+ SDL_AudioSpec format;
+ memset(&format, 0, sizeof(SDL_AudioSpec));
+
+ if (micWavBuffer) delete[] micWavBuffer;
+ micWavBuffer = nullptr;
+ micWavLength = 0;
+
+ u8* buf;
+ u32 len;
+ if (!SDL_LoadWAV(name, &format, &buf, &len))
+ return;
+
+ const u64 dstfreq = 44100;
+
+ if (format.format == AUDIO_S16 || format.format == AUDIO_U16)
+ {
+ int srcinc = format.channels;
+ len /= (2 * srcinc);
+
+ micWavLength = (len * dstfreq) / format.freq;
+ if (micWavLength < 735) micWavLength = 735;
+ micWavBuffer = new s16[micWavLength];
+
+ float res_incr = len / (float)micWavLength;
+ float res_timer = 0;
+ int res_pos = 0;
+
+ for (int i = 0; i < micWavLength; i++)
+ {
+ u16 val = ((u16*)buf)[res_pos];
+ if (SDL_AUDIO_ISUNSIGNED(format.format)) val ^= 0x8000;
+
+ micWavBuffer[i] = val;
+
+ res_timer += res_incr;
+ while (res_timer >= 1.0)
+ {
+ res_timer -= 1.0;
+ res_pos += srcinc;
+ }
+ }
+ }
+ else if (format.format == AUDIO_S8 || format.format == AUDIO_U8)
+ {
+ int srcinc = format.channels;
+ len /= srcinc;
+
+ micWavLength = (len * dstfreq) / format.freq;
+ if (micWavLength < 735) micWavLength = 735;
+ micWavBuffer = new s16[micWavLength];
+
+ float res_incr = len / (float)micWavLength;
+ float res_timer = 0;
+ int res_pos = 0;
+
+ for (int i = 0; i < micWavLength; i++)
+ {
+ u16 val = buf[res_pos] << 8;
+ if (SDL_AUDIO_ISUNSIGNED(format.format)) val ^= 0x8000;
+
+ micWavBuffer[i] = val;
+
+ res_timer += res_incr;
+ while (res_timer >= 1.0)
+ {
+ res_timer -= 1.0;
+ res_pos += srcinc;
+ }
+ }
+ }
+ else
+ printf("bad WAV format %08X\n", format.format);
+
+ SDL_FreeWAV(buf);
+}
+
+void micCallback(void* data, Uint8* stream, int len)
+{
+ s16* input = (s16*)stream;
+ len /= sizeof(s16);
+
+ int maxlen = sizeof(micExtBuffer) / sizeof(s16);
+
+ if ((micExtBufferWritePos + len) > maxlen)
+ {
+ u32 len1 = maxlen - micExtBufferWritePos;
+ memcpy(&micExtBuffer[micExtBufferWritePos], &input[0], len1*sizeof(s16));
+ memcpy(&micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16));
+ micExtBufferWritePos = len - len1;
+ }
+ else
+ {
+ memcpy(&micExtBuffer[micExtBufferWritePos], input, len*sizeof(s16));
+ micExtBufferWritePos += len;
+ }
+}
+
+void micProcess()
+{
+ int type = Config::MicInputType;
+ bool cmd = Input::HotkeyDown(HK_Mic);
+
+ if (type != 1 && !cmd)
+ {
+ type = 0;
+ }
+
+ switch (type)
+ {
+ case 0: // no mic
+ Frontend::Mic_FeedSilence();
+ break;
+
+ case 1: // host mic
+ case 3: // WAV
+ Frontend::Mic_FeedExternalBuffer();
+ break;
+
+ case 2: // white noise
+ Frontend::Mic_FeedNoise();
+ break;
+ }
+}
+
+
+EmuThread::EmuThread(QObject* parent) : QThread(parent)
+{
+ EmuStatus = 0;
+ EmuRunning = 2;
+ RunningSomething = false;
+
+ connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(update()));
+ connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString)));
+ connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart()));
+ connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop()));
+ connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger()));
+ connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger()));
+ connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged()));
+
+ if (mainWindow->hasOGL) initOpenGL();
+}
+
+void EmuThread::initOpenGL()
+{
+ QOpenGLContext* windowctx = mainWindow->getOGLContext();
+ QSurfaceFormat format = windowctx->format();
+
+ format.setSwapInterval(0);
+
+ oglSurface = new QOffscreenSurface();
+ oglSurface->setFormat(format);
+ oglSurface->create();
+ if (!oglSurface->isValid())
+ {
+ // TODO handle this!
+ printf("oglSurface shat itself :(\n");
+ delete oglSurface;
+ return;
+ }
+
+ oglContext = new QOpenGLContext();
+ oglContext->setFormat(oglSurface->format());
+ oglContext->setShareContext(windowctx);
+ if (!oglContext->create())
+ {
+ // TODO handle this!
+ printf("oglContext shat itself :(\n");
+ delete oglContext;
+ delete oglSurface;
+ return;
+ }
+
+ oglContext->moveToThread(this);
+}
+
+void EmuThread::deinitOpenGL()
+{
+ delete oglContext;
+ delete oglSurface;
+}
+
+void* oglGetProcAddress(const char* proc)
+{
+ return emuThread->oglGetProcAddress(proc);
+}
+
+void* EmuThread::oglGetProcAddress(const char* proc)
+{
+ return (void*)oglContext->getProcAddress(proc);
+}
+
+void EmuThread::run()
+{
+ bool hasOGL = mainWindow->hasOGL;
+ u32 mainScreenPos[3];
+
+ NDS::Init();
+
+ mainScreenPos[0] = 0;
+ mainScreenPos[1] = 0;
+ mainScreenPos[2] = 0;
+ autoScreenSizing = 0;
+
+ videoSettingsDirty = false;
+ videoSettings.Soft_Threaded = Config::Threaded3D != 0;
+ videoSettings.GL_ScaleFactor = Config::GL_ScaleFactor;
+
+ if (hasOGL)
+ {
+ oglContext->makeCurrent(oglSurface);
+ videoRenderer = OpenGL::Init() ? Config::_3DRenderer : 0;
+ }
+ else
+ videoRenderer = 0;
+
+ GPU::InitRenderer(videoRenderer);
+ GPU::SetRenderSettings(videoRenderer, videoSettings);
+
+ Input::Init();
+
+ u32 nframes = 0;
+ u32 starttick = SDL_GetTicks();
+ u32 lasttick = starttick;
+ u32 lastmeasuretick = lasttick;
+ u32 fpslimitcount = 0;
+
+ char melontitle[100];
+
+ while (EmuRunning != 0)
+ {
+ Input::Process();
+
+ if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
+
+ if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause();
+ if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset();
+
+ if (GBACart::CartInserted && GBACart::HasSolarSensor)
+ {
+ if (Input::HotkeyPressed(HK_SolarSensorDecrease))
+ {
+ if (GBACart_SolarSensor::LightLevel > 0) GBACart_SolarSensor::LightLevel--;
+ char msg[64];
+ sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel);
+ OSD::AddMessage(0, msg);
+ }
+ if (Input::HotkeyPressed(HK_SolarSensorIncrease))
+ {
+ if (GBACart_SolarSensor::LightLevel < 10) GBACart_SolarSensor::LightLevel++;
+ char msg[64];
+ sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel);
+ OSD::AddMessage(0, msg);
+ }
+ }
+
+ if (EmuRunning == 1)
+ {
+ EmuStatus = 1;
+
+ // update render settings if needed
+ if (videoSettingsDirty)
+ {
+ if (hasOGL != mainWindow->hasOGL)
+ {
+ hasOGL = mainWindow->hasOGL;
+ if (hasOGL)
+ {
+ oglContext->makeCurrent(oglSurface);
+ videoRenderer = OpenGL::Init() ? Config::_3DRenderer : 0;
+ }
+ else
+ videoRenderer = 0;
+ }
+ else
+ videoRenderer = hasOGL ? Config::_3DRenderer : 0;
+
+ videoSettingsDirty = false;
+ videoSettings.Soft_Threaded = Config::Threaded3D != 0;
+ videoSettings.GL_ScaleFactor = Config::GL_ScaleFactor;
+ GPU::SetRenderSettings(videoRenderer, videoSettings);
+ }
+
+ // process input and hotkeys
+ NDS::SetKeyMask(Input::InputMask);
+
+ if (Input::HotkeyPressed(HK_Lid))
+ {
+ bool lid = !NDS::IsLidClosed();
+ NDS::SetLidClosed(lid);
+ OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened");
+ }
+
+ // microphone input
+ micProcess();
+
+ // auto screen layout
+ if (Config::ScreenSizing == 3)
+ {
+ mainScreenPos[2] = mainScreenPos[1];
+ mainScreenPos[1] = mainScreenPos[0];
+ mainScreenPos[0] = NDS::PowerControl9 >> 15;
+
+ int guess;
+ if (mainScreenPos[0] == mainScreenPos[2] &&
+ mainScreenPos[0] != mainScreenPos[1])
+ {
+ // constant flickering, likely displaying 3D on both screens
+ // TODO: when both screens are used for 2D only...???
+ guess = 0;
+ }
+ else
+ {
+ if (mainScreenPos[0] == 1)
+ guess = 1;
+ else
+ guess = 2;
+ }
+
+ if (guess != autoScreenSizing)
+ {
+ autoScreenSizing = guess;
+ emit screenLayoutChange();
+ }
+ }
+
+ // emulate
+ u32 nlines = NDS::RunFrame();
+
+#ifdef MELONCAP
+ MelonCap::Update();
+#endif // MELONCAP
+
+ if (EmuRunning == 0) break;
+
+ emit windowUpdate();
+
+ bool fastforward = Input::HotkeyDown(HK_FastForward);
+
+ if (Config::AudioSync && (!fastforward) && audioDevice)
+ {
+ SDL_LockMutex(audioSyncLock);
+ while (SPU::GetOutputSize() > 1024)
+ {
+ int ret = SDL_CondWaitTimeout(audioSync, audioSyncLock, 500);
+ if (ret == SDL_MUTEX_TIMEDOUT) break;
+ }
+ SDL_UnlockMutex(audioSyncLock);
+ }
+
+ float framerate = (1000.0f * nlines) / (60.0f * 263.0f);
+
+ {
+ u32 curtick = SDL_GetTicks();
+ u32 delay = curtick - lasttick;
+
+ bool limitfps = Config::LimitFPS && !fastforward;
+ if (limitfps)
+ {
+ float wantedtickF = starttick + (framerate * (fpslimitcount+1));
+ u32 wantedtick = (u32)ceil(wantedtickF);
+ if (curtick < wantedtick) SDL_Delay(wantedtick - curtick);
+
+ lasttick = SDL_GetTicks();
+ fpslimitcount++;
+ if ((abs(wantedtickF - (float)wantedtick) < 0.001312) || (fpslimitcount > 60))
+ {
+ fpslimitcount = 0;
+ starttick = lasttick;
+ }
+ }
+ else
+ {
+ if (delay < 1) SDL_Delay(1);
+ lasttick = SDL_GetTicks();
+ }
+ }
+
+ nframes++;
+ if (nframes >= 30)
+ {
+ u32 tick = SDL_GetTicks();
+ u32 diff = tick - lastmeasuretick;
+ lastmeasuretick = tick;
+
+ u32 fps;
+ if (diff < 1) fps = 77777;
+ else fps = (nframes * 1000) / diff;
+ nframes = 0;
+
+ float fpstarget;
+ if (framerate < 1) fpstarget = 999;
+ else fpstarget = 1000.0f/framerate;
+
+ sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
+ changeWindowTitle(melontitle);
+ }
+ }
+ else
+ {
+ // paused
+ nframes = 0;
+ lasttick = SDL_GetTicks();
+ starttick = lasttick;
+ lastmeasuretick = lasttick;
+ fpslimitcount = 0;
+
+ emit windowUpdate();
+
+ EmuStatus = EmuRunning;
+
+ sprintf(melontitle, "melonDS " MELONDS_VERSION);
+ changeWindowTitle(melontitle);
+
+ SDL_Delay(75);
+ }
+ }
+
+ EmuStatus = 0;
+
+ GPU::DeInitRenderer();
+ NDS::DeInit();
+ //Platform::LAN_DeInit();
+
+ if (hasOGL)
+ {
+ oglContext->doneCurrent();
+ deinitOpenGL();
+ }
+}
+
+void EmuThread::changeWindowTitle(char* title)
+{
+ emit windowTitleChange(QString(title));
+}
+
+void EmuThread::emuRun()
+{
+ EmuRunning = 1;
+ RunningSomething = true;
+
+ // checkme
+ emit windowEmuStart();
+ if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0);
+ if (micDevice) SDL_PauseAudioDevice(micDevice, 0);
+}
+
+void EmuThread::emuPause()
+{
+ PrevEmuStatus = EmuRunning;
+ EmuRunning = 2;
+ while (EmuStatus != 2);
+
+ if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1);
+ if (micDevice) SDL_PauseAudioDevice(micDevice, 1);
+}
+
+void EmuThread::emuUnpause()
+{
+ EmuRunning = PrevEmuStatus;
+
+ if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0);
+ if (micDevice) SDL_PauseAudioDevice(micDevice, 0);
+}
+
+void EmuThread::emuStop()
+{
+ EmuRunning = 0;
+
+ if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1);
+ if (micDevice) SDL_PauseAudioDevice(micDevice, 1);
+}
+
+bool EmuThread::emuIsRunning()
+{
+ return (EmuRunning == 1);
+}
+
+
+void ScreenHandler::screenSetupLayout(int w, int h)
+{
+ int sizing = Config::ScreenSizing;
+ if (sizing == 3) sizing = autoScreenSizing;
+
+ Frontend::SetupScreenLayout(w, h,
+ Config::ScreenLayout,
+ Config::ScreenRotation,
+ sizing,
+ Config::ScreenGap,
+ Config::IntegerScaling != 0);
+
+ Frontend::GetScreenTransforms(screenMatrix[0], screenMatrix[1]);
+}
+
+QSize ScreenHandler::screenGetMinSize()
+{
+ bool isHori = (Config::ScreenRotation == 1 || Config::ScreenRotation == 3);
+ int gap = Config::ScreenGap;
+
+ int w = 256;
+ int h = 192;
+
+ if (Config::ScreenLayout == 0) // natural
+ {
+ if (isHori)
+ return QSize(h+gap+h, w);
+ else
+ return QSize(w, h+gap+h);
+ }
+ else if (Config::ScreenLayout == 1) // vertical
+ {
+ if (isHori)
+ return QSize(h, w+gap+w);
+ else
+ return QSize(w, h+gap+h);
+ }
+ else // horizontal
+ {
+ if (isHori)
+ return QSize(h+gap+h, w);
+ else
+ return QSize(w+gap+w, h);
+ }
+}
+
+void ScreenHandler::screenOnMousePress(QMouseEvent* event)
+{
+ event->accept();
+ if (event->button() != Qt::LeftButton) return;
+
+ int x = event->pos().x();
+ int y = event->pos().y();
+
+ Frontend::GetTouchCoords(x, y);
+
+ if (x >= 0 && x < 256 && y >= 0 && y < 192)
+ {
+ touching = true;
+ NDS::TouchScreen(x, y);
+ }
+}
+
+void ScreenHandler::screenOnMouseRelease(QMouseEvent* event)
+{
+ event->accept();
+ if (event->button() != Qt::LeftButton) return;
+
+ if (touching)
+ {
+ touching = false;
+ NDS::ReleaseScreen();
+ }
+}
+
+void ScreenHandler::screenOnMouseMove(QMouseEvent* event)
+{
+ event->accept();
+ if (!(event->buttons() & Qt::LeftButton)) return;
+ if (!touching) return;
+
+ int x = event->pos().x();
+ int y = event->pos().y();
+
+ Frontend::GetTouchCoords(x, y);
+
+ // clamp to screen range
+ if (x < 0) x = 0;
+ else if (x > 255) x = 255;
+ if (y < 0) y = 0;
+ else if (y > 191) y = 191;
+
+ NDS::TouchScreen(x, y);
+}
+
+
+ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent)
+{
+ screen[0] = QImage(256, 192, QImage::Format_RGB32);
+ screen[1] = QImage(256, 192, QImage::Format_RGB32);
+
+ screenTrans[0].reset();
+ screenTrans[1].reset();
+
+ touching = false;
+
+ OSD::Init(nullptr);
+}
+
+ScreenPanelNative::~ScreenPanelNative()
+{
+ OSD::DeInit(nullptr);
+}
+
+void ScreenPanelNative::setupScreenLayout()
+{
+ int w = width();
+ int h = height();
+ float* mtx;
+
+ screenSetupLayout(w, h);
+
+ mtx = screenMatrix[0];
+ screenTrans[0].setMatrix(mtx[0], mtx[1], 0.f,
+ mtx[2], mtx[3], 0.f,
+ mtx[4], mtx[5], 1.f);
+
+ mtx = screenMatrix[1];
+ screenTrans[1].setMatrix(mtx[0], mtx[1], 0.f,
+ mtx[2], mtx[3], 0.f,
+ mtx[4], mtx[5], 1.f);
+}
+
+void ScreenPanelNative::paintEvent(QPaintEvent* event)
+{
+ QPainter painter(this);
+
+ // fill background
+ painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
+
+ int frontbuf = GPU::FrontBuffer;
+ if (!GPU::Framebuffer[frontbuf][0] || !GPU::Framebuffer[frontbuf][1]) return;
+
+ memcpy(screen[0].scanLine(0), GPU::Framebuffer[frontbuf][0], 256*192*4);
+ memcpy(screen[1].scanLine(0), GPU::Framebuffer[frontbuf][1], 256*192*4);
+
+ painter.setRenderHint(QPainter::SmoothPixmapTransform, Config::ScreenFilter!=0);
+
+ QRect screenrc(0, 0, 256, 192);
+
+ painter.setTransform(screenTrans[0]);
+ painter.drawImage(screenrc, screen[0]);
+
+ painter.setTransform(screenTrans[1]);
+ painter.drawImage(screenrc, screen[1]);
+
+ OSD::Update(nullptr);
+ OSD::DrawNative(painter);
+}
+
+void ScreenPanelNative::resizeEvent(QResizeEvent* event)
+{
+ setupScreenLayout();
+}
+
+void ScreenPanelNative::mousePressEvent(QMouseEvent* event)
+{
+ screenOnMousePress(event);
+}
+
+void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event)
+{
+ screenOnMouseRelease(event);
+}
+
+void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event)
+{
+ screenOnMouseMove(event);
+}
+
+void ScreenPanelNative::onScreenLayoutChanged()
+{
+ setMinimumSize(screenGetMinSize());
+ setupScreenLayout();
+}
+
+
+ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QOpenGLWidget(parent)
+{
+ touching = false;
+
+}
+
+ScreenPanelGL::~ScreenPanelGL()
+{
+ makeCurrent();
+
+ OSD::DeInit(this);
+
+ glDeleteTextures(1, &screenTexture);
+
+ glDeleteVertexArrays(1, &screenVertexArray);
+ glDeleteBuffers(1, &screenVertexBuffer);
+
+ delete screenShader;
+
+ doneCurrent();
+}
+
+void ScreenPanelGL::setupScreenLayout()
+{
+ int w = width();
+ int h = height();
+
+ screenSetupLayout(w, h);
+}
+
+void ScreenPanelGL::initializeGL()
+{
+ initializeOpenGLFunctions();
+
+ const GLubyte* renderer = glGetString(GL_RENDERER); // get renderer string
+ const GLubyte* version = glGetString(GL_VERSION); // version as a string
+ printf("OpenGL: renderer: %s\n", renderer);
+ printf("OpenGL: version: %s\n", version);
+
+ glClearColor(0, 0, 0, 1);
+
+ screenShader = new QOpenGLShaderProgram(this);
+ screenShader->addShaderFromSourceCode(QOpenGLShader::Vertex, kScreenVS);
+ screenShader->addShaderFromSourceCode(QOpenGLShader::Fragment, kScreenFS);
+
+ GLuint pid = screenShader->programId();
+ glBindAttribLocation(pid, 0, "vPosition");
+ glBindAttribLocation(pid, 1, "vTexcoord");
+ glBindFragDataLocation(pid, 0, "oColor");
+
+ screenShader->link();
+
+ screenShader->bind();
+ screenShader->setUniformValue("ScreenTex", (GLint)0);
+ screenShader->release();
+
+
+ float vertices[] =
+ {
+ 0, 0, 0, 0,
+ 0, 192, 0, 0.5,
+ 256, 192, 1, 0.5,
+ 0, 0, 0, 0,
+ 256, 192, 1, 0.5,
+ 256, 0, 1, 0,
+
+ 0, 0, 0, 0.5,
+ 0, 192, 0, 1,
+ 256, 192, 1, 1,
+ 0, 0, 0, 0.5,
+ 256, 192, 1, 1,
+ 256, 0, 1, 0.5
+ };
+
+ glGenBuffers(1, &screenVertexBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+ glGenVertexArrays(1, &screenVertexArray);
+ glBindVertexArray(screenVertexArray);
+ glEnableVertexAttribArray(0); // position
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0));
+ glEnableVertexAttribArray(1); // texcoord
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4));
+
+ glGenTextures(1, &screenTexture);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, screenTexture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 192*2, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+ OSD::Init(this);
+}
+
+void ScreenPanelGL::paintGL()
+{
+ int w = width();
+ int h = height();
+ float factor = devicePixelRatioF();
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, w*factor, h*factor);
+
+ screenShader->bind();
+
+ screenShader->setUniformValue("uScreenSize", (float)w*factor, (float)h*factor);
+
+ int frontbuf = GPU::FrontBuffer;
+ glActiveTexture(GL_TEXTURE0);
+
+ if (GPU::Renderer != 0)
+ {
+ // hardware-accelerated render
+ GPU::GLCompositor::BindOutputTexture();
+ }
+ else
+ {
+ // regular render
+ glBindTexture(GL_TEXTURE_2D, screenTexture);
+
+ if (GPU::Framebuffer[frontbuf][0] && GPU::Framebuffer[frontbuf][1])
+ {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA,
+ GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][0]);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 192, GL_RGBA,
+ GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][1]);
+ }
+ }
+
+ GLint filter = Config::ScreenFilter ? GL_LINEAR : GL_NEAREST;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
+
+ glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
+ glBindVertexArray(screenVertexArray);
+
+ GLint transloc = screenShader->uniformLocation("uTransform");
+
+ glUniformMatrix2x3fv(transloc, 1, GL_TRUE, screenMatrix[0]);
+ glDrawArrays(GL_TRIANGLES, 0, 2*3);
+
+ glUniformMatrix2x3fv(transloc, 1, GL_TRUE, screenMatrix[1]);
+ glDrawArrays(GL_TRIANGLES, 2*3, 2*3);
+
+ screenShader->release();
+
+ OSD::Update(this);
+ OSD::DrawGL(this, w*factor, h*factor);
+}
+
+void ScreenPanelGL::resizeEvent(QResizeEvent* event)
+{
+ setupScreenLayout();
+
+ QOpenGLWidget::resizeEvent(event);
+}
+
+void ScreenPanelGL::resizeGL(int w, int h)
+{
+}
+
+void ScreenPanelGL::mousePressEvent(QMouseEvent* event)
+{
+ screenOnMousePress(event);
+}
+
+void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event)
+{
+ screenOnMouseRelease(event);
+}
+
+void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event)
+{
+ screenOnMouseMove(event);
+}
+
+void ScreenPanelGL::onScreenLayoutChanged()
+{
+ setMinimumSize(screenGetMinSize());
+ setupScreenLayout();
+}
+
+
+MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
+{
+ setWindowTitle("melonDS " MELONDS_VERSION);
+ setAttribute(Qt::WA_DeleteOnClose);
+ setAcceptDrops(true);
+
+ QMenuBar* menubar = new QMenuBar();
+ {
+ QMenu* menu = menubar->addMenu("File");
+
+ actOpenROM = menu->addAction("Open ROM...");
+ connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile);
+
+ //actBootFirmware = menu->addAction("Launch DS menu");
+ actBootFirmware = menu->addAction("Boot firmware");
+ connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware);
+
+ menu->addSeparator();
+
+ {
+ QMenu* submenu = menu->addMenu("Save state");
+
+ for (int i = 1; i < 9; i++)
+ {
+ actSaveState[i] = submenu->addAction(QString("%1").arg(i));
+ actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1)));
+ actSaveState[i]->setData(QVariant(i));
+ connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState);
+ }
+
+ actSaveState[0] = submenu->addAction("File...");
+ actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9));
+ actSaveState[0]->setData(QVariant(0));
+ connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState);
+ }
+ {
+ QMenu* submenu = menu->addMenu("Load state");
+
+ for (int i = 1; i < 9; i++)
+ {
+ actLoadState[i] = submenu->addAction(QString("%1").arg(i));
+ actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1));
+ actLoadState[i]->setData(QVariant(i));
+ connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState);
+ }
+
+ actLoadState[0] = submenu->addAction("File...");
+ actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9));
+ actLoadState[0]->setData(QVariant(0));
+ connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState);
+ }
+
+ actUndoStateLoad = menu->addAction("Undo state load");
+ actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12));
+ connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad);
+
+ menu->addSeparator();
+
+ actQuit = menu->addAction("Quit");
+ connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit);
+ }
+ {
+ QMenu* menu = menubar->addMenu("System");
+
+ actPause = menu->addAction("Pause");
+ actPause->setCheckable(true);
+ connect(actPause, &QAction::triggered, this, &MainWindow::onPause);
+
+ actReset = menu->addAction("Reset");
+ connect(actReset, &QAction::triggered, this, &MainWindow::onReset);
+
+ actStop = menu->addAction("Stop");
+ connect(actStop, &QAction::triggered, this, &MainWindow::onStop);
+ }
+ {
+ QMenu* menu = menubar->addMenu("Config");
+
+ actEmuSettings = menu->addAction("Emu settings");
+ connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
+
+ actInputConfig = menu->addAction("Input and hotkeys");
+ connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig);
+
+ actVideoSettings = menu->addAction("Video settings");
+ connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings);
+
+ actAudioSettings = menu->addAction("Audio settings");
+ connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings);
+
+ actWifiSettings = menu->addAction("Wifi settings");
+ connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings);
+
+ {
+ QMenu* submenu = menu->addMenu("Savestate settings");
+
+ actSavestateSRAMReloc = submenu->addAction("Separate savefiles");
+ actSavestateSRAMReloc->setCheckable(true);
+ connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc);
+ }
+
+ menu->addSeparator();
+
+ {
+ QMenu* submenu = menu->addMenu("Screen size");
+
+ for (int i = 0; i < 4; i++)
+ {
+ int data = i+1;
+ actScreenSize[i] = submenu->addAction(QString("%1x").arg(data));
+ actScreenSize[i]->setData(QVariant(data));
+ connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize);
+ }
+ }
+ {
+ QMenu* submenu = menu->addMenu("Screen rotation");
+ grpScreenRotation = new QActionGroup(submenu);
+
+ for (int i = 0; i < 4; i++)
+ {
+ int data = i*90;
+ actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data));
+ actScreenRotation[i]->setActionGroup(grpScreenRotation);
+ actScreenRotation[i]->setData(QVariant(i));
+ actScreenRotation[i]->setCheckable(true);
+ }
+
+ connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation);
+ }
+ {
+ QMenu* submenu = menu->addMenu("Screen gap");
+ grpScreenGap = new QActionGroup(submenu);
+
+ const int screengap[] = {0, 1, 8, 64, 90, 128};
+
+ for (int i = 0; i < 6; i++)
+ {
+ int data = screengap[i];
+ actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data));
+ actScreenGap[i]->setActionGroup(grpScreenGap);
+ actScreenGap[i]->setData(QVariant(data));
+ actScreenGap[i]->setCheckable(true);
+ }
+
+ connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap);
+ }
+ {
+ QMenu* submenu = menu->addMenu("Screen layout");
+ grpScreenLayout = new QActionGroup(submenu);
+
+ const char* screenlayout[] = {"Natural", "Vertical", "Horizontal"};
+
+ for (int i = 0; i < 3; i++)
+ {
+ actScreenLayout[i] = submenu->addAction(QString(screenlayout[i]));
+ actScreenLayout[i]->setActionGroup(grpScreenLayout);
+ actScreenLayout[i]->setData(QVariant(i));
+ actScreenLayout[i]->setCheckable(true);
+ }
+
+ connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout);
+ }
+ {
+ QMenu* submenu = menu->addMenu("Screen sizing");
+ grpScreenSizing = new QActionGroup(submenu);
+
+ const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto"};
+
+ for (int i = 0; i < 4; i++)
+ {
+ actScreenSizing[i] = submenu->addAction(QString(screensizing[i]));
+ actScreenSizing[i]->setActionGroup(grpScreenSizing);
+ actScreenSizing[i]->setData(QVariant(i));
+ actScreenSizing[i]->setCheckable(true);
+ }
+
+ connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing);
+
+ submenu->addSeparator();
+
+ actIntegerScaling = submenu->addAction("Force integer scaling");
+ actIntegerScaling->setCheckable(true);
+ connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling);
+ }
+
+ actScreenFiltering = menu->addAction("Screen filtering");
+ actScreenFiltering->setCheckable(true);
+ connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering);
+
+ actShowOSD = menu->addAction("Show OSD");
+ actShowOSD->setCheckable(true);
+ connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD);
+
+ menu->addSeparator();
+
+ actLimitFramerate = menu->addAction("Limit framerate");
+ actLimitFramerate->setCheckable(true);
+ connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate);
+
+ actAudioSync = menu->addAction("Audio sync");
+ actAudioSync->setCheckable(true);
+ connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync);
+ }
+ setMenuBar(menubar);
+
+ resize(Config::WindowWidth, Config::WindowHeight);
+
+ show();
+ createScreenPanel();
+
+ for (int i = 0; i < 9; i++)
+ {
+ actSaveState[i]->setEnabled(false);
+ actLoadState[i]->setEnabled(false);
+ }
+ actUndoStateLoad->setEnabled(false);
+
+ actPause->setEnabled(false);
+ actReset->setEnabled(false);
+ actStop->setEnabled(false);
+
+
+ actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM != 0);
+
+ actScreenRotation[Config::ScreenRotation]->setChecked(true);
+
+ for (int i = 0; i < 6; i++)
+ {
+ if (actScreenGap[i]->data().toInt() == Config::ScreenGap)
+ {
+ actScreenGap[i]->setChecked(true);
+ break;
+ }
+ }
+
+ actScreenLayout[Config::ScreenLayout]->setChecked(true);
+ actScreenSizing[Config::ScreenSizing]->setChecked(true);
+ actIntegerScaling->setChecked(Config::IntegerScaling != 0);
+
+ actScreenFiltering->setChecked(Config::ScreenFilter != 0);
+ actShowOSD->setChecked(Config::ShowOSD != 0);
+
+ actLimitFramerate->setChecked(Config::LimitFPS != 0);
+ actAudioSync->setChecked(Config::AudioSync != 0);
+}
+
+MainWindow::~MainWindow()
+{
+}
+
+void MainWindow::createScreenPanel()
+{
+ hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0);
+
+ if (hasOGL)
+ {
+ ScreenPanelGL* panelGL = new ScreenPanelGL(this);
+ panelGL->show();
+
+ if (!panelGL->isValid())
+ hasOGL = false;
+ else
+ {
+ QSurfaceFormat fmt = panelGL->format();
+ if (fmt.majorVersion() < 3 || (fmt.majorVersion() == 3 && fmt.minorVersion() < 2))
+ hasOGL = false;
+ }
+
+ if (!hasOGL)
+ delete panelGL;
+ else
+ panel = panelGL;
+ }
+
+ if (!hasOGL)
+ {
+ panel = new ScreenPanelNative(this);
+ panel->show();
+ }
+
+ setCentralWidget(panel);
+ connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged()));
+ emit screenLayoutChange();
+}
+
+QOpenGLContext* MainWindow::getOGLContext()
+{
+ if (!hasOGL) return nullptr;
+
+ QOpenGLWidget* glpanel = (QOpenGLWidget*)panel;
+ return glpanel->context();
+}
+
+void MainWindow::resizeEvent(QResizeEvent* event)
+{
+ int w = event->size().width();
+ int h = event->size().height();
+
+ Config::WindowWidth = w;
+ Config::WindowHeight = h;
+
+ // TODO: detect when the window gets maximized!
+}
+
+void MainWindow::keyPressEvent(QKeyEvent* event)
+{
+ if (event->isAutoRepeat()) return;
+
+ Input::KeyPress(event);
+}
+
+void MainWindow::keyReleaseEvent(QKeyEvent* event)
+{
+ if (event->isAutoRepeat()) return;
+
+ Input::KeyRelease(event);
+}
+
+
+void MainWindow::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (!event->mimeData()->hasUrls()) return;
+
+ QList<QUrl> urls = event->mimeData()->urls();
+ if (urls.count() > 1) return; // not handling more than one file at once
+
+ QString filename = urls.at(0).toLocalFile();
+ QString ext = filename.right(3);
+
+ if (ext == "nds" || ext == "srl" || (ext == "gba" && RunningSomething))
+ event->acceptProposedAction();
+}
+
+void MainWindow::dropEvent(QDropEvent* event)
+{
+ if (!event->mimeData()->hasUrls()) return;
+
+ QList<QUrl> urls = event->mimeData()->urls();
+ if (urls.count() > 1) return; // not handling more than one file at once
+
+ emuThread->emuPause();
+
+ QString filename = urls.at(0).toLocalFile();
+ QString ext = filename.right(3);
+
+ char _filename[1024];
+ strncpy(_filename, filename.toStdString().c_str(), 1023); _filename[1023] = '\0';
+
+ int slot; int res;
+ if (ext == "gba")
+ {
+ slot = 1;
+ res = Frontend::LoadROM(_filename, Frontend::ROMSlot_GBA);
+ }
+ else
+ {
+ slot = 0;
+ res = Frontend::LoadROM(_filename, Frontend::ROMSlot_NDS);
+ }
+
+ if (res != Frontend::Load_OK)
+ {
+ QMessageBox::critical(this,
+ "melonDS",
+ loadErrorStr(res));
+ emuThread->emuUnpause();
+ }
+ else if (slot == 1)
+ {
+ // checkme
+ emuThread->emuUnpause();
+ }
+ else
+ {
+ emuThread->emuRun();
+ }
+}
+
+
+QString MainWindow::loadErrorStr(int error)
+{
+ switch (error)
+ {
+ case Frontend::Load_BIOS9Missing:
+ return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings.";
+ case Frontend::Load_BIOS9Bad:
+ return "DS ARM9 BIOS is not a valid BIOS dump.";
+
+ case Frontend::Load_BIOS7Missing:
+ return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings.";
+ case Frontend::Load_BIOS7Bad:
+ return "DS ARM7 BIOS is not a valid BIOS dump.";
+
+ case Frontend::Load_FirmwareMissing:
+ return "DS firmware was not found or could not be accessed. Check your emu settings.";
+ case Frontend::Load_FirmwareBad:
+ return "DS firmware is not a valid firmware dump.";
+ case Frontend::Load_FirmwareNotBootable:
+ return "DS firmware is not bootable.";
+
+ case Frontend::Load_DSiBIOS9Missing:
+ return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings.";
+ case Frontend::Load_DSiBIOS9Bad:
+ return "DSi ARM9 BIOS is not a valid BIOS dump.";
+
+ case Frontend::Load_DSiBIOS7Missing:
+ return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings.";
+ case Frontend::Load_DSiBIOS7Bad:
+ return "DSi ARM7 BIOS is not a valid BIOS dump.";
+
+ case Frontend::Load_DSiNANDMissing:
+ return "DSi NAND was not found or could not be accessed. Check your emu settings.";
+ case Frontend::Load_DSiNANDBad:
+ return "DSi NAND is not a valid NAND dump.";
+
+ case Frontend::Load_ROMLoadError:
+ return "Failed to load the ROM. Make sure the file is accessible and isn't used by another application.";
+
+ default: return "Unknown error during launch; smack Arisotura.";
+ }
+}
+
+
+void MainWindow::onOpenFile()
+{
+ emuThread->emuPause();
+
+ QString filename = QFileDialog::getOpenFileName(this,
+ "Open ROM",
+ Config::LastROMFolder,
+ "DS ROMs (*.nds *.dsi *.srl);;GBA ROMs (*.gba);;Any file (*.*)");
+ if (filename.isEmpty())
+ {
+ emuThread->emuUnpause();
+ return;
+ }
+
+ // TODO: validate the input file!!
+ // * check that it is a proper ROM
+ // * ensure the binary offsets are sane
+ // * etc
+
+ // this shit is stupid
+ char file[1024];
+ strncpy(file, filename.toStdString().c_str(), 1023); file[1023] = '\0';
+
+ int pos = strlen(file)-1;
+ while (file[pos] != '/' && file[pos] != '\\' && pos > 0) pos--;
+ strncpy(Config::LastROMFolder, file, pos);
+ Config::LastROMFolder[pos] = '\0';
+ char* ext = &file[strlen(file)-3];
+
+ int slot; int res;
+ if (!strcasecmp(ext, "gba"))
+ {
+ slot = 1;
+ res = Frontend::LoadROM(file, Frontend::ROMSlot_GBA);
+ }
+ else
+ {
+ slot = 0;
+ res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS);
+ }
+
+ if (res != Frontend::Load_OK)
+ {
+ QMessageBox::critical(this,
+ "melonDS",
+ loadErrorStr(res));
+ emuThread->emuUnpause();
+ }
+ else if (slot == 1)
+ {
+ // checkme
+ emuThread->emuUnpause();
+ }
+ else
+ {
+ emuThread->emuRun();
+ }
+}
+
+void MainWindow::onBootFirmware()
+{
+ // TODO: check the whole GBA cart shito
+
+ emuThread->emuPause();
+
+ int res = Frontend::LoadBIOS();
+ if (res != Frontend::Load_OK)
+ {
+ QMessageBox::critical(this,
+ "melonDS",
+ loadErrorStr(res));
+ emuThread->emuUnpause();
+ }
+ else
+ {
+ emuThread->emuRun();
+ }
+}
+
+void MainWindow::onSaveState()
+{
+ int slot = ((QAction*)sender())->data().toInt();
+
+ emuThread->emuPause();
+
+ char filename[1024];
+ if (slot > 0)
+ {
+ Frontend::GetSavestateName(slot, filename, 1024);
+ }
+ else
+ {
+ // TODO: specific 'last directory' for savestate files?
+ QString qfilename = QFileDialog::getSaveFileName(this,
+ "Save state",
+ Config::LastROMFolder,
+ "melonDS savestates (*.mln);;Any file (*.*)");
+ if (qfilename.isEmpty())
+ {
+ emuThread->emuUnpause();
+ return;
+ }
+
+ strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0';
+ }
+
+ if (Frontend::SaveState(filename))
+ {
+ char msg[64];
+ if (slot > 0) sprintf(msg, "State saved to slot %d", slot);
+ else sprintf(msg, "State saved to file");
+ OSD::AddMessage(0, msg);
+
+ actLoadState[slot]->setEnabled(true);
+ }
+ else
+ {
+ OSD::AddMessage(0xFFA0A0, "State save failed");
+ }
+
+ emuThread->emuUnpause();
+}
+
+void MainWindow::onLoadState()
+{
+ int slot = ((QAction*)sender())->data().toInt();
+
+ emuThread->emuPause();
+
+ char filename[1024];
+ if (slot > 0)
+ {
+ Frontend::GetSavestateName(slot, filename, 1024);
+ }
+ else
+ {
+ // TODO: specific 'last directory' for savestate files?
+ QString qfilename = QFileDialog::getOpenFileName(this,
+ "Load state",
+ Config::LastROMFolder,
+ "melonDS savestates (*.ml*);;Any file (*.*)");
+ if (qfilename.isEmpty())
+ {
+ emuThread->emuUnpause();
+ return;
+ }
+
+ strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0';
+ }
+
+ if (!Platform::FileExists(filename))
+ {
+ char msg[64];
+ if (slot > 0) sprintf(msg, "State slot %d is empty", slot);
+ else sprintf(msg, "State file does not exist");
+ OSD::AddMessage(0xFFA0A0, msg);
+
+ emuThread->emuUnpause();
+ return;
+ }
+
+ if (Frontend::LoadState(filename))
+ {
+ char msg[64];
+ if (slot > 0) sprintf(msg, "State loaded from slot %d", slot);
+ else sprintf(msg, "State loaded from file");
+ OSD::AddMessage(0, msg);
+ }
+ else
+ {
+ OSD::AddMessage(0xFFA0A0, "State load failed");
+ }
+
+ emuThread->emuUnpause();
+}
+
+void MainWindow::onUndoStateLoad()
+{
+ emuThread->emuPause();
+ Frontend::UndoStateLoad();
+ emuThread->emuUnpause();
+
+ OSD::AddMessage(0, "State load undone");
+}
+
+void MainWindow::onQuit()
+{
+ QApplication::quit();
+}
+
+
+void MainWindow::onPause(bool checked)
+{
+ if (!RunningSomething) return;
+
+ if (checked)
+ {
+ emuThread->emuPause();
+ OSD::AddMessage(0, "Paused");
+ }
+ else
+ {
+ emuThread->emuUnpause();
+ OSD::AddMessage(0, "Resumed");
+ }
+}
+
+void MainWindow::onReset()
+{
+ if (!RunningSomething) return;
+
+ emuThread->emuPause();
+
+ actUndoStateLoad->setEnabled(false);
+
+ int res = Frontend::Reset();
+ if (res != Frontend::Load_OK)
+ {
+ QMessageBox::critical(this,
+ "melonDS",
+ loadErrorStr(res));
+ emuThread->emuUnpause();
+ }
+ else
+ {
+ OSD::AddMessage(0, "Reset");
+ emuThread->emuRun();
+ }
+}
+
+void MainWindow::onStop()
+{
+ if (!RunningSomething) return;
+
+ emuThread->emuPause();
+ NDS::Stop();
+}
+
+
+void MainWindow::onOpenEmuSettings()
+{
+ EmuSettingsDialog::openDlg(this);
+}
+
+void MainWindow::onOpenInputConfig()
+{
+ emuThread->emuPause();
+
+ InputConfigDialog* dlg = InputConfigDialog::openDlg(this);
+ connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished);
+}
+
+void MainWindow::onInputConfigFinished(int res)
+{
+ emuThread->emuUnpause();
+}
+
+void MainWindow::onOpenVideoSettings()
+{
+ VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this);
+ connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings);
+}
+
+void MainWindow::onOpenAudioSettings()
+{
+ AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this);
+ connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished);
+}
+
+void MainWindow::onAudioSettingsFinished(int res)
+{
+ if (Config::MicInputType == 3)
+ {
+ micLoadWav(Config::MicWavPath);
+ Frontend::Mic_SetExternalBuffer(micWavBuffer, micWavLength);
+ }
+ else
+ {
+ delete[] micWavBuffer;
+ micWavBuffer = nullptr;
+
+ if (Config::MicInputType == 1)
+ Frontend::Mic_SetExternalBuffer(micExtBuffer, sizeof(micExtBuffer)/sizeof(s16));
+ else
+ Frontend::Mic_SetExternalBuffer(NULL, 0);
+ }
+}
+
+void MainWindow::onOpenWifiSettings()
+{
+ WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this);
+ connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished);
+}
+
+void MainWindow::onWifiSettingsFinished(int res)
+{
+ emuThread->emuPause();
+
+ if (Wifi::MPInited)
+ {
+ Platform::MP_DeInit();
+ Platform::MP_Init();
+ }
+
+ Platform::LAN_DeInit();
+ Platform::LAN_Init();
+
+ emuThread->emuUnpause();
+}
+
+void MainWindow::onChangeSavestateSRAMReloc(bool checked)
+{
+ Config::SavestateRelocSRAM = checked?1:0;
+}
+
+void MainWindow::onChangeScreenSize()
+{
+ int factor = ((QAction*)sender())->data().toInt();
+
+ bool isHori = (Config::ScreenRotation == 1 || Config::ScreenRotation == 3);
+ int gap = Config::ScreenGap;
+
+ int w = 256*factor;
+ int h = 192*factor;
+
+ QSize diff = size() - panel->size();
+
+ if (Config::ScreenLayout == 0) // natural
+ {
+ if (isHori)
+ resize(QSize(h+gap+h, w) + diff);
+ else
+ resize(QSize(w, h+gap+h) + diff);
+ }
+ else if (Config::ScreenLayout == 1) // vertical
+ {
+ if (isHori)
+ resize(QSize(h, w+gap+w) + diff);
+ else
+ resize(QSize(w, h+gap+h) + diff);
+ }
+ else // horizontal
+ {
+ if (isHori)
+ resize(QSize(h+gap+h, w) + diff);
+ else
+ resize(QSize(w+gap+w, h) + diff);
+ }
+}
+
+void MainWindow::onChangeScreenRotation(QAction* act)
+{
+ int rot = act->data().toInt();
+ Config::ScreenRotation = rot;
+
+ emit screenLayoutChange();
+}
+
+void MainWindow::onChangeScreenGap(QAction* act)
+{
+ int gap = act->data().toInt();
+ Config::ScreenGap = gap;
+
+ emit screenLayoutChange();
+}
+
+void MainWindow::onChangeScreenLayout(QAction* act)
+{
+ int layout = act->data().toInt();
+ Config::ScreenLayout = layout;
+
+ emit screenLayoutChange();
+}
+
+void MainWindow::onChangeScreenSizing(QAction* act)
+{
+ int sizing = act->data().toInt();
+ Config::ScreenSizing = sizing;
+
+ emit screenLayoutChange();
+}
+
+void MainWindow::onChangeIntegerScaling(bool checked)
+{
+ Config::IntegerScaling = checked?1:0;
+
+ emit screenLayoutChange();
+}
+
+void MainWindow::onChangeScreenFiltering(bool checked)
+{
+ Config::ScreenFilter = checked?1:0;
+}
+
+void MainWindow::onChangeShowOSD(bool checked)
+{
+ Config::ShowOSD = checked?1:0;
+}
+
+void MainWindow::onChangeLimitFramerate(bool checked)
+{
+ Config::LimitFPS = checked?1:0;
+}
+
+void MainWindow::onChangeAudioSync(bool checked)
+{
+ Config::AudioSync = checked?1:0;
+}
+
+
+void MainWindow::onTitleUpdate(QString title)
+{
+ setWindowTitle(title);
+}
+
+void MainWindow::onEmuStart()
+{
+ for (int i = 1; i < 9; i++)
+ {
+ actSaveState[i]->setEnabled(true);
+ actLoadState[i]->setEnabled(Frontend::SavestateExists(i));
+ }
+ actSaveState[0]->setEnabled(true);
+ actLoadState[0]->setEnabled(true);
+ actUndoStateLoad->setEnabled(false);
+
+ actPause->setEnabled(true);
+ actPause->setChecked(false);
+ actReset->setEnabled(true);
+ actStop->setEnabled(true);
+}
+
+void MainWindow::onEmuStop()
+{
+ emuThread->emuPause();
+
+ for (int i = 0; i < 9; i++)
+ {
+ actSaveState[i]->setEnabled(false);
+ actLoadState[i]->setEnabled(false);
+ }
+ actUndoStateLoad->setEnabled(false);
+
+ actPause->setEnabled(false);
+ actReset->setEnabled(false);
+ actStop->setEnabled(false);
+}
+
+void MainWindow::onUpdateVideoSettings(bool glchange)
+{
+ if (glchange)
+ {
+ emuThread->emuPause();
+
+ if (hasOGL) emuThread->deinitOpenGL();
+ delete panel;
+ createScreenPanel();
+ connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(update()));
+ if (hasOGL) emuThread->initOpenGL();
+ }
+
+ videoSettingsDirty = true;
+
+ if (glchange)
+ emuThread->emuUnpause();
+}
+
+
+void emuStop()
+{
+ RunningSomething = false;
+
+ Frontend::UnloadROM(Frontend::ROMSlot_NDS);
+ Frontend::UnloadROM(Frontend::ROMSlot_GBA);
+
+ emit emuThread->windowEmuStop();
+
+ OSD::AddMessage(0xFFC040, "Shutdown");
+}
+
+
+
+int main(int argc, char** argv)
+{
+ srand(time(NULL));
+
+ printf("melonDS " MELONDS_VERSION "\n");
+ printf(MELONDS_URL "\n");
+
+ Platform::Init(argc, argv);
+
+ QApplication melon(argc, argv);
+ melon.setWindowIcon(QIcon(":/melon-icon"));
+
+ // http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+
+ if (SDL_Init(SDL_INIT_HAPTIC) < 0)
+ {
+ printf("SDL couldn't init rumble\n");
+ }
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0)
+ {
+ QMessageBox::critical(NULL, "melonDS", "SDL shat itself :(");
+ return 1;
+ }
+
+ SDL_JoystickEventState(SDL_ENABLE);
+
+ Config::Load();
+
+#define SANITIZE(var, min, max) { if (var < min) var = min; else if (var > max) var = max; }
+ SANITIZE(Config::ConsoleType, 0, 1);
+ SANITIZE(Config::_3DRenderer, 0, 1);
+ SANITIZE(Config::ScreenVSyncInterval, 1, 20);
+ SANITIZE(Config::GL_ScaleFactor, 1, 16);
+ SANITIZE(Config::AudioVolume, 0, 256);
+ SANITIZE(Config::MicInputType, 0, 3);
+ SANITIZE(Config::ScreenRotation, 0, 3);
+ SANITIZE(Config::ScreenGap, 0, 500);
+ SANITIZE(Config::ScreenLayout, 0, 2);
+ SANITIZE(Config::ScreenSizing, 0, 3);
+#undef SANITIZE
+
+ QSurfaceFormat format;
+ format.setDepthBufferSize(24);
+ format.setStencilBufferSize(8);
+ format.setVersion(3, 2);
+ format.setProfile(QSurfaceFormat::CoreProfile);
+ format.setSwapInterval(0);
+ QSurfaceFormat::setDefaultFormat(format);
+
+ audioSync = SDL_CreateCond();
+ audioSyncLock = SDL_CreateMutex();
+
+ audioFreq = 48000; // TODO: make configurable?
+ SDL_AudioSpec whatIwant, whatIget;
+ memset(&whatIwant, 0, sizeof(SDL_AudioSpec));
+ whatIwant.freq = audioFreq;
+ whatIwant.format = AUDIO_S16LSB;
+ whatIwant.channels = 2;
+ whatIwant.samples = 1024;
+ whatIwant.callback = audioCallback;
+ audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
+ if (!audioDevice)
+ {
+ printf("Audio init failed: %s\n", SDL_GetError());
+ }
+ else
+ {
+ audioFreq = whatIget.freq;
+ printf("Audio output frequency: %d Hz\n", audioFreq);
+ SDL_PauseAudioDevice(audioDevice, 1);
+ }
+
+ memset(&whatIwant, 0, sizeof(SDL_AudioSpec));
+ whatIwant.freq = 44100;
+ whatIwant.format = AUDIO_S16LSB;
+ whatIwant.channels = 1;
+ whatIwant.samples = 1024;
+ whatIwant.callback = micCallback;
+ micDevice = SDL_OpenAudioDevice(NULL, 1, &whatIwant, &whatIget, 0);
+ if (!micDevice)
+ {
+ printf("Mic init failed: %s\n", SDL_GetError());
+ }
+ else
+ {
+ SDL_PauseAudioDevice(micDevice, 1);
+ }
+
+
+ memset(micExtBuffer, 0, sizeof(micExtBuffer));
+ micExtBufferWritePos = 0;
+ micWavBuffer = nullptr;
+
+ Frontend::Init_ROM();
+ Frontend::Init_Audio(audioFreq);
+
+ if (Config::MicInputType == 1)
+ {
+ Frontend::Mic_SetExternalBuffer(micExtBuffer, sizeof(micExtBuffer)/sizeof(s16));
+ }
+ else if (Config::MicInputType == 3)
+ {
+ micLoadWav(Config::MicWavPath);
+ Frontend::Mic_SetExternalBuffer(micWavBuffer, micWavLength);
+ }
+
+ Input::JoystickID = Config::JoystickID;
+ Input::OpenJoystick();
+
+ mainWindow = new MainWindow();
+
+ emuThread = new EmuThread();
+ emuThread->start();
+ emuThread->emuPause();
+
+ if (argc > 1)
+ {
+ char* file = argv[1];
+ char* ext = &file[strlen(file)-3];
+
+ if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl"))
+ {
+ int res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS);
+
+ if (res == Frontend::Load_OK)
+ {
+ if (argc > 2)
+ {
+ file = argv[2];
+ ext = &file[strlen(file)-3];
+
+ if (!strcasecmp(ext, "gba"))
+ {
+ Frontend::LoadROM(file, Frontend::ROMSlot_GBA);
+ }
+ }
+
+ emuThread->emuRun();
+ }
+ }
+ }
+
+ int ret = melon.exec();
+
+ emuThread->emuStop();
+ emuThread->wait();
+ delete emuThread;
+
+ Input::CloseJoystick();
+
+ if (audioDevice) SDL_CloseAudioDevice(audioDevice);
+ if (micDevice) SDL_CloseAudioDevice(micDevice);
+
+ SDL_DestroyCond(audioSync);
+ SDL_DestroyMutex(audioSyncLock);
+
+ if (micWavBuffer) delete[] micWavBuffer;
+
+ Config::Save();
+
+ SDL_Quit();
+ Platform::DeInit();
+ return ret;
+}
+
+#ifdef __WIN32__
+
+#include <windows.h>
+
+int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdshow)
+{
+ int argc = 0;
+ wchar_t** argv_w = CommandLineToArgvW(GetCommandLineW(), &argc);
+ char* nullarg = "";
+
+ char** argv = new char*[argc];
+ for (int i = 0; i < argc; i++)
+ {
+ if (!argv_w) { argv[i] = nullarg; continue; }
+ int len = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, NULL, 0, NULL, NULL);
+ if (len < 1) { argv[i] = nullarg; continue; }
+ argv[i] = new char[len];
+ int res = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, argv[i], len, NULL, NULL);
+ if (res != len) { delete[] argv[i]; argv[i] = nullarg; }
+ }
+
+ if (argv_w) LocalFree(argv_w);
+
+ if (AttachConsole(ATTACH_PARENT_PROCESS))
+ {
+ freopen("CONOUT$", "w", stdout);
+ freopen("CONOUT$", "w", stderr);
+ printf("\n");
+ }
+
+ int ret = main(argc, argv);
+
+ printf("\n\n>");
+
+ for (int i = 0; i < argc; i++) if (argv[i] != nullarg) delete[] argv[i];
+ delete[] argv;
+
+ return ret;
+}
+
+#endif