Merge remote-tracking branch 'upstream/master' into interpreter-fixes

This commit is contained in:
Jaklyy 2024-09-29 22:41:52 -04:00
commit 19e0b18d15
27 changed files with 752 additions and 379 deletions

View File

@ -28,7 +28,7 @@ jobs:
- name: Set up vcpkg
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1
vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da
- name: Build
uses: lukka/run-cmake@v10
with:

View File

@ -4,7 +4,7 @@ on:
push:
branches:
- master
- ci/vcpkg-update
- ci/*
pull_request:
branches:
- master
@ -27,7 +27,7 @@ jobs:
- name: Set up vcpkg
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: 1de2026f28ead93ff1773e6e680387643e914ea1
vcpkgGitCommitId: 3508985146f1b1d248c67ead13f8f54be5b4f5da
- name: Configure
run: cmake --preset=release-mingw-x86_64
- name: Build

View File

@ -9,7 +9,7 @@ if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}")
endif()
FetchContent_Declare(vcpkg
GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git"
GIT_TAG 2024.07.12
GIT_TAG 2024.08.23
SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
FetchContent_MakeAvailable(vcpkg)
endif()
@ -25,6 +25,11 @@ else()
option(USE_QT6 "Build using Qt 6 instead of 5" OFF)
endif()
# Since the Linux build pulls in glib anyway, we can just use upstream libslirp
if (UNIX AND NOT APPLE)
option(USE_SYSTEM_LIBSLIRP "Use system libslirp instead of the bundled version" ON)
endif()
if (NOT USE_QT6)
list(APPEND VCPKG_MANIFEST_FEATURES qt5)
set(VCPKG_MANIFEST_NO_DEFAULT_FEATURES ON)
@ -62,6 +67,14 @@ if (USE_RECOMMENDED_TRIPLETS)
# TODO Windows arm64 if possible
set(_CAN_TARGET_AS_HOST ON)
set(_WANTED_TRIPLET x64-mingw-static-release)
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL Linux)
# Can't really detect cross compiling here.
set(_CAN_TARGET_AS_HOST ON)
if (_HOST_PROCESSOR STREQUAL x86_64)
set(_WANTED_TRIPLET x64-linux-release)
elseif(_HOST_PROCESSOR STREQUAL "aarch64")
set(_WANTED_TRIPLET arm64-linux-release)
endif()
endif()
# Don't override it if the user set something else

View File

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1723175592,
"narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=",
"lastModified": 1725432240,
"narHash": "sha256-+yj+xgsfZaErbfYM3T+QvEE2hU7UuE+Jf0fJCJ8uPS0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5e0ca22929f3342b19569b21b2f3462f053e497b",
"rev": "ad416d066ca1222956472ab7d0555a6946746a80",
"type": "github"
},
"original": {

View File

@ -25,23 +25,23 @@
cmake
ninja
pkg-config
kdePackages.wrapQtAppsHook
qt6.wrapQtAppsHook
];
buildInputs = (with pkgs; [
kdePackages.qtbase
kdePackages.qtmultimedia
extra-cmake-modules
qt6.qtbase
qt6.qtmultimedia
SDL2
zstd
libarchive
libGL
libslirp
enet
]) ++ optionals isLinux [
pkgs.wayland
pkgs.kdePackages.qtwayland
];
]) ++ optionals (!isDarwin) (with pkgs; [
kdePackages.extra-cmake-modules
qt6.qtwayland
wayland
]);
cmakeFlags = [
(cmakeBool "USE_QT6" true)
@ -65,8 +65,27 @@
apps.default = flake-utils.lib.mkApp {
drv = self.packages.${system}.default;
};
devShells.default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.default ];
devShells = {
default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.default ];
};
# Shell for building static melonDS release builds with vcpkg
# Use mkShellNoCC to ensure Nix's gcc/clang and stdlib isn't used
vcpkg = pkgs.mkShellNoCC {
packages = with pkgs; [
autoconf
autoconf-archive
automake
cmake
cups.dev # Needed by qtbase despite not enabling print support
git
iconv.dev
libtool
ninja
pkg-config
];
};
};
}
);

View File

@ -274,6 +274,8 @@ void GPU3D::Reset() noexcept
memset(MatEmission, 0, sizeof(MatSpecular));
UseShininessTable = false;
// Shininess table seems to be uninitialized garbage, at least on n3dsxl hw?
// Also doesn't seem to be cleared properly unless the system is fully powered off?
memset(ShininessTable, 0, sizeof(ShininessTable));
PolygonAttr = 0;
@ -1459,67 +1461,86 @@ void GPU3D::CalculateLighting() noexcept
TexCoords[1] = RawTexCoords[1] + (((s64)Normal[0]*TexMatrix[1] + (s64)Normal[1]*TexMatrix[5] + (s64)Normal[2]*TexMatrix[9]) >> 21);
}
s32 normaltrans[3];
normaltrans[0] = (Normal[0]*VecMatrix[0] + Normal[1]*VecMatrix[4] + Normal[2]*VecMatrix[8]) >> 12;
normaltrans[1] = (Normal[0]*VecMatrix[1] + Normal[1]*VecMatrix[5] + Normal[2]*VecMatrix[9]) >> 12;
normaltrans[2] = (Normal[0]*VecMatrix[2] + Normal[1]*VecMatrix[6] + Normal[2]*VecMatrix[10]) >> 12;
VertexColor[0] = MatEmission[0];
VertexColor[1] = MatEmission[1];
VertexColor[2] = MatEmission[2];
s32 normaltrans[3]; // should be 1 bit sign 10 bits frac
normaltrans[0] = ((Normal[0]*VecMatrix[0] + Normal[1]*VecMatrix[4] + Normal[2]*VecMatrix[8]) << 9) >> 21;
normaltrans[1] = ((Normal[0]*VecMatrix[1] + Normal[1]*VecMatrix[5] + Normal[2]*VecMatrix[9]) << 9) >> 21;
normaltrans[2] = ((Normal[0]*VecMatrix[2] + Normal[1]*VecMatrix[6] + Normal[2]*VecMatrix[10]) << 9) >> 21;
s32 c = 0;
u32 vtxbuff[3] =
{
(u32)MatEmission[0] << 14,
(u32)MatEmission[1] << 14,
(u32)MatEmission[2] << 14
};
for (int i = 0; i < 4; i++)
{
if (!(CurPolygonAttr & (1<<i)))
continue;
// overflow handling (for example, if the normal length is >1)
// according to some hardware tests
// * diffuse level is saturated to 255
// * shininess level mirrors back to 0 and is ANDed with 0xFF, that before being squared
// TODO: check how it behaves when the computed shininess is >=0x200
// (credit to azusa for working out most of the details of the diff. algorithm, and essentially the entire spec. algorithm)
// calculate dot product
// bottom 9 bits are discarded after multiplying and before adding
s32 dot = ((LightDirection[i][0]*normaltrans[0]) >> 9) +
((LightDirection[i][1]*normaltrans[1]) >> 9) +
((LightDirection[i][2]*normaltrans[2]) >> 9);
s32 difflevel = (-(LightDirection[i][0]*normaltrans[0] +
LightDirection[i][1]*normaltrans[1] +
LightDirection[i][2]*normaltrans[2])) >> 10;
if (difflevel < 0) difflevel = 0;
else if (difflevel > 255) difflevel = 255;
s32 shinelevel;
if (dot > 0)
{
// -- diffuse lighting --
// convert dot to signed 11 bit int
// then we truncate the result of the multiplications to an unsigned 20 bits before adding to the vtx color
s32 diffdot = (dot << 21) >> 21;
vtxbuff[0] += (MatDiffuse[0] * LightColor[i][0] * diffdot) & 0xFFFFF;
vtxbuff[1] += (MatDiffuse[1] * LightColor[i][1] * diffdot) & 0xFFFFF;
vtxbuff[2] += (MatDiffuse[2] * LightColor[i][2] * diffdot) & 0xFFFFF;
s32 shinelevel = -(((LightDirection[i][0]>>1)*normaltrans[0] +
(LightDirection[i][1]>>1)*normaltrans[1] +
((LightDirection[i][2]-0x200)>>1)*normaltrans[2]) >> 10);
if (shinelevel < 0) shinelevel = 0;
else if (shinelevel > 255) shinelevel = (0x100 - shinelevel) & 0xFF;
shinelevel = ((shinelevel * shinelevel) >> 7) - 0x100; // really (2*shinelevel*shinelevel)-1
if (shinelevel < 0) shinelevel = 0;
// -- specular lighting --
// reuse the dot product from diffuse lighting
dot += normaltrans[2];
// convert to s11, then square it, and truncate to 10 bits
dot = (dot << 21) >> 21;
dot = ((dot * dot) >> 10) & 0x3FF;
// multiply dot and reciprocal, the subtract '1'
shinelevel = ((dot * SpecRecip[i]) >> 8) - (1<<9);
if (shinelevel < 0) shinelevel = 0;
else
{
// sign extend to convert to signed 14 bit integer
shinelevel = (shinelevel << 18) >> 18;
if (shinelevel < 0) shinelevel = 0; // for some reason there seems to be a redundant check for <0?
else if (shinelevel > 0x1FF) shinelevel = 0x1FF;
}
}
else shinelevel = 0;
// convert shinelevel to use for lookup in the shininess table if enabled.
if (UseShininessTable)
{
// checkme
shinelevel >>= 1;
shinelevel >>= 2;
shinelevel = ShininessTable[shinelevel];
shinelevel <<= 1;
}
VertexColor[0] += ((MatSpecular[0] * LightColor[i][0] * shinelevel) >> 13);
VertexColor[0] += ((MatDiffuse[0] * LightColor[i][0] * difflevel) >> 13);
VertexColor[0] += ((MatAmbient[0] * LightColor[i][0]) >> 5);
VertexColor[1] += ((MatSpecular[1] * LightColor[i][1] * shinelevel) >> 13);
VertexColor[1] += ((MatDiffuse[1] * LightColor[i][1] * difflevel) >> 13);
VertexColor[1] += ((MatAmbient[1] * LightColor[i][1]) >> 5);
VertexColor[2] += ((MatSpecular[2] * LightColor[i][2] * shinelevel) >> 13);
VertexColor[2] += ((MatDiffuse[2] * LightColor[i][2] * difflevel) >> 13);
VertexColor[2] += ((MatAmbient[2] * LightColor[i][2]) >> 5);
if (VertexColor[0] > 31) VertexColor[0] = 31;
if (VertexColor[1] > 31) VertexColor[1] = 31;
if (VertexColor[2] > 31) VertexColor[2] = 31;
// Note: ambient seems to be a plain bitshift
vtxbuff[0] += ((MatSpecular[0] * shinelevel) + (MatAmbient[0] << 9)) * LightColor[i][0];
vtxbuff[1] += ((MatSpecular[1] * shinelevel) + (MatAmbient[1] << 9)) * LightColor[i][1];
vtxbuff[2] += ((MatSpecular[2] * shinelevel) + (MatAmbient[2] << 9)) * LightColor[i][2];
c++;
}
VertexColor[0] = (vtxbuff[0] >> 14 > 31) ? 31 : (vtxbuff[0] >> 14);
VertexColor[1] = (vtxbuff[1] >> 14 > 31) ? 31 : (vtxbuff[1] >> 14);
VertexColor[2] = (vtxbuff[2] >> 14 > 31) ? 31 : (vtxbuff[2] >> 14);
if (c < 1) c = 1;
NormalPipeline = 7;
AddCycles(c);
@ -2012,9 +2033,15 @@ void GPU3D::ExecuteCommand() noexcept
dir[0] = (s16)((entry.Param & 0x000003FF) << 6) >> 6;
dir[1] = (s16)((entry.Param & 0x000FFC00) >> 4) >> 6;
dir[2] = (s16)((entry.Param & 0x3FF00000) >> 14) >> 6;
LightDirection[l][0] = (dir[0]*VecMatrix[0] + dir[1]*VecMatrix[4] + dir[2]*VecMatrix[8]) >> 12;
LightDirection[l][1] = (dir[0]*VecMatrix[1] + dir[1]*VecMatrix[5] + dir[2]*VecMatrix[9]) >> 12;
LightDirection[l][2] = (dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) >> 12;
// the order of operations here is very specific: discard bottom 12 bits -> negate -> then sign extend to convert to 11 bit signed int
// except for when used to calculate the specular reciprocal; then it's: sign extend -> discard lsb -> negate.
LightDirection[l][0] = (-((dir[0]*VecMatrix[0] + dir[1]*VecMatrix[4] + dir[2]*VecMatrix[8] ) >> 12) << 21) >> 21;
LightDirection[l][1] = (-((dir[0]*VecMatrix[1] + dir[1]*VecMatrix[5] + dir[2]*VecMatrix[9] ) >> 12) << 21) >> 21;
LightDirection[l][2] = (-((dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) >> 12) << 21) >> 21;
s32 den = -(((dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) << 9) >> 21) + (1<<9);
if (den == 0) SpecRecip[l] = 0;
else SpecRecip[l] = (1<<18) / den;
}
AddCycles(5);
break;

View File

@ -286,6 +286,7 @@ public:
s16 Normal[3] {};
s16 LightDirection[4][3] {};
s32 SpecRecip[4] {};
u8 LightColor[4][3] {};
u8 MatDiffuse[3] {};
u8 MatAmbient[3] {};

View File

@ -1830,7 +1830,7 @@ const ROMListEntry ROMList[] =
{0x45564E43, 0x10000000, 0x00000005},
{0x45564F59, 0x00800000, 0x00000001},
{0x45565041, 0x00800000, 0x00000002},
{0x45565042, 0x00800000, 0x00000004},
{0x45565042, 0x00800000, 0x00000002},
{0x45565043, 0x04000000, 0x00000002},
{0x45565056, 0x04000000, 0x00000002},
{0x45565059, 0x04000000, 0x00000001},
@ -6804,4 +6804,4 @@ const ROMListEntry ROMList[] =
const size_t ROMListEntryCount = sizeof(ROMList) / sizeof(ROMListEntry);
}
}

View File

@ -1,12 +1,13 @@
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "../CRC32.h"
#include "../Platform.h"
#include "hexutil.h"
#include "GdbProto.h"
#include "GdbStub.h"
using namespace melonDS;
using Platform::Log;
@ -878,6 +879,7 @@ ExecResult GdbStub::Handle_v_Stopped(GdbStub* stub, const u8* cmd, ssize_t len)
ExecResult GdbStub::Handle_v_MustReplyEmpty(GdbStub* stub, const u8* cmd, ssize_t len)
{
printf("must reply empty\n");
stub->Resp(NULL, 0);
return ExecResult::Ok;
}
@ -886,6 +888,7 @@ ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len)
{
if (len < 1)
{
printf("insufficient length");
stub->RespStr("E01");
return ExecResult::Ok;
}
@ -902,6 +905,7 @@ ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len)
stub->RespStr("OK");
return ExecResult::MustBreak;
default:
printf("invalid continue %c %s\n", cmd[0], cmd);
stub->RespStr("E01");
return ExecResult::Ok;
}

View File

@ -5,12 +5,14 @@
#include <winsock2.h>
#endif
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <algorithm>
#ifndef _WIN32
#include <poll.h>
@ -21,8 +23,7 @@
#include "../Platform.h"
#include "hexutil.h"
#include "GdbProto.h"
#include "GdbStub.h"
using namespace melonDS;
using Platform::Log;
@ -42,87 +43,128 @@ namespace Gdb
* vKill;pid
* qRcmd? qSupported?
*/
u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY];
ssize_t Cmdlen;
namespace Proto
Gdb::ReadResult GdbStub::TryParsePacket(size_t start, size_t& packetStart, size_t& packetSize, size_t& packetContentSize)
{
u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY];
u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5];
ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/])
{
static ssize_t dataoff = 0;
ssize_t recv_total = dataoff;
ssize_t cksumoff = -1;
u8 sum = 0;
bool first = true;
//printf("--- dataoff=%zd\n", dataoff);
if (dataoff != 0) {
printf("--- got preexisting: %s\n", PacketBuf);
ssize_t datastart = 0;
while (true)
RecvBuffer[RecvBufferFilled] = '\0';
//Log(LogLevel::Debug, "[GDB] Trying to parse packet %s %d %d\n", &RecvBuffer[0], start, RecvBufferFilled);
size_t i = start;
while (i < RecvBufferFilled)
{
char curChar = RecvBuffer[i++];
if (curChar == '\x04') return ReadResult::Eof;
else if (curChar == '\x03')
{
if (PacketBuf[datastart] == '\x04') return ReadResult::Eof;
else if (PacketBuf[datastart] == '+' || PacketBuf[datastart] == '-')
packetStart = i - 1;
packetSize = packetContentSize = 1;
return ReadResult::Break;
}
else if (curChar == '+' || curChar == '-') continue;
else if (curChar == '$')
{
packetStart = i;
uint8_t checksumGot = 0;
while (i < RecvBufferFilled)
{
/*if (PacketBuf[datastart] == '+') SendAck(connfd);
else SendNak(connfd);*/
++datastart;
continue;
}
else if (PacketBuf[datastart] == '$')
{
++datastart;
break;
}
else
{
__builtin_trap();
return ReadResult::Wut;
curChar = RecvBuffer[i];
if (curChar == '#' && i + 2 < RecvBufferFilled)
{
u8 checksumShould = (hex2nyb(RecvBuffer[i+1]) << 4)
| hex2nyb(RecvBuffer[i+2]);
Log(LogLevel::Debug, "[GDB] found pkt, checksumGot: %02x vs %02x\n", checksumShould, checksumGot);
if (checksumShould != checksumGot)
{
return ReadResult::CksumErr;
}
packetContentSize = i - packetStart;
packetSize = packetContentSize + 3;
return ReadResult::CmdRecvd;
}
else
{
checksumGot += curChar;
}
i++;
}
}
printf("--- datastart=%zd\n", datastart);
for (ssize_t i = datastart; i < dataoff; ++i)
else
{
if (PacketBuf[i] == '#')
{
cksumoff = i + 1;
printf("--- cksumoff=%zd\n", cksumoff);
break;
}
sum += PacketBuf[i];
}
if (cksumoff >= 0)
{
recv_total = dataoff - datastart + 1;
dataoff = cksumoff + 2 - datastart + 1;
cksumoff -= datastart - 1;
memmove(&PacketBuf[1], &PacketBuf[datastart], recv_total);
PacketBuf[0] = '$';
PacketBuf[recv_total] = 0;
printf("=== cksumoff=%zi recv_total=%zi datastart=%zi dataoff=%zi\n==> %s\n",
cksumoff, recv_total, datastart, dataoff, PacketBuf);
//break;
Log(LogLevel::Error, "[GDB] Received unknown character %c (%d)\n", curChar, curChar);
return ReadResult::Wut;
}
}
while (cksumoff < 0)
{
u8* pkt = &PacketBuf[dataoff];
ssize_t n, blehoff = 0;
return ReadResult::NoPacket;
}
memset(pkt, 0, sizeof(PacketBuf) - dataoff);
Gdb::ReadResult GdbStub::ParseAndSetupPacket()
{
// This complicated logic seems to be unfortunately necessary
// to handle the case of packet resends when we answered too slowly.
// GDB only expects a single response (as it assumes the previous packet was dropped)
size_t i = 0;
size_t prevPacketStart = SIZE_MAX, prevPacketSize, prevPacketContentSize;
size_t packetStart, packetSize, packetContentSize;
ReadResult result, prevResult;
while (true)
{
ReadResult result = TryParsePacket(i, packetStart, packetSize, packetContentSize);
if (result == ReadResult::NoPacket)
break;
if (result != ReadResult::CmdRecvd && result != ReadResult::Break)
return result;
// looks like there is a different packet coming up
// so we quit here
if (prevPacketStart != SIZE_MAX &&
(packetContentSize != prevPacketContentSize ||
memcmp(&RecvBuffer[packetStart], &RecvBuffer[prevPacketStart], prevPacketContentSize) != 0))
{
Log(LogLevel::Debug, "[GDB] found differing packet further back %zu %zu\n", packetContentSize, prevPacketContentSize);
break;
}
i = packetStart + packetSize;
prevPacketStart = packetStart;
prevPacketSize = packetSize;
prevPacketContentSize = packetContentSize;
prevResult = result;
}
if (prevPacketStart != SIZE_MAX)
{
memcpy(&Cmdbuf[0], &RecvBuffer[prevPacketStart], prevPacketContentSize);
Cmdbuf[prevPacketContentSize] = '\0';
Cmdlen = static_cast<ssize_t>(prevPacketContentSize);
RecvBufferFilled -= prevPacketStart + prevPacketSize;
if (RecvBufferFilled > 0)
memmove(&RecvBuffer[0], &RecvBuffer[prevPacketStart + prevPacketSize], RecvBufferFilled);
assert(prevResult == ReadResult::CmdRecvd || prevResult == ReadResult::Break);
return prevResult;
}
assert(result == ReadResult::NoPacket);
return ReadResult::NoPacket;
}
ReadResult GdbStub::MsgRecv()
{
{
ReadResult result = ParseAndSetupPacket();
if (result != ReadResult::NoPacket)
return result;
}
bool first = true;
while (true)
{
int flag = 0;
#if MOCKTEST
static bool FIRST = false;
@ -142,113 +184,35 @@ ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/])
#else
#ifndef _WIN32
if (first) flag |= MSG_DONTWAIT;
n = recv(connfd, pkt, sizeof(PacketBuf) - dataoff, flag);
ssize_t receivedNum = recv(ConnFd, &RecvBuffer[RecvBufferFilled], sizeof(RecvBuffer) - RecvBufferFilled, flag);
Log(LogLevel::Debug, "[GDB] receiving from stream %d\n", receivedNum);
#else
// fuck windows
n = recv(connfd, (char*)pkt, sizeof(PacketBuf) - dataoff, flag);
ssize_t receivedNum = recv(ConnFd, (char*)&RecvBuffer[RecvBufferFilled], sizeof(RecvBuffer) - RecvBufferFilled, flag);
#endif
#endif
if (n <= 0)
if (receivedNum <= 0)
{
if (first) return ReadResult::NoPacket;
else
{
Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", n, errno, strerror(errno));
Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", receivedNum, errno, strerror(errno));
return ReadResult::Eof;
}
}
RecvBufferFilled += static_cast<u32>(receivedNum);
Log(LogLevel::Debug, "[GDB] recv() %zd bytes: '%s' (%02x)\n", n, pkt, pkt[0]);
first = false;
do
{
if (dataoff == 0)
{
if (pkt[blehoff] == '\x04') return ReadResult::Eof;
else if (pkt[blehoff] == '\x03') return ReadResult::Break;
else if (pkt[blehoff] != '$')
{
++blehoff;
--n;
}
else break;
if (n == 0) goto next_outer;
}
}
while (true);
if (blehoff > 0)
{
memmove(pkt, &pkt[blehoff], n - blehoff + 1);
n -= blehoff - 1; // ???
}
recv_total += n;
Log(LogLevel::Debug, "[GDB] recv() after skipping: n=%zd, recv_total=%zd\n", n, recv_total);
for (ssize_t i = (dataoff == 0) ? 1 : 0; i < n; ++i)
{
u8 v = pkt[i];
if (v == '#')
{
cksumoff = dataoff + i + 1;
break;
}
sum += pkt[i];
}
if (cksumoff < 0)
{
// oops, need more data
dataoff += n;
}
next_outer:;
ReadResult result = ParseAndSetupPacket();
if (result != ReadResult::NoPacket)
return result;
}
u8 ck = (hex2nyb(PacketBuf[cksumoff+0]) << 4)
| hex2nyb(PacketBuf[cksumoff+1]);
Log(LogLevel::Debug, "[GDB] got pkt, checksum: %02x vs %02x\n", ck, sum);
if (ck != sum)
{
//__builtin_trap();
return ReadResult::CksumErr;
}
if (cksumoff + 2 > recv_total)
{
Log(LogLevel::Error, "[GDB] BIG MISTAKE: %zi > %zi which shouldn't happen!\n", cksumoff + 2, recv_total);
//__builtin_trap();
return ReadResult::Wut;
}
else
{
Cmdlen = cksumoff - 2;
memcpy(Cmdbuf, &PacketBuf[1], Cmdlen);
Cmdbuf[Cmdlen] = 0;
if (cksumoff + 2 < recv_total) {
// huh, we have the start of the next packet
dataoff = recv_total - (cksumoff + 2);
memmove(PacketBuf, &PacketBuf[cksumoff + 2], (size_t)dataoff);
PacketBuf[dataoff] = 0;
Log(LogLevel::Debug, "[GDB] got more: cksumoff=%zd, recvtotal=%zd, remain=%zd\n==> %s\n", cksumoff, recv_total, dataoff, PacketBuf);
}
else dataoff = 0;
}
return ReadResult::CmdRecvd;
}
int SendAck(int connfd)
int GdbStub::SendAck()
{
if (NoAck) return 1;
Log(LogLevel::Debug, "[GDB] send ack\n");
u8 v = '+';
#if MOCKTEST
@ -257,14 +221,16 @@ int SendAck(int connfd)
#ifdef _WIN32
// fuck windows
return send(connfd, (const char*)&v, 1, 0);
return send(ConnFd, (const char*)&v, 1, 0);
#else
return send(connfd, &v, 1, 0);
return send(ConnFd, &v, 1, 0);
#endif
}
int SendNak(int connfd)
int GdbStub::SendNak()
{
if (NoAck) return 1;
Log(LogLevel::Debug, "[GDB] send nak\n");
u8 v = '-';
#if MOCKTEST
@ -273,13 +239,13 @@ int SendNak(int connfd)
#ifdef _WIN32
// fuck windows
return send(connfd, (const char*)&v, 1, 0);
return send(ConnFd, (const char*)&v, 1, 0);
#else
return send(connfd, &v, 1, 0);
return send(ConnFd, &v, 1, 0);
#endif
}
int WaitAckBlocking(int connfd, u8* ackp, int to_ms)
int GdbStub::WaitAckBlocking(u8* ackp, int to_ms)
{
#if MOCKTEST
*ackp = '+';
@ -289,18 +255,18 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms)
#ifdef _WIN32
fd_set infd, outfd, errfd;
FD_ZERO(&infd); FD_ZERO(&outfd); FD_ZERO(&errfd);
FD_SET(connfd, &infd);
FD_SET(ConnFd, &infd);
struct timeval to;
to.tv_sec = to_ms / 1000;
to.tv_usec = (to_ms % 1000) * 1000;
int r = select(connfd+1, &infd, &outfd, &errfd, &to);
int r = select(ConnFd+1, &infd, &outfd, &errfd, &to);
if (FD_ISSET(connfd, &errfd)) return -1;
else if (FD_ISSET(connfd, &infd))
if (FD_ISSET(ConnFd, &errfd)) return -1;
else if (FD_ISSET(ConnFd, &infd))
{
r = recv(connfd, (char*)ackp, 1, 0);
r = recv(ConnFd, (char*)ackp, 1, 0);
if (r < 0) return r;
return 0;
}
@ -309,7 +275,7 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms)
#else
struct pollfd pfd;
pfd.fd = connfd;
pfd.fd = ConnFd;
pfd.events = POLLIN;
pfd.revents = 0;
@ -319,14 +285,14 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms)
if (pfd.revents & (POLLHUP|POLLERR)) return -69;
r = recv(connfd, ackp, 1, 0);
r = recv(ConnFd, ackp, 1, 0);
if (r < 0) return r;
return (r == 1) ? 0 : -1;
#endif
}
int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack)
int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack)
{
u8 cksum = 0;
int tries = 0;
@ -359,22 +325,22 @@ int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2,
ssize_t r;
u8 ack;
Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", RespBuf);
Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", &RespBuf[0]);
#if MOCKTEST
r = totallen+4;
#else
#ifdef _WIN32
r = send(connfd, (const char*)RespBuf, totallen+4, 0);
r = send(ConnFd, (const char*)&RespBuf[0], totallen+4, 0);
#else
r = send(connfd, RespBuf, totallen+4, 0);
r = send(ConnFd, &RespBuf[0], totallen+4, 0);
#endif
#endif
if (r < 0) return r;
if (noack) break;
r = WaitAckBlocking(connfd, &ack, 2000);
//Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack);
r = WaitAckBlocking(&ack, 2000);
Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack);
if (r == 0 && ack == '+') break;
++tries;
@ -386,5 +352,4 @@ int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2,
}
}

View File

@ -1,41 +0,0 @@
#ifndef GDBPROTO_H_
#define GDBPROTO_H_
#include <string.h>
#include <sys/types.h>
#include "GdbStub.h" /* class GdbStub */
#define MOCKTEST 0
namespace Gdb {
using namespace melonDS;
constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128;
extern u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY];
extern ssize_t Cmdlen;
namespace Proto {
extern u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY];
extern u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5];
Gdb::ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]);
int SendAck(int connfd);
int SendNak(int connfd);
int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack);
int WaitAckBlocking(int connfd, u8* ackp, int to_ms);
}
}
#endif

View File

@ -23,7 +23,7 @@
#include "../Platform.h"
#include "GdbProto.h"
#include "GdbStub.h"
using namespace melonDS;
using Platform::Log;
@ -304,7 +304,7 @@ StubState GdbStub::Poll(bool wait)
if (ConnFd < 0) return StubState::NoConn;
u8 a;
if (Proto::WaitAckBlocking(ConnFd, &a, 1000) < 0)
if (WaitAckBlocking(&a, 1000) < 0)
{
Log(LogLevel::Error, "[GDB] inital handshake: didn't receive inital ack!\n");
close(ConnFd);
@ -380,7 +380,7 @@ StubState GdbStub::Poll(bool wait)
#endif
#endif
ReadResult res = Proto::MsgRecv(ConnFd, Cmdbuf);
ReadResult res = MsgRecv();
switch (res)
{
@ -422,11 +422,12 @@ ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler*
// check if prefix matches
if (!strncmp((const char*)cmd, handlers[i].SubStr, strlen(handlers[i].SubStr)))
{
if (SendAck() < 0)
// ack should have already been sent by CmdExec
/*if (SendAck() < 0)
{
Log(LogLevel::Error, "[GDB] send packet ack failed!\n");
return ExecResult::NetErr;
}
}*/
return handlers[i].Handler(this, &cmd[strlen(handlers[i].SubStr)], len-strlen(handlers[i].SubStr));
}
}
@ -444,7 +445,7 @@ ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler*
ExecResult GdbStub::CmdExec(const CmdHandler* handlers)
{
Log(LogLevel::Debug, "[GDB] command in: '%s'\n", Cmdbuf);
Log(LogLevel::Debug, "[GDB] command in: '%s'\n", &Cmdbuf[0]);
for (size_t i = 0; handlers[i].Handler != NULL; ++i)
{
@ -644,24 +645,13 @@ StubState GdbStub::CheckWatchpt(u32 addr, int kind, bool enter, bool stay)
return StubState::CheckNoHit;
}
int GdbStub::SendAck()
{
if (NoAck) return 1;
return Proto::SendAck(ConnFd);
}
int GdbStub::SendNak()
{
if (NoAck) return 1;
return Proto::SendNak(ConnFd);
}
int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2)
{
return Proto::Resp(ConnFd, data1, len1, data2, len2, NoAck);
return Resp(data1, len1, data2, len2, NoAck);
}
int GdbStub::RespC(const char* data1, size_t len1, const u8* data2, size_t len2)
{
return Proto::Resp(ConnFd, (const u8*)data1, len1, data2, len2, NoAck);
return Resp((const u8*)data1, len1, data2, len2, NoAck);
}
#if defined(__GCC__) || defined(__clang__)
__attribute__((__format__(printf, 2/*includes implicit this*/, 3)))
@ -670,19 +660,19 @@ int GdbStub::RespFmt(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int r = vsnprintf((char*)&Proto::RespBuf[1], sizeof(Proto::RespBuf)-5, fmt, args);
int r = vsnprintf((char*)&RespBuf[1], sizeof(RespBuf)-5, fmt, args);
va_end(args);
if (r < 0) return r;
if ((size_t)r >= sizeof(Proto::RespBuf)-5)
if ((size_t)r >= sizeof(RespBuf)-5)
{
Log(LogLevel::Error, "[GDB] truncated response in send_fmt()! (lost %zd bytes)\n",
(ssize_t)r - (ssize_t)(sizeof(Proto::RespBuf)-5));
r = sizeof(Proto::RespBuf)-5;
(ssize_t)r - (ssize_t)(sizeof(RespBuf)-5));
r = sizeof(RespBuf)-5;
}
return Resp(&Proto::RespBuf[1], r);
return Resp(&RespBuf[1], r);
}
int GdbStub::RespStr(const char* str)

View File

@ -86,6 +86,8 @@ enum class ExecResult
Continue
};
constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128;
class GdbStub;
typedef ExecResult (*GdbProtoCmd)(GdbStub* stub, const u8* cmd, ssize_t len);
@ -141,9 +143,6 @@ public:
Gdb::ExecResult CmdExec(const CmdHandler* handlers);
public:
int SendAck();
int SendNak();
int Resp(const u8* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0);
int RespC(const char* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0);
#if defined(__GCC__) || defined(__clang__)
@ -158,7 +157,20 @@ private:
void Disconnect();
StubState HandlePacket();
private:
Gdb::ReadResult MsgRecv();
Gdb::ReadResult TryParsePacket(size_t start, size_t& packetStart, size_t& packetSize, size_t& packetContentSize);
Gdb::ReadResult ParseAndSetupPacket();
void SetupCommand(size_t packetStart, size_t packetSize);
int SendAck();
int SendNak();
int Resp(const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack);
int WaitAckBlocking(u8* ackp, int to_ms);
StubCallbacks* Cb;
//struct sockaddr_in server, client;
@ -172,6 +184,13 @@ private:
bool StatFlag;
bool NoAck;
std::array<u8, GDBPROTO_BUFFER_CAPACITY> RecvBuffer;
u32 RecvBufferFilled = 0;
std::array<u8, GDBPROTO_BUFFER_CAPACITY> RespBuf;
std::array<u8, GDBPROTO_BUFFER_CAPACITY> Cmdbuf;
ssize_t Cmdlen;
std::map<u32, BpWp> BpList;
std::vector<BpWp> WpList;

View File

@ -160,7 +160,12 @@ if (BUILD_STATIC)
if (WIN32 AND USE_QT6)
qt_import_plugins(melonDS INCLUDE Qt::QModernWindowsStylePlugin)
endif()
target_link_options(melonDS PRIVATE -static)
if (USE_VCPKG AND UNIX AND NOT APPLE)
pkg_check_modules(ALSA REQUIRED IMPORTED_TARGET alsa)
target_link_libraries(melonDS PRIVATE PkgConfig::ALSA)
else()
target_link_options(melonDS PRIVATE -static)
endif()
endif()
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

View File

@ -55,7 +55,6 @@ DefaultList<int> DefaultInts =
{"Screen.VSyncInterval", 1},
{"3D.Renderer", renderer3D_Software},
{"3D.GL.ScaleFactor", 1},
{"MaxFPS", 1000},
#ifdef JIT_ENABLED
{"JIT.MaxBlockSize", 32},
#endif
@ -71,7 +70,7 @@ DefaultList<int> DefaultInts =
#ifdef GDBSTUB_ENABLED
{"Instance*.Gdb.ARM7.Port", 3334},
{"Instance*.Gdb.ARM9.Port", 3333},
#endif,
#endif
{"LAN.HostNumPlayers", 16},
};
@ -120,6 +119,13 @@ DefaultList<std::string> DefaultStrings =
{"Instance*.Firmware.Username", "melonDS"}
};
DefaultList<double> DefaultDoubles =
{
{"TargetFPS", 60.0},
{"FastForwardFPS", 1000.0},
{"SlowmoFPS", 30.0},
};
LegacyEntry LegacyFile[] =
{
{"Key_A", 0, "Keyboard.A", true},
@ -153,7 +159,7 @@ LegacyEntry LegacyFile[] =
{"HKKey_Pause", 0, "Keyboard.HK_Pause", true},
{"HKKey_Reset", 0, "Keyboard.HK_Reset", true},
{"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true},
{"HKKey_FastForwardToggle", 0, "Keyboard.HK_FastForwardToggle", true},
{"HKKey_FastForwardToggle", 0, "Keyboard.HK_FrameLimitToggle", true},
{"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true},
{"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true},
{"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true},
@ -169,7 +175,7 @@ LegacyEntry LegacyFile[] =
{"HKJoy_Pause", 0, "Joystick.HK_Pause", true},
{"HKJoy_Reset", 0, "Joystick.HK_Reset", true},
{"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true},
{"HKJoy_FastForwardToggle", 0, "Joystick.HK_FastForwardToggle", true},
{"HKJoy_FastForwardToggle", 0, "Joystick.HK_FrameLimitToggle", true},
{"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true},
{"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true},
{"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true},
@ -434,6 +440,18 @@ std::string Array::GetString(const int id)
return tval.as_string();
}
double Array::GetDouble(const int id)
{
while (Data.size() < id+1)
Data.push_back(0.0);
toml::value& tval = Data[id];
if (!tval.is_floating())
tval = 0.0;
return tval.as_floating();
}
void Array::SetInt(const int id, int val)
{
while (Data.size() < id+1)
@ -470,6 +488,15 @@ void Array::SetString(const int id, const std::string& val)
tval = val;
}
void Array::SetDouble(const int id, double val)
{
while (Data.size() < id+1)
Data.push_back(0.0);
toml::value& tval = Data[id];
tval = val;
}
/*Table::Table()// : Data(toml::value())
{
@ -562,6 +589,15 @@ std::string Table::GetString(const std::string& path)
return tval.as_string();
}
double Table::GetDouble(const std::string& path)
{
toml::value& tval = ResolvePath(path);
if (!tval.is_floating())
tval = FindDefault(path, 0.0, DefaultDoubles);
return tval.as_floating();
}
void Table::SetInt(const std::string& path, int val)
{
std::string rngkey = GetDefaultKey(PathPrefix+path);
@ -593,6 +629,12 @@ void Table::SetString(const std::string& path, const std::string& val)
tval = val;
}
void Table::SetDouble(const std::string& path, double val)
{
toml::value& tval = ResolvePath(path);
tval = val;
}
toml::value& Table::ResolvePath(const std::string& path)
{
toml::value* ret = &Data;

View File

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

View File

@ -88,7 +88,31 @@ EmuInstance::EmuInstance(int inst) : deleting(false),
cheatsOn = localCfg.GetBool("EnableCheats");
doLimitFPS = globalCfg.GetBool("LimitFPS");
maxFPS = globalCfg.GetInt("MaxFPS");
double val = globalCfg.GetDouble("TargetFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n");
targetFPS = 1.0 / 60.0;
}
else targetFPS = 1.0 / val;
val = globalCfg.GetDouble("FastForwardFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Fast-Forward FPS in config invalid\n");
fastForwardFPS = 1.0 / 60.0;
}
else fastForwardFPS = 1.0 / val;
val = globalCfg.GetDouble("SlowmoFPS");
if (val == 0.0)
{
Platform::Log(Platform::LogLevel::Error, "Slow-Mo FPS in config invalid\n");
slowmoFPS = 1.0 / 60.0;
}
else slowmoFPS = 1.0 / val;
doAudioSync = globalCfg.GetBool("AudioSync");
mpAudioMode = globalCfg.GetInt("MP.AudioMode");
@ -1153,14 +1177,14 @@ bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGB
#endif
#ifdef GDBSTUB_ENABLED
Config::Table gdbopt = globalCfg.GetTable("Gdb");
Config::Table gdbopt = localCfg.GetTable("Gdb");
GDBArgs _gdbargs {
static_cast<u16>(gdbopt.GetInt("ARM7.Port")),
static_cast<u16>(gdbopt.GetInt("ARM9.Port")),
gdbopt.GetBool("ARM7.BreakOnStartup"),
gdbopt.GetBool("ARM9.BreakOnStartup"),
};
auto gdbargs = gdbopt.GetBool("Enable") ? std::make_optional(_gdbargs) : std::nullopt;
auto gdbargs = gdbopt.GetBool("Enabled") ? std::make_optional(_gdbargs) : std::nullopt;
#else
optional<GDBArgs> gdbargs = std::nullopt;
#endif

View File

@ -36,7 +36,7 @@ enum
HK_Pause,
HK_Reset,
HK_FastForward,
HK_FastForwardToggle,
HK_FrameLimitToggle,
HK_FullscreenToggle,
HK_SwapScreens,
HK_SwapScreenEmphasis,
@ -46,6 +46,9 @@ enum
HK_PowerButton,
HK_VolumeUp,
HK_VolumeDown,
HK_SlowMo,
HK_FastForwardToggle,
HK_SlowMoToggle,
HK_MAX
};
@ -252,7 +255,12 @@ public:
std::unique_ptr<SaveManager> firmwareSave;
bool doLimitFPS;
int maxFPS;
double curFPS;
double targetFPS;
double fastForwardFPS;
double slowmoFPS;
bool fastForwardToggled;
bool slowmoToggled;
bool doAudioSync;
private:

View File

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

View File

@ -95,7 +95,7 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new
#endif
#ifdef GDBSTUB_ENABLED
ui->cbGdbEnabled->setChecked(cfg.GetBool("Gdb.Enabled"));
ui->cbGdbEnabled->setChecked(instcfg.GetBool("Gdb.Enabled"));
ui->intGdbPortA7->setValue(instcfg.GetInt("Gdb.ARM7.Port"));
ui->intGdbPortA9->setValue(instcfg.GetInt("Gdb.ARM9.Port"));
ui->cbGdbBOSA7->setChecked(instcfg.GetBool("Gdb.ARM7.BreakOnStartup"));
@ -286,7 +286,7 @@ void EmuSettingsDialog::done(int r)
cfg.SetBool("JIT.FastMemory", ui->chkJITFastMemory->isChecked());
#endif
#ifdef GDBSTUB_ENABLED
cfg.SetBool("Gdb.Enabled", ui->cbGdbEnabled->isChecked());
instcfg.SetBool("Gdb.Enabled", ui->cbGdbEnabled->isChecked());
instcfg.SetInt("Gdb.ARM7.Port", ui->intGdbPortA7->value());
instcfg.SetInt("Gdb.ARM9.Port", ui->intGdbPortA9->value());
instcfg.SetBool("Gdb.ARM7.BreakOnStartup", ui->cbGdbBOSA7->isChecked());

View File

@ -149,12 +149,17 @@ void EmuThread::run()
char melontitle[100];
bool fastforward = false;
bool slowmo = false;
emuInstance->fastForwardToggled = false;
emuInstance->slowmoToggled = false;
while (emuStatus != emuStatus_Exit)
{
MPInterface::Get().Process();
emuInstance->inputProcess();
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
if (emuInstance->hotkeyPressed(HK_FrameLimitToggle)) emit windowLimitFPSChange();
if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause();
if (emuInstance->hotkeyPressed(HK_Reset)) emuReset();
@ -332,22 +337,34 @@ void EmuThread::run()
emit windowUpdate();
winUpdateCount = 0;
}
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emuInstance->fastForwardToggled = !emuInstance->fastForwardToggled;
if (emuInstance->hotkeyPressed(HK_SlowMoToggle)) emuInstance->slowmoToggled = !emuInstance->slowmoToggled;
bool fastforward = emuInstance->hotkeyDown(HK_FastForward);
bool enablefastforward = emuInstance->hotkeyDown(HK_FastForward) | emuInstance->fastForwardToggled;
bool enableslowmo = emuInstance->hotkeyDown(HK_SlowMo) | emuInstance->slowmoToggled;
if (useOpenGL)
{
// when using OpenGL: when toggling fast-forward, change the vsync interval
if (emuInstance->hotkeyPressed(HK_FastForward))
// when using OpenGL: when toggling fast-forward or slowmo, change the vsync interval
if ((enablefastforward || enableslowmo) && !(fastforward || slowmo))
{
emuInstance->setVSyncGL(false);
}
else if (emuInstance->hotkeyReleased(HK_FastForward))
else if (!(enablefastforward || enableslowmo) && (fastforward || slowmo))
{
emuInstance->setVSyncGL(true);
}
}
fastforward = enablefastforward;
slowmo = enableslowmo;
if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS;
else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS;
else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1.0 / 1000.0;
else emuInstance->curFPS = emuInstance->targetFPS;
if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1)
{
DSi* dsi = static_cast<DSi*>(emuInstance->nds);
@ -361,23 +378,19 @@ void EmuThread::run()
emuInstance->audioVolume = volumeLevel * (256.0 / 31.0);
}
if (emuInstance->doAudioSync && !fastforward)
if (emuInstance->doAudioSync && !(fastforward || slowmo))
emuInstance->audioSync();
double frametimeStep = nlines / (60.0 * 263.0);
{
bool limitfps = emuInstance->doLimitFPS && !fastforward;
double practicalFramelimit = limitfps ? frametimeStep : 1.0 / emuInstance->maxFPS;
double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
frameLimitError += practicalFramelimit - (curtime - lastTime);
if (frameLimitError < -practicalFramelimit)
frameLimitError = -practicalFramelimit;
if (frameLimitError > practicalFramelimit)
frameLimitError = practicalFramelimit;
frameLimitError += emuInstance->curFPS - (curtime - lastTime);
if (frameLimitError < -emuInstance->curFPS)
frameLimitError = -emuInstance->curFPS;
if (frameLimitError > emuInstance->curFPS)
frameLimitError = emuInstance->curFPS;
if (round(frameLimitError * 1000.0) > 0.0)
{

View File

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

View File

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

View File

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

View File

@ -6,12 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>337</width>
<height>275</height>
<width>389</width>
<height>356</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -20,6 +20,9 @@
<string>Interface settings - melonDS</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetFixedSize</enum>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
@ -95,32 +98,209 @@
<property name="title">
<string>Framerate </string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Fast-forward limit</string>
<layout class="QGridLayout" name="gridLayout">
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="buddy">
<cstring>spinMaxFPS</cstring>
<property name="verticalSpacing">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinMaxFPS">
<property name="suffix">
<string> FPS</string>
</property>
<property name="minimum">
<number>60</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target FPS</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Fast-Forward</string>
</property>
<property name="buddy">
<cstring>spinFFW</cstring>
</property>
</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>
</layout>
</widget>
@ -128,10 +308,10 @@
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>

View File

@ -780,6 +780,10 @@ void MainWindow::closeEvent(QCloseEvent* event)
Config::Save();
emuInstance->deleteWindow(windowID, false);
// emuInstance may be deleted
// prevent use after free from us
emuInstance = nullptr;
QMainWindow::closeEvent(event);
}
@ -970,7 +974,10 @@ void MainWindow::focusInEvent(QFocusEvent* event)
void MainWindow::focusOutEvent(QFocusEvent* event)
{
emuInstance->audioMute();
// focusOutEvent is called through the window close event handler
// prevent use after free
if (emuInstance)
emuInstance->audioMute();
}
void MainWindow::onAppStateChanged(Qt::ApplicationState state)
@ -1939,8 +1946,9 @@ void MainWindow::onOpenInterfaceSettings()
void MainWindow::onUpdateInterfaceSettings()
{
pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus");
emuInstance->maxFPS = globalCfg.GetInt("MaxFPS");
emuInstance->targetFPS = 1.0 / globalCfg.GetDouble("TargetFPS");
emuInstance->fastForwardFPS = 1.0 / globalCfg.GetDouble("FastForwardFPS");
emuInstance->slowmoFPS = 1.0 / globalCfg.GetDouble("SlowmoFPS");
panel->setMouseHide(globalCfg.GetBool("MouseHide"),
globalCfg.GetInt("MouseHideSeconds")*1000);
}

View File

@ -2,9 +2,22 @@
"default-features": ["qt6"],
"dependencies": [
"sdl2",
{
"name": "sdl2",
"platform": "linux",
"features": [ "alsa" ]
},
"libarchive",
"zstd",
"enet"
"enet",
{
"name": "ecm",
"platform": "linux"
},
{
"name": "libslirp",
"platform": "linux"
}
],
"features": {
"qt6": {
@ -15,6 +28,12 @@
"default-features": false,
"features": ["gui", "png", "thread", "widgets", "opengl", "zstd", "harfbuzz"]
},
{
"name": "qtbase",
"platform": "linux",
"default-features": false,
"features": ["dbus", "xcb", "xkb", "xcb-xlib", "freetype", "fontconfig"]
},
{
"name": "qtbase",
"host": true,
@ -24,6 +43,12 @@
"name": "qtmultimedia",
"default-features": false
},
{
"name": "qtmultimedia",
"platform": "linux",
"features": ["gstreamer"],
"default-features": false
},
"qtsvg"
]
},