/* Copyright 2016-2023 melonDS team 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #include #include #include #ifndef APPLE #include #endif #endif #include #include "OpenGLSupport.h" #include "duckstation/gl/context.h" #include "main.h" #include "Input.h" #include "CheatsDialog.h" #include "DateTimeDialog.h" #include "EmuSettingsDialog.h" #include "InputConfig/InputConfigDialog.h" #include "VideoSettingsDialog.h" #include "CameraSettingsDialog.h" #include "AudioSettingsDialog.h" #include "FirmwareSettingsDialog.h" #include "PathSettingsDialog.h" #include "MPSettingsDialog.h" #include "WifiSettingsDialog.h" #include "InterfaceSettingsDialog.h" #include "ROMInfoDialog.h" #include "RAMInfoDialog.h" #include "TitleManagerDialog.h" #include "PowerManagement/PowerManagementDialog.h" #include "AudioInOut.h" #include "types.h" #include "version.h" #include "FrontendUtil.h" #include "OSD.h" #include "Args.h" #include "NDS.h" #include "NDSCart.h" #include "GBACart.h" #include "GPU.h" #include "SPU.h" #include "Wifi.h" #include "Platform.h" #include "LocalMP.h" #include "Config.h" #include "RTC.h" #include "DSi.h" #include "DSi_I2C.h" #include "GPU3D_Soft.h" #include "GPU3D_OpenGL.h" #include "Savestate.h" #include "main_shaders.h" #include "ROMManager.h" #include "ArchiveUtil.h" #include "CameraManager.h" #include "CLI.h" // TODO: uniform variable spelling using namespace melonDS; QString NdsRomMimeType = "application/x-nintendo-ds-rom"; QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; QString GbaRomMimeType = "application/x-gba-rom"; QStringList GbaRomExtensions { ".gba", ".agb" }; // This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). QStringList ArchiveMimeTypes { #ifdef ARCHIVE_SUPPORT_ENABLED "application/zip", "application/x-7z-compressed", "application/vnd.rar", // *.rar "application/x-tar", "application/x-compressed-tar", // *.tar.gz "application/x-xz-compressed-tar", "application/x-bzip-compressed-tar", "application/x-lz4-compressed-tar", "application/x-zstd-compressed-tar", "application/x-tarz", // *.tar.Z "application/x-lzip-compressed-tar", "application/x-lzma-compressed-tar", "application/x-lrzip-compressed-tar", "application/x-tzo", // *.tar.lzo #endif }; QStringList ArchiveExtensions { #ifdef ARCHIVE_SUPPORT_ENABLED ".zip", ".7z", ".rar", ".tar", ".tar.gz", ".tgz", ".tar.xz", ".txz", ".tar.bz2", ".tbz2", ".tar.lz4", ".tlz4", ".tar.zst", ".tzst", ".tar.Z", ".taz", ".tar.lz", ".tar.lzma", ".tlz", ".tar.lrz", ".tlrz", ".tar.lzo", ".tzo" #endif }; bool RunningSomething; MainWindow* mainWindow; EmuThread* emuThread; int autoScreenSizing = 0; int videoRenderer; bool videoSettingsDirty; CameraManager* camManager[2]; bool camStarted[2]; //extern int AspectRatiosNum; EmuThread::EmuThread(QObject* parent) : QThread(parent) { EmuStatus = emuStatus_Exit; EmuRunning = emuStatus_Paused; EmuPauseStack = EmuPauseStackRunning; RunningSomething = false; connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); 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(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged())); connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); auto glPanel = dynamic_cast(mainWindow->panel); if (glPanel) glPanel->transferLayout(this); } std::unique_ptr EmuThread::CreateConsole( std::unique_ptr&& ndscart, std::unique_ptr&& gbacart ) noexcept { auto arm7bios = ROMManager::LoadARM7BIOS(); if (!arm7bios) return nullptr; auto arm9bios = ROMManager::LoadARM9BIOS(); if (!arm9bios) return nullptr; auto firmware = ROMManager::LoadFirmware(Config::ConsoleType); if (!firmware) return nullptr; #ifdef JIT_ENABLED JITArgs jitargs { static_cast(Config::JIT_MaxBlockSize), Config::JIT_LiteralOptimisations, Config::JIT_BranchOptimisations, Config::JIT_FastMemory, }; #endif #ifdef GDBSTUB_ENABLED GDBArgs gdbargs { static_cast(Config::GdbPortARM7), static_cast(Config::GdbPortARM9), Config::GdbARM7BreakOnStartup, Config::GdbARM9BreakOnStartup, }; #endif NDSArgs ndsargs { std::move(ndscart), std::move(gbacart), *arm9bios, *arm7bios, std::move(*firmware), #ifdef JIT_ENABLED Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt, #else std::nullopt, #endif static_cast(Config::AudioBitDepth), static_cast(Config::AudioInterp), #ifdef GDBSTUB_ENABLED Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt, #else std::nullopt, #endif }; if (Config::ConsoleType == 1) { auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); if (!arm7ibios) return nullptr; auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); if (!arm9ibios) return nullptr; auto nand = ROMManager::LoadNAND(*arm7ibios); if (!nand) return nullptr; auto sdcard = ROMManager::LoadDSiSDCard(); DSiArgs args { std::move(ndsargs), *arm9ibios, *arm7ibios, std::move(*nand), std::move(sdcard), Config::DSiFullBIOSBoot, }; args.GBAROM = nullptr; return std::make_unique(std::move(args)); } return std::make_unique(std::move(ndsargs)); } bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept { // Let's get the cart we want to use; // if we wnat to keep the cart, we'll eject it from the existing console first. std::unique_ptr nextndscart; if (std::holds_alternative(ndsargs)) { // If we want to keep the existing cart (if any)... nextndscart = NDS ? NDS->EjectCart() : nullptr; ndsargs = {}; } else if (const auto ptr = std::get_if>(&ndsargs)) { nextndscart = std::move(*ptr); ndsargs = {}; } if (nextndscart && nextndscart->Type() == NDSCart::Homebrew) { // Load DLDISDCard will return nullopt if the SD card is disabled; // SetSDCard will accept nullopt, which means no SD card auto& homebrew = static_cast(*nextndscart); homebrew.SetSDCard(ROMManager::LoadDLDISDCard()); } std::unique_ptr nextgbacart; if (std::holds_alternative(gbaargs)) { nextgbacart = NDS ? NDS->EjectGBACart() : nullptr; } else if (const auto ptr = std::get_if>(&gbaargs)) { nextgbacart = std::move(*ptr); gbaargs = {}; } if (!NDS || NDS->ConsoleType != Config::ConsoleType) { // If we're switching between DS and DSi mode, or there's no console... // To ensure the destructor is called before a new one is created, // as the presence of global signal handlers still complicates things a bit NDS = nullptr; NDS::Current = nullptr; NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart)); NDS->Reset(); NDS::Current = NDS.get(); return NDS != nullptr; } auto arm9bios = ROMManager::LoadARM9BIOS(); if (!arm9bios) return false; auto arm7bios = ROMManager::LoadARM7BIOS(); if (!arm7bios) return false; auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType); if (!firmware) return false; if (NDS->ConsoleType == 1) { // If the console we're updating is a DSi... DSi& dsi = static_cast(*NDS); auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); if (!arm9ibios) return false; auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); if (!arm7ibios) return false; auto nandimage = ROMManager::LoadNAND(*arm7ibios); if (!nandimage) return false; auto dsisdcard = ROMManager::LoadDSiSDCard(); dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot); dsi.ARM7iBIOS = *arm7ibios; dsi.ARM9iBIOS = *arm9ibios; dsi.SetNAND(std::move(*nandimage)); dsi.SetSDCard(std::move(dsisdcard)); // We're moving the optional, not the card // (inserting std::nullopt here is okay, it means no card) dsi.EjectGBACart(); } if (NDS->ConsoleType == 0) { NDS->SetGBACart(std::move(nextgbacart)); } #ifdef JIT_ENABLED JITArgs jitargs { static_cast(Config::JIT_MaxBlockSize), Config::JIT_LiteralOptimisations, Config::JIT_BranchOptimisations, Config::JIT_FastMemory, }; NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt); #endif NDS->SetARM7BIOS(*arm7bios); NDS->SetARM9BIOS(*arm9bios); NDS->SetFirmware(std::move(*firmware)); NDS->SetNDSCart(std::move(nextndscart)); NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); NDS->SPU.SetDegrade10Bit(static_cast(Config::AudioBitDepth)); NDS::Current = NDS.get(); return true; } void EmuThread::updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix) { screenSettingsLock.lock(); if (lastScreenWidth != windowInfo.surface_width || lastScreenHeight != windowInfo.surface_height) { if (oglContext) oglContext->ResizeSurface(windowInfo.surface_width, windowInfo.surface_height); lastScreenWidth = windowInfo.surface_width; lastScreenHeight = windowInfo.surface_height; } this->filter = filter; this->windowInfo = windowInfo; this->numScreens = numScreens; memcpy(this->screenKind, screenKind, sizeof(int)*numScreens); memcpy(this->screenMatrix, screenMatrix, sizeof(float)*numScreens*6); screenSettingsLock.unlock(); } void EmuThread::initOpenGL() { GL::Context* windowctx = mainWindow->getOGLContext(); oglContext = windowctx; oglContext->MakeCurrent(); OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); GLuint pid = screenShaderProgram[2]; glBindAttribLocation(pid, 0, "vPosition"); glBindAttribLocation(pid, 1, "vTexcoord"); glBindFragDataLocation(pid, 0, "oColor"); OpenGL::LinkShaderProgram(screenShaderProgram); glUseProgram(pid); glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); // to prevent bleeding between both parts of the screen // with bilinear filtering enabled const int paddedHeight = 192*2+2; const float padPixels = 1.f / paddedHeight; const float vertices[] = { 0.f, 0.f, 0.f, 0.f, 0.f, 192.f, 0.f, 0.5f - padPixels, 256.f, 192.f, 1.f, 0.5f - padPixels, 0.f, 0.f, 0.f, 0.f, 256.f, 192.f, 1.f, 0.5f - padPixels, 256.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.5f + padPixels, 0.f, 192.f, 0.f, 1.f, 256.f, 192.f, 1.f, 1.f, 0.f, 0.f, 0.f, 0.5f + padPixels, 256.f, 192.f, 1.f, 1.f, 256.f, 0.f, 1.f, 0.5f + padPixels }; 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, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // fill the padding u8 zeroData[256*4*4]; memset(zeroData, 0, sizeof(zeroData)); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); OSD::Init(true); oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); } void EmuThread::deinitOpenGL() { glDeleteTextures(1, &screenTexture); glDeleteVertexArrays(1, &screenVertexArray); glDeleteBuffers(1, &screenVertexBuffer); OpenGL::DeleteShaderProgram(screenShaderProgram); OSD::DeInit(); oglContext->DoneCurrent(); oglContext = nullptr; lastScreenWidth = lastScreenHeight = -1; } void EmuThread::run() { u32 mainScreenPos[3]; Platform::FileHandle* file; UpdateConsole(nullptr, nullptr); // No carts are inserted when melonDS first boots mainScreenPos[0] = 0; mainScreenPos[1] = 0; mainScreenPos[2] = 0; autoScreenSizing = 0; videoSettingsDirty = false; if (mainWindow->hasOGL) { initOpenGL(); videoRenderer = Config::_3DRenderer; } else { videoRenderer = 0; } if (videoRenderer == 0) { // If we're using the software renderer... NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); } else { auto glrenderer = melonDS::GLRenderer::New(); glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); NDS->GPU.SetRenderer3D(std::move(glrenderer)); } Input::Init(); u32 nframes = 0; double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; double frameLimitError = 0.0; double lastMeasureTime = lastTime; u32 winUpdateCount = 0, winUpdateFreq = 1; u8 dsiVolumeLevel = 0x1F; file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); if (file) { RTC::StateData state; Platform::FileRead(&state, sizeof(state), 1, file); Platform::CloseFile(file); NDS->RTC.SetState(state); } char melontitle[100]; while (EmuRunning != emuStatus_Exit) { Input::Process(); if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) { EmuStatus = emuStatus_Running; if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; if (Input::HotkeyPressed(HK_SolarSensorDecrease)) { int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); if (level != -1) { char msg[64]; sprintf(msg, "Solar sensor level: %d", level); OSD::AddMessage(0, msg); } } if (Input::HotkeyPressed(HK_SolarSensorIncrease)) { int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); if (level != -1) { char msg[64]; sprintf(msg, "Solar sensor level: %d", level); OSD::AddMessage(0, msg); } } if (NDS->ConsoleType == 1) { DSi& dsi = static_cast(*NDS); double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; // Handle power button if (Input::HotkeyDown(HK_PowerButton)) { dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); } else if (Input::HotkeyReleased(HK_PowerButton)) { dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); } // Handle volume buttons if (Input::HotkeyDown(HK_VolumeUp)) { dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); } else if (Input::HotkeyReleased(HK_VolumeUp)) { dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); } if (Input::HotkeyDown(HK_VolumeDown)) { dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); } else if (Input::HotkeyReleased(HK_VolumeDown)) { dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); } dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); } // update render settings if needed // HACK: // once the fast forward hotkey is released, we need to update vsync // to the old setting again if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward)) { if (oglContext) { oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); videoRenderer = Config::_3DRenderer; } #ifdef OGLRENDERER_ENABLED else #endif { videoRenderer = 0; } videoRenderer = oglContext ? Config::_3DRenderer : 0; videoSettingsDirty = false; if (videoRenderer == 0) { // If we're using the software renderer... NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); } else { auto glrenderer = melonDS::GLRenderer::New(); glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); NDS->GPU.SetRenderer3D(std::move(glrenderer)); } } // 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 AudioInOut::MicProcess(*NDS); // auto screen layout if (Config::ScreenSizing == Frontend::screenSizing_Auto) { 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 = Frontend::screenSizing_Even; } else { if (mainScreenPos[0] == 1) guess = Frontend::screenSizing_EmphTop; else guess = Frontend::screenSizing_EmphBot; } if (guess != autoScreenSizing) { autoScreenSizing = guess; emit screenLayoutChange(); } } // emulate u32 nlines = NDS->RunFrame(); if (ROMManager::NDSSave) ROMManager::NDSSave->CheckFlush(); if (ROMManager::GBASave) ROMManager::GBASave->CheckFlush(); if (ROMManager::FirmwareSave) ROMManager::FirmwareSave->CheckFlush(); if (!oglContext) { FrontBufferLock.lock(); FrontBuffer = NDS->GPU.FrontBuffer; FrontBufferLock.unlock(); } else { FrontBuffer = NDS->GPU.FrontBuffer; drawScreenGL(); } #ifdef MELONCAP MelonCap::Update(); #endif // MELONCAP if (EmuRunning == emuStatus_Exit) break; winUpdateCount++; if (winUpdateCount >= winUpdateFreq && !oglContext) { emit windowUpdate(); winUpdateCount = 0; } bool fastforward = Input::HotkeyDown(HK_FastForward); if (fastforward && oglContext && Config::ScreenVSync) { oglContext->SetSwapInterval(0); } if (Config::DSiVolumeSync && NDS->ConsoleType == 1) { DSi& dsi = static_cast(*NDS); u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); if (volumeLevel != dsiVolumeLevel) { dsiVolumeLevel = volumeLevel; emit syncVolumeLevel(); } Config::AudioVolume = volumeLevel * (256.0 / 31.0); } if (Config::AudioSync && !fastforward) AudioInOut::AudioSync(*emuThread->NDS); double frametimeStep = nlines / (60.0 * 263.0); { bool limitfps = Config::LimitFPS && !fastforward; double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0; double curtime = SDL_GetPerformanceCounter() * perfCountsSec; frameLimitError += practicalFramelimit - (curtime - lastTime); if (frameLimitError < -practicalFramelimit) frameLimitError = -practicalFramelimit; if (frameLimitError > practicalFramelimit) frameLimitError = practicalFramelimit; if (round(frameLimitError * 1000.0) > 0.0) { SDL_Delay(round(frameLimitError * 1000.0)); double timeBeforeSleep = curtime; curtime = SDL_GetPerformanceCounter() * perfCountsSec; frameLimitError -= curtime - timeBeforeSleep; } lastTime = curtime; } nframes++; if (nframes >= 30) { double time = SDL_GetPerformanceCounter() * perfCountsSec; double dt = time - lastMeasureTime; lastMeasureTime = time; u32 fps = round(nframes / dt); nframes = 0; float fpstarget = 1.0/frametimeStep; winUpdateFreq = fps / (u32)round(fpstarget); if (winUpdateFreq < 1) winUpdateFreq = 1; int inst = Platform::InstanceID(); if (inst == 0) sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); else sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); changeWindowTitle(melontitle); } } else { // paused nframes = 0; lastTime = SDL_GetPerformanceCounter() * perfCountsSec; lastMeasureTime = lastTime; emit windowUpdate(); EmuStatus = EmuRunning; int inst = Platform::InstanceID(); if (inst == 0) sprintf(melontitle, "melonDS " MELONDS_VERSION); else sprintf(melontitle, "melonDS (%d)", inst+1); changeWindowTitle(melontitle); SDL_Delay(75); if (oglContext) drawScreenGL(); ContextRequestKind contextRequest = ContextRequest; if (contextRequest == contextRequest_InitGL) { initOpenGL(); ContextRequest = contextRequest_None; } else if (contextRequest == contextRequest_DeInitGL) { deinitOpenGL(); ContextRequest = contextRequest_None; } } } file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); if (file) { RTC::StateData state; NDS->RTC.GetState(state); Platform::FileWrite(&state, sizeof(state), 1, file); Platform::CloseFile(file); } EmuStatus = emuStatus_Exit; NDS::Current = nullptr; // nds is out of scope, so unique_ptr cleans it up for us } void EmuThread::changeWindowTitle(char* title) { emit windowTitleChange(QString(title)); } void EmuThread::emuRun() { EmuRunning = emuStatus_Running; EmuPauseStack = EmuPauseStackRunning; RunningSomething = true; // checkme emit windowEmuStart(); AudioInOut::Enable(); } void EmuThread::initContext() { ContextRequest = contextRequest_InitGL; while (ContextRequest != contextRequest_None); } void EmuThread::deinitContext() { ContextRequest = contextRequest_DeInitGL; while (ContextRequest != contextRequest_None); } void EmuThread::emuPause() { EmuPauseStack++; if (EmuPauseStack > EmuPauseStackPauseThreshold) return; PrevEmuStatus = EmuRunning; EmuRunning = emuStatus_Paused; while (EmuStatus != emuStatus_Paused); AudioInOut::Disable(); } void EmuThread::emuUnpause() { if (EmuPauseStack < EmuPauseStackPauseThreshold) return; EmuPauseStack--; if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; EmuRunning = PrevEmuStatus; AudioInOut::Enable(); } void EmuThread::emuStop() { EmuRunning = emuStatus_Exit; EmuPauseStack = EmuPauseStackRunning; AudioInOut::Disable(); } void EmuThread::emuFrameStep() { if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); EmuRunning = emuStatus_FrameStep; } bool EmuThread::emuIsRunning() { return EmuRunning == emuStatus_Running; } bool EmuThread::emuIsActive() { return (RunningSomething == 1); } void EmuThread::drawScreenGL() { if (!NDS) return; int w = windowInfo.surface_width; int h = windowInfo.surface_height; float factor = windowInfo.surface_scale; glBindFramebuffer(GL_FRAMEBUFFER, 0); glDisable(GL_DEPTH_TEST); glDepthMask(false); glDisable(GL_BLEND); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, w, h); glUseProgram(screenShaderProgram[2]); glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); int frontbuf = FrontBuffer; glActiveTexture(GL_TEXTURE0); #ifdef OGLRENDERER_ENABLED if (NDS->GPU.GetRenderer3D().Accelerated) { // hardware-accelerated render static_cast(NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf); } else #endif { // regular render glBindTexture(GL_TEXTURE_2D, screenTexture); if (NDS->GPU.Framebuffer[frontbuf][0] && NDS->GPU.Framebuffer[frontbuf][1]) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][0].get()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][1].get()); } } screenSettingsLock.lock(); GLint filter = this->filter ? 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); for (int i = 0; i < numScreens; i++) { glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3); } screenSettingsLock.unlock(); OSD::Update(); OSD::DrawGL(w, h); oglContext->SwapBuffers(); } static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) { return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) { return filename.endsWith(ext, cs); }); } static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames) { return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) { return mimetype.inherits(superTypeName); }); } static bool NdsRomByExtension(const QString& filename) { return FileExtensionInList(filename, NdsRomExtensions); } static bool GbaRomByExtension(const QString& filename) { return FileExtensionInList(filename, GbaRomExtensions); } static bool SupportedArchiveByExtension(const QString& filename) { return FileExtensionInList(filename, ArchiveExtensions); } static bool NdsRomByMimetype(const QMimeType& mimetype) { return mimetype.inherits(NdsRomMimeType); } static bool GbaRomByMimetype(const QMimeType& mimetype) { return mimetype.inherits(GbaRomMimeType); } static bool SupportedArchiveByMimetype(const QMimeType& mimetype) { return MimeTypeInList(mimetype, ArchiveMimeTypes); } static bool ZstdNdsRomByExtension(const QString& filename) { return filename.endsWith(".zst", Qt::CaseInsensitive) && NdsRomByExtension(filename.left(filename.size() - 4)); } static bool ZstdGbaRomByExtension(const QString& filename) { return filename.endsWith(".zst", Qt::CaseInsensitive) && GbaRomByExtension(filename.left(filename.size() - 4)); } static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive = false) { if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename)) return true; if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename)) return true; const auto matchmode = insideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchmode); return NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype) || SupportedArchiveByMimetype(mimetype); } void emuStop() { RunningSomething = false; emit emuThread->windowEmuStop(); } MelonApplication::MelonApplication(int& argc, char** argv) : QApplication(argc, argv) { #if !defined(Q_OS_APPLE) setWindowIcon(QIcon(":/melon-icon")); #if defined(Q_OS_UNIX) setDesktopFileName(QString("net.kuribo64.melonDS")); #endif #endif } bool MelonApplication::event(QEvent *event) { if (event->type() == QEvent::FileOpen) { QFileOpenEvent *openEvent = static_cast(event); emuThread->emuPause(); const QStringList file = mainWindow->splitArchivePath(openEvent->file(), true); if (!mainWindow->preloadROMs(file, {}, true)) emuThread->emuUnpause(); } return QApplication::event(event); } int main(int argc, char** argv) { srand(time(nullptr)); qputenv("QT_SCALE_FACTOR", "1"); printf("melonDS " MELONDS_VERSION "\n"); printf(MELONDS_URL "\n"); // easter egg - not worth checking other cases for something so dumb if (argc != 0 && (!strcasecmp(argv[0], "derpDS") || !strcasecmp(argv[0], "./derpDS"))) printf("did you just call me a derp???\n"); Platform::Init(argc, argv); MelonApplication melon(argc, argv); CLI::CommandLineOptions* options = CLI::ManageArgs(melon); // 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_JOYSTICK) < 0) { printf("SDL couldn't init joystick\n"); } if (SDL_Init(SDL_INIT_AUDIO) < 0) { const char* err = SDL_GetError(); QString errorStr = "Failed to initialize SDL. This could indicate an issue with your audio driver.\n\nThe error was: "; errorStr += err; QMessageBox::critical(NULL, "melonDS", errorStr); return 1; } SDL_JoystickEventState(SDL_ENABLE); SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_EnableScreenSaver(); SDL_DisableScreenSaver(); Config::Load(); #define SANITIZE(var, min, max) { var = std::clamp(var, min, max); } SANITIZE(Config::ConsoleType, 0, 1); #ifdef OGLRENDERER_ENABLED SANITIZE(Config::_3DRenderer, 0, 1); // 0 is the software renderer, 1 is the OpenGL renderer #else SANITIZE(Config::_3DRenderer, 0, 0); #endif SANITIZE(Config::ScreenVSyncInterval, 1, 20); SANITIZE(Config::GL_ScaleFactor, 1, 16); SANITIZE(Config::AudioInterp, 0, 3); SANITIZE(Config::AudioVolume, 0, 256); SANITIZE(Config::MicInputType, 0, (int)micInputType_MAX); SANITIZE(Config::ScreenRotation, 0, (int)Frontend::screenRot_MAX); SANITIZE(Config::ScreenGap, 0, 500); SANITIZE(Config::ScreenLayout, 0, (int)Frontend::screenLayout_MAX); SANITIZE(Config::ScreenSizing, 0, (int)Frontend::screenSizing_MAX); SANITIZE(Config::ScreenAspectTop, 0, AspectRatiosNum); SANITIZE(Config::ScreenAspectBot, 0, AspectRatiosNum); #undef SANITIZE camStarted[0] = false; camStarted[1] = false; camManager[0] = new CameraManager(0, 640, 480, true); camManager[1] = new CameraManager(1, 640, 480, true); camManager[0]->setXFlip(Config::Camera[0].XFlip); camManager[1]->setXFlip(Config::Camera[1].XFlip); Input::JoystickID = Config::JoystickID; Input::OpenJoystick(); mainWindow = new MainWindow(); if (options->fullscreen) ToggleFullscreen(mainWindow); emuThread = new EmuThread(); emuThread->start(); emuThread->emuPause(); AudioInOut::Init(emuThread); ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); AudioInOut::AudioMute(mainWindow); QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged); bool memberSyntaxUsed = false; const auto prepareRomPath = [&](const std::optional& romPath, const std::optional& romArchivePath) -> QStringList { if (!romPath.has_value()) return {}; if (romArchivePath.has_value()) return { *romPath, *romArchivePath }; const QStringList path = mainWindow->splitArchivePath(*romPath, true); if (path.size() > 1) memberSyntaxUsed = true; return path; }; const QStringList dsfile = prepareRomPath(options->dsRomPath, options->dsRomArchivePath); const QStringList gbafile = prepareRomPath(options->gbaRomPath, options->gbaRomArchivePath); if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n"); mainWindow->preloadROMs(dsfile, gbafile, options->boot); int ret = melon.exec(); delete options; emuThread->emuStop(); emuThread->wait(); delete emuThread; Input::CloseJoystick(); AudioInOut::DeInit(); delete camManager[0]; delete camManager[1]; Config::Save(); SDL_Quit(); Platform::DeInit(); return ret; } #ifdef __WIN32__ #include int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdshow) { int ret = main(__argc, __argv); printf("\n\n>"); return ret; } #endif