diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index b2dda12d..f47b3a4a 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -9,6 +9,11 @@ on: branches: - master +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions + jobs: build-macos: strategy: @@ -28,12 +33,13 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da + vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4 - name: Build uses: lukka/run-cmake@v10 with: configurePreset: release-mac-${{ matrix.arch }} buildPreset: release-mac-${{ matrix.arch }} + configurePresetAdditionalArgs: "['-DMELONDS_EMBED_BUILD_INFO=ON']" - name: Compress app bundle shell: bash run: | diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 7f574c6c..c9578694 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -8,6 +8,11 @@ on: branches: - master +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions + jobs: build-x86_64: name: x86_64 @@ -23,7 +28,7 @@ jobs: sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \ qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2 liblua5.4-dev - name: Configure - run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr + run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON - name: Build run: | cmake --build build @@ -74,7 +79,8 @@ jobs: -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config \ -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \ -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \ - -DUSE_QT6=ON + -DUSE_QT6=ON \ + -DMELONDS_EMBED_BUILD_INFO=ON - name: Build shell: bash run: | diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 30dbd2a8..c3350b4d 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -9,6 +9,11 @@ on: branches: - master +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions + jobs: build: runs-on: windows-latest @@ -27,9 +32,9 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da + vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4 - name: Configure - run: cmake --preset=release-mingw-x86_64 + run: cmake --preset=release-mingw-x86_64 -DMELONDS_EMBED_BUILD_INFO=ON - name: Build run: cmake --build --preset=release-mingw-x86_64 - uses: actions/upload-artifact@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 40583949..55bf825f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ include(CMakeDependentOption) include(CheckIPOSupported) include(SetupCCache) +include(Sanitizers) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake index f8c33fd4..c1eb522d 100644 --- a/cmake/ConfigureVcpkg.cmake +++ b/cmake/ConfigureVcpkg.cmake @@ -9,7 +9,7 @@ if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") endif() FetchContent_Declare(vcpkg GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" - GIT_TAG 2024.08.23 + GIT_TAG 2024.10.21 SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") FetchContent_MakeAvailable(vcpkg) endif() diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 00000000..9c09da28 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,8 @@ +set(SANITIZE "" CACHE STRING "Sanitizers to enable.") + +string(REGEX MATCHALL "[^,]+" ENABLED_SANITIZERS "${SANITIZE}") + +foreach(SANITIZER ${ENABLED_SANITIZERS}) + add_compile_options("-fsanitize=${SANITIZER}") + add_link_options("-fsanitize=${SANITIZER}") +endforeach() \ No newline at end of file diff --git a/flake.lock b/flake.lock index 9f0fe239..be75f57f 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1725432240, - "narHash": "sha256-+yj+xgsfZaErbfYM3T+QvEE2hU7UuE+Jf0fJCJ8uPS0=", + "lastModified": 1729665710, + "narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ad416d066ca1222956472ab7d0555a6946746a80", + "rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 9f3a96f7..77fc0993 100644 --- a/flake.nix +++ b/flake.nix @@ -12,13 +12,16 @@ inherit (pkgs.lib) cmakeBool optionals makeLibraryPath; inherit (pkgs.stdenv) isLinux isDarwin; - versionSuffix = with self; if sourceInfo?dirtyShortRev + revision = with self; if sourceInfo?dirtyRev + then sourceInfo.dirtyRev + else sourceInfo.rev; + shortRevision = with self; if sourceInfo?dirtyShortRev then sourceInfo.dirtyShortRev else sourceInfo.shortRev; - melonDS = pkgs.stdenv.mkDerivation { + melonDS = pkgs.qt6.qtbase.stdenv.mkDerivation { pname = "melonDS"; - version = "0.9.5-${versionSuffix}"; + version = "0.9.5-${shortRevision}"; src = ./.; nativeBuildInputs = with pkgs; [ @@ -47,8 +50,13 @@ cmakeFlags = [ (cmakeBool "USE_QT6" true) (cmakeBool "USE_SYSTEM_LIBSLIRP" true) + (cmakeBool "MELONDS_EMBED_BUILD_INFO" true) ]; + env.MELONDS_GIT_HASH = revision; + env.MELONDS_GIT_BRANCH = "(unknown)"; + env.MELONDS_BUILD_PROVIDER = "Nix"; + qtWrapperArgs = optionals isLinux [ "--prefix LD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap pkgs.wayland ]}" ] ++ optionals isDarwin [ @@ -67,7 +75,7 @@ drv = self.packages.${system}.default; }; devShells = { - default = pkgs.mkShell { + default = pkgs.mkShell.override { stdenv = pkgs.qt6.qtbase.stdenv; } { inputsFrom = [ self.packages.${system}.default ]; }; diff --git a/src/ARM.cpp b/src/ARM.cpp index 4cf70749..b7b703da 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -110,6 +110,7 @@ const u32 ARM::ConditionTable[16] = ARM::ARM(u32 num, bool jit, std::optional gdb, melonDS::NDS& nds) : #ifdef GDBSTUB_ENABLED GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0), + BreakOnStartup(gdb ? (num ? gdb->ARM7BreakOnStartup : gdb->ARM9BreakOnStartup) : false), #endif Num(num), // well uh NDS(nds) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3e177835..1f947d11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -128,6 +128,17 @@ if (ENABLE_JIT) endif() set(MELONDS_VERSION_SUFFIX "$ENV{MELONDS_VERSION_SUFFIX}" CACHE STRING "Suffix to add to displayed melonDS version") +option(MELONDS_EMBED_BUILD_INFO "Embed detailed build info into the binary" OFF) +set(MELONDS_GIT_BRANCH "$ENV{MELONDS_GIT_BRANCH}" CACHE STRING "The Git branch used for this build") +set(MELONDS_GIT_HASH "$ENV{MELONDS_GIT_HASH}" CACHE STRING "The hash of the Git commit") +set(MELONDS_BUILD_PROVIDER "$ENV{MELONDS_BUILD_PROVIDER}" CACHE STRING "The name of the provider of this build") + +if (MELONDS_EMBED_BUILD_INFO) + target_compile_definitions(core PUBLIC MELONDS_EMBED_BUILD_INFO) + if (NOT MELONDS_GIT_BRANCH OR NOT MELONDS_GIT_HASH OR NOT MELONDS_BUILD_PROVIDER) + message(FATAL_ERROR "When embedding build information, all fields must be filled out. See src/CMakeLists.txt.") + endif() +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/version.h") target_sources(core PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/version.h") diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp index 792bf12d..9827bdbe 100644 --- a/src/DSi_NWifi.cpp +++ b/src/DSi_NWifi.cpp @@ -1445,7 +1445,6 @@ void DSi_NWifi::CheckRX() int rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); while (rxlen > 0) { - //printf("WMI packet recv %04X %04X %04X\n", *(u16*)&LANBuffer[0], *(u16*)&LANBuffer[2], *(u16*)&LANBuffer[4]); // check destination MAC if (*(u32*)&LANBuffer[0] != 0xFFFFFFFF || *(u16*)&LANBuffer[4] != 0xFFFF) { @@ -1508,6 +1507,7 @@ void DSi_NWifi::CheckRX() Mailbox[8].Write(LANBuffer[14+i]); DrainRXBuffer(); + return; } } diff --git a/src/GPU.h b/src/GPU.h index 26e9d5df..5c373ca8 100644 --- a/src/GPU.h +++ b/src/GPU.h @@ -499,6 +499,17 @@ public: OAMDirty |= 1 << (addr / 1024); } + template + inline T ReadVRAMFlat_Texture(u32 addr) const + { + return *(T*)&VRAMFlat_Texture[addr & 0x7FFFF]; + } + template + inline T ReadVRAMFlat_TexPal(u32 addr) const + { + return *(T*)&VRAMFlat_TexPal[addr & 0x1FFFF]; + } + void SetPowerCnt(u32 val) noexcept; void StartFrame() noexcept; diff --git a/src/GPU3D_Soft.cpp b/src/GPU3D_Soft.cpp index 1221ed59..a9d0bd64 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -193,10 +193,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 1: // A3I5 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x1F)<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + ((pixel&0x1F)<<1)); *alpha = ((pixel >> 3) & 0x1C) + (pixel >> 6); } break; @@ -204,12 +204,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 2: // 4-color { vramaddr += (((t * width) + s) >> 2); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); pixel >>= ((s & 0x3) << 1); pixel &= 0x3; texpal <<= 3; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -217,12 +217,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 3: // 16-color { vramaddr += (((t * width) + s) >> 1); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); if (s & 0x1) pixel >>= 4; else pixel &= 0xF; texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -230,10 +230,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 4: // 256-color { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -253,31 +253,31 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s val = 0; else { - val = ReadVRAM_Texture(vramaddr, gpu); + val = gpu.ReadVRAMFlat_Texture(vramaddr); val >>= (2 * (s & 0x3)); } - u16 palinfo = ReadVRAM_Texture(slot1addr, gpu); + u16 palinfo = gpu.ReadVRAMFlat_Texture(slot1addr); u32 paloffset = (palinfo & 0x3FFF) << 2; texpal <<= 4; switch (val & 0x3) { case 0: - *color = ReadVRAM_TexPal(texpal + paloffset, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); *alpha = 31; break; case 1: - *color = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); *alpha = 31; break; case 2: if ((palinfo >> 14) == 1) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -294,8 +294,8 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -311,20 +311,20 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s *color = r | g | b; } else - *color = ReadVRAM_TexPal(texpal + paloffset + 4, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 4); *alpha = 31; break; case 3: if ((palinfo >> 14) == 2) { - *color = ReadVRAM_TexPal(texpal + paloffset + 6, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 6); *alpha = 31; } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -353,10 +353,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 6: // A5I3 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x7)<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + ((pixel&0x7)<<1)); *alpha = (pixel >> 3); } break; @@ -364,7 +364,7 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 7: // direct color { vramaddr += (((t * width) + s) << 1); - *color = ReadVRAM_Texture(vramaddr, gpu); + *color = gpu.ReadVRAMFlat_Texture(vramaddr); *alpha = (*color & 0x8000) ? 31 : 0; } break; @@ -1659,8 +1659,8 @@ void SoftRenderer::ClearBuffers(const GPU& gpu) { for (int x = 0; x < 256; x++) { - u16 val2 = ReadVRAM_Texture(0x40000 + (yoff << 9) + (xoff << 1), gpu); - u16 val3 = ReadVRAM_Texture(0x60000 + (yoff << 9) + (xoff << 1), gpu); + u16 val2 = gpu.ReadVRAMFlat_Texture(0x40000 + (yoff << 9) + (xoff << 1)); + u16 val3 = gpu.ReadVRAMFlat_Texture(0x60000 + (yoff << 9) + (xoff << 1)); // TODO: confirm color conversion u32 r = (val2 << 1) & 0x3E; if (r) r++; diff --git a/src/GPU3D_Soft.h b/src/GPU3D_Soft.h index 55a698b0..73d02e4f 100644 --- a/src/GPU3D_Soft.h +++ b/src/GPU3D_Soft.h @@ -430,16 +430,6 @@ private: s32 ycoverage, ycov_incr; }; - template - inline T ReadVRAM_Texture(u32 addr, const GPU& gpu) const - { - return *(T*)&gpu.VRAMFlat_Texture[addr & 0x7FFFF]; - } - template - inline T ReadVRAM_TexPal(u32 addr, const GPU& gpu) const - { - return *(T*)&gpu.VRAMFlat_TexPal[addr & 0x1FFFF]; - } u32 AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept; struct RendererPolygon diff --git a/src/GPU3D_Texcache.cpp b/src/GPU3D_Texcache.cpp index 196009e6..a6a40a04 100644 --- a/src/GPU3D_Texcache.cpp +++ b/src/GPU3D_Texcache.cpp @@ -75,11 +75,11 @@ inline u32 ConvertRGB5ToRGB6(u16 val) } template -void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData) +void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu) { for (u32 i = 0; i < width*height; i++) { - u16 value = *(u16*)&texData[i * 2]; + u16 value = gpu.ReadVRAMFlat_Texture(addr + i * 2); switch (outputFmt) { @@ -96,28 +96,28 @@ void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData) } } -template void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData); +template void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu); template -void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u8* texAuxData, u16* palData) +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u32 addr, u32 addrAux, u32 palAddr, GPU& gpu) { // we process a whole block at the time for (int y = 0; y < height / 4; y++) { for (int x = 0; x < width / 4; x++) { - u32 data = ((u32*)texData)[x + y * (width / 4)]; - u16 auxData = ((u16*)texAuxData)[x + y * (width / 4)]; + u32 data = gpu.ReadVRAMFlat_Texture(addr + (x + y * (width / 4))*4); + u16 auxData = gpu.ReadVRAMFlat_Texture(addrAux + (x + y * (width / 4))*2); - u32 paletteOffset = auxData & 0x3FFF; - u16 color0 = palData[paletteOffset*2] | 0x8000; - u16 color1 = palData[paletteOffset*2+1] | 0x8000; - u16 color2, color3; + u32 paletteOffset = palAddr + (auxData & 0x3FFF) * 4; + u16 color0 = gpu.ReadVRAMFlat_TexPal(paletteOffset) | 0x8000; + u16 color1 = gpu.ReadVRAMFlat_TexPal(paletteOffset+2) | 0x8000; + u16 color2 = gpu.ReadVRAMFlat_TexPal(paletteOffset+4) | 0x8000; + u16 color3 = gpu.ReadVRAMFlat_TexPal(paletteOffset+6) | 0x8000; switch ((auxData >> 14) & 0x3) { case 0: - color2 = palData[paletteOffset*2+2] | 0x8000; color3 = 0; break; case 1: @@ -137,8 +137,6 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u color3 = 0; break; case 2: - color2 = palData[paletteOffset*2+2] | 0x8000; - color3 = palData[paletteOffset*2+3] | 0x8000; break; case 3: { @@ -179,7 +177,8 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u { for (int i = 0; i < 4; i++) { - u16 color = (packed >> 16 * (data >> 2 * (i + j * 4))) & 0xFFFF; + u32 colorIdx = 16 * ((data >> 2 * (i + j * 4)) & 0x3); + u16 color = (packed >> colorIdx) & 0xFFFF; u32 res; switch (outputFmt) { @@ -197,20 +196,20 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u } } -template void ConvertCompressedTexture(u32, u32, u32*, u8*, u8*, u16*); +template void ConvertCompressedTexture(u32, u32, u32*, u32, u32, u32, GPU&); template -void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData) +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, GPU& gpu) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - u8 val = texData[x + y * width]; + u8 val = gpu.ReadVRAMFlat_Texture(addr + x + y * width); u32 idx = val & ((1 << Y) - 1); - u16 color = palData[idx]; + u16 color = gpu.ReadVRAMFlat_TexPal(palAddr + idx * 2); u32 alpha = (val >> Y) & ((1 << X) - 1); if (X != 5) alpha = alpha * 4 + alpha / 2; @@ -228,22 +227,24 @@ void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* pa } } -template void ConvertAXIYTexture(u32, u32, u32*, u8*, u16*); -template void ConvertAXIYTexture(u32, u32, u32*, u8*, u16*); +template void ConvertAXIYTexture(u32, u32, u32*, u32, u32, GPU&); +template void ConvertAXIYTexture(u32, u32, u32*, u32, u32, GPU&); template -void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData, bool color0Transparent) +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, bool color0Transparent, GPU& gpu) { for (int y = 0; y < height; y++) { - for (int x = 0; x < width / (8 / colorBits); x++) + for (int x = 0; x < width / (16 / colorBits); x++) { - u8 val = texData[x + y * (width / (8 / colorBits))]; + // smallest possible row is 8 pixels with 2bpp => fits in u16 + u16 val = gpu.ReadVRAMFlat_Texture(addr + 2 * (x + y * (width / (16 / colorBits)))); - for (int i = 0; i < 8 / colorBits; i++) + for (int i = 0; i < 16 / colorBits; i++) { - u32 index = (val >> (i * colorBits)) & ((1 << colorBits) - 1); - u16 color = palData[index]; + u32 index = val & ((1 << colorBits) - 1); + val >>= colorBits; + u16 color = gpu.ReadVRAMFlat_TexPal(palAddr + index * 2); bool transparent = color0Transparent && index == 0; u32 res; @@ -256,14 +257,14 @@ void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* case outputFmt_BGRA8: res = ConvertRGB5ToBGR8(color) | (transparent ? 0 : 0xFF000000); break; } - output[x * (8 / colorBits) + y * width + i] = res; + output[x * (16 / colorBits) + y * width + i] = res; } } } } -template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); -template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); -template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); } \ No newline at end of file diff --git a/src/GPU3D_Texcache.h b/src/GPU3D_Texcache.h index 214c6254..f2cd6416 100644 --- a/src/GPU3D_Texcache.h +++ b/src/GPU3D_Texcache.h @@ -32,13 +32,13 @@ enum }; template -void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData); +void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu); template -void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u8* texAuxData, u16* palData); +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u32 addr, u32 addrAux, u32 palAddr, GPU& gpu); template -void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData); +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, GPU& gpu); template -void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData, bool color0Transparent); +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, bool color0Transparent, GPU& gpu); template class Texcache @@ -48,6 +48,50 @@ public: : TexLoader(texloader) // probably better if this would be a move constructor??? {} + u64 MaskedHash(u8* vram, u32 vramSize, u32 addr, u32 size) + { + u64 hash = 0; + + while (size > 0) + { + u32 pieceSize; + if (addr + size > vramSize) + // wraps around, only do the part inside + pieceSize = vramSize - addr; + else + // fits completely inside + pieceSize = size; + + hash = XXH64(&vram[addr], pieceSize, hash); + + addr += pieceSize; + addr &= (vramSize - 1); + assert(size >= pieceSize); + size -= pieceSize; + } + + return hash; + } + + bool CheckInvalid(u32 start, u32 size, u64 oldHash, u64* dirty, u8* vram, u32 vramSize) + { + u32 startBit = start / VRAMDirtyGranularity; + u32 bitsCount = ((start + size + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; + + u32 startEntry = startBit >> 6; + u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; + for (u32 j = startEntry; j < startEntry + entriesCount; j++) + { + if (GetRangedBitMask(j, startBit, bitsCount) & dirty[j & ((vramSize / VRAMDirtyGranularity)-1)]) + { + if (MaskedHash(vram, vramSize, start, size) != oldHash) + return true; + } + } + + return false; + } + bool Update(GPU& gpu) { auto textureDirty = gpu.VRAMDirty_Texture.DeriveState(gpu.VRAMMap_Texture, gpu); @@ -66,40 +110,21 @@ public: { for (u32 i = 0; i < 2; i++) { - u32 startBit = entry.TextureRAMStart[i] / VRAMDirtyGranularity; - u32 bitsCount = ((entry.TextureRAMStart[i] + entry.TextureRAMSize[i] + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; - - u32 startEntry = startBit >> 6; - u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; - for (u32 j = startEntry; j < startEntry + entriesCount; j++) - { - if (GetRangedBitMask(j, startBit, bitsCount) & textureDirty.Data[j]) - { - u64 newTexHash = XXH3_64bits(&gpu.VRAMFlat_Texture[entry.TextureRAMStart[i]], entry.TextureRAMSize[i]); - - if (newTexHash != entry.TextureHash[i]) - goto invalidate; - } - } + if (CheckInvalid(entry.TextureRAMStart[i], entry.TextureRAMSize[i], + entry.TextureHash[i], + textureDirty.Data, + gpu.VRAMFlat_Texture, sizeof(gpu.VRAMFlat_Texture))) + goto invalidate; } } if (texPalChanged && entry.TexPalSize > 0) { - u32 startBit = entry.TexPalStart / VRAMDirtyGranularity; - u32 bitsCount = ((entry.TexPalStart + entry.TexPalSize + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; - - u32 startEntry = startBit >> 6; - u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; - for (u32 j = startEntry; j < startEntry + entriesCount; j++) - { - if (GetRangedBitMask(j, startBit, bitsCount) & texPalDirty.Data[j]) - { - u64 newPalHash = XXH3_64bits(&gpu.VRAMFlat_TexPal[entry.TexPalStart], entry.TexPalSize); - if (newPalHash != entry.TexPalHash) - goto invalidate; - } - } + if (CheckInvalid(entry.TexPalStart, entry.TexPalSize, + entry.TexPalHash, + texPalDirty.Data, + gpu.VRAMFlat_TexPal, sizeof(gpu.VRAMFlat_TexPal))) + goto invalidate; } it++; @@ -163,17 +188,13 @@ public: { entry.TextureRAMSize[0] = width*height*2; - ConvertBitmapTexture(width, height, DecodingBuffer, &gpu.VRAMFlat_Texture[addr]); + ConvertBitmapTexture(width, height, DecodingBuffer, addr, gpu); } else if (fmt == 5) { - u8* texData = &gpu.VRAMFlat_Texture[addr]; u32 slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1); if (addr >= 0x40000) slot1addr += 0x10000; - u8* texAuxData = &gpu.VRAMFlat_Texture[slot1addr]; - - u16* palData = (u16*)(gpu.VRAMFlat_TexPal + palBase*16); entry.TextureRAMSize[0] = width*height/16*4; entry.TextureRAMStart[1] = slot1addr; @@ -181,7 +202,7 @@ public: entry.TexPalStart = palBase*16; entry.TexPalSize = 0x10000; - ConvertCompressedTexture(width, height, DecodingBuffer, texData, texAuxData, palData); + ConvertCompressedTexture(width, height, DecodingBuffer, addr, slot1addr, entry.TexPalStart, gpu); } else { @@ -204,30 +225,29 @@ public: entry.TexPalStart = palAddr; entry.TexPalSize = numPalEntries*2; - u8* texData = &gpu.VRAMFlat_Texture[addr]; - u16* palData = (u16*)(gpu.VRAMFlat_TexPal + palAddr); - //assert(entry.TexPalStart+entry.TexPalSize <= 128*1024*1024); bool color0Transparent = texParam & (1 << 29); switch (fmt) { - case 1: ConvertAXIYTexture(width, height, DecodingBuffer, texData, palData); break; - case 6: ConvertAXIYTexture(width, height, DecodingBuffer, texData, palData); break; - case 2: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; - case 3: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; - case 4: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; + case 1: ConvertAXIYTexture(width, height, DecodingBuffer, addr, palAddr, gpu); break; + case 6: ConvertAXIYTexture(width, height, DecodingBuffer, addr, palAddr, gpu); break; + case 2: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; + case 3: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; + case 4: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; } } for (int i = 0; i < 2; i++) { if (entry.TextureRAMSize[i]) - entry.TextureHash[i] = XXH3_64bits(&gpu.VRAMFlat_Texture[entry.TextureRAMStart[i]], entry.TextureRAMSize[i]); + entry.TextureHash[i] = MaskedHash(gpu.VRAMFlat_Texture, sizeof(gpu.VRAMFlat_Texture), + entry.TextureRAMStart[i], entry.TextureRAMSize[i]); } if (entry.TexPalSize) - entry.TexPalHash = XXH3_64bits(&gpu.VRAMFlat_TexPal[entry.TexPalStart], entry.TexPalSize); + entry.TexPalHash = MaskedHash(gpu.VRAMFlat_TexPal, sizeof(gpu.VRAMFlat_TexPal), + entry.TexPalStart, entry.TexPalSize); auto& texArrays = TexArrays[widthLog2][heightLog2]; auto& freeTextures = FreeTextures[widthLog2][heightLog2]; diff --git a/src/RTC.cpp b/src/RTC.cpp index fe262644..24e00de7 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -40,7 +40,7 @@ RTC::RTC(melonDS::NDS& nds) : NDS(nds) // indicate the power was off // this will be changed if a previously saved RTC state is loaded - State.StatusReg1 = 0x80; + State.StatusReg1 = 0x80 | (1<<1); } RTC::~RTC() @@ -943,4 +943,4 @@ void RTC::Write(u16 val, bool byte) IO = (IO & 0x0001) | (val & 0xFFFE); } -} \ No newline at end of file +} diff --git a/src/debug/GdbStub.cpp b/src/debug/GdbStub.cpp index 14a8670a..b055794a 100644 --- a/src/debug/GdbStub.cpp +++ b/src/debug/GdbStub.cpp @@ -101,6 +101,15 @@ bool GdbStub::Init() Log(LogLevel::Error, "[GDB] err: can't create a socket fd\n"); goto err; } + { + // Make sure the port can be reused immediately after melonDS stops and/or restarts + int enable = 1; +#ifdef _WIN32 + setsockopt(SockFd, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(enable)); +#else + setsockopt(SockFd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); +#endif + } #ifndef __linux__ SocketSetBlocking(SockFd, false); #endif diff --git a/src/frontend/qt_sdl/AboutDialog.cpp b/src/frontend/qt_sdl/AboutDialog.cpp new file mode 100644 index 00000000..22022de9 --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.cpp @@ -0,0 +1,59 @@ +/* + 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 "AboutDialog.h" + +#include +#include + +#include "ui_AboutDialog.h" + +#include "version.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + + ui->lblVersionInfo->setText("Version " MELONDS_VERSION); +#ifdef MELONDS_EMBED_BUILD_INFO + ui->lblBuildInfo->setText( + "Branch: " MELONDS_GIT_BRANCH "\n" + "Commit: " MELONDS_GIT_HASH "\n" + "Built by: " MELONDS_BUILD_PROVIDER + ); +#else + ui->lblBuildInfo->hide(); +#endif + adjustSize(); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} + +void AboutDialog::openWebsite() +{ + QDesktopServices::openUrl(QUrl(MELONDS_URL)); +} + +void AboutDialog::openGitHub() +{ + QDesktopServices::openUrl(QUrl("https://github.com/melonDS-emu/melonDS"));; +} diff --git a/src/frontend/qt_sdl/AboutDialog.h b/src/frontend/qt_sdl/AboutDialog.h new file mode 100644 index 00000000..eb328f5b --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.h @@ -0,0 +1,50 @@ +/* + 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/. +*/ + +#ifndef MELONDS_ABOUTDIALOG_H +#define MELONDS_ABOUTDIALOG_H + +#include + + +QT_BEGIN_NAMESPACE +namespace Ui +{ + class AboutDialog; +} +QT_END_NAMESPACE + +class AboutDialog : public QDialog +{ +Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = nullptr); + + ~AboutDialog() override; + +private slots: + static void openWebsite(); + static void openGitHub(); + +private: + Ui::AboutDialog *ui; +}; + + +#endif //MELONDS_ABOUTDIALOG_H diff --git a/src/frontend/qt_sdl/AboutDialog.ui b/src/frontend/qt_sdl/AboutDialog.ui new file mode 100644 index 00000000..89ca7a36 --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.ui @@ -0,0 +1,300 @@ + + + AboutDialog + + + + 0 + 0 + 600 + 304 + + + + + 0 + 0 + + + + + 600 + 0 + + + + About melonDS + + + false + + + + 0 + + + QLayout::SizeConstraint::SetFixedSize + + + 12 + + + + + 24 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + + + true + + + + 0 + 0 + + + + + 128 + 128 + + + + + 128 + 128 + + + + + + + Qt::TextFormat::PlainText + + + :/melon-icon + + + true + + + Qt::AlignmentFlag::AlignCenter + + + 0 + + + 0 + + + + + + + 12 + + + + + true + + + + 32 + + + + melonDS + + + + + + + true + + + [VERSION INFO PLACEHOLDER] + + + Qt::TextFormat::MarkdownText + + + + + + + true + + + By Arisotura, the melonDS team <a href="https://github.com/melonDS-emu/melonDS/graphs/contributors">and contributors</a>. + + + + + + + true + + + [EMBEDDED BUILD INFO PLACEHOLDER] + + + Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextSelectableByMouse + + + + + + + false + + + + 10 + + + + <html><head/><body><p>Licensed under the GNU General Public License v3 or any newer version.</p><p>melonDS is intended only for use with software that you own.</p><p>The Nintendo DS and Nintendo DSi systems are the property of Nintendo.<br />melonDS and the melonDS team are not affiliated with or endorsed by Nintendo.</p></body></html> + + + Qt::TextFormat::RichText + + + false + + + + + + + + + + + 12 + + + 12 + + + + + true + + + Qt::Orientation::Horizontal + + + + 40 + 0 + + + + + + + + true + + + Visit the &website + + + true + + + + + + + true + + + View on &GitHub + + + true + + + + + + + true + + + &Close + + + true + + + + + + + + + + + + + btnClose + clicked() + AboutDialog + accept() + + + 586 + 261 + + + 294 + 154 + + + + + btnOpenGitHub + clicked() + AboutDialog + openGitHub() + + + 449 + 243 + + + 299 + 139 + + + + + btnOpenWebsite + clicked() + AboutDialog + openWebsite() + + + 345 + 245 + + + 96 + 275 + + + + + + openWebsite() + openGitHub() + + diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index 37f856ac..5c0c9330 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -170,6 +170,12 @@ void AudioSettingsDialog::on_AudioSettingsDialog_accepted() void AudioSettingsDialog::on_AudioSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + auto& cfg = emuInstance->getGlobalConfig(); auto& instcfg = emuInstance->getLocalConfig(); cfg.SetInt("Audio.Interpolation", oldInterp); diff --git a/src/frontend/qt_sdl/CLI.cpp b/src/frontend/qt_sdl/CLI.cpp index 299ce65b..5e352cae 100644 --- a/src/frontend/qt_sdl/CLI.cpp +++ b/src/frontend/qt_sdl/CLI.cpp @@ -96,7 +96,7 @@ CommandLineOptions* ManageArgs(QApplication& melon) } else { - options->errorsToDisplay += "Option -a/--archive-file given, but no archive specified!"; + Log(LogLevel::Error, "Option -a/--archive-file given, but no archive specified!"); } } @@ -108,7 +108,7 @@ CommandLineOptions* ManageArgs(QApplication& melon) } else { - options->errorsToDisplay += "Option -A/--archive-file-gba given, but no archive specified!"; + Log(LogLevel::Error, "Option -A/--archive-file-gba given, but no archive specified!"); } } #endif diff --git a/src/frontend/qt_sdl/CLI.h b/src/frontend/qt_sdl/CLI.h index 4997e6a7..beb120bf 100644 --- a/src/frontend/qt_sdl/CLI.h +++ b/src/frontend/qt_sdl/CLI.h @@ -28,8 +28,6 @@ namespace CLI { struct CommandLineOptions { - QStringList errorsToDisplay = {}; - std::optional dsRomPath; std::optional dsRomArchivePath; std::optional gbaRomPath; diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 55026912..f5b09219 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -37,6 +37,9 @@ set(SOURCES_QT_SDL QPathInput.h SaveManager.cpp CameraManager.cpp + AboutDialog.cpp + AboutDialog.h + AboutDialog.ui ArchiveUtil.h ArchiveUtil.cpp diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp index 39c05cef..63b7a76e 100644 --- a/src/frontend/qt_sdl/CameraSettingsDialog.cpp +++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp @@ -163,6 +163,12 @@ void CameraSettingsDialog::on_CameraSettingsDialog_accepted() void CameraSettingsDialog::on_CameraSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + for (int i = 0; i < 2; i++) { camManager[i]->stop(); diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 580af72e..02be5b65 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -55,7 +55,6 @@ DefaultList DefaultInts = {"Screen.VSyncInterval", 1}, {"3D.Renderer", renderer3D_Software}, {"3D.GL.ScaleFactor", 1}, - {"MaxFPS", 1000}, #ifdef JIT_ENABLED {"JIT.MaxBlockSize", 32}, #endif @@ -81,7 +80,7 @@ RangeList IntRanges = {"3D.Renderer", {0, renderer3D_Max-1}}, {"Screen.VSyncInterval", {1, 20}}, {"3D.GL.ScaleFactor", {1, 16}}, - {"Audio.Interpolation", {0, 3}}, + {"Audio.Interpolation", {0, 4}}, {"Instance*.Audio.Volume", {0, 256}}, {"Mic.InputType", {0, micInputType_MAX-1}}, {"Instance*.Window*.ScreenRotation", {0, screenRot_MAX-1}}, @@ -100,7 +99,7 @@ DefaultList DefaultBools = {"3D.Soft.Threaded", true}, {"3D.GL.HiresCoordinates", true}, {"LimitFPS", true}, - {"Window*.ShowOSD", true}, + {"Instance*.Window*.ShowOSD", true}, {"Emu.DirectBoot", true}, {"Instance*.DS.Battery.LevelOkay", true}, {"Instance*.DSi.Battery.Charging", true}, @@ -120,6 +119,13 @@ DefaultList DefaultStrings = {"Instance*.Firmware.Username", "melonDS"} }; +DefaultList DefaultDoubles = +{ + {"TargetFPS", 60.0}, + {"FastForwardFPS", 1000.0}, + {"SlowmoFPS", 30.0}, +}; + LegacyEntry LegacyFile[] = { {"Key_A", 0, "Keyboard.A", true}, @@ -153,7 +159,7 @@ LegacyEntry LegacyFile[] = {"HKKey_Pause", 0, "Keyboard.HK_Pause", true}, {"HKKey_Reset", 0, "Keyboard.HK_Reset", true}, {"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true}, - {"HKKey_FastForwardToggle", 0, "Keyboard.HK_FastForwardToggle", true}, + {"HKKey_FastForwardToggle", 0, "Keyboard.HK_FrameLimitToggle", true}, {"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true}, {"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true}, {"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true}, @@ -169,7 +175,7 @@ LegacyEntry LegacyFile[] = {"HKJoy_Pause", 0, "Joystick.HK_Pause", true}, {"HKJoy_Reset", 0, "Joystick.HK_Reset", true}, {"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true}, - {"HKJoy_FastForwardToggle", 0, "Joystick.HK_FastForwardToggle", true}, + {"HKJoy_FastForwardToggle", 0, "Joystick.HK_FrameLimitToggle", true}, {"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true}, {"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true}, {"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true}, @@ -434,6 +440,18 @@ std::string Array::GetString(const int id) return tval.as_string(); } +double Array::GetDouble(const int id) +{ + while (Data.size() < id+1) + Data.push_back(0.0); + + toml::value& tval = Data[id]; + if (!tval.is_floating()) + tval = 0.0; + + return tval.as_floating(); +} + void Array::SetInt(const int id, int val) { while (Data.size() < id+1) @@ -470,6 +488,15 @@ void Array::SetString(const int id, const std::string& val) tval = val; } +void Array::SetDouble(const int id, double val) +{ + while (Data.size() < id+1) + Data.push_back(0.0); + + toml::value& tval = Data[id]; + tval = val; +} + /*Table::Table()// : Data(toml::value()) { @@ -562,6 +589,15 @@ std::string Table::GetString(const std::string& path) return tval.as_string(); } +double Table::GetDouble(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_floating()) + tval = FindDefault(path, 0.0, DefaultDoubles); + + return tval.as_floating(); +} + void Table::SetInt(const std::string& path, int val) { std::string rngkey = GetDefaultKey(PathPrefix+path); @@ -593,6 +629,12 @@ void Table::SetString(const std::string& path, const std::string& val) tval = val; } +void Table::SetDouble(const std::string& path, double val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + toml::value& Table::ResolvePath(const std::string& path) { toml::value* ret = &Data; diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 4ca88b46..9e6d3ea4 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -61,11 +61,13 @@ public: int64_t GetInt64(const int id); bool GetBool(const int id); std::string GetString(const int id); + double GetDouble(const int id); void SetInt(const int id, int val); void SetInt64(const int id, int64_t val); void SetBool(const int id, bool val); void SetString(const int id, const std::string& val); + void SetDouble(const int id, double val); // convenience @@ -99,11 +101,13 @@ public: int64_t GetInt64(const std::string& path); bool GetBool(const std::string& path); std::string GetString(const std::string& path); + double GetDouble(const std::string& path); void SetInt(const std::string& path, int val); void SetInt64(const std::string& path, int64_t val); void SetBool(const std::string& path, bool val); void SetString(const std::string& path, const std::string& val); + void SetDouble(const std::string& path, double val); // convenience diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp index e48f29a2..90116692 100644 --- a/src/frontend/qt_sdl/EmuInstance.cpp +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -88,7 +88,31 @@ EmuInstance::EmuInstance(int inst) : deleting(false), cheatsOn = localCfg.GetBool("EnableCheats"); doLimitFPS = globalCfg.GetBool("LimitFPS"); - maxFPS = globalCfg.GetInt("MaxFPS"); + + double val = globalCfg.GetDouble("TargetFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n"); + targetFPS = 1.0 / 60.0; + } + else targetFPS = 1.0 / val; + + val = globalCfg.GetDouble("FastForwardFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Fast-Forward FPS in config invalid\n"); + fastForwardFPS = 1.0 / 60.0; + } + else fastForwardFPS = 1.0 / val; + + val = globalCfg.GetDouble("SlowmoFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Slow-Mo FPS in config invalid\n"); + slowmoFPS = 1.0 / 60.0; + } + else slowmoFPS = 1.0 / val; + doAudioSync = globalCfg.GetBool("AudioSync"); mpAudioMode = globalCfg.GetInt("MP.AudioMode"); @@ -113,6 +137,16 @@ EmuInstance::EmuInstance(int inst) : deleting(false), emuThread->start(); emuThread->emuPause(); + + // if any extra windows were saved as enabled, open them + for (int i = 1; i < kMaxWindows; i++) + { + //Config::Table tbl = localCfg.GetTable("Window"+std::to_string(i), "Window0"); + std::string key = "Window" + std::to_string(i) + ".Enabled"; + bool enable = localCfg.GetBool(key); + if (enable) + createWindow(i); + } } EmuInstance::~EmuInstance() @@ -130,6 +164,13 @@ EmuInstance::~EmuInstance() audioDeInit(); inputDeInit(); + + NDS::Current = nullptr; + if (nds) + { + saveRTCData(); + delete nds; + } } @@ -142,7 +183,7 @@ std::string EmuInstance::instanceFileSuffix() return suffix; } -void EmuInstance::createWindow() +void EmuInstance::createWindow(int id) { if (numWindows >= kMaxWindows) { @@ -150,16 +191,20 @@ void EmuInstance::createWindow() return; } - int id = -1; - for (int i = 0; i < kMaxWindows; i++) + if (id == -1) { - if (windowList[i]) continue; - id = i; - break; + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) continue; + id = i; + break; + } } if (id == -1) return; + if (windowList[id]) + return; MainWindow* win = new MainWindow(id, this, topWindow); if (!topWindow) topWindow = win; @@ -168,6 +213,16 @@ void EmuInstance::createWindow() numWindows++; emuThread->attachWindow(win); + + // if creating a secondary window, we may need to initialize its OpenGL context here + if (win->hasOpenGL() && (id != 0)) + emuThread->initContext(id); + + bool enable = (numWindows < kMaxWindows); + doOnAllWindows([=](MainWindow* win) + { + win->actNewWindow->setEnabled(enable); + }); } void EmuInstance::deleteWindow(int id, bool close) @@ -177,12 +232,8 @@ void EmuInstance::deleteWindow(int id, bool close) MainWindow* win = windowList[id]; if (!win) return; - if (win->hasOpenGL() && win == mainWindow) - { - // we intentionally don't unpause here - emuThread->emuPause(); - emuThread->deinitContext(); - } + if (win->hasOpenGL()) + emuThread->deinitContext(id); emuThread->detachWindow(win); @@ -195,11 +246,20 @@ void EmuInstance::deleteWindow(int id, bool close) if (close) win->close(); - if ((!mainWindow) && (!deleting)) + if (numWindows == 0) { - // if we closed this instance's main window, delete the instance + // if we closed the last window, delete the instance + // if the main window is closed, Qt will take care of closing any secondary windows deleteEmuInstance(instanceID); } + else + { + bool enable = (numWindows < kMaxWindows); + doOnAllWindows([=](MainWindow* win) + { + win->actNewWindow->setEnabled(enable); + }); + } } void EmuInstance::deleteAllWindows() @@ -208,6 +268,57 @@ void EmuInstance::deleteAllWindows() deleteWindow(i, true); } +void EmuInstance::doOnAllWindows(std::function func, int exclude) +{ + for (int i = 0; i < kMaxWindows; i++) + { + if (i == exclude) continue; + if (!windowList[i]) continue; + + func(windowList[i]); + } +} + +void EmuInstance::saveEnabledWindows() +{ + doOnAllWindows([=](MainWindow* win) + { + win->saveEnabled(true); + }); +} + + +void EmuInstance::broadcastCommand(int cmd, QVariant param) +{ + broadcastInstanceCommand(cmd, param, instanceID); +} + +void EmuInstance::handleCommand(int cmd, QVariant& param) +{ + switch (cmd) + { + case InstCmd_Pause: + emuThread->emuPause(false); + break; + + case InstCmd_Unpause: + emuThread->emuUnpause(false); + break; + + case InstCmd_UpdateRecentFiles: + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->loadRecentFilesMenu(true); + } + break; + + /*case InstCmd_UpdateVideoSettings: + mainWindow->updateVideoSettings(param.value()); + break;*/ + } +} + void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...) { @@ -261,24 +372,18 @@ bool EmuInstance::usesOpenGL() (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); } -void EmuInstance::initOpenGL() +void EmuInstance::initOpenGL(int win) { - for (int i = 0; i < kMaxWindows; i++) - { - if (windowList[i]) - windowList[i]->initOpenGL(); - } + if (windowList[win]) + windowList[win]->initOpenGL(); setVSyncGL(true); } -void EmuInstance::deinitOpenGL() +void EmuInstance::deinitOpenGL(int win) { - for (int i = 0; i < kMaxWindows; i++) - { - if (windowList[i]) - windowList[i]->deinitOpenGL(); - } + if (windowList[win]) + windowList[win]->deinitOpenGL(); } void EmuInstance::setVSyncGL(bool vsync) @@ -1081,6 +1186,30 @@ void EmuInstance::setBatteryLevels() } } +void EmuInstance::loadRTCData() +{ + auto 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); + } +} + +void EmuInstance::saveRTCData() +{ + auto 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); + } +} + void EmuInstance::setDateTime() { QDateTime hosttime = QDateTime::currentDateTime(); @@ -1092,6 +1221,9 @@ void EmuInstance::setDateTime() bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGBAArgs&& _gbaargs) noexcept { + // update the console type + consoleType = globalCfg.GetInt("Emu.ConsoleType"); + // 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; @@ -1125,8 +1257,6 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB } - int consoletype = globalCfg.GetInt("Emu.ConsoleType"); - auto arm9bios = loadARM9BIOS(); if (!arm9bios) return false; @@ -1135,7 +1265,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB if (!arm7bios) return false; - auto firmware = loadFirmware(consoletype); + auto firmware = loadFirmware(consoleType); if (!firmware) return false; @@ -1179,7 +1309,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB NDSArgs* args = &ndsargs; std::optional dsiargs = std::nullopt; - if (consoletype == 1) + if (consoleType == 1) { ndsargs.GBAROM = nullptr; @@ -1210,19 +1340,24 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB args = &(*dsiargs); } - - if ((!nds) || (consoletype != nds->ConsoleType)) + if ((!nds) || (consoleType != nds->ConsoleType)) { NDS::Current = nullptr; - if (nds) delete nds; + if (nds) + { + saveRTCData(); + delete nds; + } - if (consoletype == 1) + if (consoleType == 1) nds = new DSi(std::move(dsiargs.value()), this); else nds = new NDS(std::move(ndsargs), this); NDS::Current = nds; nds->Reset(); + loadRTCData(); + //emuThread->updateVideoRenderer(); // not actually needed? } else { @@ -1236,7 +1371,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB nds->SPU.SetInterpolation(args->Interpolation); nds->SPU.SetDegrade10Bit(args->BitDepth); - if (consoletype == 1) + if (consoleType == 1) { DSi* dsi = (DSi*)nds; DSiArgs& _dsiargs = *dsiargs; @@ -1258,8 +1393,6 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB void EmuInstance::reset() { - consoleType = globalCfg.GetInt("Emu.ConsoleType"); - updateConsole(Keep {}, Keep {}); if (consoleType == 1) ejectGBACart(); @@ -1949,25 +2082,36 @@ bool EmuInstance::gbaCartInserted() return gbaCartType != -1; } +QString EmuInstance::gbaAddonName(int addon) +{ + switch (addon) + { + case GBAAddon_RumblePak: + return "Rumble Pak"; + case GBAAddon_RAMExpansion: + return "Memory expansion"; + } + + return "???"; +} + QString EmuInstance::gbaCartLabel() { if (consoleType == 1) return "none (DSi)"; - switch (gbaCartType) + if (gbaCartType == 0) { - case 0: - { - QString ret = QString::fromStdString(baseGBAROMName); + QString ret = QString::fromStdString(baseGBAROMName); - int maxlen = 32; - if (ret.length() > maxlen) - ret = ret.left(maxlen-6) + "..." + ret.right(3); + int maxlen = 32; + if (ret.length() > maxlen) + ret = ret.left(maxlen-6) + "..." + ret.right(3); - return ret; - } - - case GBAAddon_RAMExpansion: - return "Memory expansion"; + return ret; + } + else if (gbaCartType != -1) + { + return gbaAddonName(gbaCartType); } return "(none)"; diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index cbee67b7..748af451 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -21,13 +21,14 @@ #include +#include "main.h" #include "NDS.h" #include "EmuThread.h" #include "Window.h" #include "Config.h" #include "SaveManager.h" -const int kMaxWindows = 16; +const int kMaxWindows = 4; enum { @@ -36,7 +37,7 @@ enum HK_Pause, HK_Reset, HK_FastForward, - HK_FastForwardToggle, + HK_FrameLimitToggle, HK_FullscreenToggle, HK_SwapScreens, HK_SwapScreenEmphasis, @@ -46,6 +47,9 @@ enum HK_PowerButton, HK_VolumeUp, HK_VolumeDown, + HK_SlowMo, + HK_FastForwardToggle, + HK_SlowMoToggle, HK_MAX }; @@ -83,14 +87,21 @@ public: melonDS::NDS* getNDS() { return nds; } MainWindow* getMainWindow() { return mainWindow; } + int getNumWindows() { return numWindows; } MainWindow* getWindow(int id) { return windowList[id]; } + void doOnAllWindows(std::function func, int exclude = -1); + void saveEnabledWindows(); + Config::Table& getGlobalConfig() { return globalCfg; } Config::Table& getLocalConfig() { return localCfg; } + void broadcastCommand(int cmd, QVariant param = QVariant()); + void handleCommand(int cmd, QVariant& param); + std::string instanceFileSuffix(); - void createWindow(); + void createWindow(int id = -1); void deleteWindow(int id, bool close); void deleteAllWindows(); @@ -100,8 +111,8 @@ public: void emuStop(melonDS::Platform::StopReason reason); bool usesOpenGL(); - void initOpenGL(); - void deinitOpenGL(); + void initOpenGL(int win); + void deinitOpenGL(int win); void setVSyncGL(bool vsync); void makeCurrentGL(); void drawScreenGL(); @@ -166,7 +177,6 @@ private: std::optional getSDCardArgs(const std::string& key) noexcept; std::optional loadSDCard(const std::string& key) noexcept; void setBatteryLevels(); - void setDateTime(); void reset(); bool bootToMenu(); melonDS::u32 decompressROM(const melonDS::u8* inContent, const melonDS::u32 inSize, std::unique_ptr& outContent); @@ -184,6 +194,7 @@ private: void loadGBAAddon(int type); void ejectGBACart(); bool gbaCartInserted(); + QString gbaAddonName(int addon); QString gbaCartLabel(); void audioInit(); @@ -220,6 +231,10 @@ private: bool hotkeyPressed(int id) { return hotkeyPress & (1< firmwareSave; bool doLimitFPS; - int maxFPS; + double curFPS; + double targetFPS; + double fastForwardFPS; + double slowmoFPS; + bool fastForwardToggled; + bool slowmoToggled; bool doAudioSync; melonDS::u32 getInputMask(){return inputMask;} diff --git a/src/frontend/qt_sdl/EmuInstanceAudio.cpp b/src/frontend/qt_sdl/EmuInstanceAudio.cpp index 21b112ea..a9bf1fb7 100644 --- a/src/frontend/qt_sdl/EmuInstanceAudio.cpp +++ b/src/frontend/qt_sdl/EmuInstanceAudio.cpp @@ -321,7 +321,7 @@ void EmuInstance::micProcess() for (int i = 0; i < 735; i++) { - tmp[i] = mic_blow[sample_pos]; + tmp[i] = mic_blow[sample_pos] ^ 0x8000; sample_pos++; if (sample_pos >= sample_len) sample_pos = 0; } diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp index 4222387f..9f48ea7f 100644 --- a/src/frontend/qt_sdl/EmuInstanceInput.cpp +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -47,7 +47,7 @@ const char* EmuInstance::hotkeyNames[HK_MAX] = "HK_Pause", "HK_Reset", "HK_FastForward", - "HK_FastForwardToggle", + "HK_FrameLimitToggle", "HK_FullscreenToggle", "HK_SwapScreens", "HK_SwapScreenEmphasis", @@ -56,7 +56,10 @@ const char* EmuInstance::hotkeyNames[HK_MAX] = "HK_FrameStep", "HK_PowerButton", "HK_VolumeUp", - "HK_VolumeDown" + "HK_VolumeDown", + "HK_SlowMo", + "HK_FastForwardToggle", + "HK_SlowMoToggle" }; diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index 7a6c0f40..b37f7118 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -215,6 +215,13 @@ void EmuSettingsDialog::verifyFirmware() void EmuSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index 3adff60c..c2f32cca 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -72,41 +72,46 @@ EmuThread::EmuThread(EmuInstance* inst, QObject* parent) : QThread(parent) void EmuThread::attachWindow(MainWindow* window) { - connect(this, SIGNAL(windowUpdate()), window->panel, SLOT(repaint())); connect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); connect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); connect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); connect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); connect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); - connect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); connect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); connect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); - connect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); connect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); + + if (window->winHasMenu()) + { + connect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + connect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); + } } void EmuThread::detachWindow(MainWindow* window) { - disconnect(this, SIGNAL(windowUpdate()), window->panel, SLOT(repaint())); disconnect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); disconnect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); disconnect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); disconnect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); disconnect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); - disconnect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); disconnect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); disconnect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); - disconnect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); disconnect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); + + if (window->winHasMenu()) + { + disconnect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + disconnect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); + } } void EmuThread::run() { Config::Table& globalCfg = emuInstance->getGlobalConfig(); u32 mainScreenPos[3]; - Platform::FileHandle* file; - emuInstance->updateConsole(nullptr, nullptr); + //emuInstance->updateConsole(nullptr, nullptr); // No carts are inserted when melonDS first boots mainScreenPos[0] = 0; @@ -114,11 +119,11 @@ void EmuThread::run() mainScreenPos[2] = 0; autoScreenSizing = 0; - videoSettingsDirty = false; + //videoSettingsDirty = false; if (emuInstance->usesOpenGL()) { - emuInstance->initOpenGL(); + emuInstance->initOpenGL(0); useOpenGL = true; videoRenderer = globalCfg.GetInt("3D.Renderer"); @@ -129,7 +134,8 @@ void EmuThread::run() videoRenderer = 0; } - updateRenderer(); + //updateRenderer(); + videoSettingsDirty = true; u32 nframes = 0; double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); @@ -140,23 +146,19 @@ void EmuThread::run() 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); - emuInstance->nds->RTC.SetState(state); - } - char melontitle[100]; + bool fastforward = false; + bool slowmo = false; + emuInstance->fastForwardToggled = false; + emuInstance->slowmoToggled = false; + while (emuStatus != emuStatus_Exit) { MPInterface::Get().Process(); emuInstance->inputProcess(); - if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); + if (emuInstance->hotkeyPressed(HK_FrameLimitToggle)) emit windowLimitFPSChange(); if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause(); if (emuInstance->hotkeyPressed(HK_Reset)) emuReset(); @@ -334,22 +336,34 @@ void EmuThread::run() emit windowUpdate(); winUpdateCount = 0; } + + if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emuInstance->fastForwardToggled = !emuInstance->fastForwardToggled; + if (emuInstance->hotkeyPressed(HK_SlowMoToggle)) emuInstance->slowmoToggled = !emuInstance->slowmoToggled; - bool fastforward = emuInstance->hotkeyDown(HK_FastForward); + bool enablefastforward = emuInstance->hotkeyDown(HK_FastForward) | emuInstance->fastForwardToggled; + bool enableslowmo = emuInstance->hotkeyDown(HK_SlowMo) | emuInstance->slowmoToggled; if (useOpenGL) { - // when using OpenGL: when toggling fast-forward, change the vsync interval - if (emuInstance->hotkeyPressed(HK_FastForward)) + // when using OpenGL: when toggling fast-forward or slowmo, change the vsync interval + if ((enablefastforward || enableslowmo) && !(fastforward || slowmo)) { emuInstance->setVSyncGL(false); } - else if (emuInstance->hotkeyReleased(HK_FastForward)) + else if (!(enablefastforward || enableslowmo) && (fastforward || slowmo)) { emuInstance->setVSyncGL(true); } } + fastforward = enablefastforward; + slowmo = enableslowmo; + + if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS; + else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS; + else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1.0 / 1000.0; + else emuInstance->curFPS = emuInstance->targetFPS; + if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1) { DSi* dsi = static_cast(emuInstance->nds); @@ -363,23 +377,19 @@ void EmuThread::run() emuInstance->audioVolume = volumeLevel * (256.0 / 31.0); } - if (emuInstance->doAudioSync && !fastforward) + if (emuInstance->doAudioSync && !(fastforward || slowmo)) emuInstance->audioSync(); double frametimeStep = nlines / (60.0 * 263.0); { - bool limitfps = emuInstance->doLimitFPS && !fastforward; - - double practicalFramelimit = limitfps ? frametimeStep : 1.0 / emuInstance->maxFPS; - double curtime = SDL_GetPerformanceCounter() * perfCountsSec; - frameLimitError += practicalFramelimit - (curtime - lastTime); - if (frameLimitError < -practicalFramelimit) - frameLimitError = -practicalFramelimit; - if (frameLimitError > practicalFramelimit) - frameLimitError = practicalFramelimit; + frameLimitError += emuInstance->curFPS - (curtime - lastTime); + if (frameLimitError < -emuInstance->curFPS) + frameLimitError = -emuInstance->curFPS; + if (frameLimitError > emuInstance->curFPS) + frameLimitError = emuInstance->curFPS; if (round(frameLimitError * 1000.0) > 0.0) { @@ -445,17 +455,6 @@ void EmuThread::run() //Lua Script Stuff (-for now happens at the end of each frame regardless of emuStatus) emit signalLuaUpdate(); } - - file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); - if (file) - { - RTC::StateData state; - emuInstance->nds->RTC.GetState(state); - Platform::FileWrite(&state, sizeof(state), 1, file); - Platform::CloseFile(file); - } - - NDS::Current = nullptr; } void EmuThread::sendMessage(Message msg) @@ -533,7 +532,8 @@ void EmuThread::handleMessages() break; case msg_EmuStop: - if (msg.stopExternal) emuInstance->nds->Stop(); + if (msg.param.value()) + emuInstance->nds->Stop(); emuStatus = emuStatus_Paused; emuActive = false; @@ -558,13 +558,97 @@ void EmuThread::handleMessages() break; case msg_InitGL: - emuInstance->initOpenGL(); + emuInstance->initOpenGL(msg.param.value()); useOpenGL = true; break; case msg_DeInitGL: - emuInstance->deinitOpenGL(); - useOpenGL = false; + emuInstance->deinitOpenGL(msg.param.value()); + if (msg.param.value() == 0) + useOpenGL = false; + break; + + case msg_BootROM: + msgResult = 0; + if (!emuInstance->loadROM(msg.param.value(), true)) + break; + + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); + msgResult = 1; + break; + + case msg_BootFirmware: + msgResult = 0; + if (!emuInstance->bootToMenu()) + break; + + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); + msgResult = 1; + break; + + case msg_InsertCart: + msgResult = 0; + if (!emuInstance->loadROM(msg.param.value(), false)) + break; + + msgResult = 1; + break; + + case msg_EjectCart: + emuInstance->ejectCart(); + break; + + case msg_InsertGBACart: + msgResult = 0; + if (!emuInstance->loadGBAROM(msg.param.value())) + break; + + msgResult = 1; + break; + + case msg_InsertGBAAddon: + msgResult = 0; + emuInstance->loadGBAAddon(msg.param.value()); + msgResult = 1; + break; + + case msg_EjectGBACart: + emuInstance->ejectGBACart(); + break; + + case msg_SaveState: + msgResult = emuInstance->saveState(msg.param.value().toStdString()); + break; + + case msg_LoadState: + msgResult = emuInstance->loadState(msg.param.value().toStdString()); + break; + + case msg_UndoStateLoad: + emuInstance->undoStateLoad(); + msgResult = 1; + break; + + case msg_ImportSavefile: + { + msgResult = 0; + auto f = Platform::OpenFile(msg.param.value().toStdString(), Platform::FileMode::Read); + if (!f) break; + + u32 len = FileLength(f); + + std::unique_ptr data = std::make_unique(len); + Platform::FileRewind(f); + Platform::FileRead(data.get(), len, 1, f); + + assert(emuInstance->nds != nullptr); + emuInstance->nds->SetNDSSave(data.get(), len); + + CloseFile(f); + msgResult = 1; + } break; } @@ -578,15 +662,15 @@ void EmuThread::changeWindowTitle(char* title) emit windowTitleChange(QString(title)); } -void EmuThread::initContext() +void EmuThread::initContext(int win) { - sendMessage(msg_InitGL); + sendMessage({.type = msg_InitGL, .param = win}); waitMessage(); } -void EmuThread::deinitContext() +void EmuThread::deinitContext(int win) { - sendMessage(msg_DeInitGL); + sendMessage({.type = msg_DeInitGL, .param = win}); waitMessage(); } @@ -596,29 +680,35 @@ void EmuThread::emuRun() waitMessage(); } -void EmuThread::emuPause() +void EmuThread::emuPause(bool broadcast) { sendMessage(msg_EmuPause); waitMessage(); + + if (broadcast) + emuInstance->broadcastCommand(InstCmd_Pause); } -void EmuThread::emuUnpause() +void EmuThread::emuUnpause(bool broadcast) { sendMessage(msg_EmuUnpause); waitMessage(); + + if (broadcast) + emuInstance->broadcastCommand(InstCmd_Unpause); } -void EmuThread::emuTogglePause() +void EmuThread::emuTogglePause(bool broadcast) { if (emuStatus == emuStatus_Paused) - emuUnpause(); + emuUnpause(broadcast); else - emuPause(); + emuPause(broadcast); } void EmuThread::emuStop(bool external) { - sendMessage({.type = msg_EmuStop, .stopExternal = external}); + sendMessage({.type = msg_EmuStop, .param = external}); waitMessage(); } @@ -652,11 +742,85 @@ bool EmuThread::emuIsActive() return emuActive; } +int EmuThread::bootROM(const QStringList& filename) +{ + sendMessage({.type = msg_BootROM, .param = filename}); + waitMessage(); + if (!msgResult) + return msgResult; + + sendMessage(msg_EmuRun); + waitMessage(); + return msgResult; +} + +int EmuThread::bootFirmware() +{ + sendMessage(msg_BootFirmware); + waitMessage(); + if (!msgResult) + return msgResult; + + sendMessage(msg_EmuRun); + waitMessage(); + return msgResult; +} + +int EmuThread::insertCart(const QStringList& filename, bool gba) +{ + MessageType msgtype = gba ? msg_InsertGBACart : msg_InsertCart; + + sendMessage({.type = msgtype, .param = filename}); + waitMessage(); + return msgResult; +} + +void EmuThread::ejectCart(bool gba) +{ + sendMessage(gba ? msg_EjectGBACart : msg_EjectCart); + waitMessage(); +} + +int EmuThread::insertGBAAddon(int type) +{ + sendMessage({.type = msg_InsertGBAAddon, .param = type}); + waitMessage(); + return msgResult; +} + +int EmuThread::saveState(const QString& filename) +{ + sendMessage({.type = msg_SaveState, .param = filename}); + waitMessage(); + return msgResult; +} + +int EmuThread::loadState(const QString& filename) +{ + sendMessage({.type = msg_LoadState, .param = filename}); + waitMessage(); + return msgResult; +} + +int EmuThread::undoStateLoad() +{ + sendMessage(msg_UndoStateLoad); + waitMessage(); + return msgResult; +} + +int EmuThread::importSavefile(const QString& filename) +{ + sendMessage(msg_EmuReset); + sendMessage({.type = msg_ImportSavefile, .param = filename}); + waitMessage(2); + return msgResult; +} + void EmuThread::updateRenderer() { if (videoRenderer != lastVideoRenderer) { - printf("creating renderer %d\n", videoRenderer); switch (videoRenderer) { case renderer3D_Software: diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h index 8c1a7c5a..549ba389 100644 --- a/src/frontend/qt_sdl/EmuThread.h +++ b/src/frontend/qt_sdl/EmuThread.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -68,15 +69,26 @@ public: msg_InitGL, msg_DeInitGL, + + msg_BootROM, + msg_BootFirmware, + msg_InsertCart, + msg_EjectCart, + msg_InsertGBACart, + msg_InsertGBAAddon, + msg_EjectGBACart, + + msg_LoadState, + msg_SaveState, + msg_UndoStateLoad, + + msg_ImportSavefile, }; struct Message { MessageType type; - union - { - bool stopExternal; - }; + QVariant param; }; void sendMessage(Message msg); @@ -92,20 +104,33 @@ public: // to be called from the UI thread void emuRun(); - void emuPause(); - void emuUnpause(); - void emuTogglePause(); + void emuPause(bool broadcast = true); + void emuUnpause(bool broadcast = true); + void emuTogglePause(bool broadcast = true); void emuStop(bool external); void emuExit(); void emuFrameStep(); void emuReset(); + int bootROM(const QStringList& filename); + int bootFirmware(); + int insertCart(const QStringList& filename, bool gba); + void ejectCart(bool gba); + int insertGBAAddon(int type); + + int saveState(const QString& filename); + int loadState(const QString& filename); + int undoStateLoad(); + + int importSavefile(const QString& filename); + bool emuIsRunning(); bool emuIsActive(); - void initContext(); - void deinitContext(); + void initContext(int win); + void deinitContext(int win); void updateVideoSettings() { videoSettingsDirty = true; } + void updateVideoRenderer() { videoSettingsDirty = true; lastVideoRenderer = -1; } int FrontBuffer = 0; QMutex FrontBufferLock; @@ -154,6 +179,8 @@ private: constexpr static int emuPauseStackPauseThreshold = 1; int emuPauseStack; + int msgResult = 0; + QMutex msgMutex; QSemaphore msgSemaphore; QQueue msgQueue; diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 5d5ecd01..1f71b5b9 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -132,6 +132,13 @@ bool FirmwareSettingsDialog::verifyMAC() void FirmwareSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h index 64986d62..3337228f 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h @@ -49,6 +49,9 @@ static constexpr std::initializer_list hk_general = HK_FrameStep, HK_FastForward, HK_FastForwardToggle, + HK_SlowMo, + HK_SlowMoToggle, + HK_FrameLimitToggle, HK_FullscreenToggle, HK_Lid, HK_Mic, @@ -65,6 +68,9 @@ static constexpr std::initializer_list hk_general_labels = "Reset", "Frame step", "Fast forward", + "Toggle fast forward", + "Slow mo", + "Toggle slow mo", "Toggle FPS limit", "Toggle fullscreen", "Close/open lid", diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index dfe30d12..bd02405e 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -39,7 +39,9 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); ui->spinMouseHideSeconds->setValue(cfg.GetInt("MouseHideSeconds")); ui->cbPauseLostFocus->setChecked(cfg.GetBool("PauseLostFocus")); - ui->spinMaxFPS->setValue(cfg.GetInt("MaxFPS")); + ui->spinTargetFPS->setValue(cfg.GetDouble("TargetFPS")); + ui->spinFFW->setValue(cfg.GetDouble("FastForwardFPS")); + ui->spinSlow->setValue(cfg.GetDouble("SlowmoFPS")); const QList themeKeys = QStyleFactory::keys(); const QString currentTheme = qApp->style()->objectName(); @@ -65,8 +67,50 @@ void InterfaceSettingsDialog::on_cbMouseHide_clicked() ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); } +void InterfaceSettingsDialog::on_pbClean_clicked() +{ + ui->spinTargetFPS->setValue(60.0000); +} + +void InterfaceSettingsDialog::on_pbAccurate_clicked() +{ + ui->spinTargetFPS->setValue(59.8261); +} + +void InterfaceSettingsDialog::on_pb2x_clicked() +{ + ui->spinFFW->setValue(ui->spinTargetFPS->value() * 2.0); +} + +void InterfaceSettingsDialog::on_pb3x_clicked() +{ + ui->spinFFW->setValue(ui->spinTargetFPS->value() * 3.0); +} + +void InterfaceSettingsDialog::on_pbMAX_clicked() +{ + ui->spinFFW->setValue(1000.0); +} + +void InterfaceSettingsDialog::on_pbHalf_clicked() +{ + ui->spinSlow->setValue(ui->spinTargetFPS->value() / 2.0); +} + +void InterfaceSettingsDialog::on_pbQuarter_clicked() +{ + ui->spinSlow->setValue(ui->spinTargetFPS->value() / 4.0); +} + void InterfaceSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + if (r == QDialog::Accepted) { auto& cfg = emuInstance->getGlobalConfig(); @@ -74,7 +118,18 @@ void InterfaceSettingsDialog::done(int r) cfg.SetBool("MouseHide", ui->cbMouseHide->isChecked()); cfg.SetInt("MouseHideSeconds", ui->spinMouseHideSeconds->value()); cfg.SetBool("PauseLostFocus", ui->cbPauseLostFocus->isChecked()); - cfg.SetInt("MaxFPS", ui->spinMaxFPS->value()); + + double val = ui->spinTargetFPS->value(); + if (val == 0.0) cfg.SetDouble("TargetFPS", 0.0001); + else cfg.SetDouble("TargetFPS", val); + + val = ui->spinFFW->value(); + if (val == 0.0) cfg.SetDouble("FastForwardFPS", 0.0001); + else cfg.SetDouble("FastForwardFPS", val); + + val = ui->spinSlow->value(); + if (val == 0.0) cfg.SetDouble("SlowmoFPS", 0.0001); + else cfg.SetDouble("SlowmoFPS", val); QString themeName = ui->cbxUITheme->currentData().toString(); cfg.SetQString("UITheme", themeName); diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.h b/src/frontend/qt_sdl/InterfaceSettingsDialog.h index bbc62df9..c960e560 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.h +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.h @@ -60,6 +60,16 @@ private slots: void on_cbMouseHide_clicked(); + void on_pbClean_clicked(); + void on_pbAccurate_clicked(); + + void on_pb2x_clicked(); + void on_pb3x_clicked(); + void on_pbMAX_clicked(); + + void on_pbHalf_clicked(); + void on_pbQuarter_clicked(); + private: Ui::InterfaceSettingsDialog* ui; diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui index 21d8434e..460a6e99 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui @@ -6,12 +6,12 @@ 0 0 - 337 - 275 + 389 + 356 - + 0 0 @@ -20,6 +20,9 @@ Interface settings - melonDS + + QLayout::SizeConstraint::SetFixedSize + @@ -95,32 +98,209 @@ Framerate - + - - - Fast-forward limit + + + 6 - - spinMaxFPS + + 2 - - - - - - FPS - - - 60 - - - 1000 - - - 1000 - - + + + + Target FPS + + + + + + + Fast-Forward + + + spinFFW + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 59.826099999999997 + + + + + + + 2 + + + + + + 63 + 16777215 + + + + 1/4 + + + + + + + + 62 + 16777215 + + + + 1/2 + + + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 29.913000000000000 + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 1000.000000000000000 + + + + + + + Slow-Mo + + + + + + + 2 + + + + + + 63 + 16777215 + + + + Accurate + + + + + + + + 62 + 16777215 + + + + Clean + + + + + + + + + 2 + + + + + + 41 + 16777215 + + + + 2x + + + + + + + + 41 + 16777215 + + + + 3x + + + + + + + + 41 + 16777215 + + + + MAX + + + + + + @@ -128,10 +308,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/src/frontend/qt_sdl/LANDialog.cpp b/src/frontend/qt_sdl/LANDialog.cpp index 32539e3f..bec9c48f 100644 --- a/src/frontend/qt_sdl/LANDialog.cpp +++ b/src/frontend/qt_sdl/LANDialog.cpp @@ -65,6 +65,12 @@ LANStartHostDialog::~LANStartHostDialog() void LANStartHostDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { if (ui->txtPlayerName->text().trimmed().isEmpty()) @@ -186,6 +192,12 @@ void LANStartClientDialog::onDirectConnect() void LANStartClientDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { if (ui->txtPlayerName->text().trimmed().isEmpty()) @@ -313,6 +325,12 @@ void LANDialog::on_btnLeaveGame_clicked() void LANDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + bool showwarning = true; if (lan().GetNumPlayers() < 2) showwarning = false; diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp index e241ba3d..54c35d15 100644 --- a/src/frontend/qt_sdl/MPSettingsDialog.cpp +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -59,6 +59,13 @@ MPSettingsDialog::~MPSettingsDialog() void MPSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + if (r == QDialog::Accepted) { auto& cfg = emuInstance->getGlobalConfig(); diff --git a/src/frontend/qt_sdl/NetplayDialog.cpp b/src/frontend/qt_sdl/NetplayDialog.cpp index e9ed6022..d7b7cf81 100644 --- a/src/frontend/qt_sdl/NetplayDialog.cpp +++ b/src/frontend/qt_sdl/NetplayDialog.cpp @@ -63,6 +63,12 @@ NetplayStartHostDialog::~NetplayStartHostDialog() void NetplayStartHostDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { std::string player = ui->txtPlayerName->text().toStdString(); @@ -94,6 +100,12 @@ NetplayStartClientDialog::~NetplayStartClientDialog() void NetplayStartClientDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + if (r == QDialog::Accepted) { std::string player = ui->txtPlayerName->text().toStdString(); diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index b1bc8301..f3a453d1 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -72,6 +72,13 @@ PathSettingsDialog::~PathSettingsDialog() void PathSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index 2c0e4fee..0b62eb88 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -762,6 +762,8 @@ ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent) setAttribute(Qt::WA_KeyCompression, false); setFocusPolicy(Qt::StrongFocus); setMinimumSize(screenGetMinSize()); + + glInited = false; } ScreenPanelGL::~ScreenPanelGL() @@ -773,14 +775,14 @@ bool ScreenPanelGL::createContext() // if our parent window is parented to another window, we will // share our OpenGL context with that window + MainWindow* ourwin = (MainWindow*)parentWidget(); MainWindow* parentwin = (MainWindow*)parentWidget()->parentWidget(); - if (parentwin) + //if (parentwin) + if (ourwin->getWindowID() != 0) { if (windowinfo.has_value()) - { - glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo); - glContext->DoneCurrent(); - } + if (glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo)) + glContext->DoneCurrent(); } else { @@ -788,10 +790,8 @@ bool ScreenPanelGL::createContext() GL::Context::Version{GL::Context::Profile::Core, 4, 3}, GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; if (windowinfo.has_value()) - { - glContext = GL::Context::Create(*windowinfo, versionsToTry); - glContext->DoneCurrent(); - } + if (glContext = GL::Context::Create(*windowinfo, versionsToTry)) + glContext->DoneCurrent(); } return glContext != nullptr; @@ -807,6 +807,7 @@ void ScreenPanelGL::setSwapInterval(int intv) void ScreenPanelGL::initOpenGL() { if (!glContext) return; + if (glInited) return; glContext->MakeCurrent(); @@ -917,11 +918,15 @@ void ScreenPanelGL::initOpenGL() overlayPosULoc = glGetUniformLocation(overlayShader, "uOverlayPos"); overlaySizeULoc = glGetUniformLocation(overlayShader, "uOverlaySize"); overlayScreenTypeULoc = glGetUniformLocation(overlayShader, "uOverlayScreenType"); + + glInited = true; + } void ScreenPanelGL::deinitOpenGL() { if (!glContext) return; + if (!glInited) return; glDeleteTextures(1, &screenTexture); @@ -960,6 +965,7 @@ void ScreenPanelGL::deinitOpenGL() glContext->DoneCurrent(); lastScreenWidth = lastScreenHeight = -1; + glInited = false; } void ScreenPanelGL::makeCurrentGL() @@ -1049,9 +1055,6 @@ void ScreenPanelGL::drawScreenGL() { if (!glContext) return; - auto nds = emuInstance->getNDS(); - if (!nds) return; - auto emuThread = emuInstance->getEmuThread(); glContext->MakeCurrent(); @@ -1070,50 +1073,54 @@ void ScreenPanelGL::drawScreenGL() glViewport(0, 0, w, h); - glUseProgram(screenShaderProgram); - glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + if (emuThread->emuIsActive()) + { + auto nds = emuInstance->getNDS(); - int frontbuf = emuThread->FrontBuffer; - glActiveTexture(GL_TEXTURE0); + glUseProgram(screenShaderProgram); + glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + + int frontbuf = emuThread->FrontBuffer; + glActiveTexture(GL_TEXTURE0); #ifdef OGLRENDERER_ENABLED - if (nds->GPU.GetRenderer3D().Accelerated) - { - // hardware-accelerated render - nds->GPU.GetRenderer3D().BindOutputTexture(frontbuf); - } - else -#endif - { - // regular render - glBindTexture(GL_TEXTURE_2D, screenTexture); - - if (nds->GPU.Framebuffer[frontbuf][0] && nds->GPU.Framebuffer[frontbuf][1]) + if (nds->GPU.GetRenderer3D().Accelerated) { - 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()); + // hardware-accelerated render + nds->GPU.GetRenderer3D().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(); } - 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(); - osdUpdate(); if (osdEnabled) { diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h index 3a94453c..49fd97b4 100644 --- a/src/frontend/qt_sdl/Screen.h +++ b/src/frontend/qt_sdl/Screen.h @@ -200,6 +200,7 @@ private: void drawOverlays(int type,int screen); std::unique_ptr glContext; + bool glInited; GLuint screenVertexBuffer, screenVertexArray; GLuint screenTexture; diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.cpp b/src/frontend/qt_sdl/VideoSettingsDialog.cpp index 7eebf5a4..619ecda3 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.cpp +++ b/src/frontend/qt_sdl/VideoSettingsDialog.cpp @@ -121,6 +121,12 @@ void VideoSettingsDialog::on_VideoSettingsDialog_accepted() void VideoSettingsDialog::on_VideoSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + bool old_gl = UsesGL(); auto& cfg = emuInstance->getGlobalConfig(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index c3f988b1..e0954c83 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -94,6 +94,13 @@ WifiSettingsDialog::~WifiSettingsDialog() void WifiSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 127d909b..18a1db88 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -81,6 +81,8 @@ #include "EmuInstance.h" #include "ArchiveUtil.h" #include "CameraManager.h" +#include "Window.h" +#include "AboutDialog.h" #include "LuaMain.h" @@ -234,7 +236,8 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : globalCfg(inst->globalCfg), localCfg(inst->localCfg), windowCfg(localCfg.GetTable("Window"+std::to_string(id), "Window0")), - emuThread(inst->getEmuThread()) + emuThread(inst->getEmuThread()), + enabledSaved(false) { #ifndef _WIN32 if (!parent) @@ -264,400 +267,421 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : setAcceptDrops(true); setFocusPolicy(Qt::ClickFocus); - QMenuBar* menubar = new QMenuBar(); - { - QMenu* menu = menubar->addMenu("File"); - - actOpenROM = menu->addAction("Open ROM..."); - connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); - actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - - /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); - connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); - actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - - recentMenu = menu->addMenu("Open recent"); - Config::Array recentROMs = globalCfg.GetArray("RecentROM"); - int numrecent = std::min(kMaxRecentROMs, (int)recentROMs.Size()); - for (int i = 0; i < numrecent; ++i) - { - std::string item = recentROMs.GetString(i); - if (!item.empty()) - recentFileList.push_back(QString::fromStdString(item)); - } - updateRecentFilesMenu(); - - //actBootFirmware = menu->addAction("Launch DS menu"); - actBootFirmware = menu->addAction("Boot firmware"); - connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); - - menu->addSeparator(); - - actCurrentCart = menu->addAction("DS slot: " + emuInstance->cartLabel()); - actCurrentCart->setEnabled(false); - - actInsertCart = menu->addAction("Insert cart..."); - connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); - - actEjectCart = menu->addAction("Eject cart"); - connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); - - menu->addSeparator(); - - actCurrentGBACart = menu->addAction("GBA slot: " + emuInstance->gbaCartLabel()); - actCurrentGBACart->setEnabled(false); - - actInsertGBACart = menu->addAction("Insert ROM cart..."); - connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - - { - QMenu* submenu = menu->addMenu("Insert add-on cart"); - - actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); - actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); - connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); - - actInsertGBAAddon[1] = submenu->addAction("Rumble Pak"); - actInsertGBAAddon[1]->setData(QVariant(GBAAddon_RumblePak)); - connect(actInsertGBAAddon[1], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); - } - - actEjectGBACart = menu->addAction("Eject cart"); - connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); - - menu->addSeparator(); - - actImportSavefile = menu->addAction("Import savefile"); - connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); - - 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(); - actOpenConfig = menu->addAction("Open melonDS directory"); - connect(actOpenConfig, &QAction::triggered, this, [&]() { - QDesktopServices::openUrl(QUrl::fromLocalFile(emuDirectory)); - }); - - menu->addSeparator(); - - actQuit = menu->addAction("Quit"); - connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); - actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); - } - { - 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); - - actFrameStep = menu->addAction("Frame step"); - connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); - - menu->addSeparator(); - - actPowerManagement = menu->addAction("Power management"); - connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); - - actDateTime = menu->addAction("Date and time"); - connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); - - menu->addSeparator(); - - actLuaScript = menu->addAction("Lua Script"); - connect(actLuaScript,&QAction::triggered,this,&MainWindow::onOpenLuaScript); - - menu->addSeparator(); - - actEnableCheats = menu->addAction("Enable cheats"); - actEnableCheats->setCheckable(true); - connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - - //if (inst == 0) - { - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); - - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - - actRAMInfo = menu->addAction("RAM search"); - connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); - - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); - } - - { - menu->addSeparator(); - QMenu* submenu = menu->addMenu("Multiplayer"); - - actMPNewInstance = submenu->addAction("Launch new instance"); - connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); - - submenu->addSeparator(); - - actLANStartHost = submenu->addAction("Host LAN game"); - connect(actLANStartHost, &QAction::triggered, this, &MainWindow::onLANStartHost); - - actLANStartClient = submenu->addAction("Join LAN game"); - connect(actLANStartClient, &QAction::triggered, this, &MainWindow::onLANStartClient); - - /*submenu->addSeparator(); - - actNPStartHost = submenu->addAction("NETPLAY HOST"); - connect(actNPStartHost, &QAction::triggered, this, &MainWindow::onNPStartHost); - - actNPStartClient = submenu->addAction("NETPLAY CLIENT"); - connect(actNPStartClient, &QAction::triggered, this, &MainWindow::onNPStartClient); - - actNPTest = submenu->addAction("NETPLAY GO"); - connect(actNPTest, &QAction::triggered, this, &MainWindow::onNPTest);*/ - } - } - { - QMenu* menu = menubar->addMenu("Config"); - - actEmuSettings = menu->addAction("Emu settings"); - connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - -#ifdef __APPLE__ - actPreferences = menu->addAction("Preferences..."); - connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - actPreferences->setMenuRole(QAction::PreferencesRole); +#if QT_VERSION_MAJOR == 6 && WIN32 + // The "windows11" theme has pretty massive padding around menubar items, this makes Config and Help not fit in a window at 1x screen sizing + // So let's reduce the padding a bit. + if (QApplication::style()->name() == "windows11") + setStyleSheet("QMenuBar::item { padding: 4px 8px; }"); #endif - 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); - - actCameraSettings = menu->addAction("Camera settings"); - connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); - - actAudioSettings = menu->addAction("Audio settings"); - connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); - - actMPSettings = menu->addAction("Multiplayer settings"); - connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); - - actWifiSettings = menu->addAction("Wifi settings"); - connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); - - actFirmwareSettings = menu->addAction("Firmware settings"); - connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); - - actInterfaceSettings = menu->addAction("Interface settings"); - connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - - actPathSettings = menu->addAction("Path settings"); - connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); + //hasMenu = (!parent); + hasMenu = true; + if (hasMenu) + { + QMenuBar * menubar = new QMenuBar(); { - QMenu* submenu = menu->addMenu("Savestate settings"); + QMenu * menu = menubar->addMenu("File"); - actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); - actSavestateSRAMReloc->setCheckable(true); - connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); - } + actOpenROM = menu->addAction("Open ROM..."); + connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); + actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - menu->addSeparator(); + /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); + connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); + actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - { - QMenu* submenu = menu->addMenu("Screen size"); + recentMenu = menu->addMenu("Open recent"); + loadRecentFilesMenu(true); + + //actBootFirmware = menu->addAction("Launch DS menu"); + actBootFirmware = menu->addAction("Boot firmware"); + connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); + + menu->addSeparator(); + + actCurrentCart = menu->addAction("DS slot: " + emuInstance->cartLabel()); + actCurrentCart->setEnabled(false); + + actInsertCart = menu->addAction("Insert cart..."); + connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); + + actEjectCart = menu->addAction("Eject cart"); + connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); + + menu->addSeparator(); + + actCurrentGBACart = menu->addAction("GBA slot: " + emuInstance->gbaCartLabel()); + actCurrentGBACart->setEnabled(false); + + actInsertGBACart = menu->addAction("Insert ROM cart..."); + connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - 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); + QMenu * submenu = menu->addMenu("Insert add-on cart"); + QAction *act; - for (int i = 0; i < screenRot_MAX; 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", "Hybrid"}; - - for (int i = 0; i < screenLayout_MAX; 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); - - submenu->addSeparator(); - - actScreenSwap = submenu->addAction("Swap screens"); - actScreenSwap->setCheckable(true); - connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); - } - { - QMenu* submenu = menu->addMenu("Screen sizing"); - grpScreenSizing = new QActionGroup(submenu); - - const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; - - for (int i = 0; i < screenSizing_MAX; 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); - } - { - QMenu* submenu = menu->addMenu("Aspect ratio"); - grpScreenAspectTop = new QActionGroup(submenu); - grpScreenAspectBot = new QActionGroup(submenu); - actScreenAspectTop = new QAction*[AspectRatiosNum]; - actScreenAspectBot = new QAction*[AspectRatiosNum]; - - for (int i = 0; i < 2; i++) - { - QActionGroup* group = grpScreenAspectTop; - QAction** actions = actScreenAspectTop; - - if (i == 1) + int addons[] = {GBAAddon_RAMExpansion, GBAAddon_RumblePak, -1}; + for (int i = 0; addons[i] != -1; i++) { - group = grpScreenAspectBot; - submenu->addSeparator(); - actions = actScreenAspectBot; + int addon = addons[i]; + act = submenu->addAction(emuInstance->gbaAddonName(addon)); + act->setData(QVariant(addon)); + connect(act, &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + actInsertGBAAddon.append(act); + } + } + + actEjectGBACart = menu->addAction("Eject cart"); + connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); + + menu->addSeparator(); + + actImportSavefile = menu->addAction("Import savefile"); + connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); + + 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); } - for (int j = 0; j < AspectRatiosNum; j++) + 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++) { - auto ratio = aspectRatios[j]; - QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); - actions[j] = submenu->addAction(label); - actions[j]->setActionGroup(group); - actions[j]->setData(QVariant(ratio.id)); - actions[j]->setCheckable(true); + 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); } - connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + 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(); + actOpenConfig = menu->addAction("Open melonDS directory"); + connect(actOpenConfig, &QAction::triggered, this, [&]() + { + QDesktopServices::openUrl(QUrl::fromLocalFile(emuDirectory)); + }); + + menu->addSeparator(); + + actQuit = menu->addAction("Quit"); + connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); + actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); + } + { + 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); + + actFrameStep = menu->addAction("Frame step"); + connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); + + menu->addSeparator(); + + actPowerManagement = menu->addAction("Power management"); + connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); + + actDateTime = menu->addAction("Date and time"); + connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); + + menu->addSeparator(); + + actLuaScript = menu->addAction("Lua Script"); + connect(actLuaScript,&QAction::triggered,this,&MainWindow::onOpenLuaScript); + + menu->addSeparator(); + + actEnableCheats = menu->addAction("Enable cheats"); + actEnableCheats->setCheckable(true); + connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); + + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } + + { + menu->addSeparator(); + QMenu * submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + + submenu->addSeparator(); + + actLANStartHost = submenu->addAction("Host LAN game"); + connect(actLANStartHost, &QAction::triggered, this, &MainWindow::onLANStartHost); + + actLANStartClient = submenu->addAction("Join LAN game"); + connect(actLANStartClient, &QAction::triggered, this, &MainWindow::onLANStartClient); + + /*submenu->addSeparator(); + + actNPStartHost = submenu->addAction("NETPLAY HOST"); + connect(actNPStartHost, &QAction::triggered, this, &MainWindow::onNPStartHost); + + actNPStartClient = submenu->addAction("NETPLAY CLIENT"); + connect(actNPStartClient, &QAction::triggered, this, &MainWindow::onNPStartClient); + + actNPTest = submenu->addAction("NETPLAY GO"); + connect(actNPTest, &QAction::triggered, this, &MainWindow::onNPTest);*/ } } - - if (parentWidget() != nullptr) // TEST { - QMenu* menu = menubar->addMenu("Test"); + QMenu * menu = menubar->addMenu("View"); - menu->addAction("Test"); + { + 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 < screenRot_MAX; 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", "Hybrid"}; + + for (int i = 0; i < screenLayout_MAX; 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); + + submenu->addSeparator(); + + actScreenSwap = submenu->addAction("Swap screens"); + actScreenSwap->setCheckable(true); + connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); + } + { + QMenu * submenu = menu->addMenu("Screen sizing"); + grpScreenSizing = new QActionGroup(submenu); + + const char *screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", + "Bottom only"}; + + for (int i = 0; i < screenSizing_MAX; 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); + } + { + QMenu * submenu = menu->addMenu("Aspect ratio"); + grpScreenAspectTop = new QActionGroup(submenu); + grpScreenAspectBot = new QActionGroup(submenu); + actScreenAspectTop = new QAction *[AspectRatiosNum]; + actScreenAspectBot = new QAction *[AspectRatiosNum]; + + for (int i = 0; i < 2; i++) + { + QActionGroup * group = grpScreenAspectTop; + QAction **actions = actScreenAspectTop; + + if (i == 1) + { + group = grpScreenAspectBot; + submenu->addSeparator(); + actions = actScreenAspectBot; + } + + for (int j = 0; j < AspectRatiosNum; j++) + { + auto ratio = aspectRatios[j]; + QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); + actions[j] = submenu->addAction(label); + actions[j]->setActionGroup(group); + actions[j]->setData(QVariant(ratio.id)); + actions[j]->setCheckable(true); + } + + connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + } + } + + menu->addSeparator(); + + actNewWindow = menu->addAction("Open new window"); + connect(actNewWindow, &QAction::triggered, this, &MainWindow::onOpenNewWindow); + + menu->addSeparator(); + + 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); + } + { + QMenu * menu = menubar->addMenu("Config"); + + actEmuSettings = menu->addAction("Emu settings"); + connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + +#ifdef __APPLE__ + actPreferences = menu->addAction("Preferences..."); + connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + actPreferences->setMenuRole(QAction::PreferencesRole); +#endif + + 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); + + actCameraSettings = menu->addAction("Camera settings"); + connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); + + actAudioSettings = menu->addAction("Audio settings"); + connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); + + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + + actWifiSettings = menu->addAction("Wifi settings"); + connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); + + actFirmwareSettings = menu->addAction("Firmware settings"); + connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + + actInterfaceSettings = menu->addAction("Interface settings"); + connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); + + actPathSettings = menu->addAction("Path settings"); + connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); + + { + QMenu * submenu = menu->addMenu("Savestate settings"); + + actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); + actSavestateSRAMReloc->setCheckable(true); + connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); + } + + 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); + } + { + QMenu * menu = menubar->addMenu("Help"); + actAbout = menu->addAction("About..."); + connect(actAbout, &QAction::triggered, this, [&] + { + auto dialog = AboutDialog(this); + dialog.exec(); + }); } - actScreenFiltering = menu->addAction("Screen filtering"); - actScreenFiltering->setCheckable(true); - connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); + setMenuBar(menubar); - 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); + if (localCfg.GetString("Firmware.Username") == "Arisotura") + actMPNewInstance->setText("Fart"); } - setMenuBar(menubar); - - if (localCfg.GetString("Firmware.Username") == "Arisotura") - actMPNewInstance->setText("Fart"); #ifdef Q_OS_MAC QPoint screenCenter = screen()->availableGeometry().center(); @@ -673,92 +697,101 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : QByteArray dec = QByteArray::fromBase64(raw, QByteArray::Base64Encoding | QByteArray::AbortOnBase64DecodingErrors); if (!dec.isEmpty()) restoreGeometry(dec); + // if the window was closed in fullscreen do not restore this + setWindowState(windowState() & ~Qt::WindowFullScreen); } show(); + panel = nullptr; createScreenPanel(); - actEjectCart->setEnabled(false); - actEjectGBACart->setEnabled(false); - - if (globalCfg.GetInt("Emu.ConsoleType") == 1) + if (hasMenu) { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - } + actEjectCart->setEnabled(false); + actEjectGBACart->setEnabled(false); - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - actImportSavefile->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); - - actEnableCheats->setChecked(localCfg.GetBool("EnableCheats")); - - actROMInfo->setEnabled(false); - actRAMInfo->setEnabled(false); - - actSavestateSRAMReloc->setChecked(globalCfg.GetBool("Savestate.RelocSRAM")); - - actScreenRotation[windowCfg.GetInt("ScreenRotation")]->setChecked(true); - - int screenGap = windowCfg.GetInt("ScreenGap"); - for (int i = 0; i < 6; i++) - { - if (actScreenGap[i]->data().toInt() == screenGap) + if (globalCfg.GetInt("Emu.ConsoleType") == 1) { - actScreenGap[i]->setChecked(true); - break; + actInsertGBACart->setEnabled(false); + for (auto act: actInsertGBAAddon) + act->setEnabled(false); } - } - actScreenLayout[windowCfg.GetInt("ScreenLayout")]->setChecked(true); - actScreenSizing[windowCfg.GetInt("ScreenSizing")]->setChecked(true); - actIntegerScaling->setChecked(windowCfg.GetBool("IntegerScaling")); + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + actImportSavefile->setEnabled(false); - actScreenSwap->setChecked(windowCfg.GetBool("ScreenSwap")); + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); - int aspectTop = windowCfg.GetInt("ScreenAspectTop"); - int aspectBot = windowCfg.GetInt("ScreenAspectBot"); - for (int i = 0; i < AspectRatiosNum; i++) - { - if (aspectTop == aspectRatios[i].id) - actScreenAspectTop[i]->setChecked(true); - if (aspectBot == aspectRatios[i].id) - actScreenAspectBot[i]->setChecked(true); - } + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); - actScreenFiltering->setChecked(windowCfg.GetBool("ScreenFilter")); - actShowOSD->setChecked(showOSD); + actSetupCheats->setEnabled(false); + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); - actLimitFramerate->setChecked(emuInstance->doLimitFPS); - actAudioSync->setChecked(emuInstance->doAudioSync); + actEnableCheats->setChecked(localCfg.GetBool("EnableCheats")); - if (emuInstance->instanceID > 0) - { - actEmuSettings->setEnabled(false); - actVideoSettings->setEnabled(false); - actMPSettings->setEnabled(false); - actWifiSettings->setEnabled(false); - actInterfaceSettings->setEnabled(false); + actROMInfo->setEnabled(false); + actRAMInfo->setEnabled(false); + + actSavestateSRAMReloc->setChecked(globalCfg.GetBool("Savestate.RelocSRAM")); + + actScreenRotation[windowCfg.GetInt("ScreenRotation")]->setChecked(true); + + int screenGap = windowCfg.GetInt("ScreenGap"); + for (int i = 0; i < 6; i++) + { + if (actScreenGap[i]->data().toInt() == screenGap) + { + actScreenGap[i]->setChecked(true); + break; + } + } + + actScreenLayout[windowCfg.GetInt("ScreenLayout")]->setChecked(true); + actScreenSizing[windowCfg.GetInt("ScreenSizing")]->setChecked(true); + actIntegerScaling->setChecked(windowCfg.GetBool("IntegerScaling")); + + actScreenSwap->setChecked(windowCfg.GetBool("ScreenSwap")); + + int aspectTop = windowCfg.GetInt("ScreenAspectTop"); + int aspectBot = windowCfg.GetInt("ScreenAspectBot"); + for (int i = 0; i < AspectRatiosNum; i++) + { + if (aspectTop == aspectRatios[i].id) + actScreenAspectTop[i]->setChecked(true); + if (aspectBot == aspectRatios[i].id) + actScreenAspectBot[i]->setChecked(true); + } + + actScreenFiltering->setChecked(windowCfg.GetBool("ScreenFilter")); + actShowOSD->setChecked(showOSD); + + actLimitFramerate->setChecked(emuInstance->doLimitFPS); + actAudioSync->setChecked(emuInstance->doAudioSync); + + if (emuInstance->instanceID > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); #ifdef __APPLE__ - actPreferences->setEnabled(false); + actPreferences->setEnabled(false); #endif // __APPLE__ + } + + if (emuThread->emuIsActive()) + onEmuStart(); } QObject::connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::onAppStateChanged); @@ -769,8 +802,11 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : MainWindow::~MainWindow() { - delete[] actScreenAspectTop; - delete[] actScreenAspectBot; + if (hasMenu) + { + delete[] actScreenAspectTop; + delete[] actScreenAspectBot; + } } void MainWindow::osdAddMessage(unsigned int color, const char* msg) @@ -779,8 +815,20 @@ void MainWindow::osdAddMessage(unsigned int color, const char* msg) panel->osdAddMessage(color, msg); } +void MainWindow::saveEnabled(bool enabled) +{ + if (enabledSaved) return; + windowCfg.SetBool("Enabled", enabled); + enabledSaved = true; +} + void MainWindow::closeEvent(QCloseEvent* event) { + if (windowID == 0) + emuInstance->saveEnabledWindows(); + else + saveEnabled(false); + QByteArray geom = saveGeometry(); QByteArray enc = geom.toBase64(QByteArray::Base64Encoding); windowCfg.SetString("Geometry", enc.toStdString()); @@ -796,6 +844,9 @@ void MainWindow::closeEvent(QCloseEvent* event) void MainWindow::createScreenPanel() { + if (panel) delete panel; + panel = nullptr; + hasOGL = globalCfg.GetBool("Screen.UseGL") || (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); @@ -806,7 +857,15 @@ void MainWindow::createScreenPanel() panel = panelGL; - panelGL->createContext(); + // Check that creating the context hasn't failed + if (panelGL->createContext() == false) + { + Log(LogLevel::Error, "Failed to create OpenGL context, falling back to Software Renderer.\n"); + hasOGL = false; + + globalCfg.SetBool("Screen.UseGL", false); + globalCfg.SetInt("3D.Renderer", renderer3D_Software); + } } if (!hasOGL) @@ -817,9 +876,12 @@ void MainWindow::createScreenPanel() } setCentralWidget(panel); - actScreenFiltering->setEnabled(hasOGL); + if (hasMenu) + actScreenFiltering->setEnabled(hasOGL); panel->osdSetEnabled(showOSD); + connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); + connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); } @@ -910,20 +972,12 @@ void MainWindow::dropEvent(QDropEvent* event) QList urls = event->mimeData()->urls(); if (urls.count() > 1) return; // not handling more than one file at once - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } const QString filename = file.last(); const bool romInsideArchive = file.size() > 1; @@ -937,9 +991,8 @@ void MainWindow::dropEvent(QDropEvent* event) if (isNdsRom) { - if (!emuInstance->loadROM(file, true)) + if (!emuThread->bootROM(file)) { - emuThread->emuUnpause(); return; } @@ -948,28 +1001,20 @@ void MainWindow::dropEvent(QDropEvent* event) recentFileList.prepend(barredFilename); updateRecentFilesMenu(); - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); - updateCartInserted(false); } else if (isGbaRom) { - if (!emuInstance->loadGBAROM(file)) + if (!emuThread->insertCart(file, true)) { - emuThread->emuUnpause(); return; } - emuThread->emuUnpause(); - updateCartInserted(true); } else { QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM."); - emuThread->emuUnpause(); return; } } @@ -1016,6 +1061,9 @@ bool MainWindow::verifySetup() bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) { + if (file.isEmpty() && gbafile.isEmpty()) + return false; + if (!verifySetup()) { return false; @@ -1024,7 +1072,8 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool gbaloaded = false; if (!gbafile.isEmpty()) { - if (!emuInstance->loadGBAROM(gbafile)) return false; + if (!emuThread->insertCart(gbafile, true)) + return false; gbaloaded = true; } @@ -1032,33 +1081,31 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool ndsloaded = false; if (!file.isEmpty()) { - if (!emuInstance->loadROM(file, true)) return false; + if (boot) + { + if (!emuThread->bootROM(file)) + return false; + } + else + { + if (!emuThread->insertCart(file, false)) + return false; + } recentFileList.removeAll(file.join("|")); recentFileList.prepend(file.join("|")); updateRecentFilesMenu(); ndsloaded = true; } - - if (boot) + else if (boot) { - if (ndsloaded) - { - emuInstance->nds->Start(); - emuThread->emuRun(); - } - else - { - onBootFirmware(); - } + if (!emuThread->bootFirmware()) + return false; } updateCartInserted(false); - if (gbaloaded) - { updateCartInserted(true); - } return true; } @@ -1169,6 +1216,8 @@ QString MainWindow::pickFileFromArchive(QString archiveFileName) QStringList MainWindow::pickROM(bool gba) { + emuThread->emuPause(); + const QString console = gba ? "GBA" : "DS"; const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; @@ -1193,53 +1242,63 @@ QStringList MainWindow::pickROM(bool gba) "All supported files (*" + allROMs + ")" + extraFilters ); - if (filename.isEmpty()) return {}; + if (filename.isEmpty()) + { + emuThread->emuUnpause(); + return {}; + } globalCfg.SetQString("LastROMFolder", QFileInfo(filename).dir().path()); - return splitArchivePath(filename, false); + auto ret = splitArchivePath(filename, false); + emuThread->emuUnpause(); + return ret; } void MainWindow::updateCartInserted(bool gba) { bool inserted; + QString label; if (gba) { - inserted = emuInstance->gbaCartInserted() && (globalCfg.GetInt("Emu.ConsoleType") == 0); - actCurrentGBACart->setText("GBA slot: " + emuInstance->gbaCartLabel()); - actEjectGBACart->setEnabled(inserted); + inserted = emuInstance->gbaCartInserted() && (emuInstance->getConsoleType() == 0); + label = "GBA slot: " + emuInstance->gbaCartLabel(); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + if (!win->hasMenu) return; + win->actCurrentGBACart->setText(label); + win->actEjectGBACart->setEnabled(inserted); + }); } else { inserted = emuInstance->cartInserted(); - actCurrentCart->setText("DS slot: " + emuInstance->cartLabel()); - actEjectCart->setEnabled(inserted); - actImportSavefile->setEnabled(inserted); - actSetupCheats->setEnabled(inserted); - actROMInfo->setEnabled(inserted); - actRAMInfo->setEnabled(inserted); + label = "DS slot: " + emuInstance->cartLabel(); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + if (!win->hasMenu) return; + win->actCurrentCart->setText(label); + win->actEjectCart->setEnabled(inserted); + win->actImportSavefile->setEnabled(inserted); + win->actSetupCheats->setEnabled(inserted); + win->actROMInfo->setEnabled(inserted); + win->actRAMInfo->setEnabled(inserted); + }); } } void MainWindow::onOpenFile() { - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } QStringList file = pickROM(false); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } - - if (!emuInstance->loadROM(file, true)) + + if (!emuThread->bootROM(file)) { - emuThread->emuUnpause(); return; } @@ -1248,10 +1307,6 @@ void MainWindow::onOpenFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); - updateCartInserted(false); } @@ -1262,12 +1317,23 @@ void MainWindow::onClearRecentFiles() updateRecentFilesMenu(); } -void MainWindow::updateRecentFilesMenu() +void MainWindow::loadRecentFilesMenu(bool loadcfg) { - recentMenu->clear(); + if (loadcfg) + { + recentFileList.clear(); - Config::Array recentroms = globalCfg.GetArray("RecentROM"); - recentroms.Clear(); + Config::Array recentROMs = globalCfg.GetArray("RecentROM"); + int numrecent = std::min(kMaxRecentROMs, (int) recentROMs.Size()); + for (int i = 0; i < numrecent; ++i) + { + std::string item = recentROMs.GetString(i); + if (!item.empty()) + recentFileList.push_back(QString::fromStdString(item)); + } + } + + recentMenu->clear(); for (int i = 0; i < recentFileList.size(); ++i) { @@ -1298,8 +1364,6 @@ void MainWindow::updateRecentFilesMenu() QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display)); actRecentFile_i->setData(item_full); connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); - - recentroms.SetQString(i, recentFileList.at(i)); } while (recentFileList.size() > 10) @@ -1312,8 +1376,24 @@ void MainWindow::updateRecentFilesMenu() if (recentFileList.empty()) actClearRecentList->setEnabled(false); +} + +void MainWindow::updateRecentFilesMenu() +{ + Config::Array recentroms = globalCfg.GetArray("RecentROM"); + recentroms.Clear(); + + for (int i = 0; i < recentFileList.size(); ++i) + { + if (i >= kMaxRecentROMs) break; + + recentroms.SetQString(i, recentFileList.at(i)); + } Config::Save(); + loadRecentFilesMenu(false); + + emuInstance->broadcastCommand(InstCmd_UpdateRecentFiles); } void MainWindow::onClickRecentFile() @@ -1321,24 +1401,15 @@ void MainWindow::onClickRecentFile() QAction *act = (QAction *)sender(); QString filename = act->data().toString(); - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } const QStringList file = splitArchivePath(filename, true); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } - - if (!emuInstance->loadROM(file, true)) + + if (!emuThread->bootROM(file)) { - emuThread->emuUnpause(); return; } @@ -1346,88 +1417,52 @@ void MainWindow::onClickRecentFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); - updateCartInserted(false); } void MainWindow::onBootFirmware() { - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } - if (!emuInstance->bootToMenu()) + if (!emuThread->bootFirmware()) { - // TODO: better error reporting? QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); - emuThread->emuUnpause(); return; } - - assert(emuInstance->nds != nullptr); - emuInstance->nds->Start(); - emuThread->emuRun(); } void MainWindow::onInsertCart() { - emuThread->emuPause(); - QStringList file = pickROM(false); if (file.isEmpty()) + return; + + if (!emuThread->insertCart(file, false)) { - emuThread->emuUnpause(); return; } - if (!emuInstance->loadROM(file, false)) - { - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - updateCartInserted(false); } void MainWindow::onEjectCart() { - emuThread->emuPause(); - - emuInstance->ejectCart(); - - emuThread->emuUnpause(); - + emuThread->ejectCart(false); updateCartInserted(false); } void MainWindow::onInsertGBACart() { - emuThread->emuPause(); - QStringList file = pickROM(true); if (file.isEmpty()) + return; + + if (!emuThread->insertCart(file, true)) { - emuThread->emuUnpause(); return; } - if (!emuInstance->loadGBAROM(file)) - { - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - updateCartInserted(true); } @@ -1436,23 +1471,13 @@ void MainWindow::onInsertGBAAddon() QAction* act = (QAction*)sender(); int type = act->data().toInt(); - emuThread->emuPause(); - - emuInstance->loadGBAAddon(type); - - emuThread->emuUnpause(); - + emuThread->insertGBAAddon(type); updateCartInserted(true); } void MainWindow::onEjectGBACart() { - emuThread->emuPause(); - - emuInstance->ejectGBACart(); - - emuThread->emuUnpause(); - + emuThread->ejectCart(true); updateCartInserted(true); } @@ -1467,30 +1492,25 @@ void MainWindow::onSaveState() { int slot = ((QAction*)sender())->data().toInt(); - emuThread->emuPause(); - - std::string filename; + QString filename; if (slot > 0) { - filename = emuInstance->getSavestateName(slot); + filename = QString::fromStdString(emuInstance->getSavestateName(slot)); } else { // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getSaveFileName(this, + emuThread->emuPause(); + filename = QFileDialog::getSaveFileName(this, "Save state", globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.mln);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); + emuThread->emuUnpause(); + if (filename.isEmpty()) return; - } - - filename = qfilename.toStdString(); } - if (emuInstance->saveState(filename)) + if (emuThread->saveState(filename)) { if (slot > 0) emuInstance->osdAddMessage(0, "State saved to slot %d", slot); else emuInstance->osdAddMessage(0, "State saved to file"); @@ -1501,8 +1521,6 @@ void MainWindow::onSaveState() { emuInstance->osdAddMessage(0xFFA0A0, "State save failed"); } - - emuThread->emuUnpause(); } void MainWindow::onLuaLoadState(const QString& filename) @@ -1516,39 +1534,33 @@ void MainWindow::onLoadState() { int slot = ((QAction*)sender())->data().toInt(); - emuThread->emuPause(); - - std::string filename; + QString filename; if (slot > 0) { - filename = emuInstance->getSavestateName(slot); + filename = QString::fromStdString(emuInstance->getSavestateName(slot)); } else { // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getOpenFileName(this, + emuThread->emuPause(); + filename = QFileDialog::getOpenFileName(this, "Load state", globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.ml*);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); + emuThread->emuUnpause(); + if (filename.isEmpty()) return; - } - - filename = qfilename.toStdString(); } - if (!Platform::FileExists(filename)) + if (!Platform::FileExists(filename.toStdString())) { if (slot > 0) emuInstance->osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); else emuInstance->osdAddMessage(0xFFA0A0, "State file does not exist"); - emuThread->emuUnpause(); return; } - if (emuInstance->loadState(filename)) + if (emuThread->loadState(filename)) { if (slot > 0) emuInstance->osdAddMessage(0, "State loaded from slot %d", slot); else emuInstance->osdAddMessage(0, "State loaded from file"); @@ -1559,38 +1571,28 @@ void MainWindow::onLoadState() { emuInstance->osdAddMessage(0xFFA0A0, "State load failed"); } - - emuThread->emuUnpause(); } void MainWindow::onUndoStateLoad() { - emuThread->emuPause(); - emuInstance->undoStateLoad(); - emuThread->emuUnpause(); + emuThread->undoStateLoad(); emuInstance->osdAddMessage(0, "State load undone"); } void MainWindow::onImportSavefile() { - emuThread->emuPause(); QString path = QFileDialog::getOpenFileName(this, "Select savefile", globalCfg.GetQString("LastROMFolder"), "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); if (path.isEmpty()) - { - emuThread->emuUnpause(); return; - } - Platform::FileHandle* f = Platform::OpenFile(path.toStdString(), Platform::FileMode::Read); - if (!f) + if (!Platform::FileExists(path.toStdString())) { QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); - emuThread->emuUnpause(); return; } @@ -1601,24 +1603,15 @@ void MainWindow::onImportSavefile() "The emulation will be reset and the current savefile overwritten.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) { - emuThread->emuUnpause(); return; } - - emuInstance->reset(); } - u32 len = FileLength(f); - - std::unique_ptr data = std::make_unique(len); - Platform::FileRewind(f); - Platform::FileRead(data.get(), len, 1, f); - - assert(emuInstance->nds != nullptr); - emuInstance->nds->SetNDSSave(data.get(), len); - - CloseFile(f); - emuThread->emuUnpause(); + if (!emuThread->importSavefile(path)) + { + QMessageBox::critical(this, "melonDS", "Could not import the given savefile."); + return; + } } void MainWindow::onQuit() @@ -1767,6 +1760,8 @@ void MainWindow::onNPTest() void MainWindow::updateMPInterface(MPInterfaceType type) { + if (!hasMenu) return; + // MP interface was changed, reflect it in the UI bool enable = (type == MPInterface_Local); @@ -1809,15 +1804,15 @@ void MainWindow::onEmuSettingsDialogFinished(int res) if (globalCfg.GetInt("Emu.ConsoleType") == 1) { actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); + for (auto act : actInsertGBAAddon) + act->setEnabled(false); actEjectGBACart->setEnabled(false); } else { actInsertGBACart->setEnabled(true); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(true); + for (auto act : actInsertGBAAddon) + act->setEnabled(true); actEjectGBACart->setEnabled(emuInstance->gbaCartInserted()); } @@ -1922,6 +1917,7 @@ void MainWindow::onUpdateAudioVolume(int vol, int dsisync) void MainWindow::onUpdateAudioSettings() { + if (!emuThread->emuIsActive()) return; assert(emuInstance->nds != nullptr); int interp = globalCfg.GetInt("Audio.Interpolation"); @@ -1983,8 +1979,9 @@ void MainWindow::onOpenInterfaceSettings() void MainWindow::onUpdateInterfaceSettings() { pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus"); - emuInstance->maxFPS = globalCfg.GetInt("MaxFPS"); - + emuInstance->targetFPS = 1.0 / globalCfg.GetDouble("TargetFPS"); + emuInstance->fastForwardFPS = 1.0 / globalCfg.GetDouble("FastForwardFPS"); + emuInstance->slowmoFPS = 1.0 / globalCfg.GetDouble("SlowmoFPS"); panel->setMouseHide(globalCfg.GetBool("MouseHide"), globalCfg.GetInt("MouseHideSeconds")*1000); } @@ -2087,6 +2084,11 @@ void MainWindow::onChangeIntegerScaling(bool checked) emit screenLayoutChange(); } +void MainWindow::onOpenNewWindow() +{ + emuInstance->createWindow(); +} + void MainWindow::onChangeScreenFiltering(bool checked) { windowCfg.SetBool("ScreenFilter", checked); @@ -2120,24 +2122,28 @@ void MainWindow::onTitleUpdate(QString title) setWindowTitle(title); } -void ToggleFullscreen(MainWindow* mainWindow) +void MainWindow::toggleFullscreen() { - if (!mainWindow->isFullScreen()) + if (!isFullScreen()) { - mainWindow->showFullScreen(); - mainWindow->menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working + showFullScreen(); + if (hasMenu) + menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working } else { - mainWindow->showNormal(); - int menuBarHeight = mainWindow->menuBar()->sizeHint().height(); - mainWindow->menuBar()->setFixedHeight(menuBarHeight); + showNormal(); + if (hasMenu) + { + int menuBarHeight = menuBar()->sizeHint().height(); + menuBar()->setFixedHeight(menuBarHeight); + } } } void MainWindow::onFullscreenToggled() { - ToggleFullscreen(this); + toggleFullscreen(); } void MainWindow::onScreenEmphasisToggled() @@ -2158,6 +2164,8 @@ void MainWindow::onScreenEmphasisToggled() void MainWindow::onEmuStart() { + if (!hasMenu) return; + for (int i = 1; i < 9; i++) { actSaveState[i]->setEnabled(true); @@ -2181,6 +2189,8 @@ void MainWindow::onEmuStart() void MainWindow::onEmuStop() { + if (!hasMenu) return; + for (int i = 0; i < 9; i++) { actSaveState[i]->setEnabled(false); @@ -2201,31 +2211,69 @@ void MainWindow::onEmuStop() void MainWindow::onEmuPause(bool pause) { + if (!hasMenu) return; + actPause->setChecked(pause); } void MainWindow::onEmuReset() { + if (!hasMenu) return; + actUndoStateLoad->setEnabled(false); } void MainWindow::onUpdateVideoSettings(bool glchange) { + if (!emuInstance) return; + + // if we have a parent window: pass the message over to the parent + // the topmost parent takes care of updating all the windows + MainWindow* parentwin = (MainWindow*)parentWidget(); + if (parentwin) + return parentwin->onUpdateVideoSettings(glchange); + + bool hadOGL = hasOGL; if (glchange) { emuThread->emuPause(); - if (hasOGL) emuThread->deinitContext(); + if (hadOGL) emuThread->deinitContext(windowID); - delete panel; createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); } emuThread->updateVideoSettings(); if (glchange) { - if (hasOGL) emuThread->initContext(); + if (hasOGL) emuThread->initContext(windowID); + } + + // update any child windows we have + auto childwins = findChildren(nullptr, Qt::FindDirectChildrenOnly); + for (auto child: childwins) + { + // child windows may belong to a different instance + // in that case we need to signal their thread appropriately + auto thread = child->getEmuInstance()->getEmuThread(); + + if (glchange) + { + if (hadOGL) thread->deinitContext(child->windowID); + child->createScreenPanel(); + } + + if (child->getWindowID() == 0) + thread->updateVideoSettings(); + + if (glchange) + { + if (hasOGL) thread->initContext(child->windowID); + } + } + + if (glchange) + { emuThread->emuUnpause(); } } diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index 85aea883..f7506ec3 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -111,8 +111,17 @@ public: EmuInstance* getEmuInstance() { return emuInstance; } Config::Table& getWindowConfig() { return windowCfg; } + LuaConsoleDialog* getLuaDialog() {return luaDialog;} + int getWindowID() { return windowID; } + + bool winHasMenu() { return hasMenu; } + + void saveEnabled(bool enabled); + + void toggleFullscreen(); + bool hasOpenGL() { return hasOGL; } GL::Context* getOGLContext(); void initOpenGL(); @@ -131,6 +140,9 @@ public: // called when the MP interface is changed void updateMPInterface(melonDS::MPInterfaceType type); + void loadRecentFilesMenu(bool loadcfg); + //void updateVideoSettings(bool glchange); + protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; @@ -213,6 +225,7 @@ private slots: void onChangeScreenSizing(QAction* act); void onChangeScreenAspect(QAction* act); void onChangeIntegerScaling(bool checked); + void onOpenNewWindow(); void onChangeScreenFiltering(bool checked); void onChangeShowOSD(bool checked); void onChangeLimitFramerate(bool checked); @@ -256,6 +269,7 @@ private: bool pausedManually; int windowID; + bool enabledSaved; EmuInstance* emuInstance; EmuThread* emuThread; @@ -268,6 +282,8 @@ private: public: ScreenPanel* panel; + bool hasMenu; + QAction* actOpenROM; QAction* actBootFirmware; QAction* actCurrentCart; @@ -275,7 +291,7 @@ public: QAction* actEjectCart; QAction* actCurrentGBACart; QAction* actInsertGBACart; - QAction* actInsertGBAAddon[2]; + QList actInsertGBAAddon; QAction* actEjectGBACart; QAction* actImportSavefile; QAction* actSaveState[9]; @@ -332,12 +348,13 @@ public: QAction** actScreenAspectTop; QActionGroup* grpScreenAspectBot; QAction** actScreenAspectBot; + QAction* actNewWindow; QAction* actScreenFiltering; QAction* actShowOSD; QAction* actLimitFramerate; QAction* actAudioSync; + + QAction* actAbout; }; -void ToggleFullscreen(MainWindow* mainWindow); - #endif // WINDOW_H diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 9a9c93cb..d940340a 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -168,6 +168,18 @@ int numEmuInstances() } +void broadcastInstanceCommand(int cmd, QVariant& param, int sourceinst) +{ + for (int i = 0; i < kMaxEmuInstances; i++) + { + if (i == sourceinst) continue; + if (!emuInstances[i]) continue; + + emuInstances[i]->handleCommand(cmd, param); + } +} + + void pathInit() { // First, check for the portable directory next to the executable. @@ -250,10 +262,8 @@ bool MelonApplication::event(QEvent *event) MainWindow* win = inst->getMainWindow(); QFileOpenEvent *openEvent = static_cast(event); - inst->getEmuThread()->emuPause(); const QStringList file = win->splitArchivePath(openEvent->file(), true); - if (!win->preloadROMs(file, {}, true)) - inst->getEmuThread()->emuUnpause(); + win->preloadROMs(file, {}, true); } return QApplication::event(event); @@ -364,6 +374,9 @@ int main(int argc, char** argv) if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n"); win->preloadROMs(dsfile, gbafile, options->boot); + + if (options->fullscreen) + win->toggleFullscreen(); } int ret = melon.exec(); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 77cdf4ee..e0d38963 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -31,6 +31,15 @@ #include "ScreenLayout.h" #include "MPInterface.h" +enum +{ + InstCmd_Pause, + InstCmd_Unpause, + + InstCmd_UpdateRecentFiles, + //InstCmd_UpdateVideoSettings, +}; + class MelonApplication : public QApplication { Q_OBJECT @@ -50,6 +59,8 @@ void deleteEmuInstance(int id); void deleteAllEmuInstances(int first = 0); int numEmuInstances(); +void broadcastInstanceCommand(int cmd, QVariant& param, int sourceinst); + void setMPInterface(melonDS::MPInterfaceType type); #endif // MAIN_H diff --git a/src/version.h.in b/src/version.h.in index 5aed0d49..9b4cd8ce 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -25,5 +25,11 @@ #define MELONDS_VERSION_SUFFIX "${MELONDS_VERSION_SUFFIX}" #define MELONDS_VERSION MELONDS_VERSION_BASE MELONDS_VERSION_SUFFIX +#ifdef MELONDS_EMBED_BUILD_INFO +#define MELONDS_GIT_BRANCH "${MELONDS_GIT_BRANCH}" +#define MELONDS_GIT_HASH "${MELONDS_GIT_HASH}" +#define MELONDS_BUILD_PROVIDER "${MELONDS_BUILD_PROVIDER}" +#endif + #endif // VERSION_H