Merge branch 'master' into master

This commit is contained in:
NPO 2024-10-28 13:41:01 -04:00 committed by GitHub
commit 5196151dac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 2334 additions and 1006 deletions

View File

@ -9,6 +9,11 @@ on:
branches: branches:
- master - master
env:
MELONDS_GIT_BRANCH: ${{ github.ref }}
MELONDS_GIT_HASH: ${{ github.sha }}
MELONDS_BUILD_PROVIDER: GitHub Actions
jobs: jobs:
build-macos: build-macos:
strategy: strategy:
@ -28,12 +33,13 @@ jobs:
- name: Set up vcpkg - name: Set up vcpkg
uses: lukka/run-vcpkg@v11 uses: lukka/run-vcpkg@v11
with: with:
vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4
- name: Build - name: Build
uses: lukka/run-cmake@v10 uses: lukka/run-cmake@v10
with: with:
configurePreset: release-mac-${{ matrix.arch }} configurePreset: release-mac-${{ matrix.arch }}
buildPreset: release-mac-${{ matrix.arch }} buildPreset: release-mac-${{ matrix.arch }}
configurePresetAdditionalArgs: "['-DMELONDS_EMBED_BUILD_INFO=ON']"
- name: Compress app bundle - name: Compress app bundle
shell: bash shell: bash
run: | run: |

View File

@ -8,6 +8,11 @@ on:
branches: branches:
- master - master
env:
MELONDS_GIT_BRANCH: ${{ github.ref }}
MELONDS_GIT_HASH: ${{ github.sha }}
MELONDS_BUILD_PROVIDER: GitHub Actions
jobs: jobs:
build-x86_64: build-x86_64:
name: 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 \ sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \
qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2 liblua5.4-dev qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2 liblua5.4-dev
- name: Configure - 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 - name: Build
run: | run: |
cmake --build build cmake --build build
@ -74,7 +79,8 @@ jobs:
-DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config \ -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config \
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \ -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \ -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \
-DUSE_QT6=ON -DUSE_QT6=ON \
-DMELONDS_EMBED_BUILD_INFO=ON
- name: Build - name: Build
shell: bash shell: bash
run: | run: |

View File

@ -9,6 +9,11 @@ on:
branches: branches:
- master - master
env:
MELONDS_GIT_BRANCH: ${{ github.ref }}
MELONDS_GIT_HASH: ${{ github.sha }}
MELONDS_BUILD_PROVIDER: GitHub Actions
jobs: jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
@ -27,9 +32,9 @@ jobs:
- name: Set up vcpkg - name: Set up vcpkg
uses: lukka/run-vcpkg@v11 uses: lukka/run-vcpkg@v11
with: with:
vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4
- name: Configure - name: Configure
run: cmake --preset=release-mingw-x86_64 run: cmake --preset=release-mingw-x86_64 -DMELONDS_EMBED_BUILD_INFO=ON
- name: Build - name: Build
run: cmake --build --preset=release-mingw-x86_64 run: cmake --build --preset=release-mingw-x86_64
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4

View File

@ -27,6 +27,7 @@ include(CMakeDependentOption)
include(CheckIPOSupported) include(CheckIPOSupported)
include(SetupCCache) include(SetupCCache)
include(Sanitizers)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")

View File

@ -9,7 +9,7 @@ if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}")
endif() endif()
FetchContent_Declare(vcpkg FetchContent_Declare(vcpkg
GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git"
GIT_TAG 2024.08.23 GIT_TAG 2024.10.21
SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
FetchContent_MakeAvailable(vcpkg) FetchContent_MakeAvailable(vcpkg)
endif() endif()

8
cmake/Sanitizers.cmake Normal file
View File

@ -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()

View File

@ -5,11 +5,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1710146030, "lastModified": 1726560853,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1725432240, "lastModified": 1729665710,
"narHash": "sha256-+yj+xgsfZaErbfYM3T+QvEE2hU7UuE+Jf0fJCJ8uPS0=", "narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "ad416d066ca1222956472ab7d0555a6946746a80", "rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -12,13 +12,16 @@
inherit (pkgs.lib) cmakeBool optionals makeLibraryPath; inherit (pkgs.lib) cmakeBool optionals makeLibraryPath;
inherit (pkgs.stdenv) isLinux isDarwin; 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 then sourceInfo.dirtyShortRev
else sourceInfo.shortRev; else sourceInfo.shortRev;
melonDS = pkgs.stdenv.mkDerivation { melonDS = pkgs.qt6.qtbase.stdenv.mkDerivation {
pname = "melonDS"; pname = "melonDS";
version = "0.9.5-${versionSuffix}"; version = "0.9.5-${shortRevision}";
src = ./.; src = ./.;
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
@ -47,8 +50,13 @@
cmakeFlags = [ cmakeFlags = [
(cmakeBool "USE_QT6" true) (cmakeBool "USE_QT6" true)
(cmakeBool "USE_SYSTEM_LIBSLIRP" 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 [ qtWrapperArgs = optionals isLinux [
"--prefix LD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap pkgs.wayland ]}" "--prefix LD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap pkgs.wayland ]}"
] ++ optionals isDarwin [ ] ++ optionals isDarwin [
@ -67,7 +75,7 @@
drv = self.packages.${system}.default; drv = self.packages.${system}.default;
}; };
devShells = { devShells = {
default = pkgs.mkShell { default = pkgs.mkShell.override { stdenv = pkgs.qt6.qtbase.stdenv; } {
inputsFrom = [ self.packages.${system}.default ]; inputsFrom = [ self.packages.${system}.default ];
}; };

View File

@ -110,6 +110,7 @@ const u32 ARM::ConditionTable[16] =
ARM::ARM(u32 num, bool jit, std::optional<GDBArgs> gdb, melonDS::NDS& nds) : ARM::ARM(u32 num, bool jit, std::optional<GDBArgs> gdb, melonDS::NDS& nds) :
#ifdef GDBSTUB_ENABLED #ifdef GDBSTUB_ENABLED
GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0), GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0),
BreakOnStartup(gdb ? (num ? gdb->ARM7BreakOnStartup : gdb->ARM9BreakOnStartup) : false),
#endif #endif
Num(num), // well uh Num(num), // well uh
NDS(nds) NDS(nds)

View File

@ -128,6 +128,17 @@ if (ENABLE_JIT)
endif() endif()
set(MELONDS_VERSION_SUFFIX "$ENV{MELONDS_VERSION_SUFFIX}" CACHE STRING "Suffix to add to displayed melonDS version") 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") 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") target_sources(core PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/version.h")

View File

@ -1445,7 +1445,6 @@ void DSi_NWifi::CheckRX()
int rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); int rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData);
while (rxlen > 0) while (rxlen > 0)
{ {
//printf("WMI packet recv %04X %04X %04X\n", *(u16*)&LANBuffer[0], *(u16*)&LANBuffer[2], *(u16*)&LANBuffer[4]);
// check destination MAC // check destination MAC
if (*(u32*)&LANBuffer[0] != 0xFFFFFFFF || *(u16*)&LANBuffer[4] != 0xFFFF) if (*(u32*)&LANBuffer[0] != 0xFFFFFFFF || *(u16*)&LANBuffer[4] != 0xFFFF)
{ {
@ -1508,6 +1507,7 @@ void DSi_NWifi::CheckRX()
Mailbox[8].Write(LANBuffer[14+i]); Mailbox[8].Write(LANBuffer[14+i]);
DrainRXBuffer(); DrainRXBuffer();
return;
} }
} }

View File

@ -499,6 +499,17 @@ public:
OAMDirty |= 1 << (addr / 1024); OAMDirty |= 1 << (addr / 1024);
} }
template <typename T>
inline T ReadVRAMFlat_Texture(u32 addr) const
{
return *(T*)&VRAMFlat_Texture[addr & 0x7FFFF];
}
template <typename T>
inline T ReadVRAMFlat_TexPal(u32 addr) const
{
return *(T*)&VRAMFlat_TexPal[addr & 0x1FFFF];
}
void SetPowerCnt(u32 val) noexcept; void SetPowerCnt(u32 val) noexcept;
void StartFrame() noexcept; void StartFrame() noexcept;

View File

@ -193,10 +193,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
case 1: // A3I5 case 1: // A3I5
{ {
vramaddr += ((t * width) + s); vramaddr += ((t * width) + s);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu); u8 pixel = gpu.ReadVRAMFlat_Texture<u8>(vramaddr);
texpal <<= 4; texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + ((pixel&0x1F)<<1), gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + ((pixel&0x1F)<<1));
*alpha = ((pixel >> 3) & 0x1C) + (pixel >> 6); *alpha = ((pixel >> 3) & 0x1C) + (pixel >> 6);
} }
break; break;
@ -204,12 +204,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
case 2: // 4-color case 2: // 4-color
{ {
vramaddr += (((t * width) + s) >> 2); vramaddr += (((t * width) + s) >> 2);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu); u8 pixel = gpu.ReadVRAMFlat_Texture<u8>(vramaddr);
pixel >>= ((s & 0x3) << 1); pixel >>= ((s & 0x3) << 1);
pixel &= 0x3; pixel &= 0x3;
texpal <<= 3; texpal <<= 3;
*color = ReadVRAM_TexPal<u16>(texpal + (pixel<<1), gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + (pixel<<1));
*alpha = (pixel==0) ? alpha0 : 31; *alpha = (pixel==0) ? alpha0 : 31;
} }
break; break;
@ -217,12 +217,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
case 3: // 16-color case 3: // 16-color
{ {
vramaddr += (((t * width) + s) >> 1); vramaddr += (((t * width) + s) >> 1);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu); u8 pixel = gpu.ReadVRAMFlat_Texture<u8>(vramaddr);
if (s & 0x1) pixel >>= 4; if (s & 0x1) pixel >>= 4;
else pixel &= 0xF; else pixel &= 0xF;
texpal <<= 4; texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + (pixel<<1), gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + (pixel<<1));
*alpha = (pixel==0) ? alpha0 : 31; *alpha = (pixel==0) ? alpha0 : 31;
} }
break; break;
@ -230,10 +230,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
case 4: // 256-color case 4: // 256-color
{ {
vramaddr += ((t * width) + s); vramaddr += ((t * width) + s);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu); u8 pixel = gpu.ReadVRAMFlat_Texture<u8>(vramaddr);
texpal <<= 4; texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + (pixel<<1), gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + (pixel<<1));
*alpha = (pixel==0) ? alpha0 : 31; *alpha = (pixel==0) ? alpha0 : 31;
} }
break; break;
@ -253,31 +253,31 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
val = 0; val = 0;
else else
{ {
val = ReadVRAM_Texture<u8>(vramaddr, gpu); val = gpu.ReadVRAMFlat_Texture<u8>(vramaddr);
val >>= (2 * (s & 0x3)); val >>= (2 * (s & 0x3));
} }
u16 palinfo = ReadVRAM_Texture<u16>(slot1addr, gpu); u16 palinfo = gpu.ReadVRAMFlat_Texture<u16>(slot1addr);
u32 paloffset = (palinfo & 0x3FFF) << 2; u32 paloffset = (palinfo & 0x3FFF) << 2;
texpal <<= 4; texpal <<= 4;
switch (val & 0x3) switch (val & 0x3)
{ {
case 0: case 0:
*color = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset);
*alpha = 31; *alpha = 31;
break; break;
case 1: case 1:
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 2, gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset + 2);
*alpha = 31; *alpha = 31;
break; break;
case 2: case 2:
if ((palinfo >> 14) == 1) if ((palinfo >> 14) == 1)
{ {
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu); u16 color0 = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset);
u16 color1 = ReadVRAM_TexPal<u16>(texpal + paloffset + 2, gpu); u16 color1 = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset + 2);
u32 r0 = color0 & 0x001F; u32 r0 = color0 & 0x001F;
u32 g0 = color0 & 0x03E0; 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) else if ((palinfo >> 14) == 3)
{ {
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu); u16 color0 = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset);
u16 color1 = ReadVRAM_TexPal<u16>(texpal + paloffset + 2, gpu); u16 color1 = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset + 2);
u32 r0 = color0 & 0x001F; u32 r0 = color0 & 0x001F;
u32 g0 = color0 & 0x03E0; u32 g0 = color0 & 0x03E0;
@ -311,20 +311,20 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
*color = r | g | b; *color = r | g | b;
} }
else else
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 4, gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset + 4);
*alpha = 31; *alpha = 31;
break; break;
case 3: case 3:
if ((palinfo >> 14) == 2) if ((palinfo >> 14) == 2)
{ {
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 6, gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset + 6);
*alpha = 31; *alpha = 31;
} }
else if ((palinfo >> 14) == 3) else if ((palinfo >> 14) == 3)
{ {
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu); u16 color0 = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset);
u16 color1 = ReadVRAM_TexPal<u16>(texpal + paloffset + 2, gpu); u16 color1 = gpu.ReadVRAMFlat_TexPal<u16>(texpal + paloffset + 2);
u32 r0 = color0 & 0x001F; u32 r0 = color0 & 0x001F;
u32 g0 = color0 & 0x03E0; u32 g0 = color0 & 0x03E0;
@ -353,10 +353,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
case 6: // A5I3 case 6: // A5I3
{ {
vramaddr += ((t * width) + s); vramaddr += ((t * width) + s);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu); u8 pixel = gpu.ReadVRAMFlat_Texture<u8>(vramaddr);
texpal <<= 4; texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + ((pixel&0x7)<<1), gpu); *color = gpu.ReadVRAMFlat_TexPal<u16>(texpal + ((pixel&0x7)<<1));
*alpha = (pixel >> 3); *alpha = (pixel >> 3);
} }
break; break;
@ -364,7 +364,7 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s
case 7: // direct color case 7: // direct color
{ {
vramaddr += (((t * width) + s) << 1); vramaddr += (((t * width) + s) << 1);
*color = ReadVRAM_Texture<u16>(vramaddr, gpu); *color = gpu.ReadVRAMFlat_Texture<u16>(vramaddr);
*alpha = (*color & 0x8000) ? 31 : 0; *alpha = (*color & 0x8000) ? 31 : 0;
} }
break; break;
@ -1659,8 +1659,8 @@ void SoftRenderer::ClearBuffers(const GPU& gpu)
{ {
for (int x = 0; x < 256; x++) for (int x = 0; x < 256; x++)
{ {
u16 val2 = ReadVRAM_Texture<u16>(0x40000 + (yoff << 9) + (xoff << 1), gpu); u16 val2 = gpu.ReadVRAMFlat_Texture<u16>(0x40000 + (yoff << 9) + (xoff << 1));
u16 val3 = ReadVRAM_Texture<u16>(0x60000 + (yoff << 9) + (xoff << 1), gpu); u16 val3 = gpu.ReadVRAMFlat_Texture<u16>(0x60000 + (yoff << 9) + (xoff << 1));
// TODO: confirm color conversion // TODO: confirm color conversion
u32 r = (val2 << 1) & 0x3E; if (r) r++; u32 r = (val2 << 1) & 0x3E; if (r) r++;

View File

@ -430,16 +430,6 @@ private:
s32 ycoverage, ycov_incr; s32 ycoverage, ycov_incr;
}; };
template <typename T>
inline T ReadVRAM_Texture(u32 addr, const GPU& gpu) const
{
return *(T*)&gpu.VRAMFlat_Texture[addr & 0x7FFFF];
}
template <typename T>
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; u32 AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept;
struct RendererPolygon struct RendererPolygon

View File

@ -75,11 +75,11 @@ inline u32 ConvertRGB5ToRGB6(u16 val)
} }
template <int outputFmt> template <int outputFmt>
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++) for (u32 i = 0; i < width*height; i++)
{ {
u16 value = *(u16*)&texData[i * 2]; u16 value = gpu.ReadVRAMFlat_Texture<u16>(addr + i * 2);
switch (outputFmt) switch (outputFmt)
{ {
@ -96,28 +96,28 @@ void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData)
} }
} }
template void ConvertBitmapTexture<outputFmt_RGB6A5>(u32 width, u32 height, u32* output, u8* texData); template void ConvertBitmapTexture<outputFmt_RGB6A5>(u32 width, u32 height, u32* output, u32 addr, GPU& gpu);
template <int outputFmt> template <int outputFmt>
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 // we process a whole block at the time
for (int y = 0; y < height / 4; y++) for (int y = 0; y < height / 4; y++)
{ {
for (int x = 0; x < width / 4; x++) for (int x = 0; x < width / 4; x++)
{ {
u32 data = ((u32*)texData)[x + y * (width / 4)]; u32 data = gpu.ReadVRAMFlat_Texture<u32>(addr + (x + y * (width / 4))*4);
u16 auxData = ((u16*)texAuxData)[x + y * (width / 4)]; u16 auxData = gpu.ReadVRAMFlat_Texture<u16>(addrAux + (x + y * (width / 4))*2);
u32 paletteOffset = auxData & 0x3FFF; u32 paletteOffset = palAddr + (auxData & 0x3FFF) * 4;
u16 color0 = palData[paletteOffset*2] | 0x8000; u16 color0 = gpu.ReadVRAMFlat_TexPal<u16>(paletteOffset) | 0x8000;
u16 color1 = palData[paletteOffset*2+1] | 0x8000; u16 color1 = gpu.ReadVRAMFlat_TexPal<u16>(paletteOffset+2) | 0x8000;
u16 color2, color3; u16 color2 = gpu.ReadVRAMFlat_TexPal<u16>(paletteOffset+4) | 0x8000;
u16 color3 = gpu.ReadVRAMFlat_TexPal<u16>(paletteOffset+6) | 0x8000;
switch ((auxData >> 14) & 0x3) switch ((auxData >> 14) & 0x3)
{ {
case 0: case 0:
color2 = palData[paletteOffset*2+2] | 0x8000;
color3 = 0; color3 = 0;
break; break;
case 1: case 1:
@ -137,8 +137,6 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u
color3 = 0; color3 = 0;
break; break;
case 2: case 2:
color2 = palData[paletteOffset*2+2] | 0x8000;
color3 = palData[paletteOffset*2+3] | 0x8000;
break; break;
case 3: case 3:
{ {
@ -179,7 +177,8 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u
{ {
for (int i = 0; i < 4; i++) 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; u32 res;
switch (outputFmt) switch (outputFmt)
{ {
@ -197,20 +196,20 @@ void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u
} }
} }
template void ConvertCompressedTexture<outputFmt_RGB6A5>(u32, u32, u32*, u8*, u8*, u16*); template void ConvertCompressedTexture<outputFmt_RGB6A5>(u32, u32, u32*, u32, u32, u32, GPU&);
template <int outputFmt, int X, int Y> template <int outputFmt, int X, int Y>
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 y = 0; y < height; y++)
{ {
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
u8 val = texData[x + y * width]; u8 val = gpu.ReadVRAMFlat_Texture<u8>(addr + x + y * width);
u32 idx = val & ((1 << Y) - 1); u32 idx = val & ((1 << Y) - 1);
u16 color = palData[idx]; u16 color = gpu.ReadVRAMFlat_TexPal<u16>(palAddr + idx * 2);
u32 alpha = (val >> Y) & ((1 << X) - 1); u32 alpha = (val >> Y) & ((1 << X) - 1);
if (X != 5) if (X != 5)
alpha = alpha * 4 + alpha / 2; alpha = alpha * 4 + alpha / 2;
@ -228,22 +227,24 @@ void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* pa
} }
} }
template void ConvertAXIYTexture<outputFmt_RGB6A5, 5, 3>(u32, u32, u32*, u8*, u16*); template void ConvertAXIYTexture<outputFmt_RGB6A5, 5, 3>(u32, u32, u32*, u32, u32, GPU&);
template void ConvertAXIYTexture<outputFmt_RGB6A5, 3, 5>(u32, u32, u32*, u8*, u16*); template void ConvertAXIYTexture<outputFmt_RGB6A5, 3, 5>(u32, u32, u32*, u32, u32, GPU&);
template <int outputFmt, int colorBits> template <int outputFmt, int colorBits>
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 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<u16>(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); u32 index = val & ((1 << colorBits) - 1);
u16 color = palData[index]; val >>= colorBits;
u16 color = gpu.ReadVRAMFlat_TexPal<u16>(palAddr + index * 2);
bool transparent = color0Transparent && index == 0; bool transparent = color0Transparent && index == 0;
u32 res; u32 res;
@ -256,14 +257,14 @@ void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16*
case outputFmt_BGRA8: res = ConvertRGB5ToBGR8(color) case outputFmt_BGRA8: res = ConvertRGB5ToBGR8(color)
| (transparent ? 0 : 0xFF000000); break; | (transparent ? 0 : 0xFF000000); break;
} }
output[x * (8 / colorBits) + y * width + i] = res; output[x * (16 / colorBits) + y * width + i] = res;
} }
} }
} }
} }
template void ConvertNColorsTexture<outputFmt_RGB6A5, 2>(u32, u32, u32*, u8*, u16*, bool); template void ConvertNColorsTexture<outputFmt_RGB6A5, 2>(u32, u32, u32*, u32, u32, bool, GPU&);
template void ConvertNColorsTexture<outputFmt_RGB6A5, 4>(u32, u32, u32*, u8*, u16*, bool); template void ConvertNColorsTexture<outputFmt_RGB6A5, 4>(u32, u32, u32*, u32, u32, bool, GPU&);
template void ConvertNColorsTexture<outputFmt_RGB6A5, 8>(u32, u32, u32*, u8*, u16*, bool); template void ConvertNColorsTexture<outputFmt_RGB6A5, 8>(u32, u32, u32*, u32, u32, bool, GPU&);
} }

View File

@ -32,13 +32,13 @@ enum
}; };
template <int outputFmt> template <int outputFmt>
void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData); void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu);
template <int outputFmt> template <int outputFmt>
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 <int outputFmt, int X, int Y> template <int outputFmt, int X, int Y>
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 <int outputFmt, int colorBits> template <int outputFmt, int colorBits>
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 <typename TexLoaderT, typename TexHandleT> template <typename TexLoaderT, typename TexHandleT>
class Texcache class Texcache
@ -48,6 +48,50 @@ public:
: TexLoader(texloader) // probably better if this would be a move constructor??? : 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) bool Update(GPU& gpu)
{ {
auto textureDirty = gpu.VRAMDirty_Texture.DeriveState(gpu.VRAMMap_Texture, gpu); auto textureDirty = gpu.VRAMDirty_Texture.DeriveState(gpu.VRAMMap_Texture, gpu);
@ -66,40 +110,21 @@ public:
{ {
for (u32 i = 0; i < 2; i++) for (u32 i = 0; i < 2; i++)
{ {
u32 startBit = entry.TextureRAMStart[i] / VRAMDirtyGranularity; if (CheckInvalid(entry.TextureRAMStart[i], entry.TextureRAMSize[i],
u32 bitsCount = ((entry.TextureRAMStart[i] + entry.TextureRAMSize[i] + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; entry.TextureHash[i],
textureDirty.Data,
u32 startEntry = startBit >> 6; gpu.VRAMFlat_Texture, sizeof(gpu.VRAMFlat_Texture)))
u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; goto invalidate;
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 (texPalChanged && entry.TexPalSize > 0) if (texPalChanged && entry.TexPalSize > 0)
{ {
u32 startBit = entry.TexPalStart / VRAMDirtyGranularity; if (CheckInvalid(entry.TexPalStart, entry.TexPalSize,
u32 bitsCount = ((entry.TexPalStart + entry.TexPalSize + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; entry.TexPalHash,
texPalDirty.Data,
u32 startEntry = startBit >> 6; gpu.VRAMFlat_TexPal, sizeof(gpu.VRAMFlat_TexPal)))
u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; goto invalidate;
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;
}
}
} }
it++; it++;
@ -163,17 +188,13 @@ public:
{ {
entry.TextureRAMSize[0] = width*height*2; entry.TextureRAMSize[0] = width*height*2;
ConvertBitmapTexture<outputFmt_RGB6A5>(width, height, DecodingBuffer, &gpu.VRAMFlat_Texture[addr]); ConvertBitmapTexture<outputFmt_RGB6A5>(width, height, DecodingBuffer, addr, gpu);
} }
else if (fmt == 5) else if (fmt == 5)
{ {
u8* texData = &gpu.VRAMFlat_Texture[addr];
u32 slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1); u32 slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1);
if (addr >= 0x40000) if (addr >= 0x40000)
slot1addr += 0x10000; slot1addr += 0x10000;
u8* texAuxData = &gpu.VRAMFlat_Texture[slot1addr];
u16* palData = (u16*)(gpu.VRAMFlat_TexPal + palBase*16);
entry.TextureRAMSize[0] = width*height/16*4; entry.TextureRAMSize[0] = width*height/16*4;
entry.TextureRAMStart[1] = slot1addr; entry.TextureRAMStart[1] = slot1addr;
@ -181,7 +202,7 @@ public:
entry.TexPalStart = palBase*16; entry.TexPalStart = palBase*16;
entry.TexPalSize = 0x10000; entry.TexPalSize = 0x10000;
ConvertCompressedTexture<outputFmt_RGB6A5>(width, height, DecodingBuffer, texData, texAuxData, palData); ConvertCompressedTexture<outputFmt_RGB6A5>(width, height, DecodingBuffer, addr, slot1addr, entry.TexPalStart, gpu);
} }
else else
{ {
@ -204,30 +225,29 @@ public:
entry.TexPalStart = palAddr; entry.TexPalStart = palAddr;
entry.TexPalSize = numPalEntries*2; 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); //assert(entry.TexPalStart+entry.TexPalSize <= 128*1024*1024);
bool color0Transparent = texParam & (1 << 29); bool color0Transparent = texParam & (1 << 29);
switch (fmt) switch (fmt)
{ {
case 1: ConvertAXIYTexture<outputFmt_RGB6A5, 3, 5>(width, height, DecodingBuffer, texData, palData); break; case 1: ConvertAXIYTexture<outputFmt_RGB6A5, 3, 5>(width, height, DecodingBuffer, addr, palAddr, gpu); break;
case 6: ConvertAXIYTexture<outputFmt_RGB6A5, 5, 3>(width, height, DecodingBuffer, texData, palData); break; case 6: ConvertAXIYTexture<outputFmt_RGB6A5, 5, 3>(width, height, DecodingBuffer, addr, palAddr, gpu); break;
case 2: ConvertNColorsTexture<outputFmt_RGB6A5, 2>(width, height, DecodingBuffer, texData, palData, color0Transparent); break; case 2: ConvertNColorsTexture<outputFmt_RGB6A5, 2>(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break;
case 3: ConvertNColorsTexture<outputFmt_RGB6A5, 4>(width, height, DecodingBuffer, texData, palData, color0Transparent); break; case 3: ConvertNColorsTexture<outputFmt_RGB6A5, 4>(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break;
case 4: ConvertNColorsTexture<outputFmt_RGB6A5, 8>(width, height, DecodingBuffer, texData, palData, color0Transparent); break; case 4: ConvertNColorsTexture<outputFmt_RGB6A5, 8>(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break;
} }
} }
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
if (entry.TextureRAMSize[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) 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& texArrays = TexArrays[widthLog2][heightLog2];
auto& freeTextures = FreeTextures[widthLog2][heightLog2]; auto& freeTextures = FreeTextures[widthLog2][heightLog2];

View File

@ -40,7 +40,7 @@ RTC::RTC(melonDS::NDS& nds) : NDS(nds)
// indicate the power was off // indicate the power was off
// this will be changed if a previously saved RTC state is loaded // this will be changed if a previously saved RTC state is loaded
State.StatusReg1 = 0x80; State.StatusReg1 = 0x80 | (1<<1);
} }
RTC::~RTC() RTC::~RTC()
@ -943,4 +943,4 @@ void RTC::Write(u16 val, bool byte)
IO = (IO & 0x0001) | (val & 0xFFFE); IO = (IO & 0x0001) | (val & 0xFFFE);
} }
} }

View File

@ -101,6 +101,15 @@ bool GdbStub::Init()
Log(LogLevel::Error, "[GDB] err: can't create a socket fd\n"); Log(LogLevel::Error, "[GDB] err: can't create a socket fd\n");
goto err; 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__ #ifndef __linux__
SocketSetBlocking(SockFd, false); SocketSetBlocking(SockFd, false);
#endif #endif

View File

@ -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 <QDesktopServices>
#include <QUrl>
#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"));;
}

View File

@ -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 <QDialog>
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

View File

@ -0,0 +1,300 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>304</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>About melonDS</string>
</property>
<property name="modal">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetFixedSize</enum>
</property>
<property name="bottomMargin">
<number>12</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
<property name="spacing">
<number>24</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QLabel" name="lblLogo">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="baseSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::TextFormat::PlainText</enum>
</property>
<property name="pixmap">
<pixmap resource="../../../res/melon.qrc">:/melon-icon</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,0,1">
<property name="spacing">
<number>12</number>
</property>
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QLabel" name="label_2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>32</pointsize>
</font>
</property>
<property name="text">
<string>melonDS</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblVersionInfo">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>[VERSION INFO PLACEHOLDER]</string>
</property>
<property name="textFormat">
<enum>Qt::TextFormat::MarkdownText</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>By Arisotura, the melonDS team &lt;a href=&quot;https://github.com/melonDS-emu/melonDS/graphs/contributors&quot;&gt;and contributors&lt;/a&gt;.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblBuildInfo">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>[EMBEDDED BUILD INFO PLACEHOLDER]</string>
</property>
<property name="textInteractionFlags">
<set>Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item alignment="Qt::AlignmentFlag::AlignBottom">
<widget class="QLabel" name="label_3">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Licensed under the GNU General Public License v3 or any newer version.&lt;/p&gt;&lt;p&gt;melonDS is intended only for use with software that you own.&lt;/p&gt;&lt;p&gt;The Nintendo DS and Nintendo DSi systems are the property of Nintendo.&lt;br /&gt;melonDS and the melonDS team are not affiliated with or endorsed by Nintendo.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::TextFormat::RichText</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>12</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnOpenWebsite">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Visit the &amp;website</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnOpenGitHub">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>View on &amp;GitHub</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClose">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Close</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../../../res/melon.qrc"/>
</resources>
<connections>
<connection>
<sender>btnClose</sender>
<signal>clicked()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>586</x>
<y>261</y>
</hint>
<hint type="destinationlabel">
<x>294</x>
<y>154</y>
</hint>
</hints>
</connection>
<connection>
<sender>btnOpenGitHub</sender>
<signal>clicked()</signal>
<receiver>AboutDialog</receiver>
<slot>openGitHub()</slot>
<hints>
<hint type="sourcelabel">
<x>449</x>
<y>243</y>
</hint>
<hint type="destinationlabel">
<x>299</x>
<y>139</y>
</hint>
</hints>
</connection>
<connection>
<sender>btnOpenWebsite</sender>
<signal>clicked()</signal>
<receiver>AboutDialog</receiver>
<slot>openWebsite()</slot>
<hints>
<hint type="sourcelabel">
<x>345</x>
<y>245</y>
</hint>
<hint type="destinationlabel">
<x>96</x>
<y>275</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>openWebsite()</slot>
<slot>openGitHub()</slot>
</slots>
</ui>

View File

@ -170,6 +170,12 @@ void AudioSettingsDialog::on_AudioSettingsDialog_accepted()
void AudioSettingsDialog::on_AudioSettingsDialog_rejected() void AudioSettingsDialog::on_AudioSettingsDialog_rejected()
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
closeDlg();
return;
}
auto& cfg = emuInstance->getGlobalConfig(); auto& cfg = emuInstance->getGlobalConfig();
auto& instcfg = emuInstance->getLocalConfig(); auto& instcfg = emuInstance->getLocalConfig();
cfg.SetInt("Audio.Interpolation", oldInterp); cfg.SetInt("Audio.Interpolation", oldInterp);

View File

@ -96,7 +96,7 @@ CommandLineOptions* ManageArgs(QApplication& melon)
} }
else 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 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 #endif

View File

@ -28,8 +28,6 @@ namespace CLI {
struct CommandLineOptions struct CommandLineOptions
{ {
QStringList errorsToDisplay = {};
std::optional<QString> dsRomPath; std::optional<QString> dsRomPath;
std::optional<QString> dsRomArchivePath; std::optional<QString> dsRomArchivePath;
std::optional<QString> gbaRomPath; std::optional<QString> gbaRomPath;

View File

@ -37,6 +37,9 @@ set(SOURCES_QT_SDL
QPathInput.h QPathInput.h
SaveManager.cpp SaveManager.cpp
CameraManager.cpp CameraManager.cpp
AboutDialog.cpp
AboutDialog.h
AboutDialog.ui
ArchiveUtil.h ArchiveUtil.h
ArchiveUtil.cpp ArchiveUtil.cpp

View File

@ -163,6 +163,12 @@ void CameraSettingsDialog::on_CameraSettingsDialog_accepted()
void CameraSettingsDialog::on_CameraSettingsDialog_rejected() void CameraSettingsDialog::on_CameraSettingsDialog_rejected()
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
closeDlg();
return;
}
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
camManager[i]->stop(); camManager[i]->stop();

View File

@ -55,7 +55,6 @@ DefaultList<int> DefaultInts =
{"Screen.VSyncInterval", 1}, {"Screen.VSyncInterval", 1},
{"3D.Renderer", renderer3D_Software}, {"3D.Renderer", renderer3D_Software},
{"3D.GL.ScaleFactor", 1}, {"3D.GL.ScaleFactor", 1},
{"MaxFPS", 1000},
#ifdef JIT_ENABLED #ifdef JIT_ENABLED
{"JIT.MaxBlockSize", 32}, {"JIT.MaxBlockSize", 32},
#endif #endif
@ -81,7 +80,7 @@ RangeList IntRanges =
{"3D.Renderer", {0, renderer3D_Max-1}}, {"3D.Renderer", {0, renderer3D_Max-1}},
{"Screen.VSyncInterval", {1, 20}}, {"Screen.VSyncInterval", {1, 20}},
{"3D.GL.ScaleFactor", {1, 16}}, {"3D.GL.ScaleFactor", {1, 16}},
{"Audio.Interpolation", {0, 3}}, {"Audio.Interpolation", {0, 4}},
{"Instance*.Audio.Volume", {0, 256}}, {"Instance*.Audio.Volume", {0, 256}},
{"Mic.InputType", {0, micInputType_MAX-1}}, {"Mic.InputType", {0, micInputType_MAX-1}},
{"Instance*.Window*.ScreenRotation", {0, screenRot_MAX-1}}, {"Instance*.Window*.ScreenRotation", {0, screenRot_MAX-1}},
@ -100,7 +99,7 @@ DefaultList<bool> DefaultBools =
{"3D.Soft.Threaded", true}, {"3D.Soft.Threaded", true},
{"3D.GL.HiresCoordinates", true}, {"3D.GL.HiresCoordinates", true},
{"LimitFPS", true}, {"LimitFPS", true},
{"Window*.ShowOSD", true}, {"Instance*.Window*.ShowOSD", true},
{"Emu.DirectBoot", true}, {"Emu.DirectBoot", true},
{"Instance*.DS.Battery.LevelOkay", true}, {"Instance*.DS.Battery.LevelOkay", true},
{"Instance*.DSi.Battery.Charging", true}, {"Instance*.DSi.Battery.Charging", true},
@ -120,6 +119,13 @@ DefaultList<std::string> DefaultStrings =
{"Instance*.Firmware.Username", "melonDS"} {"Instance*.Firmware.Username", "melonDS"}
}; };
DefaultList<double> DefaultDoubles =
{
{"TargetFPS", 60.0},
{"FastForwardFPS", 1000.0},
{"SlowmoFPS", 30.0},
};
LegacyEntry LegacyFile[] = LegacyEntry LegacyFile[] =
{ {
{"Key_A", 0, "Keyboard.A", true}, {"Key_A", 0, "Keyboard.A", true},
@ -153,7 +159,7 @@ LegacyEntry LegacyFile[] =
{"HKKey_Pause", 0, "Keyboard.HK_Pause", true}, {"HKKey_Pause", 0, "Keyboard.HK_Pause", true},
{"HKKey_Reset", 0, "Keyboard.HK_Reset", true}, {"HKKey_Reset", 0, "Keyboard.HK_Reset", true},
{"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true}, {"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true},
{"HKKey_FastForwardToggle", 0, "Keyboard.HK_FastForwardToggle", true}, {"HKKey_FastForwardToggle", 0, "Keyboard.HK_FrameLimitToggle", true},
{"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true}, {"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true},
{"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true}, {"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true},
{"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true}, {"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true},
@ -169,7 +175,7 @@ LegacyEntry LegacyFile[] =
{"HKJoy_Pause", 0, "Joystick.HK_Pause", true}, {"HKJoy_Pause", 0, "Joystick.HK_Pause", true},
{"HKJoy_Reset", 0, "Joystick.HK_Reset", true}, {"HKJoy_Reset", 0, "Joystick.HK_Reset", true},
{"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true}, {"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true},
{"HKJoy_FastForwardToggle", 0, "Joystick.HK_FastForwardToggle", true}, {"HKJoy_FastForwardToggle", 0, "Joystick.HK_FrameLimitToggle", true},
{"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true}, {"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true},
{"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true}, {"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true},
{"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true}, {"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true},
@ -434,6 +440,18 @@ std::string Array::GetString(const int id)
return tval.as_string(); return tval.as_string();
} }
double Array::GetDouble(const int id)
{
while (Data.size() < id+1)
Data.push_back(0.0);
toml::value& tval = Data[id];
if (!tval.is_floating())
tval = 0.0;
return tval.as_floating();
}
void Array::SetInt(const int id, int val) void Array::SetInt(const int id, int val)
{ {
while (Data.size() < id+1) while (Data.size() < id+1)
@ -470,6 +488,15 @@ void Array::SetString(const int id, const std::string& val)
tval = val; tval = val;
} }
void Array::SetDouble(const int id, double val)
{
while (Data.size() < id+1)
Data.push_back(0.0);
toml::value& tval = Data[id];
tval = val;
}
/*Table::Table()// : Data(toml::value()) /*Table::Table()// : Data(toml::value())
{ {
@ -562,6 +589,15 @@ std::string Table::GetString(const std::string& path)
return tval.as_string(); return tval.as_string();
} }
double Table::GetDouble(const std::string& path)
{
toml::value& tval = ResolvePath(path);
if (!tval.is_floating())
tval = FindDefault(path, 0.0, DefaultDoubles);
return tval.as_floating();
}
void Table::SetInt(const std::string& path, int val) void Table::SetInt(const std::string& path, int val)
{ {
std::string rngkey = GetDefaultKey(PathPrefix+path); std::string rngkey = GetDefaultKey(PathPrefix+path);
@ -593,6 +629,12 @@ void Table::SetString(const std::string& path, const std::string& val)
tval = val; tval = val;
} }
void Table::SetDouble(const std::string& path, double val)
{
toml::value& tval = ResolvePath(path);
tval = val;
}
toml::value& Table::ResolvePath(const std::string& path) toml::value& Table::ResolvePath(const std::string& path)
{ {
toml::value* ret = &Data; toml::value* ret = &Data;

View File

@ -61,11 +61,13 @@ public:
int64_t GetInt64(const int id); int64_t GetInt64(const int id);
bool GetBool(const int id); bool GetBool(const int id);
std::string GetString(const int id); std::string GetString(const int id);
double GetDouble(const int id);
void SetInt(const int id, int val); void SetInt(const int id, int val);
void SetInt64(const int id, int64_t val); void SetInt64(const int id, int64_t val);
void SetBool(const int id, bool val); void SetBool(const int id, bool val);
void SetString(const int id, const std::string& val); void SetString(const int id, const std::string& val);
void SetDouble(const int id, double val);
// convenience // convenience
@ -99,11 +101,13 @@ public:
int64_t GetInt64(const std::string& path); int64_t GetInt64(const std::string& path);
bool GetBool(const std::string& path); bool GetBool(const std::string& path);
std::string GetString(const std::string& path); std::string GetString(const std::string& path);
double GetDouble(const std::string& path);
void SetInt(const std::string& path, int val); void SetInt(const std::string& path, int val);
void SetInt64(const std::string& path, int64_t val); void SetInt64(const std::string& path, int64_t val);
void SetBool(const std::string& path, bool val); void SetBool(const std::string& path, bool val);
void SetString(const std::string& path, const std::string& val); void SetString(const std::string& path, const std::string& val);
void SetDouble(const std::string& path, double val);
// convenience // convenience

View File

@ -88,7 +88,31 @@ EmuInstance::EmuInstance(int inst) : deleting(false),
cheatsOn = localCfg.GetBool("EnableCheats"); cheatsOn = localCfg.GetBool("EnableCheats");
doLimitFPS = globalCfg.GetBool("LimitFPS"); doLimitFPS = globalCfg.GetBool("LimitFPS");
maxFPS = globalCfg.GetInt("MaxFPS");
double val = globalCfg.GetDouble("TargetFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n");
targetFPS = 1.0 / 60.0;
}
else targetFPS = 1.0 / val;
val = globalCfg.GetDouble("FastForwardFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Fast-Forward FPS in config invalid\n");
fastForwardFPS = 1.0 / 60.0;
}
else fastForwardFPS = 1.0 / val;
val = globalCfg.GetDouble("SlowmoFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Slow-Mo FPS in config invalid\n");
slowmoFPS = 1.0 / 60.0;
}
else slowmoFPS = 1.0 / val;
doAudioSync = globalCfg.GetBool("AudioSync"); doAudioSync = globalCfg.GetBool("AudioSync");
mpAudioMode = globalCfg.GetInt("MP.AudioMode"); mpAudioMode = globalCfg.GetInt("MP.AudioMode");
@ -113,6 +137,16 @@ EmuInstance::EmuInstance(int inst) : deleting(false),
emuThread->start(); emuThread->start();
emuThread->emuPause(); emuThread->emuPause();
// if any extra windows were saved as enabled, open them
for (int i = 1; i < kMaxWindows; i++)
{
//Config::Table tbl = localCfg.GetTable("Window"+std::to_string(i), "Window0");
std::string key = "Window" + std::to_string(i) + ".Enabled";
bool enable = localCfg.GetBool(key);
if (enable)
createWindow(i);
}
} }
EmuInstance::~EmuInstance() EmuInstance::~EmuInstance()
@ -130,6 +164,13 @@ EmuInstance::~EmuInstance()
audioDeInit(); audioDeInit();
inputDeInit(); inputDeInit();
NDS::Current = nullptr;
if (nds)
{
saveRTCData();
delete nds;
}
} }
@ -142,7 +183,7 @@ std::string EmuInstance::instanceFileSuffix()
return suffix; return suffix;
} }
void EmuInstance::createWindow() void EmuInstance::createWindow(int id)
{ {
if (numWindows >= kMaxWindows) if (numWindows >= kMaxWindows)
{ {
@ -150,16 +191,20 @@ void EmuInstance::createWindow()
return; return;
} }
int id = -1; if (id == -1)
for (int i = 0; i < kMaxWindows; i++)
{ {
if (windowList[i]) continue; for (int i = 0; i < kMaxWindows; i++)
id = i; {
break; if (windowList[i]) continue;
id = i;
break;
}
} }
if (id == -1) if (id == -1)
return; return;
if (windowList[id])
return;
MainWindow* win = new MainWindow(id, this, topWindow); MainWindow* win = new MainWindow(id, this, topWindow);
if (!topWindow) topWindow = win; if (!topWindow) topWindow = win;
@ -168,6 +213,16 @@ void EmuInstance::createWindow()
numWindows++; numWindows++;
emuThread->attachWindow(win); 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) void EmuInstance::deleteWindow(int id, bool close)
@ -177,12 +232,8 @@ void EmuInstance::deleteWindow(int id, bool close)
MainWindow* win = windowList[id]; MainWindow* win = windowList[id];
if (!win) return; if (!win) return;
if (win->hasOpenGL() && win == mainWindow) if (win->hasOpenGL())
{ emuThread->deinitContext(id);
// we intentionally don't unpause here
emuThread->emuPause();
emuThread->deinitContext();
}
emuThread->detachWindow(win); emuThread->detachWindow(win);
@ -195,11 +246,20 @@ void EmuInstance::deleteWindow(int id, bool close)
if (close) if (close)
win->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); deleteEmuInstance(instanceID);
} }
else
{
bool enable = (numWindows < kMaxWindows);
doOnAllWindows([=](MainWindow* win)
{
win->actNewWindow->setEnabled(enable);
});
}
} }
void EmuInstance::deleteAllWindows() void EmuInstance::deleteAllWindows()
@ -208,6 +268,57 @@ void EmuInstance::deleteAllWindows()
deleteWindow(i, true); deleteWindow(i, true);
} }
void EmuInstance::doOnAllWindows(std::function<void(MainWindow*)> 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<bool>());
break;*/
}
}
void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...) void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...)
{ {
@ -261,24 +372,18 @@ bool EmuInstance::usesOpenGL()
(globalCfg.GetInt("3D.Renderer") != renderer3D_Software); (globalCfg.GetInt("3D.Renderer") != renderer3D_Software);
} }
void EmuInstance::initOpenGL() void EmuInstance::initOpenGL(int win)
{ {
for (int i = 0; i < kMaxWindows; i++) if (windowList[win])
{ windowList[win]->initOpenGL();
if (windowList[i])
windowList[i]->initOpenGL();
}
setVSyncGL(true); setVSyncGL(true);
} }
void EmuInstance::deinitOpenGL() void EmuInstance::deinitOpenGL(int win)
{ {
for (int i = 0; i < kMaxWindows; i++) if (windowList[win])
{ windowList[win]->deinitOpenGL();
if (windowList[i])
windowList[i]->deinitOpenGL();
}
} }
void EmuInstance::setVSyncGL(bool vsync) void EmuInstance::setVSyncGL(bool vsync)
@ -1081,6 +1186,30 @@ void EmuInstance::setBatteryLevels()
} }
} }
void EmuInstance::loadRTCData()
{
auto file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read);
if (file)
{
RTC::StateData state;
Platform::FileRead(&state, sizeof(state), 1, file);
Platform::CloseFile(file);
nds->RTC.SetState(state);
}
}
void EmuInstance::saveRTCData()
{
auto file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write);
if (file)
{
RTC::StateData state;
nds->RTC.GetState(state);
Platform::FileWrite(&state, sizeof(state), 1, file);
Platform::CloseFile(file);
}
}
void EmuInstance::setDateTime() void EmuInstance::setDateTime()
{ {
QDateTime hosttime = QDateTime::currentDateTime(); QDateTime hosttime = QDateTime::currentDateTime();
@ -1092,6 +1221,9 @@ void EmuInstance::setDateTime()
bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGBAArgs&& _gbaargs) noexcept 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; // 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. // if we wnat to keep the cart, we'll eject it from the existing console first.
std::unique_ptr<NDSCart::CartCommon> nextndscart; std::unique_ptr<NDSCart::CartCommon> nextndscart;
@ -1125,8 +1257,6 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
} }
int consoletype = globalCfg.GetInt("Emu.ConsoleType");
auto arm9bios = loadARM9BIOS(); auto arm9bios = loadARM9BIOS();
if (!arm9bios) if (!arm9bios)
return false; return false;
@ -1135,7 +1265,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
if (!arm7bios) if (!arm7bios)
return false; return false;
auto firmware = loadFirmware(consoletype); auto firmware = loadFirmware(consoleType);
if (!firmware) if (!firmware)
return false; return false;
@ -1179,7 +1309,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
NDSArgs* args = &ndsargs; NDSArgs* args = &ndsargs;
std::optional<DSiArgs> dsiargs = std::nullopt; std::optional<DSiArgs> dsiargs = std::nullopt;
if (consoletype == 1) if (consoleType == 1)
{ {
ndsargs.GBAROM = nullptr; ndsargs.GBAROM = nullptr;
@ -1210,19 +1340,24 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
args = &(*dsiargs); args = &(*dsiargs);
} }
if ((!nds) || (consoleType != nds->ConsoleType))
if ((!nds) || (consoletype != nds->ConsoleType))
{ {
NDS::Current = nullptr; 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); nds = new DSi(std::move(dsiargs.value()), this);
else else
nds = new NDS(std::move(ndsargs), this); nds = new NDS(std::move(ndsargs), this);
NDS::Current = nds; NDS::Current = nds;
nds->Reset(); nds->Reset();
loadRTCData();
//emuThread->updateVideoRenderer(); // not actually needed?
} }
else else
{ {
@ -1236,7 +1371,7 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
nds->SPU.SetInterpolation(args->Interpolation); nds->SPU.SetInterpolation(args->Interpolation);
nds->SPU.SetDegrade10Bit(args->BitDepth); nds->SPU.SetDegrade10Bit(args->BitDepth);
if (consoletype == 1) if (consoleType == 1)
{ {
DSi* dsi = (DSi*)nds; DSi* dsi = (DSi*)nds;
DSiArgs& _dsiargs = *dsiargs; DSiArgs& _dsiargs = *dsiargs;
@ -1258,8 +1393,6 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
void EmuInstance::reset() void EmuInstance::reset()
{ {
consoleType = globalCfg.GetInt("Emu.ConsoleType");
updateConsole(Keep {}, Keep {}); updateConsole(Keep {}, Keep {});
if (consoleType == 1) ejectGBACart(); if (consoleType == 1) ejectGBACart();
@ -1949,25 +2082,36 @@ bool EmuInstance::gbaCartInserted()
return gbaCartType != -1; 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() QString EmuInstance::gbaCartLabel()
{ {
if (consoleType == 1) return "none (DSi)"; 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; int maxlen = 32;
if (ret.length() > maxlen) if (ret.length() > maxlen)
ret = ret.left(maxlen-6) + "..." + ret.right(3); ret = ret.left(maxlen-6) + "..." + ret.right(3);
return ret; return ret;
} }
else if (gbaCartType != -1)
case GBAAddon_RAMExpansion: {
return "Memory expansion"; return gbaAddonName(gbaCartType);
} }
return "(none)"; return "(none)";

View File

@ -21,13 +21,14 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "main.h"
#include "NDS.h" #include "NDS.h"
#include "EmuThread.h" #include "EmuThread.h"
#include "Window.h" #include "Window.h"
#include "Config.h" #include "Config.h"
#include "SaveManager.h" #include "SaveManager.h"
const int kMaxWindows = 16; const int kMaxWindows = 4;
enum enum
{ {
@ -36,7 +37,7 @@ enum
HK_Pause, HK_Pause,
HK_Reset, HK_Reset,
HK_FastForward, HK_FastForward,
HK_FastForwardToggle, HK_FrameLimitToggle,
HK_FullscreenToggle, HK_FullscreenToggle,
HK_SwapScreens, HK_SwapScreens,
HK_SwapScreenEmphasis, HK_SwapScreenEmphasis,
@ -46,6 +47,9 @@ enum
HK_PowerButton, HK_PowerButton,
HK_VolumeUp, HK_VolumeUp,
HK_VolumeDown, HK_VolumeDown,
HK_SlowMo,
HK_FastForwardToggle,
HK_SlowMoToggle,
HK_MAX HK_MAX
}; };
@ -83,14 +87,21 @@ public:
melonDS::NDS* getNDS() { return nds; } melonDS::NDS* getNDS() { return nds; }
MainWindow* getMainWindow() { return mainWindow; } MainWindow* getMainWindow() { return mainWindow; }
int getNumWindows() { return numWindows; }
MainWindow* getWindow(int id) { return windowList[id]; } MainWindow* getWindow(int id) { return windowList[id]; }
void doOnAllWindows(std::function<void(MainWindow*)> func, int exclude = -1);
void saveEnabledWindows();
Config::Table& getGlobalConfig() { return globalCfg; } Config::Table& getGlobalConfig() { return globalCfg; }
Config::Table& getLocalConfig() { return localCfg; } Config::Table& getLocalConfig() { return localCfg; }
void broadcastCommand(int cmd, QVariant param = QVariant());
void handleCommand(int cmd, QVariant& param);
std::string instanceFileSuffix(); std::string instanceFileSuffix();
void createWindow(); void createWindow(int id = -1);
void deleteWindow(int id, bool close); void deleteWindow(int id, bool close);
void deleteAllWindows(); void deleteAllWindows();
@ -100,8 +111,8 @@ public:
void emuStop(melonDS::Platform::StopReason reason); void emuStop(melonDS::Platform::StopReason reason);
bool usesOpenGL(); bool usesOpenGL();
void initOpenGL(); void initOpenGL(int win);
void deinitOpenGL(); void deinitOpenGL(int win);
void setVSyncGL(bool vsync); void setVSyncGL(bool vsync);
void makeCurrentGL(); void makeCurrentGL();
void drawScreenGL(); void drawScreenGL();
@ -166,7 +177,6 @@ private:
std::optional<melonDS::FATStorageArgs> getSDCardArgs(const std::string& key) noexcept; std::optional<melonDS::FATStorageArgs> getSDCardArgs(const std::string& key) noexcept;
std::optional<melonDS::FATStorage> loadSDCard(const std::string& key) noexcept; std::optional<melonDS::FATStorage> loadSDCard(const std::string& key) noexcept;
void setBatteryLevels(); void setBatteryLevels();
void setDateTime();
void reset(); void reset();
bool bootToMenu(); bool bootToMenu();
melonDS::u32 decompressROM(const melonDS::u8* inContent, const melonDS::u32 inSize, std::unique_ptr<melonDS::u8[]>& outContent); melonDS::u32 decompressROM(const melonDS::u8* inContent, const melonDS::u32 inSize, std::unique_ptr<melonDS::u8[]>& outContent);
@ -184,6 +194,7 @@ private:
void loadGBAAddon(int type); void loadGBAAddon(int type);
void ejectGBACart(); void ejectGBACart();
bool gbaCartInserted(); bool gbaCartInserted();
QString gbaAddonName(int addon);
QString gbaCartLabel(); QString gbaCartLabel();
void audioInit(); void audioInit();
@ -220,6 +231,10 @@ private:
bool hotkeyPressed(int id) { return hotkeyPress & (1<<id); } bool hotkeyPressed(int id) { return hotkeyPress & (1<<id); }
bool hotkeyReleased(int id) { return hotkeyRelease & (1<<id); } bool hotkeyReleased(int id) { return hotkeyRelease & (1<<id); }
void loadRTCData();
void saveRTCData();
void setDateTime();
bool deleting; bool deleting;
int instanceID; int instanceID;
@ -253,7 +268,12 @@ public:
std::unique_ptr<SaveManager> firmwareSave; std::unique_ptr<SaveManager> firmwareSave;
bool doLimitFPS; bool doLimitFPS;
int maxFPS; double curFPS;
double targetFPS;
double fastForwardFPS;
double slowmoFPS;
bool fastForwardToggled;
bool slowmoToggled;
bool doAudioSync; bool doAudioSync;
melonDS::u32 getInputMask(){return inputMask;} melonDS::u32 getInputMask(){return inputMask;}

View File

@ -321,7 +321,7 @@ void EmuInstance::micProcess()
for (int i = 0; i < 735; i++) for (int i = 0; i < 735; i++)
{ {
tmp[i] = mic_blow[sample_pos]; tmp[i] = mic_blow[sample_pos] ^ 0x8000;
sample_pos++; sample_pos++;
if (sample_pos >= sample_len) sample_pos = 0; if (sample_pos >= sample_len) sample_pos = 0;
} }

View File

@ -47,7 +47,7 @@ const char* EmuInstance::hotkeyNames[HK_MAX] =
"HK_Pause", "HK_Pause",
"HK_Reset", "HK_Reset",
"HK_FastForward", "HK_FastForward",
"HK_FastForwardToggle", "HK_FrameLimitToggle",
"HK_FullscreenToggle", "HK_FullscreenToggle",
"HK_SwapScreens", "HK_SwapScreens",
"HK_SwapScreenEmphasis", "HK_SwapScreenEmphasis",
@ -56,7 +56,10 @@ const char* EmuInstance::hotkeyNames[HK_MAX] =
"HK_FrameStep", "HK_FrameStep",
"HK_PowerButton", "HK_PowerButton",
"HK_VolumeUp", "HK_VolumeUp",
"HK_VolumeDown" "HK_VolumeDown",
"HK_SlowMo",
"HK_FastForwardToggle",
"HK_SlowMoToggle"
}; };

View File

@ -215,6 +215,13 @@ void EmuSettingsDialog::verifyFirmware()
void EmuSettingsDialog::done(int r) void EmuSettingsDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
closeDlg();
return;
}
needsReset = false; needsReset = false;
if (r == QDialog::Accepted) if (r == QDialog::Accepted)

View File

@ -72,41 +72,46 @@ EmuThread::EmuThread(EmuInstance* inst, QObject* parent) : QThread(parent)
void EmuThread::attachWindow(MainWindow* window) 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(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString)));
connect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); connect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart()));
connect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); connect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop()));
connect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); connect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool)));
connect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); 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(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int)));
connect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); connect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled()));
connect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger()));
connect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); 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) 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(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString)));
disconnect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); disconnect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart()));
disconnect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); disconnect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop()));
disconnect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); disconnect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool)));
disconnect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); 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(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int)));
disconnect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); disconnect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled()));
disconnect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger()));
disconnect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); 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() void EmuThread::run()
{ {
Config::Table& globalCfg = emuInstance->getGlobalConfig(); Config::Table& globalCfg = emuInstance->getGlobalConfig();
u32 mainScreenPos[3]; u32 mainScreenPos[3];
Platform::FileHandle* file;
emuInstance->updateConsole(nullptr, nullptr); //emuInstance->updateConsole(nullptr, nullptr);
// No carts are inserted when melonDS first boots // No carts are inserted when melonDS first boots
mainScreenPos[0] = 0; mainScreenPos[0] = 0;
@ -114,11 +119,11 @@ void EmuThread::run()
mainScreenPos[2] = 0; mainScreenPos[2] = 0;
autoScreenSizing = 0; autoScreenSizing = 0;
videoSettingsDirty = false; //videoSettingsDirty = false;
if (emuInstance->usesOpenGL()) if (emuInstance->usesOpenGL())
{ {
emuInstance->initOpenGL(); emuInstance->initOpenGL(0);
useOpenGL = true; useOpenGL = true;
videoRenderer = globalCfg.GetInt("3D.Renderer"); videoRenderer = globalCfg.GetInt("3D.Renderer");
@ -129,7 +134,8 @@ void EmuThread::run()
videoRenderer = 0; videoRenderer = 0;
} }
updateRenderer(); //updateRenderer();
videoSettingsDirty = true;
u32 nframes = 0; u32 nframes = 0;
double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency();
@ -140,23 +146,19 @@ void EmuThread::run()
u32 winUpdateCount = 0, winUpdateFreq = 1; u32 winUpdateCount = 0, winUpdateFreq = 1;
u8 dsiVolumeLevel = 0x1F; 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]; char melontitle[100];
bool fastforward = false;
bool slowmo = false;
emuInstance->fastForwardToggled = false;
emuInstance->slowmoToggled = false;
while (emuStatus != emuStatus_Exit) while (emuStatus != emuStatus_Exit)
{ {
MPInterface::Get().Process(); MPInterface::Get().Process();
emuInstance->inputProcess(); emuInstance->inputProcess();
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); if (emuInstance->hotkeyPressed(HK_FrameLimitToggle)) emit windowLimitFPSChange();
if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause(); if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause();
if (emuInstance->hotkeyPressed(HK_Reset)) emuReset(); if (emuInstance->hotkeyPressed(HK_Reset)) emuReset();
@ -334,22 +336,34 @@ void EmuThread::run()
emit windowUpdate(); emit windowUpdate();
winUpdateCount = 0; winUpdateCount = 0;
} }
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emuInstance->fastForwardToggled = !emuInstance->fastForwardToggled;
if (emuInstance->hotkeyPressed(HK_SlowMoToggle)) emuInstance->slowmoToggled = !emuInstance->slowmoToggled;
bool fastforward = emuInstance->hotkeyDown(HK_FastForward); bool enablefastforward = emuInstance->hotkeyDown(HK_FastForward) | emuInstance->fastForwardToggled;
bool enableslowmo = emuInstance->hotkeyDown(HK_SlowMo) | emuInstance->slowmoToggled;
if (useOpenGL) if (useOpenGL)
{ {
// when using OpenGL: when toggling fast-forward, change the vsync interval // when using OpenGL: when toggling fast-forward or slowmo, change the vsync interval
if (emuInstance->hotkeyPressed(HK_FastForward)) if ((enablefastforward || enableslowmo) && !(fastforward || slowmo))
{ {
emuInstance->setVSyncGL(false); emuInstance->setVSyncGL(false);
} }
else if (emuInstance->hotkeyReleased(HK_FastForward)) else if (!(enablefastforward || enableslowmo) && (fastforward || slowmo))
{ {
emuInstance->setVSyncGL(true); emuInstance->setVSyncGL(true);
} }
} }
fastforward = enablefastforward;
slowmo = enableslowmo;
if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS;
else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS;
else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1.0 / 1000.0;
else emuInstance->curFPS = emuInstance->targetFPS;
if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1) if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1)
{ {
DSi* dsi = static_cast<DSi*>(emuInstance->nds); DSi* dsi = static_cast<DSi*>(emuInstance->nds);
@ -363,23 +377,19 @@ void EmuThread::run()
emuInstance->audioVolume = volumeLevel * (256.0 / 31.0); emuInstance->audioVolume = volumeLevel * (256.0 / 31.0);
} }
if (emuInstance->doAudioSync && !fastforward) if (emuInstance->doAudioSync && !(fastforward || slowmo))
emuInstance->audioSync(); emuInstance->audioSync();
double frametimeStep = nlines / (60.0 * 263.0); double frametimeStep = nlines / (60.0 * 263.0);
{ {
bool limitfps = emuInstance->doLimitFPS && !fastforward;
double practicalFramelimit = limitfps ? frametimeStep : 1.0 / emuInstance->maxFPS;
double curtime = SDL_GetPerformanceCounter() * perfCountsSec; double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
frameLimitError += practicalFramelimit - (curtime - lastTime); frameLimitError += emuInstance->curFPS - (curtime - lastTime);
if (frameLimitError < -practicalFramelimit) if (frameLimitError < -emuInstance->curFPS)
frameLimitError = -practicalFramelimit; frameLimitError = -emuInstance->curFPS;
if (frameLimitError > practicalFramelimit) if (frameLimitError > emuInstance->curFPS)
frameLimitError = practicalFramelimit; frameLimitError = emuInstance->curFPS;
if (round(frameLimitError * 1000.0) > 0.0) if (round(frameLimitError * 1000.0) > 0.0)
{ {
@ -445,17 +455,6 @@ void EmuThread::run()
//Lua Script Stuff (-for now happens at the end of each frame regardless of emuStatus) //Lua Script Stuff (-for now happens at the end of each frame regardless of emuStatus)
emit signalLuaUpdate(); emit signalLuaUpdate();
} }
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write);
if (file)
{
RTC::StateData state;
emuInstance->nds->RTC.GetState(state);
Platform::FileWrite(&state, sizeof(state), 1, file);
Platform::CloseFile(file);
}
NDS::Current = nullptr;
} }
void EmuThread::sendMessage(Message msg) void EmuThread::sendMessage(Message msg)
@ -533,7 +532,8 @@ void EmuThread::handleMessages()
break; break;
case msg_EmuStop: case msg_EmuStop:
if (msg.stopExternal) emuInstance->nds->Stop(); if (msg.param.value<bool>())
emuInstance->nds->Stop();
emuStatus = emuStatus_Paused; emuStatus = emuStatus_Paused;
emuActive = false; emuActive = false;
@ -558,13 +558,97 @@ void EmuThread::handleMessages()
break; break;
case msg_InitGL: case msg_InitGL:
emuInstance->initOpenGL(); emuInstance->initOpenGL(msg.param.value<int>());
useOpenGL = true; useOpenGL = true;
break; break;
case msg_DeInitGL: case msg_DeInitGL:
emuInstance->deinitOpenGL(); emuInstance->deinitOpenGL(msg.param.value<int>());
useOpenGL = false; if (msg.param.value<int>() == 0)
useOpenGL = false;
break;
case msg_BootROM:
msgResult = 0;
if (!emuInstance->loadROM(msg.param.value<QStringList>(), 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<QStringList>(), false))
break;
msgResult = 1;
break;
case msg_EjectCart:
emuInstance->ejectCart();
break;
case msg_InsertGBACart:
msgResult = 0;
if (!emuInstance->loadGBAROM(msg.param.value<QStringList>()))
break;
msgResult = 1;
break;
case msg_InsertGBAAddon:
msgResult = 0;
emuInstance->loadGBAAddon(msg.param.value<int>());
msgResult = 1;
break;
case msg_EjectGBACart:
emuInstance->ejectGBACart();
break;
case msg_SaveState:
msgResult = emuInstance->saveState(msg.param.value<QString>().toStdString());
break;
case msg_LoadState:
msgResult = emuInstance->loadState(msg.param.value<QString>().toStdString());
break;
case msg_UndoStateLoad:
emuInstance->undoStateLoad();
msgResult = 1;
break;
case msg_ImportSavefile:
{
msgResult = 0;
auto f = Platform::OpenFile(msg.param.value<QString>().toStdString(), Platform::FileMode::Read);
if (!f) break;
u32 len = FileLength(f);
std::unique_ptr<u8[]> data = std::make_unique<u8[]>(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; break;
} }
@ -578,15 +662,15 @@ void EmuThread::changeWindowTitle(char* title)
emit windowTitleChange(QString(title)); emit windowTitleChange(QString(title));
} }
void EmuThread::initContext() void EmuThread::initContext(int win)
{ {
sendMessage(msg_InitGL); sendMessage({.type = msg_InitGL, .param = win});
waitMessage(); waitMessage();
} }
void EmuThread::deinitContext() void EmuThread::deinitContext(int win)
{ {
sendMessage(msg_DeInitGL); sendMessage({.type = msg_DeInitGL, .param = win});
waitMessage(); waitMessage();
} }
@ -596,29 +680,35 @@ void EmuThread::emuRun()
waitMessage(); waitMessage();
} }
void EmuThread::emuPause() void EmuThread::emuPause(bool broadcast)
{ {
sendMessage(msg_EmuPause); sendMessage(msg_EmuPause);
waitMessage(); waitMessage();
if (broadcast)
emuInstance->broadcastCommand(InstCmd_Pause);
} }
void EmuThread::emuUnpause() void EmuThread::emuUnpause(bool broadcast)
{ {
sendMessage(msg_EmuUnpause); sendMessage(msg_EmuUnpause);
waitMessage(); waitMessage();
if (broadcast)
emuInstance->broadcastCommand(InstCmd_Unpause);
} }
void EmuThread::emuTogglePause() void EmuThread::emuTogglePause(bool broadcast)
{ {
if (emuStatus == emuStatus_Paused) if (emuStatus == emuStatus_Paused)
emuUnpause(); emuUnpause(broadcast);
else else
emuPause(); emuPause(broadcast);
} }
void EmuThread::emuStop(bool external) void EmuThread::emuStop(bool external)
{ {
sendMessage({.type = msg_EmuStop, .stopExternal = external}); sendMessage({.type = msg_EmuStop, .param = external});
waitMessage(); waitMessage();
} }
@ -652,11 +742,85 @@ bool EmuThread::emuIsActive()
return emuActive; return emuActive;
} }
int EmuThread::bootROM(const QStringList& filename)
{
sendMessage({.type = msg_BootROM, .param = filename});
waitMessage();
if (!msgResult)
return msgResult;
sendMessage(msg_EmuRun);
waitMessage();
return msgResult;
}
int EmuThread::bootFirmware()
{
sendMessage(msg_BootFirmware);
waitMessage();
if (!msgResult)
return msgResult;
sendMessage(msg_EmuRun);
waitMessage();
return msgResult;
}
int EmuThread::insertCart(const QStringList& filename, bool gba)
{
MessageType msgtype = gba ? msg_InsertGBACart : msg_InsertCart;
sendMessage({.type = msgtype, .param = filename});
waitMessage();
return msgResult;
}
void EmuThread::ejectCart(bool gba)
{
sendMessage(gba ? msg_EjectGBACart : msg_EjectCart);
waitMessage();
}
int EmuThread::insertGBAAddon(int type)
{
sendMessage({.type = msg_InsertGBAAddon, .param = type});
waitMessage();
return msgResult;
}
int EmuThread::saveState(const QString& filename)
{
sendMessage({.type = msg_SaveState, .param = filename});
waitMessage();
return msgResult;
}
int EmuThread::loadState(const QString& filename)
{
sendMessage({.type = msg_LoadState, .param = filename});
waitMessage();
return msgResult;
}
int EmuThread::undoStateLoad()
{
sendMessage(msg_UndoStateLoad);
waitMessage();
return msgResult;
}
int EmuThread::importSavefile(const QString& filename)
{
sendMessage(msg_EmuReset);
sendMessage({.type = msg_ImportSavefile, .param = filename});
waitMessage(2);
return msgResult;
}
void EmuThread::updateRenderer() void EmuThread::updateRenderer()
{ {
if (videoRenderer != lastVideoRenderer) if (videoRenderer != lastVideoRenderer)
{ {
printf("creating renderer %d\n", videoRenderer);
switch (videoRenderer) switch (videoRenderer)
{ {
case renderer3D_Software: case renderer3D_Software:

View File

@ -23,6 +23,7 @@
#include <QMutex> #include <QMutex>
#include <QSemaphore> #include <QSemaphore>
#include <QQueue> #include <QQueue>
#include <QVariant>
#include <atomic> #include <atomic>
#include <variant> #include <variant>
@ -68,15 +69,26 @@ public:
msg_InitGL, msg_InitGL,
msg_DeInitGL, msg_DeInitGL,
msg_BootROM,
msg_BootFirmware,
msg_InsertCart,
msg_EjectCart,
msg_InsertGBACart,
msg_InsertGBAAddon,
msg_EjectGBACart,
msg_LoadState,
msg_SaveState,
msg_UndoStateLoad,
msg_ImportSavefile,
}; };
struct Message struct Message
{ {
MessageType type; MessageType type;
union QVariant param;
{
bool stopExternal;
};
}; };
void sendMessage(Message msg); void sendMessage(Message msg);
@ -92,20 +104,33 @@ public:
// to be called from the UI thread // to be called from the UI thread
void emuRun(); void emuRun();
void emuPause(); void emuPause(bool broadcast = true);
void emuUnpause(); void emuUnpause(bool broadcast = true);
void emuTogglePause(); void emuTogglePause(bool broadcast = true);
void emuStop(bool external); void emuStop(bool external);
void emuExit(); void emuExit();
void emuFrameStep(); void emuFrameStep();
void emuReset(); void emuReset();
int bootROM(const QStringList& filename);
int bootFirmware();
int insertCart(const QStringList& filename, bool gba);
void ejectCart(bool gba);
int insertGBAAddon(int type);
int saveState(const QString& filename);
int loadState(const QString& filename);
int undoStateLoad();
int importSavefile(const QString& filename);
bool emuIsRunning(); bool emuIsRunning();
bool emuIsActive(); bool emuIsActive();
void initContext(); void initContext(int win);
void deinitContext(); void deinitContext(int win);
void updateVideoSettings() { videoSettingsDirty = true; } void updateVideoSettings() { videoSettingsDirty = true; }
void updateVideoRenderer() { videoSettingsDirty = true; lastVideoRenderer = -1; }
int FrontBuffer = 0; int FrontBuffer = 0;
QMutex FrontBufferLock; QMutex FrontBufferLock;
@ -154,6 +179,8 @@ private:
constexpr static int emuPauseStackPauseThreshold = 1; constexpr static int emuPauseStackPauseThreshold = 1;
int emuPauseStack; int emuPauseStack;
int msgResult = 0;
QMutex msgMutex; QMutex msgMutex;
QSemaphore msgSemaphore; QSemaphore msgSemaphore;
QQueue<Message> msgQueue; QQueue<Message> msgQueue;

View File

@ -132,6 +132,13 @@ bool FirmwareSettingsDialog::verifyMAC()
void FirmwareSettingsDialog::done(int r) void FirmwareSettingsDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
closeDlg();
return;
}
needsReset = false; needsReset = false;
if (r == QDialog::Accepted) if (r == QDialog::Accepted)

View File

@ -49,6 +49,9 @@ static constexpr std::initializer_list<int> hk_general =
HK_FrameStep, HK_FrameStep,
HK_FastForward, HK_FastForward,
HK_FastForwardToggle, HK_FastForwardToggle,
HK_SlowMo,
HK_SlowMoToggle,
HK_FrameLimitToggle,
HK_FullscreenToggle, HK_FullscreenToggle,
HK_Lid, HK_Lid,
HK_Mic, HK_Mic,
@ -65,6 +68,9 @@ static constexpr std::initializer_list<const char*> hk_general_labels =
"Reset", "Reset",
"Frame step", "Frame step",
"Fast forward", "Fast forward",
"Toggle fast forward",
"Slow mo",
"Toggle slow mo",
"Toggle FPS limit", "Toggle FPS limit",
"Toggle fullscreen", "Toggle fullscreen",
"Close/open lid", "Close/open lid",

View File

@ -39,7 +39,9 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare
ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked());
ui->spinMouseHideSeconds->setValue(cfg.GetInt("MouseHideSeconds")); ui->spinMouseHideSeconds->setValue(cfg.GetInt("MouseHideSeconds"));
ui->cbPauseLostFocus->setChecked(cfg.GetBool("PauseLostFocus")); ui->cbPauseLostFocus->setChecked(cfg.GetBool("PauseLostFocus"));
ui->spinMaxFPS->setValue(cfg.GetInt("MaxFPS")); ui->spinTargetFPS->setValue(cfg.GetDouble("TargetFPS"));
ui->spinFFW->setValue(cfg.GetDouble("FastForwardFPS"));
ui->spinSlow->setValue(cfg.GetDouble("SlowmoFPS"));
const QList<QString> themeKeys = QStyleFactory::keys(); const QList<QString> themeKeys = QStyleFactory::keys();
const QString currentTheme = qApp->style()->objectName(); const QString currentTheme = qApp->style()->objectName();
@ -65,8 +67,50 @@ void InterfaceSettingsDialog::on_cbMouseHide_clicked()
ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked());
} }
void InterfaceSettingsDialog::on_pbClean_clicked()
{
ui->spinTargetFPS->setValue(60.0000);
}
void InterfaceSettingsDialog::on_pbAccurate_clicked()
{
ui->spinTargetFPS->setValue(59.8261);
}
void InterfaceSettingsDialog::on_pb2x_clicked()
{
ui->spinFFW->setValue(ui->spinTargetFPS->value() * 2.0);
}
void InterfaceSettingsDialog::on_pb3x_clicked()
{
ui->spinFFW->setValue(ui->spinTargetFPS->value() * 3.0);
}
void InterfaceSettingsDialog::on_pbMAX_clicked()
{
ui->spinFFW->setValue(1000.0);
}
void InterfaceSettingsDialog::on_pbHalf_clicked()
{
ui->spinSlow->setValue(ui->spinTargetFPS->value() / 2.0);
}
void InterfaceSettingsDialog::on_pbQuarter_clicked()
{
ui->spinSlow->setValue(ui->spinTargetFPS->value() / 4.0);
}
void InterfaceSettingsDialog::done(int r) void InterfaceSettingsDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
closeDlg();
return;
}
if (r == QDialog::Accepted) if (r == QDialog::Accepted)
{ {
auto& cfg = emuInstance->getGlobalConfig(); auto& cfg = emuInstance->getGlobalConfig();
@ -74,7 +118,18 @@ void InterfaceSettingsDialog::done(int r)
cfg.SetBool("MouseHide", ui->cbMouseHide->isChecked()); cfg.SetBool("MouseHide", ui->cbMouseHide->isChecked());
cfg.SetInt("MouseHideSeconds", ui->spinMouseHideSeconds->value()); cfg.SetInt("MouseHideSeconds", ui->spinMouseHideSeconds->value());
cfg.SetBool("PauseLostFocus", ui->cbPauseLostFocus->isChecked()); cfg.SetBool("PauseLostFocus", ui->cbPauseLostFocus->isChecked());
cfg.SetInt("MaxFPS", ui->spinMaxFPS->value());
double val = ui->spinTargetFPS->value();
if (val == 0.0) cfg.SetDouble("TargetFPS", 0.0001);
else cfg.SetDouble("TargetFPS", val);
val = ui->spinFFW->value();
if (val == 0.0) cfg.SetDouble("FastForwardFPS", 0.0001);
else cfg.SetDouble("FastForwardFPS", val);
val = ui->spinSlow->value();
if (val == 0.0) cfg.SetDouble("SlowmoFPS", 0.0001);
else cfg.SetDouble("SlowmoFPS", val);
QString themeName = ui->cbxUITheme->currentData().toString(); QString themeName = ui->cbxUITheme->currentData().toString();
cfg.SetQString("UITheme", themeName); cfg.SetQString("UITheme", themeName);

View File

@ -60,6 +60,16 @@ private slots:
void on_cbMouseHide_clicked(); void on_cbMouseHide_clicked();
void on_pbClean_clicked();
void on_pbAccurate_clicked();
void on_pb2x_clicked();
void on_pb3x_clicked();
void on_pbMAX_clicked();
void on_pbHalf_clicked();
void on_pbQuarter_clicked();
private: private:
Ui::InterfaceSettingsDialog* ui; Ui::InterfaceSettingsDialog* ui;

View File

@ -6,12 +6,12 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>337</width> <width>389</width>
<height>275</height> <height>356</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@ -20,6 +20,9 @@
<string>Interface settings - melonDS</string> <string>Interface settings - melonDS</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0"> <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetFixedSize</enum>
</property>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
@ -95,32 +98,209 @@
<property name="title"> <property name="title">
<string>Framerate </string> <string>Framerate </string>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0"> <layout class="QVBoxLayout" name="verticalLayout_5">
<item> <item>
<widget class="QLabel" name="label_3"> <layout class="QGridLayout" name="gridLayout">
<property name="text"> <property name="horizontalSpacing">
<string>Fast-forward limit</string> <number>6</number>
</property> </property>
<property name="buddy"> <property name="verticalSpacing">
<cstring>spinMaxFPS</cstring> <number>2</number>
</property> </property>
</widget> <item row="0" column="0">
</item> <widget class="QLabel" name="label_5">
<item> <property name="text">
<widget class="QSpinBox" name="spinMaxFPS"> <string>Target FPS</string>
<property name="suffix"> </property>
<string> FPS</string> </widget>
</property> </item>
<property name="minimum"> <item row="1" column="0">
<number>60</number> <widget class="QLabel" name="label_3">
</property> <property name="text">
<property name="maximum"> <string>Fast-Forward</string>
<number>1000</number> </property>
</property> <property name="buddy">
<property name="value"> <cstring>spinFFW</cstring>
<number>1000</number> </property>
</property> </widget>
</widget> </item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="spinTargetFPS">
<property name="suffix">
<string> FPS</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>59.826099999999997</double>
</property>
</widget>
</item>
<item row="2" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="pbQuarter">
<property name="maximumSize">
<size>
<width>63</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>1/4</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbHalf">
<property name="maximumSize">
<size>
<width>62</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>1/2</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="spinSlow">
<property name="suffix">
<string> FPS</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>29.913000000000000</double>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="spinFFW">
<property name="suffix">
<string> FPS</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
</property>
<property name="value">
<double>1000.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Slow-Mo</string>
</property>
</widget>
</item>
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="pbAccurate">
<property name="maximumSize">
<size>
<width>63</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Accurate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbClean">
<property name="maximumSize">
<size>
<width>62</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Clean</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="pb2x">
<property name="maximumSize">
<size>
<width>41</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>2x</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pb3x">
<property name="maximumSize">
<size>
<width>41</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>3x</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbMAX">
<property name="maximumSize">
<size>
<width>41</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>MAX</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -128,10 +308,10 @@
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -65,6 +65,12 @@ LANStartHostDialog::~LANStartHostDialog()
void LANStartHostDialog::done(int r) void LANStartHostDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
return;
}
if (r == QDialog::Accepted) if (r == QDialog::Accepted)
{ {
if (ui->txtPlayerName->text().trimmed().isEmpty()) if (ui->txtPlayerName->text().trimmed().isEmpty())
@ -186,6 +192,12 @@ void LANStartClientDialog::onDirectConnect()
void LANStartClientDialog::done(int r) void LANStartClientDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
return;
}
if (r == QDialog::Accepted) if (r == QDialog::Accepted)
{ {
if (ui->txtPlayerName->text().trimmed().isEmpty()) if (ui->txtPlayerName->text().trimmed().isEmpty())
@ -313,6 +325,12 @@ void LANDialog::on_btnLeaveGame_clicked()
void LANDialog::done(int r) void LANDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
return;
}
bool showwarning = true; bool showwarning = true;
if (lan().GetNumPlayers() < 2) if (lan().GetNumPlayers() < 2)
showwarning = false; showwarning = false;

View File

@ -59,6 +59,13 @@ MPSettingsDialog::~MPSettingsDialog()
void MPSettingsDialog::done(int r) void MPSettingsDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
closeDlg();
return;
}
if (r == QDialog::Accepted) if (r == QDialog::Accepted)
{ {
auto& cfg = emuInstance->getGlobalConfig(); auto& cfg = emuInstance->getGlobalConfig();

View File

@ -63,6 +63,12 @@ NetplayStartHostDialog::~NetplayStartHostDialog()
void NetplayStartHostDialog::done(int r) void NetplayStartHostDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
return;
}
if (r == QDialog::Accepted) if (r == QDialog::Accepted)
{ {
std::string player = ui->txtPlayerName->text().toStdString(); std::string player = ui->txtPlayerName->text().toStdString();
@ -94,6 +100,12 @@ NetplayStartClientDialog::~NetplayStartClientDialog()
void NetplayStartClientDialog::done(int r) void NetplayStartClientDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
return;
}
if (r == QDialog::Accepted) if (r == QDialog::Accepted)
{ {
std::string player = ui->txtPlayerName->text().toStdString(); std::string player = ui->txtPlayerName->text().toStdString();

View File

@ -72,6 +72,13 @@ PathSettingsDialog::~PathSettingsDialog()
void PathSettingsDialog::done(int r) void PathSettingsDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
closeDlg();
return;
}
needsReset = false; needsReset = false;
if (r == QDialog::Accepted) if (r == QDialog::Accepted)

View File

@ -762,6 +762,8 @@ ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent)
setAttribute(Qt::WA_KeyCompression, false); setAttribute(Qt::WA_KeyCompression, false);
setFocusPolicy(Qt::StrongFocus); setFocusPolicy(Qt::StrongFocus);
setMinimumSize(screenGetMinSize()); setMinimumSize(screenGetMinSize());
glInited = false;
} }
ScreenPanelGL::~ScreenPanelGL() ScreenPanelGL::~ScreenPanelGL()
@ -773,14 +775,14 @@ bool ScreenPanelGL::createContext()
// if our parent window is parented to another window, we will // if our parent window is parented to another window, we will
// share our OpenGL context with that window // share our OpenGL context with that window
MainWindow* ourwin = (MainWindow*)parentWidget();
MainWindow* parentwin = (MainWindow*)parentWidget()->parentWidget(); MainWindow* parentwin = (MainWindow*)parentWidget()->parentWidget();
if (parentwin) //if (parentwin)
if (ourwin->getWindowID() != 0)
{ {
if (windowinfo.has_value()) if (windowinfo.has_value())
{ if (glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo))
glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo); glContext->DoneCurrent();
glContext->DoneCurrent();
}
} }
else else
{ {
@ -788,10 +790,8 @@ bool ScreenPanelGL::createContext()
GL::Context::Version{GL::Context::Profile::Core, 4, 3}, GL::Context::Version{GL::Context::Profile::Core, 4, 3},
GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; GL::Context::Version{GL::Context::Profile::Core, 3, 2}};
if (windowinfo.has_value()) if (windowinfo.has_value())
{ if (glContext = GL::Context::Create(*windowinfo, versionsToTry))
glContext = GL::Context::Create(*windowinfo, versionsToTry); glContext->DoneCurrent();
glContext->DoneCurrent();
}
} }
return glContext != nullptr; return glContext != nullptr;
@ -807,6 +807,7 @@ void ScreenPanelGL::setSwapInterval(int intv)
void ScreenPanelGL::initOpenGL() void ScreenPanelGL::initOpenGL()
{ {
if (!glContext) return; if (!glContext) return;
if (glInited) return;
glContext->MakeCurrent(); glContext->MakeCurrent();
@ -917,11 +918,15 @@ void ScreenPanelGL::initOpenGL()
overlayPosULoc = glGetUniformLocation(overlayShader, "uOverlayPos"); overlayPosULoc = glGetUniformLocation(overlayShader, "uOverlayPos");
overlaySizeULoc = glGetUniformLocation(overlayShader, "uOverlaySize"); overlaySizeULoc = glGetUniformLocation(overlayShader, "uOverlaySize");
overlayScreenTypeULoc = glGetUniformLocation(overlayShader, "uOverlayScreenType"); overlayScreenTypeULoc = glGetUniformLocation(overlayShader, "uOverlayScreenType");
glInited = true;
} }
void ScreenPanelGL::deinitOpenGL() void ScreenPanelGL::deinitOpenGL()
{ {
if (!glContext) return; if (!glContext) return;
if (!glInited) return;
glDeleteTextures(1, &screenTexture); glDeleteTextures(1, &screenTexture);
@ -960,6 +965,7 @@ void ScreenPanelGL::deinitOpenGL()
glContext->DoneCurrent(); glContext->DoneCurrent();
lastScreenWidth = lastScreenHeight = -1; lastScreenWidth = lastScreenHeight = -1;
glInited = false;
} }
void ScreenPanelGL::makeCurrentGL() void ScreenPanelGL::makeCurrentGL()
@ -1049,9 +1055,6 @@ void ScreenPanelGL::drawScreenGL()
{ {
if (!glContext) return; if (!glContext) return;
auto nds = emuInstance->getNDS();
if (!nds) return;
auto emuThread = emuInstance->getEmuThread(); auto emuThread = emuInstance->getEmuThread();
glContext->MakeCurrent(); glContext->MakeCurrent();
@ -1070,50 +1073,54 @@ void ScreenPanelGL::drawScreenGL()
glViewport(0, 0, w, h); glViewport(0, 0, w, h);
glUseProgram(screenShaderProgram); if (emuThread->emuIsActive())
glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); {
auto nds = emuInstance->getNDS();
int frontbuf = emuThread->FrontBuffer; glUseProgram(screenShaderProgram);
glActiveTexture(GL_TEXTURE0); glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor);
int frontbuf = emuThread->FrontBuffer;
glActiveTexture(GL_TEXTURE0);
#ifdef OGLRENDERER_ENABLED #ifdef OGLRENDERER_ENABLED
if (nds->GPU.GetRenderer3D().Accelerated) 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])
{ {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, // hardware-accelerated render
GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][0].get()); nds->GPU.GetRenderer3D().BindOutputTexture(frontbuf);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, } else
GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][1].get()); #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(); osdUpdate();
if (osdEnabled) if (osdEnabled)
{ {

View File

@ -200,6 +200,7 @@ private:
void drawOverlays(int type,int screen); void drawOverlays(int type,int screen);
std::unique_ptr<GL::Context> glContext; std::unique_ptr<GL::Context> glContext;
bool glInited;
GLuint screenVertexBuffer, screenVertexArray; GLuint screenVertexBuffer, screenVertexArray;
GLuint screenTexture; GLuint screenTexture;

View File

@ -121,6 +121,12 @@ void VideoSettingsDialog::on_VideoSettingsDialog_accepted()
void VideoSettingsDialog::on_VideoSettingsDialog_rejected() void VideoSettingsDialog::on_VideoSettingsDialog_rejected()
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
closeDlg();
return;
}
bool old_gl = UsesGL(); bool old_gl = UsesGL();
auto& cfg = emuInstance->getGlobalConfig(); auto& cfg = emuInstance->getGlobalConfig();

View File

@ -94,6 +94,13 @@ WifiSettingsDialog::~WifiSettingsDialog()
void WifiSettingsDialog::done(int r) void WifiSettingsDialog::done(int r)
{ {
if (!((MainWindow*)parent())->getEmuInstance())
{
QDialog::done(r);
closeDlg();
return;
}
needsReset = false; needsReset = false;
if (r == QDialog::Accepted) if (r == QDialog::Accepted)

File diff suppressed because it is too large Load Diff

View File

@ -111,8 +111,17 @@ public:
EmuInstance* getEmuInstance() { return emuInstance; } EmuInstance* getEmuInstance() { return emuInstance; }
Config::Table& getWindowConfig() { return windowCfg; } Config::Table& getWindowConfig() { return windowCfg; }
LuaConsoleDialog* getLuaDialog() {return luaDialog;} LuaConsoleDialog* getLuaDialog() {return luaDialog;}
int getWindowID() { return windowID; }
bool winHasMenu() { return hasMenu; }
void saveEnabled(bool enabled);
void toggleFullscreen();
bool hasOpenGL() { return hasOGL; } bool hasOpenGL() { return hasOGL; }
GL::Context* getOGLContext(); GL::Context* getOGLContext();
void initOpenGL(); void initOpenGL();
@ -131,6 +140,9 @@ public:
// called when the MP interface is changed // called when the MP interface is changed
void updateMPInterface(melonDS::MPInterfaceType type); void updateMPInterface(melonDS::MPInterfaceType type);
void loadRecentFilesMenu(bool loadcfg);
//void updateVideoSettings(bool glchange);
protected: protected:
void keyPressEvent(QKeyEvent* event) override; void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override;
@ -213,6 +225,7 @@ private slots:
void onChangeScreenSizing(QAction* act); void onChangeScreenSizing(QAction* act);
void onChangeScreenAspect(QAction* act); void onChangeScreenAspect(QAction* act);
void onChangeIntegerScaling(bool checked); void onChangeIntegerScaling(bool checked);
void onOpenNewWindow();
void onChangeScreenFiltering(bool checked); void onChangeScreenFiltering(bool checked);
void onChangeShowOSD(bool checked); void onChangeShowOSD(bool checked);
void onChangeLimitFramerate(bool checked); void onChangeLimitFramerate(bool checked);
@ -256,6 +269,7 @@ private:
bool pausedManually; bool pausedManually;
int windowID; int windowID;
bool enabledSaved;
EmuInstance* emuInstance; EmuInstance* emuInstance;
EmuThread* emuThread; EmuThread* emuThread;
@ -268,6 +282,8 @@ private:
public: public:
ScreenPanel* panel; ScreenPanel* panel;
bool hasMenu;
QAction* actOpenROM; QAction* actOpenROM;
QAction* actBootFirmware; QAction* actBootFirmware;
QAction* actCurrentCart; QAction* actCurrentCart;
@ -275,7 +291,7 @@ public:
QAction* actEjectCart; QAction* actEjectCart;
QAction* actCurrentGBACart; QAction* actCurrentGBACart;
QAction* actInsertGBACart; QAction* actInsertGBACart;
QAction* actInsertGBAAddon[2]; QList<QAction*> actInsertGBAAddon;
QAction* actEjectGBACart; QAction* actEjectGBACart;
QAction* actImportSavefile; QAction* actImportSavefile;
QAction* actSaveState[9]; QAction* actSaveState[9];
@ -332,12 +348,13 @@ public:
QAction** actScreenAspectTop; QAction** actScreenAspectTop;
QActionGroup* grpScreenAspectBot; QActionGroup* grpScreenAspectBot;
QAction** actScreenAspectBot; QAction** actScreenAspectBot;
QAction* actNewWindow;
QAction* actScreenFiltering; QAction* actScreenFiltering;
QAction* actShowOSD; QAction* actShowOSD;
QAction* actLimitFramerate; QAction* actLimitFramerate;
QAction* actAudioSync; QAction* actAudioSync;
QAction* actAbout;
}; };
void ToggleFullscreen(MainWindow* mainWindow);
#endif // WINDOW_H #endif // WINDOW_H

View File

@ -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() void pathInit()
{ {
// First, check for the portable directory next to the executable. // First, check for the portable directory next to the executable.
@ -250,10 +262,8 @@ bool MelonApplication::event(QEvent *event)
MainWindow* win = inst->getMainWindow(); MainWindow* win = inst->getMainWindow();
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent*>(event); QFileOpenEvent *openEvent = static_cast<QFileOpenEvent*>(event);
inst->getEmuThread()->emuPause();
const QStringList file = win->splitArchivePath(openEvent->file(), true); const QStringList file = win->splitArchivePath(openEvent->file(), true);
if (!win->preloadROMs(file, {}, true)) win->preloadROMs(file, {}, true);
inst->getEmuThread()->emuUnpause();
} }
return QApplication::event(event); 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"); if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n");
win->preloadROMs(dsfile, gbafile, options->boot); win->preloadROMs(dsfile, gbafile, options->boot);
if (options->fullscreen)
win->toggleFullscreen();
} }
int ret = melon.exec(); int ret = melon.exec();

View File

@ -31,6 +31,15 @@
#include "ScreenLayout.h" #include "ScreenLayout.h"
#include "MPInterface.h" #include "MPInterface.h"
enum
{
InstCmd_Pause,
InstCmd_Unpause,
InstCmd_UpdateRecentFiles,
//InstCmd_UpdateVideoSettings,
};
class MelonApplication : public QApplication class MelonApplication : public QApplication
{ {
Q_OBJECT Q_OBJECT
@ -50,6 +59,8 @@ void deleteEmuInstance(int id);
void deleteAllEmuInstances(int first = 0); void deleteAllEmuInstances(int first = 0);
int numEmuInstances(); int numEmuInstances();
void broadcastInstanceCommand(int cmd, QVariant& param, int sourceinst);
void setMPInterface(melonDS::MPInterfaceType type); void setMPInterface(melonDS::MPInterfaceType type);
#endif // MAIN_H #endif // MAIN_H

View File

@ -25,5 +25,11 @@
#define MELONDS_VERSION_SUFFIX "${MELONDS_VERSION_SUFFIX}" #define MELONDS_VERSION_SUFFIX "${MELONDS_VERSION_SUFFIX}"
#define MELONDS_VERSION MELONDS_VERSION_BASE 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 #endif // VERSION_H