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 1bcf2f66..044d01ee 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
- 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 8236ccd3..8d500c03 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; [
@@ -46,8 +49,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 [
@@ -66,7 +74,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/res/melon.qrc b/res/melon.qrc
index 38915bbf..3c5824d6 100644
--- a/res/melon.qrc
+++ b/res/melon.qrc
@@ -2,5 +2,6 @@
icon/melon_256x256.png
+ melon.svg
diff --git a/src/ARM.cpp b/src/ARM.cpp
index 5a08bce4..abb864d6 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/ARMInterpreter_LoadStore.cpp b/src/ARMInterpreter_LoadStore.cpp
index c5b25eb2..7b34d5af 100644
--- a/src/ARMInterpreter_LoadStore.cpp
+++ b/src/ARMInterpreter_LoadStore.cpp
@@ -609,8 +609,8 @@ void A_LDM(ARM* cpu)
}
}
- u32 pc;
- if ((cpu->CurInstr & (1<<15)))
+ u32 pc = 0;
+ if (cpu->CurInstr & (1<<15))
{
if (preinc) base += 4;
dabort |= !(first ? cpu->DataRead32 (base, &pc)
@@ -622,11 +622,6 @@ void A_LDM(ARM* cpu)
pc &= ~0x1;
}
- // switch back to previous regs
- if ((cpu->CurInstr & (1<<22)) && !(cpu->CurInstr & (1<<15)))
- cpu->UpdateMode((cpu->CPSR&~0x1F)|0x10, cpu->CPSR, true);
-
-
if (__builtin_popcount(cpu->CurInstr & 0xFFFF) == 1) [[unlikely]] // single reg
{
if (cpu->Num == 0 && cpu->DataRegion == Mem9_ITCM) cpu->NDS.ARM9Timestamp += 1;
@@ -666,6 +661,10 @@ void A_LDM(ARM* cpu)
else
cpu->R[baseid] = wbbase;
}
+
+ // switch back to previous regs
+ if ((cpu->CurInstr & (1<<22)) && !(cpu->CurInstr & (1<<15)))
+ cpu->UpdateMode((cpu->CPSR&~0x1F)|0x10, cpu->CPSR, true);
// jump if pc got written
if (cpu->CurInstr & (1<<15))
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/GBACart.cpp b/src/GBACart.cpp
index 4fd42894..a62aca6b 100644
--- a/src/GBACart.cpp
+++ b/src/GBACart.cpp
@@ -582,6 +582,11 @@ int CartGameSolarSensor::SetInput(int num, bool pressed)
return -1;
}
+void CartGameSolarSensor::SetLightLevel(u8 level) noexcept
+{
+ LightLevel = std::clamp(level, 0, 10);
+}
+
void CartGameSolarSensor::ProcessGPIO()
{
if (GPIO.data & 4) return; // Boktai chip select
diff --git a/src/GBACart.h b/src/GBACart.h
index f6fb95dd..726a234d 100644
--- a/src/GBACart.h
+++ b/src/GBACart.h
@@ -158,6 +158,8 @@ public:
void DoSavestate(Savestate* file) override;
int SetInput(int num, bool pressed) override;
+ void SetLightLevel(u8 level) noexcept;
+ [[nodiscard]] u8 GetLightLevel() const noexcept { return LightLevel; }
protected:
void ProcessGPIO() override;
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..5f2aef19 100644
--- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp
@@ -41,7 +41,7 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui(
emuInstance = ((MainWindow*)parent)->getEmuInstance();
auto& cfg = emuInstance->getGlobalConfig();
auto& instcfg = emuInstance->getLocalConfig();
- bool emuActive = emuInstance->getEmuThread()->emuIsActive();
+ bool emuActive = emuInstance->emuIsActive();
oldInterp = cfg.GetInt("Audio.Interpolation");
oldBitDepth = cfg.GetInt("Audio.BitDepth");
@@ -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 524fa13d..7dc4a00c 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
@@ -119,6 +122,10 @@ elseif (APPLE)
target_sources(melonDS PRIVATE
../duckstation/gl/context_agl.mm
)
+ set_source_files_properties(
+ ../duckstation/gl/context_agl.mm
+ PROPERTIES COMPILE_OPTIONS "-Wno-deprecated-declarations"
+ )
else()
find_package(X11 REQUIRED)
find_package(EGL REQUIRED)
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 0a161b6f..02be5b65 100644
--- a/src/frontend/qt_sdl/Config.cpp
+++ b/src/frontend/qt_sdl/Config.cpp
@@ -80,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}},
@@ -99,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},
diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp
index 54bae531..2ac3f42d 100644
--- a/src/frontend/qt_sdl/EmuInstance.cpp
+++ b/src/frontend/qt_sdl/EmuInstance.cpp
@@ -93,25 +93,25 @@ EmuInstance::EmuInstance(int inst) : deleting(false),
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n");
- targetFPS = 1.0 / 60.0;
+ targetFPS = 60.0;
}
- else targetFPS = 1.0 / val;
+ else targetFPS = 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;
+ fastForwardFPS = 60.0;
}
- else fastForwardFPS = 1.0 / val;
+ else fastForwardFPS = 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;
+ slowmoFPS = 60.0;
}
- else slowmoFPS = 1.0 / val;
+ else slowmoFPS = val;
doAudioSync = globalCfg.GetBool("AudioSync");
@@ -136,7 +136,15 @@ EmuInstance::EmuInstance(int inst) : deleting(false),
createWindow();
emuThread->start();
- emuThread->emuPause();
+
+ // if any extra windows were saved as enabled, open them
+ for (int i = 1; i < kMaxWindows; i++)
+ {
+ std::string key = "Window" + std::to_string(i) + ".Enabled";
+ bool enable = localCfg.GetBool(key);
+ if (enable)
+ createWindow(i);
+ }
}
EmuInstance::~EmuInstance()
@@ -144,8 +152,6 @@ EmuInstance::~EmuInstance()
deleting = true;
deleteAllWindows();
- MPInterface::Get().End(instanceID);
-
emuThread->emuExit();
emuThread->wait();
delete emuThread;
@@ -154,6 +160,13 @@ EmuInstance::~EmuInstance()
audioDeInit();
inputDeInit();
+
+ NDS::Current = nullptr;
+ if (nds)
+ {
+ saveRTCData();
+ delete nds;
+ }
}
@@ -166,7 +179,7 @@ std::string EmuInstance::instanceFileSuffix()
return suffix;
}
-void EmuInstance::createWindow()
+void EmuInstance::createWindow(int id)
{
if (numWindows >= kMaxWindows)
{
@@ -174,16 +187,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;
@@ -192,6 +209,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)
@@ -201,12 +228,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);
@@ -219,11 +242,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()
@@ -232,6 +264,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, ...)
{
@@ -285,24 +368,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)
@@ -1080,7 +1157,7 @@ std::optional EmuInstance::loadSDCard(const string& key) noexcept
void EmuInstance::enableCheats(bool enable)
{
cheatsOn = enable;
- if (cheatFile)
+ if (cheatsOn && cheatFile)
nds->AREngine.Cheats = cheatFile->GetCodes();
else
nds->AREngine.Cheats.clear();
@@ -1105,6 +1182,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();
@@ -1116,6 +1217,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;
@@ -1149,8 +1253,6 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
}
- int consoletype = globalCfg.GetInt("Emu.ConsoleType");
-
auto arm9bios = loadARM9BIOS();
if (!arm9bios)
return false;
@@ -1159,7 +1261,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
if (!arm7bios)
return false;
- auto firmware = loadFirmware(consoletype);
+ auto firmware = loadFirmware(consoleType);
if (!firmware)
return false;
@@ -1203,7 +1305,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
NDSArgs* args = &ndsargs;
std::optional dsiargs = std::nullopt;
- if (consoletype == 1)
+ if (consoleType == 1)
{
ndsargs.GBAROM = nullptr;
@@ -1234,19 +1336,25 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
args = &(*dsiargs);
}
-
- if ((!nds) || (consoletype != nds->ConsoleType))
+ renderLock.lock();
+ 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
{
@@ -1260,7 +1368,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;
@@ -1276,14 +1384,13 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
dsi->EjectGBACart();
}
}
+ renderLock.unlock();
return true;
}
void EmuInstance::reset()
{
- consoleType = globalCfg.GetInt("Emu.ConsoleType");
-
updateConsole(Keep {}, Keep {});
if (consoleType == 1) ejectGBACart();
@@ -1973,25 +2080,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 04290f56..a135a52c 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
{
@@ -86,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();
@@ -103,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();
@@ -139,6 +147,11 @@ public:
int getJoystickID() { return joystickID; }
SDL_Joystick* getJoystick() { return joystick; }
+ void touchScreen(int x, int y);
+ void releaseScreen();
+
+ QMutex renderLock;
+
private:
static int lastSep(const std::string& path);
std::string getAssetPath(bool gba, const std::string& configpath, const std::string& ext, const std::string& file);
@@ -168,7 +181,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);
@@ -186,6 +198,7 @@ private:
void loadGBAAddon(int type);
void ejectGBACart();
bool gbaCartInserted();
+ QString gbaAddonName(int addon);
QString gbaCartLabel();
void audioInit();
@@ -222,6 +235,10 @@ private:
bool hotkeyPressed(int id) { return hotkeyPress & (1<micLock);
int maxlen = sizeof(micExtBuffer) / sizeof(s16);
+ if ((inst->micExtBufferCount + len) > maxlen)
+ len = maxlen - inst->micExtBufferCount;
+
if ((inst->micExtBufferWritePos + len) > maxlen)
{
u32 len1 = maxlen - inst->micExtBufferWritePos;
@@ -121,11 +125,15 @@ void EmuInstance::micCallback(void* data, Uint8* stream, int len)
memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], input, len*sizeof(s16));
inst->micExtBufferWritePos += len;
}
+
+ inst->micExtBufferCount += len;
+ SDL_UnlockMutex(inst->micLock);
}
void EmuInstance::audioMute()
{
audioMuted = false;
+ if (numEmuInstances() < 2) return;
switch (mpAudioMode)
{
@@ -134,10 +142,16 @@ void EmuInstance::audioMute()
break;
case 2: // only currently focused instance
- //if (mainWindow != nullptr)
- // audioMuted = !mainWindow->isActiveWindow();
- // TODO!!
- printf("TODO!! audioMute mode 2\n");
+ audioMuted = true;
+ for (int i = 0; i < kMaxWindows; i++)
+ {
+ if (!windowList[i]) continue;
+ if (windowList[i]->isFocused())
+ {
+ audioMuted = false;
+ break;
+ }
+ }
break;
}
}
@@ -270,6 +284,8 @@ void EmuInstance::micLoadWav(const std::string& name)
void EmuInstance::micProcess()
{
+ SDL_LockMutex(micLock);
+
int type = micInputType;
bool cmd = hotkeyDown(HK_Mic);
@@ -278,6 +294,8 @@ void EmuInstance::micProcess()
type = micInputType_Silence;
}
+ const int kFrameLen = 735;
+
switch (type)
{
case micInputType_Silence: // no mic
@@ -289,21 +307,35 @@ void EmuInstance::micProcess()
case micInputType_Wav: // WAV
if (micBuffer)
{
- if ((micBufferReadPos + 735) > micBufferLength)
- {
- s16 tmp[735];
- u32 len1 = micBufferLength - micBufferReadPos;
- memcpy(&tmp[0], &micBuffer[micBufferReadPos], len1*sizeof(s16));
- memcpy(&tmp[len1], &micBuffer[0], (735 - len1)*sizeof(s16));
+ int len = kFrameLen;
+ if (micExtBufferCount < len)
+ len = micExtBufferCount;
- nds->MicInputFrame(tmp, 735);
- micBufferReadPos = 735 - len1;
+ s16 tmp[kFrameLen];
+
+ if ((micBufferReadPos + len) > micBufferLength)
+ {
+ u32 part1 = micBufferLength - micBufferReadPos;
+ memcpy(&tmp[0], &micBuffer[micBufferReadPos], part1*sizeof(s16));
+ memcpy(&tmp[part1], &micBuffer[0], (len - part1)*sizeof(s16));
+
+ micBufferReadPos = len - part1;
}
else
{
- nds->MicInputFrame(&micBuffer[micBufferReadPos], 735);
- micBufferReadPos += 735;
+ memcpy(&tmp[0], &micBuffer[micBufferReadPos], len*sizeof(s16));
+
+ micBufferReadPos += len;
}
+
+ if (len < kFrameLen)
+ {
+ for (int i = len; i < kFrameLen; i++)
+ tmp[i] = tmp[len-1];
+ }
+ nds->MicInputFrame(tmp, 735);
+
+ micExtBufferCount -= len;
}
else
{
@@ -317,19 +349,21 @@ void EmuInstance::micProcess()
int sample_len = sizeof(mic_blow) / sizeof(u16);
static int sample_pos = 0;
- s16 tmp[735];
+ s16 tmp[kFrameLen];
- for (int i = 0; i < 735; i++)
+ for (int i = 0; i < kFrameLen; i++)
{
- tmp[i] = mic_blow[sample_pos];
+ tmp[i] = mic_blow[sample_pos] ^ 0x8000;
sample_pos++;
if (sample_pos >= sample_len) sample_pos = 0;
}
- nds->MicInputFrame(tmp, 735);
+ nds->MicInputFrame(tmp, kFrameLen);
}
break;
}
+
+ SDL_UnlockMutex(micLock);
}
void EmuInstance::setupMicInputData()
@@ -402,12 +436,15 @@ void EmuInstance::audioInit()
memset(micExtBuffer, 0, sizeof(micExtBuffer));
micExtBufferWritePos = 0;
+ micExtBufferCount = 0;
micWavBuffer = nullptr;
micBuffer = nullptr;
micBufferLength = 0;
micBufferReadPos = 0;
+ micLock = SDL_CreateMutex();
+
setupMicInputData();
}
@@ -425,6 +462,9 @@ void EmuInstance::audioDeInit()
if (micWavBuffer) delete[] micWavBuffer;
micWavBuffer = nullptr;
+
+ if (micLock) SDL_DestroyMutex(micLock);
+ micLock = nullptr;
}
void EmuInstance::audioSync()
diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp
index bb06c242..aa1c529f 100644
--- a/src/frontend/qt_sdl/EmuInstanceInput.cpp
+++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp
@@ -74,6 +74,10 @@ void EmuInstance::inputInit()
hotkeyMask = 0;
lastHotkeyMask = 0;
+ isTouching = false;
+ touchX = 0;
+ touchY = 0;
+
joystick = nullptr;
controller = nullptr;
hasRumble = false;
@@ -353,3 +357,15 @@ void EmuInstance::inputProcess()
hotkeyRelease = lastHotkeyMask & ~hotkeyMask;
lastHotkeyMask = hotkeyMask;
}
+
+void EmuInstance::touchScreen(int x, int y)
+{
+ touchX = x;
+ touchY = y;
+ isTouching = true;
+}
+
+void EmuInstance::releaseScreen()
+{
+ isTouching = false;
+}
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 4ce4efda..f767c6db 100644
--- a/src/frontend/qt_sdl/EmuThread.cpp
+++ b/src/frontend/qt_sdl/EmuThread.cpp
@@ -70,41 +70,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;
@@ -112,11 +117,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");
@@ -127,7 +132,8 @@ void EmuThread::run()
videoRenderer = 0;
}
- updateRenderer();
+ //updateRenderer();
+ videoSettingsDirty = true;
u32 nframes = 0;
double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency();
@@ -138,15 +144,6 @@ 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;
@@ -234,6 +231,7 @@ void EmuThread::run()
// update render settings if needed
if (videoSettingsDirty)
{
+ emuInstance->renderLock.lock();
if (useOpenGL)
{
emuInstance->setVSyncGL(true);
@@ -249,11 +247,17 @@ void EmuThread::run()
updateRenderer();
videoSettingsDirty = false;
+ emuInstance->renderLock.unlock();
}
// process input and hotkeys
emuInstance->nds->SetKeyMask(emuInstance->inputMask);
+ if (emuInstance->isTouching)
+ emuInstance->nds->TouchScreen(emuInstance->touchX, emuInstance->touchY);
+ else
+ emuInstance->nds->ReleaseScreen();
+
if (emuInstance->hotkeyPressed(HK_Lid))
{
bool lid = !emuInstance->nds->IsLidClosed();
@@ -317,13 +321,13 @@ void EmuThread::run()
if (!useOpenGL)
{
- FrontBufferLock.lock();
- FrontBuffer = emuInstance->nds->GPU.FrontBuffer;
- FrontBufferLock.unlock();
+ frontBufferLock.lock();
+ frontBuffer = emuInstance->nds->GPU.FrontBuffer;
+ frontBufferLock.unlock();
}
else
{
- FrontBuffer = emuInstance->nds->GPU.FrontBuffer;
+ frontBuffer = emuInstance->nds->GPU.FrontBuffer;
emuInstance->drawScreenGL();
}
@@ -362,7 +366,7 @@ void EmuThread::run()
if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS;
else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS;
- else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1.0 / 1000.0;
+ else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1000.0;
else emuInstance->curFPS = emuInstance->targetFPS;
if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1)
@@ -381,16 +385,18 @@ void EmuThread::run()
if (emuInstance->doAudioSync && !(fastforward || slowmo))
emuInstance->audioSync();
- double frametimeStep = nlines / (60.0 * 263.0);
+ double frametimeStep = nlines / (emuInstance->curFPS * 263.0);
+
+ if (frametimeStep < 0.001) frametimeStep = 0.001;
{
double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
- frameLimitError += emuInstance->curFPS - (curtime - lastTime);
- if (frameLimitError < -emuInstance->curFPS)
- frameLimitError = -emuInstance->curFPS;
- if (frameLimitError > emuInstance->curFPS)
- frameLimitError = emuInstance->curFPS;
+ frameLimitError += frametimeStep - (curtime - lastTime);
+ if (frameLimitError < -frametimeStep)
+ frameLimitError = -frametimeStep;
+ if (frameLimitError > frametimeStep)
+ frameLimitError = frametimeStep;
if (round(frameLimitError * 1000.0) > 0.0)
{
@@ -418,10 +424,11 @@ void EmuThread::run()
winUpdateFreq = fps / (u32)round(fpstarget);
if (winUpdateFreq < 1)
winUpdateFreq = 1;
-
+
+ double actualfps = (59.8261 * 263.0) / nlines;
int inst = emuInstance->instanceID;
if (inst == 0)
- sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
+ sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, actualfps);
else
sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1);
changeWindowTitle(melontitle);
@@ -453,17 +460,6 @@ void EmuThread::run()
handleMessages();
}
-
- 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)
@@ -482,7 +478,8 @@ void EmuThread::waitMessage(int num)
void EmuThread::waitAllMessages()
{
if (QThread::currentThread() == this) return;
- msgSemaphore.acquire(msgSemaphore.available());
+ while (!msgQueue.empty())
+ msgSemaphore.acquire();
}
void EmuThread::handleMessages()
@@ -498,6 +495,7 @@ void EmuThread::handleMessages()
emuPauseStack = emuPauseStackRunning;
emuInstance->audioDisable();
+ MPInterface::Get().End(emuInstance->instanceID);
break;
case msg_EmuRun:
@@ -541,7 +539,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;
@@ -566,13 +565,101 @@ 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;
+
+ case msg_EnableCheats:
+ emuInstance->enableCheats(msg.param.value());
break;
}
@@ -586,15 +673,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();
}
@@ -604,29 +691,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();
}
@@ -660,11 +753,91 @@ 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::enableCheats(bool enable)
+{
+ sendMessage({.type = msg_EnableCheats, .param = enable});
+ waitMessage();
+}
+
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 cd36eb4c..f28c0604 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,28 @@ 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,
+
+ msg_EnableCheats,
};
struct Message
{
MessageType type;
- union
- {
- bool stopExternal;
- };
+ QVariant param;
};
void sendMessage(Message msg);
@@ -92,23 +106,38 @@ 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);
+
+ void enableCheats(bool enable);
+
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;
+ int frontBuffer = 0;
+ QMutex frontBufferLock;
signals:
void windowUpdate();
@@ -152,6 +181,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/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp
index 2e7e75c9..bd02405e 100644
--- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp
@@ -104,6 +104,13 @@ void InterfaceSettingsDialog::on_pbQuarter_clicked()
void InterfaceSettingsDialog::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/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/OSD_shaders.h b/src/frontend/qt_sdl/OSD_shaders.h
index a2a6af70..253fcdc5 100644
--- a/src/frontend/qt_sdl/OSD_shaders.h
+++ b/src/frontend/qt_sdl/OSD_shaders.h
@@ -26,6 +26,7 @@ uniform vec2 uScreenSize;
uniform ivec2 uOSDPos;
uniform ivec2 uOSDSize;
uniform float uScaleFactor;
+uniform float uTexScale;
in vec2 vPosition;
@@ -35,8 +36,8 @@ void main()
{
vec4 fpos;
- vec2 osdpos = (vPosition * vec2(uOSDSize * uScaleFactor));
- fTexcoord = osdpos;
+ vec2 osdpos = (vPosition * vec2(uOSDSize));
+ fTexcoord = osdpos * uTexScale;
osdpos += uOSDPos;
fpos.xy = ((osdpos * 2.0) / uScreenSize * uScaleFactor) - 1.0;
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 425d99b5..10abe1ce 100644
--- a/src/frontend/qt_sdl/Screen.cpp
+++ b/src/frontend/qt_sdl/Screen.cpp
@@ -46,11 +46,13 @@
#include "main_shaders.h"
#include "OSD_shaders.h"
#include "font.h"
+#include "version.h"
using namespace melonDS;
const u32 kOSDMargin = 6;
+const int kLogoWidth = 192;
ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent)
@@ -80,6 +82,29 @@ ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent)
loadConfig();
setFilter(mainWindow->getWindowConfig().GetBool("ScreenFilter"));
+
+ splashLogo = QPixmap(":/melon-logo");
+
+ strncpy(splashText[0].text, "File->Open ROM...", 256);
+ splashText[0].id = 0x80000000;
+ splashText[0].color = 0;
+ splashText[0].rendered = false;
+ splashText[0].rainbowstart = -1;
+
+ strncpy(splashText[1].text, "to get started", 256);
+ splashText[1].id = 0x80000001;
+ splashText[1].color = 0;
+ splashText[1].rendered = false;
+ splashText[1].rainbowstart = -1;
+
+ std::string url = MELONDS_URL;
+ int urlpos = url.find("://");
+ urlpos = (urlpos == std::string::npos) ? 0 : urlpos+3;
+ strncpy(splashText[2].text, url.c_str() + urlpos, 256);
+ splashText[2].id = 0x80000002;
+ splashText[2].color = 0;
+ splashText[2].rendered = false;
+ splashText[2].rainbowstart = -1;
}
ScreenPanel::~ScreenPanel()
@@ -150,6 +175,8 @@ void ScreenPanel::setupScreenLayout()
aspectBot);
numScreens = layout.GetScreenTransforms(screenMatrix[0], screenKind);
+
+ calcSplashLayout();
}
QSize ScreenPanel::screenGetMinSize(int factor = 1)
@@ -222,6 +249,7 @@ void ScreenPanel::resizeEvent(QResizeEvent* event)
void ScreenPanel::mousePressEvent(QMouseEvent* event)
{
event->accept();
+ if (!emuInstance->emuIsActive()) { touching = false; return; }
if (event->button() != Qt::LeftButton) return;
int x = event->pos().x();
@@ -230,21 +258,20 @@ void ScreenPanel::mousePressEvent(QMouseEvent* event)
if (layout.GetTouchCoords(x, y, false))
{
touching = true;
- assert(emuInstance->getNDS() != nullptr);
- emuInstance->getNDS()->TouchScreen(x, y);
+ emuInstance->touchScreen(x, y);
}
}
void ScreenPanel::mouseReleaseEvent(QMouseEvent* event)
{
event->accept();
+ if (!emuInstance->emuIsActive()) { touching = false; return; }
if (event->button() != Qt::LeftButton) return;
if (touching)
{
touching = false;
- assert(emuInstance->getNDS() != nullptr);
- emuInstance->getNDS()->ReleaseScreen();
+ emuInstance->releaseScreen();
}
}
@@ -254,6 +281,7 @@ void ScreenPanel::mouseMoveEvent(QMouseEvent* event)
showCursor();
+ if (!emuInstance->emuIsActive()) return;
//if (!(event->buttons() & Qt::LeftButton)) return;
if (!touching) return;
@@ -262,14 +290,14 @@ void ScreenPanel::mouseMoveEvent(QMouseEvent* event)
if (layout.GetTouchCoords(x, y, true))
{
- assert(emuInstance->getNDS() != nullptr);
- emuInstance->getNDS()->TouchScreen(x, y);
+ emuInstance->touchScreen(x, y);
}
}
void ScreenPanel::tabletEvent(QTabletEvent* event)
{
event->accept();
+ if (!emuInstance->emuIsActive()) { touching = false; return; }
switch(event->type())
{
@@ -287,16 +315,14 @@ void ScreenPanel::tabletEvent(QTabletEvent* event)
if (layout.GetTouchCoords(x, y, event->type()==QEvent::TabletMove))
{
touching = true;
- assert(emuInstance->getNDS() != nullptr);
- emuInstance->getNDS()->TouchScreen(x, y);
+ emuInstance->touchScreen(x, y);
}
}
break;
case QEvent::TabletRelease:
if (touching)
{
- assert(emuInstance->getNDS() != nullptr);
- emuInstance->getNDS()->ReleaseScreen();
+ emuInstance->releaseScreen();
touching = false;
}
break;
@@ -313,6 +339,7 @@ void ScreenPanel::touchEvent(QTouchEvent* event)
#endif
event->accept();
+ if (!emuInstance->emuIsActive()) { touching = false; return; }
switch(event->type())
{
@@ -333,16 +360,14 @@ void ScreenPanel::touchEvent(QTouchEvent* event)
if (layout.GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate))
{
touching = true;
- assert(emuInstance->getNDS() != nullptr);
- emuInstance->getNDS()->TouchScreen(x, y);
+ emuInstance->touchScreen(x, y);
}
}
break;
case QEvent::TouchEnd:
if (touching)
{
- assert(emuInstance->getNDS() != nullptr);
- emuInstance->getNDS()->ReleaseScreen();
+ emuInstance->releaseScreen();
touching = false;
}
break;
@@ -360,6 +385,10 @@ bool ScreenPanel::event(QEvent* event)
touchEvent((QTouchEvent*)event);
return true;
}
+ else if (event->type() == QEvent::FocusIn)
+ mainWindow->onFocusIn();
+ else if (event->type() == QEvent::FocusOut)
+ mainWindow->onFocusOut();
return QWidget::event(event);
}
@@ -478,8 +507,14 @@ void ScreenPanel::osdRenderItem(OSDItem* item)
u32 color = item->color;
bool rainbow = (color == 0);
- u32 ticks = (u32)QDateTime::currentMSecsSinceEpoch();
- u32 rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600;
+ u32 rainbowinc;
+ if (item->rainbowstart == -1)
+ {
+ u32 ticks = (u32) QDateTime::currentMSecsSinceEpoch();
+ rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600;
+ }
+ else
+ rainbowinc = (u32)item->rainbowstart;
color |= 0xFF000000;
const u32 shadow = 0xE0000000;
@@ -577,6 +612,8 @@ void ScreenPanel::osdRenderItem(OSDItem* item)
bitmap[(y * w) + x] = shadow;
}
}
+
+ item->rainbowend = (int)rainbowinc;
}
void ScreenPanel::osdDeleteItem(OSDItem* item)
@@ -598,11 +635,12 @@ void ScreenPanel::osdAddMessage(unsigned int color, const char* text)
OSDItem item;
- item.id = osdID++;
+ item.id = (osdID++) & 0x7FFFFFFF;
item.timestamp = QDateTime::currentMSecsSinceEpoch();
strncpy(item.text, text, 255); item.text[255] = '\0';
item.color = color;
item.rendered = false;
+ item.rainbowstart = -1;
osdItems.push_back(item);
@@ -636,6 +674,73 @@ void ScreenPanel::osdUpdate()
it++;
}
+ // render splashscreen text items if needed
+
+ int rainbowinc = -1;
+ bool needrecalc = false;
+
+ for (int i = 0; i < 3; i++)
+ {
+ if (!splashText[i].rendered)
+ {
+ splashText[i].rainbowstart = rainbowinc;
+ osdRenderItem(&splashText[i]);
+ splashText[i].rendered = true;
+ rainbowinc = splashText[i].rainbowend;
+ needrecalc = true;
+ }
+ }
+
+ osdMutex.unlock();
+
+ if (needrecalc)
+ calcSplashLayout();
+}
+
+void ScreenPanel::calcSplashLayout()
+{
+ if (!splashText[0].rendered)
+ return;
+
+ osdMutex.lock();
+
+ int w = width();
+ int h = height();
+
+ int xlogo = (w - kLogoWidth) / 2;
+ int ylogo = (h - kLogoWidth) / 2;
+
+ // top text
+ int totalwidth = splashText[0].bitmap.width() + 6 + splashText[1].bitmap.width();
+ if (totalwidth >= w)
+ {
+ // stacked vertically
+ splashPos[0].setX((width() - splashText[0].bitmap.width()) / 2);
+ splashPos[1].setX((width() - splashText[1].bitmap.width()) / 2);
+
+ int basey = ylogo / 2;
+ splashPos[0].setY(basey - splashText[0].bitmap.height() - 1);
+ splashPos[1].setY(basey + 1);
+ }
+ else
+ {
+ // horizontal
+ splashPos[0].setX((w - totalwidth) / 2);
+ splashPos[1].setX(splashPos[0].x() + splashText[0].bitmap.width() + 6);
+
+ int basey = (ylogo - splashText[0].bitmap.height()) / 2;
+ splashPos[0].setY(basey);
+ splashPos[1].setY(basey);
+ }
+
+ // bottom text
+ splashPos[2].setX((w - splashText[2].bitmap.width()) / 2);
+ splashPos[2].setY(ylogo + kLogoWidth + ((ylogo - splashText[2].bitmap.height()) / 2));
+
+ // logo
+ splashPos[3].setX(xlogo);
+ splashPos[3].setY(ylogo);
+
osdMutex.unlock();
}
@@ -678,20 +783,21 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event)
if (emuThread->emuIsActive())
{
+ emuInstance->renderLock.lock();
auto nds = emuInstance->getNDS();
assert(nds != nullptr);
- emuThread->FrontBufferLock.lock();
- int frontbuf = emuThread->FrontBuffer;
+ emuThread->frontBufferLock.lock();
+ int frontbuf = emuThread->frontBuffer;
if (!nds->GPU.Framebuffer[frontbuf][0] || !nds->GPU.Framebuffer[frontbuf][1])
{
- emuThread->FrontBufferLock.unlock();
+ emuThread->frontBufferLock.unlock();
return;
}
memcpy(screen[0].scanLine(0), nds->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4);
memcpy(screen[1].scanLine(0), nds->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4);
- emuThread->FrontBufferLock.unlock();
+ emuThread->frontBufferLock.unlock();
QRect screenrc(0, 0, 256, 192);
@@ -700,9 +806,24 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event)
painter.setTransform(screenTrans[i]);
painter.drawImage(screenrc, screen[screenKind[i]]);
}
+ emuInstance->renderLock.unlock();
}
osdUpdate();
+
+ if (!emuThread->emuIsActive())
+ {
+ // splashscreen
+ osdMutex.lock();
+
+ painter.drawPixmap(QRect(splashPos[3], QSize(kLogoWidth, kLogoWidth)), splashLogo);
+
+ for (int i = 0; i < 3; i++)
+ painter.drawImage(splashPos[i], splashText[i].bitmap);
+
+ osdMutex.unlock();
+ }
+
if (osdEnabled)
{
osdMutex.lock();
@@ -736,6 +857,8 @@ ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent)
setAttribute(Qt::WA_KeyCompression, false);
setFocusPolicy(Qt::StrongFocus);
setMinimumSize(screenGetMinSize());
+
+ glInited = false;
}
ScreenPanelGL::~ScreenPanelGL()
@@ -747,14 +870,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
{
@@ -762,10 +885,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;
@@ -781,6 +902,7 @@ void ScreenPanelGL::setSwapInterval(int intv)
void ScreenPanelGL::initOpenGL()
{
if (!glContext) return;
+ if (glInited) return;
glContext->MakeCurrent();
@@ -856,6 +978,7 @@ void ScreenPanelGL::initOpenGL()
osdPosULoc = glGetUniformLocation(osdShader, "uOSDPos");
osdSizeULoc = glGetUniformLocation(osdShader, "uOSDSize");
osdScaleFactorULoc = glGetUniformLocation(osdShader, "uScaleFactor");
+ osdTexScaleULoc = glGetUniformLocation(osdShader, "uTexScale");
const float osdvertices[6*2] =
{
@@ -876,12 +999,26 @@ void ScreenPanelGL::initOpenGL()
glEnableVertexAttribArray(0); // position
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0));
+ // splash logo texture
+ QImage logo = splashLogo.scaled(kLogoWidth*2, kLogoWidth*2).toImage();
+ GLuint tex;
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, logo.width(), logo.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, logo.bits());
+ logoTexture = tex;
+
transferLayout();
+ glInited = true;
}
void ScreenPanelGL::deinitOpenGL()
{
if (!glContext) return;
+ if (!glInited) return;
glDeleteTextures(1, &screenTexture);
@@ -900,12 +1037,15 @@ void ScreenPanelGL::deinitOpenGL()
glDeleteVertexArrays(1, &osdVertexArray);
glDeleteBuffers(1, &osdVertexBuffer);
+ glDeleteTextures(1, &logoTexture);
+
glDeleteProgram(osdShader);
glContext->DoneCurrent();
lastScreenWidth = lastScreenHeight = -1;
+ glInited = false;
}
void ScreenPanelGL::makeCurrentGL()
@@ -947,9 +1087,6 @@ void ScreenPanelGL::drawScreenGL()
{
if (!glContext) return;
- auto nds = emuInstance->getNDS();
- if (!nds) return;
-
auto emuThread = emuInstance->getEmuThread();
glContext->MakeCurrent();
@@ -968,51 +1105,101 @@ 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 (!emuThread->emuIsActive())
+ {
+ // splashscreen
+ osdMutex.lock();
+
+ glUseProgram(osdShader);
+
+ glUniform2f(osdScreenSizeULoc, w, h);
+ glUniform1f(osdScaleFactorULoc, factor);
+ glUniform1f(osdTexScaleULoc, 2.0);
+
+ glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer);
+ glBindVertexArray(osdVertexArray);
+
+ glActiveTexture(GL_TEXTURE0);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ glBindTexture(GL_TEXTURE_2D, logoTexture);
+ glUniform2i(osdPosULoc, splashPos[3].x(), splashPos[3].y());
+ glUniform2i(osdSizeULoc, kLogoWidth, kLogoWidth);
+ glDrawArrays(GL_TRIANGLES, 0, 2*3);
+
+ glUniform1f(osdTexScaleULoc, 1.0);
+
+ for (int i = 0; i < 3; i++)
+ {
+ OSDItem& item = splashText[i];
+
+ if (!osdTextures.count(item.id))
+ continue;
+
+ glBindTexture(GL_TEXTURE_2D, osdTextures[item.id]);
+ glUniform2i(osdPosULoc, splashPos[i].x(), splashPos[i].y());
+ glUniform2i(osdSizeULoc, item.bitmap.width(), item.bitmap.height());
+ glDrawArrays(GL_TRIANGLES, 0, 2*3);
+ }
+
+ glDisable(GL_BLEND);
+ glUseProgram(0);
+
+ osdMutex.unlock();
+ }
+
if (osdEnabled)
{
osdMutex.lock();
@@ -1023,6 +1210,7 @@ void ScreenPanelGL::drawScreenGL()
glUniform2f(osdScreenSizeULoc, w, h);
glUniform1f(osdScaleFactorULoc, factor);
+ glUniform1f(osdTexScaleULoc, 1.0);
glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer);
glBindVertexArray(osdVertexArray);
diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h
index f3662d8d..a988815e 100644
--- a/src/frontend/qt_sdl/Screen.h
+++ b/src/frontend/qt_sdl/Screen.h
@@ -110,6 +110,9 @@ protected:
bool rendered;
QImage bitmap;
+
+ int rainbowstart;
+ int rainbowend;
};
QMutex osdMutex;
@@ -117,6 +120,10 @@ protected:
unsigned int osdID;
std::deque osdItems;
+ QPixmap splashLogo;
+ OSDItem splashText[3];
+ QPoint splashPos[4];
+
void loadConfig();
virtual void setupScreenLayout();
@@ -141,6 +148,8 @@ protected:
virtual void osdDeleteItem(OSDItem* item);
void osdUpdate();
+
+ void calcSplashLayout();
};
@@ -197,11 +206,12 @@ private:
void setupScreenLayout() override;
std::unique_ptr glContext;
+ bool glInited;
GLuint screenVertexBuffer, screenVertexArray;
GLuint screenTexture;
GLuint screenShaderProgram;
- GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc;
+ GLint screenShaderTransformULoc, screenShaderScreenSizeULoc;
QMutex screenSettingsLock;
WindowInfo windowInfo;
@@ -210,11 +220,14 @@ private:
GLuint osdShader;
GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc;
- GLfloat osdScaleFactorULoc;
+ GLint osdScaleFactorULoc;
+ GLint osdTexScaleULoc;
GLuint osdVertexArray;
GLuint osdVertexBuffer;
std::map osdTextures;
+ GLuint logoTexture;
+
void osdRenderItem(OSDItem* item) override;
void osdDeleteItem(OSDItem* item) override;
};
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 3020defd..596a0f5c 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"
using namespace melonDS;
@@ -232,7 +234,9 @@ 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),
+ focused(true)
{
#ifndef _WIN32
if (!parent)
@@ -262,395 +266,416 @@ 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();
-
- 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();
+
+ 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();
@@ -666,92 +691,102 @@ 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);
+ actEnableCheats->setEnabled(false);
+ 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);
@@ -762,8 +797,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)
@@ -772,8 +810,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());
@@ -789,6 +839,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);
@@ -799,7 +852,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)
@@ -810,9 +871,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();
}
@@ -903,20 +967,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;
@@ -930,9 +986,8 @@ void MainWindow::dropEvent(QDropEvent* event)
if (isNdsRom)
{
- if (!emuInstance->loadROM(file, true))
+ if (!emuThread->bootROM(file))
{
- emuThread->emuUnpause();
return;
}
@@ -941,41 +996,46 @@ 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;
}
}
void MainWindow::focusInEvent(QFocusEvent* event)
{
- emuInstance->audioMute();
+ onFocusIn();
}
void MainWindow::focusOutEvent(QFocusEvent* event)
+{
+ onFocusOut();
+}
+
+void MainWindow::onFocusIn()
+{
+ focused = true;
+ if (emuInstance)
+ emuInstance->audioMute();
+}
+
+void MainWindow::onFocusOut()
{
// focusOutEvent is called through the window close event handler
// prevent use after free
+ focused = false;
if (emuInstance)
emuInstance->audioMute();
}
@@ -1009,6 +1069,9 @@ bool MainWindow::verifySetup()
bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot)
{
+ if (file.isEmpty() && gbafile.isEmpty())
+ return false;
+
if (!verifySetup())
{
return false;
@@ -1017,7 +1080,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;
}
@@ -1025,33 +1089,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;
}
@@ -1162,6 +1224,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;
@@ -1186,53 +1250,64 @@ 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->actEnableCheats->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;
}
@@ -1241,10 +1316,6 @@ void MainWindow::onOpenFile()
recentFileList.prepend(filename);
updateRecentFilesMenu();
- assert(emuInstance->nds != nullptr);
- emuInstance->nds->Start();
- emuThread->emuRun();
-
updateCartInserted(false);
}
@@ -1255,12 +1326,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)
{
@@ -1291,8 +1373,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)
@@ -1305,8 +1385,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()
@@ -1314,24 +1410,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;
}
@@ -1339,88 +1426,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);
}
@@ -1429,23 +1480,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);
}
@@ -1453,30 +1494,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");
@@ -1487,47 +1523,39 @@ void MainWindow::onSaveState()
{
emuInstance->osdAddMessage(0xFFA0A0, "State save failed");
}
-
- emuThread->emuUnpause();
}
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");
@@ -1538,38 +1566,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;
}
@@ -1580,24 +1598,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()
@@ -1660,7 +1669,12 @@ void MainWindow::onOpenPowerManagement()
void MainWindow::onEnableCheats(bool checked)
{
localCfg.SetBool("EnableCheats", checked);
- emuInstance->enableCheats(checked);
+ emuThread->enableCheats(checked);
+
+ emuInstance->doOnAllWindows([=](MainWindow* win)
+ {
+ win->actEnableCheats->setChecked(checked);
+ }, windowID);
}
void MainWindow::onSetupCheats()
@@ -1730,6 +1744,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);
@@ -1772,15 +1788,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());
}
@@ -1885,6 +1901,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");
@@ -1946,9 +1963,9 @@ void MainWindow::onOpenInterfaceSettings()
void MainWindow::onUpdateInterfaceSettings()
{
pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus");
- emuInstance->targetFPS = 1.0 / globalCfg.GetDouble("TargetFPS");
- emuInstance->fastForwardFPS = 1.0 / globalCfg.GetDouble("FastForwardFPS");
- emuInstance->slowmoFPS = 1.0 / globalCfg.GetDouble("SlowmoFPS");
+ emuInstance->targetFPS = globalCfg.GetDouble("TargetFPS");
+ emuInstance->fastForwardFPS = globalCfg.GetDouble("FastForwardFPS");
+ emuInstance->slowmoFPS = globalCfg.GetDouble("SlowmoFPS");
panel->setMouseHide(globalCfg.GetBool("MouseHide"),
globalCfg.GetInt("MouseHideSeconds")*1000);
}
@@ -2051,6 +2068,11 @@ void MainWindow::onChangeIntegerScaling(bool checked)
emit screenLayoutChange();
}
+void MainWindow::onOpenNewWindow()
+{
+ emuInstance->createWindow();
+}
+
void MainWindow::onChangeScreenFiltering(bool checked)
{
windowCfg.SetBool("ScreenFilter", checked);
@@ -2084,24 +2106,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()
@@ -2122,6 +2148,8 @@ void MainWindow::onScreenEmphasisToggled()
void MainWindow::onEmuStart()
{
+ if (!hasMenu) return;
+
for (int i = 1; i < 9; i++)
{
actSaveState[i]->setEnabled(true);
@@ -2145,6 +2173,8 @@ void MainWindow::onEmuStart()
void MainWindow::onEmuStop()
{
+ if (!hasMenu) return;
+
for (int i = 0; i < 9; i++)
{
actSaveState[i]->setEnabled(false);
@@ -2165,31 +2195,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 30d97b17..9f652f54 100644
--- a/src/frontend/qt_sdl/Window.h
+++ b/src/frontend/qt_sdl/Window.h
@@ -110,6 +110,13 @@ public:
EmuInstance* getEmuInstance() { return emuInstance; }
Config::Table& getWindowConfig() { return windowCfg; }
+ int getWindowID() { return windowID; }
+
+ bool winHasMenu() { return hasMenu; }
+
+ void saveEnabled(bool enabled);
+
+ void toggleFullscreen();
bool hasOpenGL() { return hasOGL; }
GL::Context* getOGLContext();
@@ -124,11 +131,18 @@ public:
void onAppStateChanged(Qt::ApplicationState state);
+ void onFocusIn();
+ void onFocusOut();
+ bool isFocused() { return focused; }
+
void osdAddMessage(unsigned int color, const char* msg);
// 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;
@@ -208,6 +222,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);
@@ -251,6 +266,9 @@ private:
bool pausedManually;
int windowID;
+ bool enabledSaved;
+
+ bool focused;
EmuInstance* emuInstance;
EmuThread* emuThread;
@@ -262,6 +280,8 @@ private:
public:
ScreenPanel* panel;
+ bool hasMenu;
+
QAction* actOpenROM;
QAction* actBootFirmware;
QAction* actCurrentCart;
@@ -269,7 +289,7 @@ public:
QAction* actEjectCart;
QAction* actCurrentGBACart;
QAction* actInsertGBACart;
- QAction* actInsertGBAAddon[2];
+ QList actInsertGBAAddon;
QAction* actEjectGBACart;
QAction* actImportSavefile;
QAction* actSaveState[9];
@@ -325,12 +345,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