Compare commits

...

98 Commits

Author SHA1 Message Date
TheLastRar
42c576cf99 FSUI: More formatting 2025-03-13 10:56:09 +01:00
PCSX2 Bot
1fd26c919b [ci skip] Qt: Update Base Translation. 2025-03-12 20:40:15 -04:00
TheLastRar
c957b558e0 FSUI: Automatic "Swap OK/Cancel" will now swap with switch controllers 2025-03-12 17:22:32 -04:00
JordanTheToaster
ec047c5972 GS: Change GetValidSize warning to DevCon 2025-03-12 17:21:52 -04:00
JordanTheToaster
08388d12d1 Config: Default to higher compression 2025-03-12 17:21:52 -04:00
lightningterror
850aeaf05e Misc: More warning fixes. 2025-03-12 17:20:56 -04:00
chaoticgd
6195f4b40e Debugger: Fix breakpoints and saved addresses lists 2025-03-12 17:20:35 -04:00
chaoticgd
92baf77509 Debugger: Add custom drop indicators and some user interface settings 2025-03-12 17:20:35 -04:00
chaoticgd
ab1cdb4c9d Debugger: Make various improvements to the UI 2025-03-12 17:20:35 -04:00
chaoticgd
4f4ff00ecf Debugger: Add System toolbar with shutdown and reset actions 2025-03-12 17:20:35 -04:00
chaoticgd
6fe97b42c3 Debugger: Allow having multiple dock widgets of the same type 2025-03-12 17:20:35 -04:00
chaoticgd
c9ac4960bc Debugger: Hook up all the debugger widgets again
New event system, context menus, and more.
2025-03-12 17:20:35 -04:00
chaoticgd
e4d7d22e78 Debugger: Hook up breakpoints and stepping again 2025-03-12 17:20:35 -04:00
chaoticgd
eb83cb70ea Debugger: Save and restore the state of the toolbars for each layout 2025-03-12 17:20:35 -04:00
chaoticgd
59210dffa9 Debugger: Add support for multiple UI layouts 2025-03-12 17:20:35 -04:00
chaoticgd
c76cca874b Debugger: Redesign UI based on KDDockWidgets 2025-03-12 17:20:35 -04:00
chaoticgd
d989ce5b44 Deps: Add KDDockWidgets 2025-03-12 17:20:35 -04:00
PCSX2 Bot
fbc95f2c86 [ci skip] PAD: Update to latest controller database. 2025-03-10 21:52:10 +01:00
TheLastRar
9fb8dacadb Input: Fix incorrect string conversion of motor binds 2025-03-09 17:42:31 +01:00
TheLastRar
d332aee542 Qt: Fix vibration binding window 2025-03-09 17:42:31 +01:00
refractionpcsx2
f2c97fc2c3 GS/HW: Tweak offsets for textures using ATN WTO 2025-03-09 16:41:36 +00:00
Ty
5ab84aaa29 CI: Flatten symbols for upload-artifact 2025-03-07 12:47:07 -05:00
JordanTheToaster
9842b11815 Deps: Update to SDL3 3.2.8 2025-03-07 09:58:04 -05:00
Ty
083fb5a1e6 CI: Generate Breakpad symbols for Linux / OSX Windows debugging ease 2025-03-06 17:38:46 -05:00
lightningterror
98cdd3446b GS/HW: Properly check PABE with source alpha for blends that check for PABE. 2025-03-06 19:18:29 +01:00
lightningterror
9bcbf43695 GS/HW: Check if pabe sw is actually enabled for ate second pass.
No need to check for PABE if it's not enabled on sw blend.
In fact on ICO flag is enabled even if ABE is disabled which is pointless.
2025-03-06 19:18:29 +01:00
TheLastRar
e3c988aa8b FileSystem: Improve handling of relative paths in RealPath() 2025-03-06 13:08:32 -05:00
TheLastRar
06be543d32 FileSystem: Don't pass file access mode into GetWin32Path() 2025-03-06 13:08:32 -05:00
Ty
1fd22dcc1c Windows: Make PCSX2 long path aware 2025-03-04 09:14:25 -05:00
refractionpcsx2
e3afdde981 GSDumpRunner: Fix compilation 2025-03-04 13:28:42 +00:00
lightningterror
698df49e5e Qt: Fix -Wsign-compare warnings. 2025-03-04 13:29:13 +01:00
TheLastRar
ff5c90ec5e SDLInputSource: Correct joystick types 2025-03-04 05:07:56 +01:00
TheLastRar
1e075d23b2 Input: Fix warnings 2025-03-04 05:07:56 +01:00
PCSX2 Bot
2b172903b9 [ci skip] Qt: Update Base Translation. 2025-03-04 01:04:44 +01:00
TheLastRar
4654a3ef6c FSUI: Formatting 2025-03-04 00:34:14 +01:00
Ty
9996061f74 SIO: Bump savestate warning from 1 hour to 2 hours 2025-03-03 12:19:09 -05:00
Ty
247a4c40d1 SIO: Remove a debugging include I somehow missed 2025-03-03 12:19:09 -05:00
PCSX2 Bot
1ffbdd9c08 [ci skip] PAD: Update to latest controller database. 2025-03-03 17:30:19 +01:00
TheLastRar
f67c0cbd2e Input: Fix migration of input profiles 2025-03-03 13:38:55 +01:00
PCSX2 Bot
ff7cc0867b [ci skip] Qt: Update Base Translation. 2025-03-03 01:36:07 +01:00
TheLastRar
ac1a6d3348 Deps: Update to SDL3 (#12311)
Co-authored-by: TheTechnician27 <TheTechnician27@users.noreply.github.com>
2025-03-02 18:04:19 -05:00
TellowKrinkle
582bba6c91 microVU: Accurate CLIP 2025-03-02 18:19:52 +00:00
TellowKrinkle
aaf156478e Interpreter: Accurate CLIP 2025-03-02 18:19:52 +00:00
TellowKrinkle
0539c177ab x86emitter: Add pblend 2025-03-02 18:19:52 +00:00
TellowKrinkle
fb1323b72f MicroVU: Declare constants inline 2025-03-02 18:19:52 +00:00
TellowKrinkle
dc557dd0e5 Interpreter: Merge broadcast min/max into one implementation 2025-03-02 18:19:08 +00:00
TellowKrinkle
2d0cfc9c2c Interpreter: Merge MAC ops into a few template functions 2025-03-02 18:19:08 +00:00
TellowKrinkle
625a25cd50 Interpreter: Merge broadcast ops into one implementation each 2025-03-02 18:19:08 +00:00
TellowKrinkle
b8a29d1cd8 Interpreter: Accurate FTOI
Plus some ITOF cleanup
2025-03-02 18:17:36 +00:00
TellowKrinkle
0fabdf9a01 Interpreter: Accurate ABS 2025-03-02 18:17:36 +00:00
refractionpcsx2
9c3ae795c8 COP2/Int: Propagate CLIP_FLAG writes to the VU0.clipflag variable for use in COP2
This value was being updated then COP2 running VCLIP would have the wrong original clip flag value to work from.
2025-03-02 18:17:05 +00:00
refractionpcsx2
de26226fa1 Core: Delete constant regs when flushing to interpreter 2025-03-02 18:17:05 +00:00
KamFretoZ
121920c074 FSUI: Add Themes 2025-03-02 11:48:29 -05:00
TellowKrinkle
05e19470b2 FileSystem: Don't leak on directory scan cancel
Fixes: 7587581d1f
2025-03-02 09:41:51 -05:00
TheLastRar
b6680e4aca FSUI: Formatting 2025-03-02 09:36:07 -05:00
TheLastRar
f9d70af841 FSUI: Auto detect when to use circle as confirm 2025-03-02 09:36:07 -05:00
TellowKrinkle
7587581d1f GameList: Allow recursive scans to be cancelled 2025-03-02 04:20:01 +01:00
PCSX2 Bot
8f19976c10 [ci skip] Qt: Update Base Translation. 2025-03-02 01:43:09 +01:00
GovanifY
8567d68433 VMManager: initialize PINE with config-provided slot
Sten broke it during the port to Qt...
2025-03-02 00:01:18 +01:00
Glebux
6542301566 Input/PAD: Make macro chords work 2025-03-01 14:38:00 -05:00
refractionpcsx2
a359f77cf6 GameDB/Link: Fix validation limit for Half Pixel Offset to allow new option 2025-03-01 02:01:46 +00:00
PCSX2 Bot
4c9a81f3d8 [ci skip] Qt: Update Base Translation. 2025-02-28 19:54:36 -05:00
refractionpcsx2
9234b493a3 Testing further tweaks to bring it closer to SW 2025-02-28 21:59:19 +00:00
refractionpcsx2
f84425b67c GS/HW: Add new HPO - Align to Native With Texture Offset 2025-02-28 21:59:19 +00:00
PCSX2 Bot
8a0c1874dd [ci skip] Qt: Update Base Translation. 2025-02-27 01:05:14 +01:00
Light
fa23628ae2 Qt: Allow recording on game boot 2025-02-26 17:17:39 -05:00
Loy2up
8a594e673d Debugger: Fix 8 byte searches (#12362) 2025-02-26 14:41:49 -05:00
Loy2up
92b9390c51 Debugger: Set default breakpoint size to 4 2025-02-25 19:07:19 -05:00
PCSX2 Bot
c5c5b2a7b9 [ci skip] Qt: Update Base Translation. 2025-02-26 01:06:43 +01:00
refractionpcsx2
32a9d0e48b GameDB: Also add Tekken 5 CRC to Taiko No Tatsujin 9 2025-02-25 18:22:38 +01:00
refractionpcsx2
80a961bb25 GameDB: Add Tekken 5 CRC (Yes really) To Tales of the Abyss 2025-02-25 18:22:38 +01:00
TheLastRar
d4e227286e FSUI: Correct description of the "Swap OK/Cancel" option 2025-02-25 10:00:18 -05:00
github-actions[bot]
ba705c8c24 Qt: Update Base Translation (#12354)
Co-authored-by: PCSX2 Bot <PCSX2Bot@users.noreply.github.com>
2025-02-24 19:11:57 -05:00
dependabot[bot]
b6ae4b173e CI: Update dependencies in /.github/workflows/scripts/releases/generate-release-notes (#12315)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-24 17:54:41 -05:00
TJnotJT
23a28be346 GS/UI: Rename dumping variables and UI labels to be more consistent/clear. 2025-02-24 17:53:01 -05:00
TJnotJT
a0e24dd36f UI: Add debug UI options for new dumping options. 2025-02-24 17:53:01 -05:00
TheLastRar
a2cde5e17b FSUI: Add a config option to swap OK/Cancel within BPM 2025-02-24 17:45:46 -05:00
JordanTheToaster
ecc46e9294 Deps: Update ZSTD to 1.5.7 2025-02-24 17:44:22 -05:00
dreamsyntax
20b1190d47 Achievements: Fix leaderboard timers persisting
Removes setting the leaderboard timer to active on receiving an update
event. This fixes having multiple timers stuck on the screen.
2025-02-24 14:05:03 -05:00
Silent
29b736bcf7 GameDB: Add TOCA Race Driver 3 memcard filters 2025-02-24 12:04:05 -05:00
Silent
a48bc76ca6 GameDB: Add NFS Undercover memcard filters
Checks from NFS Carbon (incl. a bugged check for MW BE)
are in the code, but unused.
2025-02-24 12:04:05 -05:00
Silent
305c01cdfa GameDB: Add NFS ProStreet memcard filters 2025-02-24 12:04:05 -05:00
Silent
88bbdf4696 GameDB: Add NFS Carbon memcard filters 2025-02-24 12:04:05 -05:00
Silent
afc11279a9 GameDB: Improve NFS Most Wanted memcard filters 2025-02-24 12:04:05 -05:00
Silent
a3fb2a84d5 GameDB: SCUS-21494 doesn't exist 2025-02-24 12:04:05 -05:00
Ty
4db23e6677 Debugger Assembler: BC1(t|f) 24 bit immediates to 16 bit immediates 2025-02-24 11:39:30 -05:00
PCSX2 Bot
5dd36a7969 [ci skip] PAD: Update to latest controller database. 2025-02-24 17:02:04 +01:00
RedPanda4552
35a3d0027e Memcard: Apply filtering when checking all possible memcard options
Prevents log being flooded with warnings when large folder memcards exceed 8 MB size
2025-02-24 16:08:00 +01:00
refractionpcsx2
02789ebd86 GS/HW: Increase Merge Sprite paving sensitivity to avoid mismerges 2025-02-23 03:33:39 +00:00
JordanTheToaster
dfd1846b93 GameDB: Various fixes 2025-02-23 01:12:43 +00:00
refractionpcsx2
872205abc6 GS/HW: Maintain scale on subsequent downscale draws 2025-02-23 01:12:07 +00:00
PCSX2 Bot
c52cebd20a [ci skip] Qt: Update Base Translation. 2025-02-22 01:04:56 +01:00
GovanifY
f449b54f87 CI: enable wayland by default 2025-02-21 12:28:28 +01:00
GovanifY
ffcb6e2f6f QT/MainWindow: disable native window rendering in wayland
This is a workaround for QTBUG-133919
2025-02-21 12:28:28 +01:00
PCSX2 Bot
5daa1aa115 [ci skip] Qt: Update Base Translation. 2025-02-21 01:09:05 +01:00
GovanifY
1dc009f752 pcsx2/SIO: correctly detect whether format status of folder mcd 2025-02-20 16:00:40 +01:00
GovanifY
009b4ff5e7 QT/Settings: ensure that a memory card is formatted before conversion 2025-02-20 16:00:40 +01:00
PCSX2 Bot
f1a947af92 [ci skip] Qt: Update Base Translation. 2025-02-19 21:00:23 -05:00
183 changed files with 15665 additions and 7801 deletions

View File

@@ -0,0 +1,13 @@
diff --git a/src/core/indicators/ClassicDropIndicatorOverlay.h b/src/core/indicators/ClassicDropIndicatorOverlay.h
index 2dfb9718a..9b01f002e 100644
--- a/src/core/indicators/ClassicDropIndicatorOverlay.h
+++ b/src/core/indicators/ClassicDropIndicatorOverlay.h
@@ -11,7 +11,7 @@
#pragma once
-#include "core/DropIndicatorOverlay.h"
+#include <kddockwidgets/core/DropIndicatorOverlay.h>
namespace KDDockWidgets {

View File

@@ -187,17 +187,6 @@ echo "Generating AppStream metainfo..."
mkdir -p "$OUTDIR/usr/share/metainfo"
"$SCRIPTDIR/generate-metainfo.sh" "$OUTDIR/usr/share/metainfo/net.pcsx2.PCSX2.appdata.xml"
# Copy in AppRun hooks.
# Unfortunately linuxdeploy is a bit lame and doesn't let us provide our own AppRun hooks, instead
# they have to come from plugins.. and screw writing one of those just to disable Wayland.
echo "Copying AppRun hooks..."
mkdir -p "$OUTDIR/apprun-hooks"
for hookpath in "$SCRIPTDIR/apprun-hooks"/*; do
hookname=$(basename "$hookpath")
cp -v "$hookpath" "$OUTDIR/apprun-hooks/$hookname"
sed -i -e 's/exec /source "$this_dir"\/apprun-hooks\/"'"$hookname"'"\nexec /' "$OUTDIR/AppRun"
done
echo "Generating AppImage..."
GIT_VERSION=$(git tag --points-at HEAD)

View File

@@ -1,9 +0,0 @@
if [[ -z "$I_WANT_A_BROKEN_WAYLAND_UI" ]]; then
echo "Forcing X11 instead of Wayland, due to various protocol limitations"
echo "and Qt issues. If you want to use Wayland, launch PCSX2 with"
echo "I_WANT_A_BROKEN_WAYLAND_UI=YES set."
export QT_QPA_PLATFORM=xcb
else
echo "Wayland is not being disabled. Do not complain when things break."
fi

View File

@@ -19,9 +19,10 @@ LIBJPEG=9f
LIBPNG=1.6.45
LIBWEBP=1.5.0
LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
SDL=SDL2-2.30.12
SDL=SDL3-3.2.8
QT=6.8.2
ZSTD=1.5.6
ZSTD=1.5.7
KDDOCKWIDGETS=2.2.1
SHADERC=2024.1
SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -37,8 +38,8 @@ fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-$LIBPNG.tar.xz
7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz
0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz
ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea $SDL.tar.gz
8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 zstd-$ZSTD.tar.gz
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
012043ce6d411e6e8a91fdc4e05e6bedcfa10fcb1347d3c33908f7fdd10dfe05 qtbase-everywhere-src-$QT.tar.xz
d2a1bbb84707b8a0aec29227b170be00f04383fbf2361943596d09e7e443c8e1 qtimageformats-everywhere-src-$QT.tar.xz
aa2579f21ca66d19cbcf31d87e9067e07932635d36869c8239d4decd0a9dc1fa qtsvg-everywhere-src-$QT.tar.xz
@@ -49,6 +50,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528 shaderc-$SHADE
aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f shaderc-glslang-$SHADERC_GLSLANG.tar.gz
5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697 shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47 shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
EOF
curl -L \
@@ -68,7 +70,8 @@ curl -L \
-o "shaderc-$SHADERC.tar.gz" "https://github.com/google/shaderc/archive/refs/tags/v$SHADERC.tar.gz" \
-o "shaderc-glslang-$SHADERC_GLSLANG.tar.gz" "https://github.com/KhronosGroup/glslang/archive/$SHADERC_GLSLANG.tar.gz" \
-o "shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Headers/archive/$SHADERC_SPIRVHEADERS.tar.gz" \
-o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz"
-o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" \
-o "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" "https://github.com/KDAB/KDDockWidgets/archive/v$KDDOCKWIDGETS.tar.gz"
shasum -a 256 --check SHASUMS
@@ -233,6 +236,16 @@ cmake --build . --parallel
ninja install
cd ../../
echo "Building KDDockWidgets..."
rm -fr "KDDockWidgets-$KDDOCKWIDGETS"
tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz"
cd "KDDockWidgets-$KDDOCKWIDGETS"
patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch"
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja
cmake --build build --parallel
ninja -C build install
cd ..
echo "Building shaderc..."
rm -fr "shaderc-$SHADERC"
tar xf "shaderc-$SHADERC.tar.gz"

View File

@@ -1,5 +1,5 @@
{
"name": "sdl2",
"name": "sdl3",
"buildsystem": "cmake-ninja",
"builddir": true,
"config-opts": [
@@ -14,8 +14,8 @@
"sources": [
{
"type": "archive",
"url": "https://libsdl.org/release/SDL2-2.30.12.tar.gz",
"sha256": "ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea"
"url": "https://libsdl.org/release/SDL3-3.2.8.tar.gz",
"sha256": "13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03"
}
],
"cleanup": [

View File

@@ -0,0 +1,32 @@
{
"name": "kddockwidgets",
"buildsystem": "cmake-ninja",
"builddir": true,
"config-opts": [
"-DKDDockWidgets_QT6=true",
"-DKDDockWidgets_EXAMPLES=false",
"-DKDDockWidgets_FRONTENDS=qtwidgets"
],
"build-options": {
"strip": true
},
"sources": [
{
"type": "git",
"url": "https://github.com/KDAB/KDDockWidgets.git",
"tag": "v2.2.1",
"commit": "3aaccddc00a11a643e0959a24677838993de15ac",
"disable-submodules": true
},
{
"type": "patch",
"path": "../../../common/kddockwidgets-dodgy-include.patch"
}
],
"cleanup": [
"/share/doc/KDDockWidgets-qt6",
"/mkspecs/modules/qt_KDDockWidgets.pri",
"/lib/cmake",
"/include"
]
}

View File

@@ -19,16 +19,17 @@
"--device=all",
"--share=network",
"--share=ipc",
"--socket=x11",
"--socket=wayland",
"--socket=fallback-x11",
"--socket=pulseaudio",
"--talk-name=org.freedesktop.ScreenSaver",
"--env=QT_QPA_PLATFORM=xcb"
"--talk-name=org.freedesktop.ScreenSaver"
],
"modules": [
"modules/10-libpcap.json",
"modules/20-sdl2.json",
"modules/20-sdl3.json",
"modules/21-libbacktrace.json",
"modules/22-shaderc.json",
"modules/23-kddockwidgets.json",
{
"name": "pcsx2",
"buildsystem": "cmake-ninja",

View File

@@ -40,8 +40,8 @@ fi
FREETYPE=2.13.3
HARFBUZZ=10.0.1
SDL=SDL2-2.30.12
ZSTD=1.5.6
SDL=SDL3-3.2.8
ZSTD=1.5.7
LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
LIBPNG=1.6.45
LIBJPEG=9f
@@ -49,6 +49,7 @@ LIBWEBP=1.5.0
FFMPEG=6.0
MOLTENVK=1.2.9
QT=6.7.2
KDDOCKWIDGETS=2.2.1
SHADERC=2024.1
SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -76,8 +77,8 @@ CMAKE_ARCH_UNIVERSAL=-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
cat > SHASUMS <<EOF
0550350666d427c74daeb85d5ac7bb353acba5f76956395995311a9c6f063289 freetype-$FREETYPE.tar.xz
e7358ea86fe10fb9261931af6f010d4358dac64f7074420ca9bc94aae2bdd542 harfbuzz-$HARFBUZZ.tar.gz
ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea $SDL.tar.gz
8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 zstd-$ZSTD.tar.gz
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-$LIBPNG.tar.xz
7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz
@@ -93,6 +94,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528 shaderc-$SHADE
aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f shaderc-glslang-$SHADERC_GLSLANG.tar.gz
5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697 shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47 shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
EOF
curl -C - -L \
@@ -114,7 +116,8 @@ curl -C - -L \
-o "shaderc-$SHADERC.tar.gz" "https://github.com/google/shaderc/archive/refs/tags/v$SHADERC.tar.gz" \
-o "shaderc-glslang-$SHADERC_GLSLANG.tar.gz" "https://github.com/KhronosGroup/glslang/archive/$SHADERC_GLSLANG.tar.gz" \
-o "shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Headers/archive/$SHADERC_SPIRVHEADERS.tar.gz" \
-o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz"
-o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" \
-o "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" "https://github.com/KDAB/KDDockWidgets/archive/v$KDDOCKWIDGETS.tar.gz"
shasum -a 256 --check SHASUMS
@@ -366,6 +369,16 @@ make "-j$NPROCS"
make install
cd ../..
echo "Building KDDockWidgets..."
rm -fr "KDDockWidgets-$KDDOCKWIDGETS"
tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz"
cd "KDDockWidgets-$KDDOCKWIDGETS"
patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch"
cmake "${CMAKE_COMMON[@]}" "$CMAKE_ARCH_UNIVERSAL" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build
cmake --build build --parallel
cmake --install build
cd ..
echo "Cleaning up..."
cd ..
rm -rf deps-build

View File

@@ -22,8 +22,8 @@ fi
FREETYPE=2.13.3
HARFBUZZ=10.0.1
SDL=SDL2-2.30.12
ZSTD=1.5.6
SDL=SDL3-3.2.8
ZSTD=1.5.7
LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
LIBPNG=1.6.45
LIBJPEG=9f
@@ -31,6 +31,7 @@ LIBWEBP=1.5.0
FFMPEG=6.0
MOLTENVK=1.2.9
QT=6.7.2
KDDOCKWIDGETS=2.2.1
SHADERC=2024.1
SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -56,8 +57,8 @@ CMAKE_COMMON=(
cat > SHASUMS <<EOF
0550350666d427c74daeb85d5ac7bb353acba5f76956395995311a9c6f063289 freetype-$FREETYPE.tar.xz
e7358ea86fe10fb9261931af6f010d4358dac64f7074420ca9bc94aae2bdd542 harfbuzz-$HARFBUZZ.tar.gz
ac356ea55e8b9dd0b2d1fa27da40ef7e238267ccf9324704850d5d47375b48ea $SDL.tar.gz
8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 zstd-$ZSTD.tar.gz
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz
926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370 libpng-$LIBPNG.tar.xz
7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz
@@ -73,6 +74,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528 shaderc-$SHADE
aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f shaderc-glslang-$SHADERC_GLSLANG.tar.gz
5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697 shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47 shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
EOF
curl -L \
@@ -94,7 +96,8 @@ curl -L \
-o "shaderc-$SHADERC.tar.gz" "https://github.com/google/shaderc/archive/refs/tags/v$SHADERC.tar.gz" \
-o "shaderc-glslang-$SHADERC_GLSLANG.tar.gz" "https://github.com/KhronosGroup/glslang/archive/$SHADERC_GLSLANG.tar.gz" \
-o "shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Headers/archive/$SHADERC_SPIRVHEADERS.tar.gz" \
-o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz"
-o "shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Tools/archive/$SHADERC_SPIRVTOOLS.tar.gz" \
-o "KDDockWidgets-$KDDOCKWIDGETS.tar.gz" "https://github.com/KDAB/KDDockWidgets/archive/v$KDDOCKWIDGETS.tar.gz"
shasum -a 256 --check SHASUMS
@@ -324,6 +327,16 @@ make "-j$NPROCS"
make install
cd ../..
echo "Building KDDockWidgets..."
rm -fr "KDDockWidgets-$KDDOCKWIDGETS"
tar xf "KDDockWidgets-$KDDOCKWIDGETS.tar.gz"
cd "KDDockWidgets-$KDDOCKWIDGETS"
patch -p1 < "$SCRIPTDIR/../common/kddockwidgets-dodgy-include.patch"
cmake "${CMAKE_COMMON[@]}" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build
cmake --build build --parallel
cmake --install build
cd ..
echo "Cleaning up..."
cd ..
rm -rf deps-build

View File

@@ -1,22 +1,33 @@
{
"name": "generate-release-notes",
"version": "1.0.0",
"lockfileVersion": 1,
"lockfileVersion": 3,
"requires": true,
"dependencies": {
"@octokit/auth-token": {
"packages": {
"": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@octokit/plugin-retry": "^3.0.9",
"@octokit/plugin-throttling": "^3.5.2",
"@octokit/rest": "^21.1.1"
}
},
"node_modules/@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"requires": {
"peer": true,
"dependencies": {
"@octokit/types": "^6.0.3"
}
},
"@octokit/core": {
"node_modules/@octokit/core": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz",
"integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==",
"requires": {
"peer": true,
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.0",
@@ -26,76 +37,60 @@
"universal-user-agent": "^6.0.0"
}
},
"@octokit/endpoint": {
"node_modules/@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"requires": {
"peer": true,
"dependencies": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/graphql": {
"node_modules/@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"requires": {
"peer": true,
"dependencies": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"node_modules/@octokit/openapi-types": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.1.0.tgz",
"integrity": "sha512-dWZfYvCCdjZzDYA3lIAMF72Q0jld8xidqCq5Ryw09eBJXZdcM6he0vWBTvw/b5UnGYqexxOyHWgfrsTlUJL3Gw=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.16.9",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.9.tgz",
"integrity": "sha512-gfSCMgz5scFKsR0dW4jaYsDJVt/UwCHp4dF7sHlmSekZvwzvLiOAGZ4MQkEsL5DW9hIk2W+UQkYZMTA1b6Wsqw==",
"requires": {
"@octokit/types": "^6.33.0"
}
},
"@octokit/plugin-request-log": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
"integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA=="
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.12.1.tgz",
"integrity": "sha512-0nY3htfl6x9UkPcqv8pm9vOC/bTA7f4IMDWln13neHRdNWQvOQgZ9fRxK7BAc74rye4yVINEFi9Yb9rnGUvosA==",
"requires": {
"@octokit/types": "^6.33.0",
"deprecation": "^2.3.1"
}
},
"@octokit/plugin-retry": {
"node_modules/@octokit/plugin-retry": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-3.0.9.tgz",
"integrity": "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ==",
"requires": {
"dependencies": {
"@octokit/types": "^6.0.3",
"bottleneck": "^2.15.3"
}
},
"@octokit/plugin-throttling": {
"node_modules/@octokit/plugin-throttling": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.5.2.tgz",
"integrity": "sha512-Eu7kfJxU8vmHqWGNszWpg+GVp2tnAfax3XQV5CkYPEE69C+KvInJXW9WajgSeW+cxYe0UVdouzCtcreGNuJo7A==",
"requires": {
"dependencies": {
"@octokit/types": "^6.0.1",
"bottleneck": "^2.15.3"
},
"peerDependencies": {
"@octokit/core": "^3.5.0"
}
},
"@octokit/request": {
"node_modules/@octokit/request": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz",
"integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==",
"requires": {
"peer": true,
"dependencies": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
@@ -104,99 +99,295 @@
"universal-user-agent": "^6.0.0"
}
},
"@octokit/request-error": {
"node_modules/@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"requires": {
"peer": true,
"dependencies": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/rest": {
"version": "18.12.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz",
"integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==",
"requires": {
"@octokit/core": "^3.5.1",
"@octokit/plugin-paginate-rest": "^2.16.8",
"@octokit/plugin-request-log": "^1.0.4",
"@octokit/plugin-rest-endpoint-methods": "^5.12.0"
"node_modules/@octokit/rest": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz",
"integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==",
"license": "MIT",
"dependencies": {
"@octokit/core": "^6.1.4",
"@octokit/plugin-paginate-rest": "^11.4.2",
"@octokit/plugin-request-log": "^5.3.1",
"@octokit/plugin-rest-endpoint-methods": "^13.3.0"
},
"engines": {
"node": ">= 18"
}
},
"@octokit/types": {
"node_modules/@octokit/rest/node_modules/@octokit/auth-token": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
"integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==",
"license": "MIT",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/core": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz",
"integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==",
"license": "MIT",
"dependencies": {
"@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.1.2",
"@octokit/request": "^9.2.1",
"@octokit/request-error": "^6.1.7",
"@octokit/types": "^13.6.2",
"before-after-hook": "^3.0.2",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/endpoint": {
"version": "10.1.3",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
"integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.6.2",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/graphql": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz",
"integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==",
"license": "MIT",
"dependencies": {
"@octokit/request": "^9.2.2",
"@octokit/types": "^13.8.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
"version": "23.0.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
"integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
"license": "MIT"
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
"version": "11.4.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.2.tgz",
"integrity": "sha512-BXJ7XPCTDXFF+wxcg/zscfgw2O/iDPtNSkwwR1W1W5c4Mb3zav/M2XvxQ23nVmKj7jpweB4g8viMeCQdm7LMVA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.7.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz",
"integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "13.3.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.1.tgz",
"integrity": "sha512-o8uOBdsyR+WR8MK9Cco8dCgvG13H1RlM1nWnK/W7TEACQBFux/vPREgKucxUfuDQ5yi1T3hGf4C5ZmZXAERgwQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.8.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
"integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
"license": "MIT",
"dependencies": {
"@octokit/endpoint": "^10.1.3",
"@octokit/request-error": "^6.1.7",
"@octokit/types": "^13.6.2",
"fast-content-type-parse": "^2.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/request-error": {
"version": "6.1.7",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz",
"integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.6.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/rest/node_modules/@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^23.0.1"
}
},
"node_modules/@octokit/rest/node_modules/before-after-hook": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==",
"license": "Apache-2.0"
},
"node_modules/@octokit/rest/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==",
"license": "ISC"
},
"node_modules/@octokit/types": {
"version": "6.33.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.33.0.tgz",
"integrity": "sha512-0zffZ048M0UhthyPXQHLz4038Ak46nMWZXkzlXvXB/M/L1jYPBceq4iZj4qjKVrvveaJrrgKdJ9+3yUuITfcCw==",
"requires": {
"dependencies": {
"@octokit/openapi-types": "^11.1.0"
}
},
"before-after-hook": {
"node_modules/before-after-hook": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==",
"peer": true
},
"bottleneck": {
"node_modules/bottleneck": {
"version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
},
"deprecation": {
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
"peer": true
},
"is-plain-object": {
"node_modules/fast-content-type-parse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz",
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node-fetch": {
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"peer": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"once": {
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"peer": true,
"dependencies": {
"wrappy": "1"
}
},
"tr46": {
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"peer": true
},
"universal-user-agent": {
"node_modules/universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==",
"peer": true
},
"webidl-conversions": {
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
"peer": true
},
"whatwg-url": {
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"peer": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wrappy": {
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"peer": true
}
}
}

View File

@@ -12,6 +12,6 @@
"dependencies": {
"@octokit/plugin-retry": "^3.0.9",
"@octokit/plugin-throttling": "^3.5.2",
"@octokit/rest": "^18.12.0"
"@octokit/rest": "^21.1.1"
}
}

View File

@@ -49,11 +49,12 @@ set LIBPNG=1645
set LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
set QT=6.8.2
set QTMINOR=6.8
set SDL=SDL2-2.30.12
set SDL=SDL3-3.2.8
set WEBP=1.5.0
set ZLIB=1.3.1
set ZLIBSHORT=131
set ZSTD=1.5.6
set ZSTD=1.5.7
set KDDOCKWIDGETS=2.2.1
set SHADERC=2024.1
set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -66,15 +67,15 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
call :downloadfile "jpegsr%LIBJPEG%.zip" https://ijg.org/files/jpegsr%LIBJPEG%.zip 6255da8c89e09d694e6800688c76145eb6870a76ac0d36c74fccd61b3940aafa || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" 7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c || goto error
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/%LZ4%.zip" 0c33119688d6b180c7e760b0acd70059222389cfd581632623784bee27e51a31 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" aa2808d0f2dc6b383c6689bf6d166e2de62db4d58be989e4b052acb31df0fba3 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 7f8ff5c8246db4145301bc122601a5f8cef25ee2c326eddb3e88668849c61ddf || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 44087aec0caa4aa81437e787917d29d97536484a682a5d51ec035878e57c0b5c || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 83c72b5dfad04854acf61d592e3f9cdc2ed894779aab8d0470d966715266caaf || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" 144d55e4d199793a76c53f19872633a79aec0314039f6f99b6a10b5be7a78fbf || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" 102539447c1c76d206f24bcca2c911270cf53991548d9c3d7d0d01855f651e3b || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 33ccac9f99a357ffd83cb2d7179a0c0ffcba85a14d23d86619d5dc9721ded42f || goto error
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 3b1c3b46e416d36931efd34663122d7f51b550c87f74de2d38249516fe7d8be5 || goto error
call :downloadfile "zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" https://github.com/facebook/zstd/commit/fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch 8df152f4969b308546306c074628de761f0b80265de7de534e3822fab22d7535 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v2.2.1.zip" 78b5e242bf47476e150175b7de934ab84069459e151beb2d5ce84fd067138aa5 || goto error
call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" 6c9f42ed6bf42750f5369b089909abfdcf0101488b4a1f41116d5159d00af8e7 || goto error
call :downloadfile "shaderc-glslang-%SHADERC_GLSLANG%.zip" "https://github.com/KhronosGroup/glslang/archive/%SHADERC_GLSLANG%.zip" 03ad8a6fa987af4653d0cfe6bdaed41bcf617f1366a151fb1574da75950cd3e8 || goto error
@@ -159,7 +160,6 @@ echo Building Zstandard...
rmdir /S /Q "zstd-%ZSTD%"
%SEVENZIP% x "-x^!zstd-%ZSTD%\tests\cli-tests\bin" "zstd-%ZSTD%.zip" || goto error
cd "zstd-%ZSTD%"
%PATCH% -p1 < "..\zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" || goto error
cmake %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DZSTD_BUILD_SHARED=ON -DZSTD_BUILD_STATIC=OFF -DZSTD_BUILD_PROGRAMS=OFF -B build -G Ninja build/cmake
cmake --build build --parallel || goto error
ninja -C build install || goto error
@@ -181,7 +181,7 @@ cd "%SDL%" || goto error
cmake -B build %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
copy build\SDL2.pdb "%INSTALLDIR%\bin" || goto error
copy build\SDL3.pdb "%INSTALLDIR%\bin" || goto error
cd .. || goto error
if %DEBUG%==1 (
@@ -243,6 +243,16 @@ cmake --build . --parallel || goto error
ninja install || goto error
cd ..\.. || goto error
echo "Building KDDockWidgets..."
rmdir /S /Q "KDDockWidgets-%KDDOCKWIDGETS%"
%SEVENZIP% x "KDDockWidgets-%KDDOCKWIDGETS%.zip" || goto error
cd "KDDockWidgets-%KDDOCKWIDGETS%" || goto error
%PATCH% -p1 < "%SCRIPTDIR%\..\common\kddockwidgets-dodgy-include.patch" || goto error
cmake %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
cd .. || goto error
echo Building shaderc...
rmdir /S /Q "shaderc-%SHADERC%"
%SEVENZIP% x "shaderc-%SHADERC%.zip" || goto error

View File

@@ -47,11 +47,12 @@ set LIBPNG=1645
set LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
set QT=6.8.2
set QTMINOR=6.8
set SDL=SDL2-2.30.12
set SDL=SDL3-3.2.8
set WEBP=1.5.0
set ZLIB=1.3.1
set ZLIBSHORT=131
set ZSTD=1.5.6
set ZSTD=1.5.7
set KDDOCKWIDGETS=2.2.1
set SHADERC=2024.1
set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -64,15 +65,15 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
call :downloadfile "jpegsr%LIBJPEG%.zip" https://ijg.org/files/jpegsr%LIBJPEG%.zip 6255da8c89e09d694e6800688c76145eb6870a76ac0d36c74fccd61b3940aafa || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" 7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c || goto error
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/%LZ4%.zip" 0c33119688d6b180c7e760b0acd70059222389cfd581632623784bee27e51a31 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" aa2808d0f2dc6b383c6689bf6d166e2de62db4d58be989e4b052acb31df0fba3 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 7f8ff5c8246db4145301bc122601a5f8cef25ee2c326eddb3e88668849c61ddf || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 44087aec0caa4aa81437e787917d29d97536484a682a5d51ec035878e57c0b5c || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 83c72b5dfad04854acf61d592e3f9cdc2ed894779aab8d0470d966715266caaf || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" 144d55e4d199793a76c53f19872633a79aec0314039f6f99b6a10b5be7a78fbf || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" 102539447c1c76d206f24bcca2c911270cf53991548d9c3d7d0d01855f651e3b || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 33ccac9f99a357ffd83cb2d7179a0c0ffcba85a14d23d86619d5dc9721ded42f || goto error
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 3b1c3b46e416d36931efd34663122d7f51b550c87f74de2d38249516fe7d8be5 || goto error
call :downloadfile "zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" https://github.com/facebook/zstd/commit/fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch 8df152f4969b308546306c074628de761f0b80265de7de534e3822fab22d7535 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v2.2.1.zip" 78b5e242bf47476e150175b7de934ab84069459e151beb2d5ce84fd067138aa5 || goto error
call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" 6c9f42ed6bf42750f5369b089909abfdcf0101488b4a1f41116d5159d00af8e7 || goto error
call :downloadfile "shaderc-glslang-%SHADERC_GLSLANG%.zip" "https://github.com/KhronosGroup/glslang/archive/%SHADERC_GLSLANG%.zip" 03ad8a6fa987af4653d0cfe6bdaed41bcf617f1366a151fb1574da75950cd3e8 || goto error
@@ -156,7 +157,6 @@ echo Building Zstandard...
rmdir /S /Q "zstd-%ZSTD%"
%SEVENZIP% x "-x^!zstd-%ZSTD%\tests\cli-tests\bin" "zstd-%ZSTD%.zip" || goto error
cd "zstd-%ZSTD%"
%PATCH% -p1 < "..\zstd-fd5f8106a58601a963ee816e6a57aa7c61fafc53.patch" || goto error
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DZSTD_BUILD_SHARED=ON -DZSTD_BUILD_STATIC=OFF -DZSTD_BUILD_PROGRAMS=OFF -B build -G Ninja build/cmake
cmake --build build --parallel || goto error
ninja -C build install || goto error
@@ -178,7 +178,7 @@ cd "%SDL%" || goto error
cmake -B build -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
copy build\SDL2.pdb "%INSTALLDIR%\bin" || goto error
copy build\SDL3.pdb "%INSTALLDIR%\bin" || goto error
cd .. || goto error
if %DEBUG%==1 (
@@ -247,6 +247,16 @@ cmake --build . --parallel || goto error
ninja install || goto error
cd ..\.. || goto error
echo "Building KDDockWidgets..."
rmdir /S /Q "KDDockWidgets-%KDDOCKWIDGETS%"
%SEVENZIP% x "KDDockWidgets-%KDDOCKWIDGETS%.zip" || goto error
cd "KDDockWidgets-%KDDOCKWIDGETS%" || goto error
%PATCH% -p1 < "%SCRIPTDIR%\..\common\kddockwidgets-dodgy-include.patch" || goto error
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DKDDockWidgets_QT6=true -DKDDockWidgets_EXAMPLES=false -DKDDockWidgets_FRONTENDS=qtwidgets -B build -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
cd .. || goto error
echo Building shaderc...
rmdir /S /Q "shaderc-%SHADERC%"
%SEVENZIP% x "shaderc-%SHADERC%.zip" || goto error

View File

@@ -167,8 +167,27 @@ jobs:
!./bin/**/*.pdb
!./bin/**/*.lib
- name: Install the Breakpad Symbol Generator
uses: baptiste0928/cargo-install@v3
with:
crate: dump_syms
- name: Generate Breakpad Symbols # Also flatten pdbs to a 'symbols' directory for upload-artifact
shell: pwsh
run: |
mkdir -Force symbols
Get-ChildItem -Path ./bin -Recurse -File | Where-Object {
($_.Extension -eq ".exe" -or $_.Extension -eq ".pdb") -and ($_.Name -notmatch "updater")
} | ForEach-Object {
& dump_syms $_.FullName >> symbols/pcsx2-qt.bpsym
}
Get-ChildItem -Path ./bin -Recurse -Filter "*.pdb" | ForEach-Object {
Copy-Item $_.FullName -Destination symbols/
}
- name: Upload artifact - with symbols
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}-symbols
path: ./bin/**/*.pdb
path: |
./symbols

View File

@@ -1270,6 +1270,7 @@ SCAJ-10015:
region: "NTSC-Unk"
gsHWFixes:
alignSprite: 1 # Fixes vertical lines.
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
SCAJ-20001:
name: "Ratchet & Clank"
region: "NTSC-Unk"
@@ -2015,7 +2016,6 @@ SCAJ-20132:
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting characters.
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
SCAJ-20133:
name: "Kagero 2 - Dark Illusion"
@@ -2237,6 +2237,7 @@ SCAJ-20163:
gsHWFixes:
halfPixelOffset: 2 # Fixes ghosting.
autoFlush: 2 # Fixes post lighting.
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
SCAJ-20164:
name: "Kingdom Hearts II"
region: "NTSC-Unk"
@@ -10418,15 +10419,6 @@ SCUS-21295:
recommendedBlendingLevel: 3 # Fixes water and grass textures.
halfPixelOffset: 4 # Mostly aligns post processing.
nativeScaling: 1 # Fixes post processing smoothness and position.
SCUS-21494:
name: "Need for Speed - Carbon Collector's Edition"
region: "NTSC-U"
compat: 5
clampModes:
eeClampMode: 3 # Fixes game hang after opening intro.
gsHWFixes:
halfPixelOffset: 2 # Fixes blurriness.
roundSprite: 2 # Fixes blurriness.
SCUS-90174:
name: "Disney/Pixar Toy Story 3 [PlayStation 2 Bundle]"
region: "NTSC-U"
@@ -12805,9 +12797,7 @@ SLAJ-25075:
- "SLAJ-25075"
- "SLPM-66232"
- "SLPM-66562"
- "SLPM-65766"
- "SLPM-66051"
- "SLPM-66960"
- "SLAJ-25054" # Underground 2 save grants extra money.
SLAJ-25076:
name: "Harry Potter and the Goblet of Fire"
region: "NTSC-Unk"
@@ -12879,6 +12869,13 @@ SLAJ-25091:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLAJ-25091"
# Most Wanted save grants extra money.
- "SLAJ-25075"
- "SLPM-66232"
# UG2 saves can be detected, but the code is unused.
- "SLAJ-25054"
SLAJ-25092:
name: "Shin Sangoku Musou 4 [PlayStation2 the Best]"
region: "NTSC-Unk"
@@ -13630,7 +13627,7 @@ SLED-53954:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLED-53977:
name: "Dragon Quest - The Journey of the Cursed King"
region: "PAL-E"
@@ -15351,7 +15348,6 @@ SLES-50723:
name: "TOCA Race Driver"
region: "PAL-M3"
gsHWFixes:
mergeSprite: 1 # Fixes lighting.
halfPixelOffset: 1 # Fixes vertical lines
SLES-50725:
name: "V-Rally 3"
@@ -15449,7 +15445,6 @@ SLES-50767:
name: "V8 Supercars Australia - Race Driver"
region: "PAL-A"
gsHWFixes:
mergeSprite: 1 # Fixes lighting.
halfPixelOffset: 1 # Fixes vertical lines
SLES-50768:
name: "Rally Championship"
@@ -15616,13 +15611,11 @@ SLES-50816:
region: "PAL-M3"
compat: 5
gsHWFixes:
mergeSprite: 1 # Fixes lighting.
halfPixelOffset: 1 # Fixes vertical lines
SLES-50818:
name: "Pro Race Driver"
region: "PAL-I"
gsHWFixes:
mergeSprite: 1 # Fixes lighting.
halfPixelOffset: 1 # Fixes vertical lines
SLES-50820:
name: "Micro Machines"
@@ -18283,7 +18276,7 @@ SLES-51914:
- "SLES-51914"
gsHWFixes:
halfPixelOffset: 4 # Mostly aligns post processing.
nativeScaling: 2 # Fixes post processing smoothness and position.
nativeScaling: 1 # Fixes post processing smoothness and position.
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
SLES-51915:
@@ -20081,7 +20074,7 @@ SLES-52636:
autoFlush: 1 # Fixes incorrect colors.
alignSprite: 1 # Fixes vertical lines such as in FMVs.
SLES-52637:
name: "TOCA Racer Driver 2"
name: "TOCA Race Driver 2"
region: "PAL-M5"
gsHWFixes:
alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex.
@@ -21524,6 +21517,9 @@ SLES-53087:
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
halfPixelOffset: 2 # Fixes depth of field alignment.
nativeScaling: 1 # Fixes depth of field.
memcardFilters:
- "SLES-53087"
- "SLES-52637" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
SLES-53088:
name: "DTM Race Driver 3"
region: "PAL-M5"
@@ -21534,6 +21530,9 @@ SLES-53088:
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
halfPixelOffset: 2 # Fixes depth of field alignment.
nativeScaling: 1 # Fixes depth of field.
memcardFilters:
- "SLES-53088"
- "SLES-52638" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
SLES-53089:
name: "V8 Supercars Australia 3"
region: "PAL-E"
@@ -21544,6 +21543,9 @@ SLES-53089:
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
halfPixelOffset: 2 # Fixes depth of field alignment.
nativeScaling: 1 # Fixes depth of field.
memcardFilters:
- "SLES-53089"
- "SLES-52639" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
SLES-53090:
name: "Circuit Blasters"
region: "PAL-M5"
@@ -22913,7 +22915,7 @@ SLES-53556:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLES-53557:
name: "Need for Speed - Most Wanted"
region: "PAL-E"
@@ -22923,12 +22925,12 @@ SLES-53557:
halfPixelOffset: 2 # Fixes blurriness.
cpuCLUTRender: 1 # Final colour adjustment LUT.
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
memcardFilters: # Reads Underground 2 save for extra money.
memcardFilters:
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
- "SLES-52725" # Underground 2 save grants extra money.
SLES-53558:
name: "Need for Speed - Most Wanted"
region: "PAL-M8"
@@ -22942,7 +22944,7 @@ SLES-53558:
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
- "SLES-52725" # Underground 2 save grants extra money.
SLES-53559:
name: "Need for Speed - Most Wanted"
region: "PAL-M7"
@@ -22956,7 +22958,7 @@ SLES-53559:
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
- "SLES-52725" # Underground 2 save grants extra money.
SLES-53560:
name: "Sonic Riders"
region: "PAL-M5"
@@ -23703,7 +23705,6 @@ SLES-53794:
gsHWFixes:
halfPixelOffset: 4 # Fix effects upscaling.
nativeScaling: 2 # Fixes post effects.
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
SLES-53796:
name: "FIFA Street 2"
@@ -23895,7 +23896,7 @@ SLES-53857:
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
- "SLES-52725" # Underground 2 save grants extra money.
SLES-53860:
name: "Dynasty Warriors 5 - Xtreme Legends"
region: "PAL-E"
@@ -24385,7 +24386,7 @@ SLES-54027:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLES-54030:
name: "Black"
region: "PAL-E"
@@ -25141,7 +25142,7 @@ SLES-54317:
region: "PAL-M5"
compat: 5
gsHWFixes:
mergeSprite: 1 # Fixes alignment on fire effects.
halfPixelOffset: 4 # Fixes alignment on fire effects.
SLES-54319:
name: "Biker Mice from Mars"
region: "PAL-M5"
@@ -25159,6 +25160,18 @@ SLES-54321:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-54322:
name: "Need for Speed - Carbon"
region: "PAL-M8"
@@ -25167,6 +25180,18 @@ SLES-54322:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-54323:
name: "Need for Speed - Carbon"
region: "PAL-I-S"
@@ -25175,6 +25200,18 @@ SLES-54323:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-54324:
name: "Need for Speed - Carbon"
region: "PAL-R"
@@ -25183,6 +25220,18 @@ SLES-54324:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-54326:
name: "Raceway - Drag Stock Racing"
region: "PAL-E"
@@ -25457,6 +25506,17 @@ SLES-54402:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-54410:
name: "Monster Trux Arenas - Special Edition"
region: "PAL-Unk"
@@ -25713,7 +25773,6 @@ SLES-54483:
compat: 5
gsHWFixes:
recommendedBlendingLevel: 4 # Fixes car reflections.
mergeSprite: 1 # Fixes bluriness.
halfPixelOffset: 4 # Fixes bluriness.
nativeScaling: 2 # Fixes post lighting.
autoFlush: 1 # Fixes post alignment.
@@ -25754,6 +25813,17 @@ SLES-54492:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-54493:
name: "Need for Speed - Carbon [Collector's Edition]"
region: "PAL-F-G"
@@ -25762,6 +25832,17 @@ SLES-54493:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-54494:
name: "Little Britain - The Video Game"
region: "PAL-E"
@@ -27325,26 +27406,132 @@ SLES-55002:
region: "PAL-E"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-55002"
- "SLES-55003"
- "SLES-55004"
- "SLES-55005"
- "SLES-55006"
# Carbon save grants extra money.
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-55003:
name: "Need for Speed - ProStreet"
region: "PAL-F-G"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-55002"
- "SLES-55003"
- "SLES-55004"
- "SLES-55005"
- "SLES-55006"
# Carbon save grants extra money.
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-55004:
name: "Need for Speed - ProStreet"
region: "PAL-I-S"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-55002"
- "SLES-55003"
- "SLES-55004"
- "SLES-55005"
- "SLES-55006"
# Carbon save grants extra money.
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-55005:
name: "Need for Speed - ProStreet"
region: "PAL-M8"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-55002"
- "SLES-55003"
- "SLES-55004"
- "SLES-55005"
- "SLES-55006"
# Carbon save grants extra money.
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857" # Black Edition detection is bugged by default, can be fixed with a patch.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-55006:
name: "Need for Speed - ProStreet"
region: "PAL-R"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLES-55002"
- "SLES-55003"
- "SLES-55004"
- "SLES-55005"
- "SLES-55006"
# Carbon save grants extra money. Detection is bugged by default, can be fixed with a patch.
- "SLES-54321"
- "SLES-54322"
- "SLES-54323"
- "SLES-54324"
- "SLES-54402"
- "SLES-54492"
- "SLES-54493"
# Most Wanted save grants extra money. Detection is bugged by default, can be fixed with a patch.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLUS-21267" # Original wrong Most Wanted serial.
# UG2 saves can be detected, but the code is unused.
- "SLES-52725"
SLES-55007:
name: "Boogie"
region: "PAL-M7"
@@ -28289,6 +28476,18 @@ SLES-55349:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLES-55349"
- "SLES-55350"
- "SLES-55351"
- "SLES-55352"
- "SLES-55353"
# Checks from Carbon are left in the code, unused.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
SLES-55350:
name: "Need for Speed - Undercover"
region: "PAL-F-G"
@@ -28299,6 +28498,18 @@ SLES-55350:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLES-55349"
- "SLES-55350"
- "SLES-55351"
- "SLES-55352"
- "SLES-55353"
# Checks from Carbon are left in the code, unused.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
SLES-55351:
name: "Need for Speed - Undercover"
region: "PAL-I-S"
@@ -28309,6 +28520,18 @@ SLES-55351:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLES-55349"
- "SLES-55350"
- "SLES-55351"
- "SLES-55352"
- "SLES-55353"
# Checks from Carbon are left in the code, unused.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
SLES-55352:
name: "Need for Speed - Undercover"
region: "PAL-SC"
@@ -28320,6 +28543,18 @@ SLES-55352:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLES-55349"
- "SLES-55350"
- "SLES-55351"
- "SLES-55352"
- "SLES-55353"
# Checks from Carbon are left in the code, unused.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
SLES-55353:
name: "Need for Speed - Undercover"
region: "PAL-M6"
@@ -28330,6 +28565,18 @@ SLES-55353:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLES-55349"
- "SLES-55350"
- "SLES-55351"
- "SLES-55352"
- "SLES-55353"
# Checks from Carbon are left in the code, unused.
- "SLES-53557"
- "SLES-53558"
- "SLES-53559"
- "SLES-53857"
- "SLES-52725"
SLES-55354:
name: "Shin Megami Tensei - Persona 3 FES"
region: "PAL-E"
@@ -29913,7 +30160,7 @@ SLKA-25093:
- "SLKA-25093"
gsHWFixes:
halfPixelOffset: 4 # Mostly aligns post processing.
nativeScaling: 2 # Fixes post processing smoothness and position.
nativeScaling: 1 # Fixes post processing smoothness and position.
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
SLKA-25095:
@@ -30261,6 +30508,11 @@ SLKA-25185:
gsHWFixes:
halfPixelOffset: 2 # Fixes blurriness.
roundSprite: 2 # Fixes blurriness.
memcardFilters:
- "SLKA-25185"
- "SLKA-25334" # Most Wanted save grants extra money.
- "SLAJ-25075" # Original wrong Black Edition serial.
- "SLKA-25241" # UG2 saves can be detected, but the code is unused.
SLKA-25186:
name: "The King of Fighters - Maximum Impact [Limited Edition]"
name-sort: "King of Fighters, The - Maximum Impact [Limited Edition]"
@@ -30947,6 +31199,9 @@ SLKA-25334:
halfPixelOffset: 2 # Fixes blurriness.
cpuCLUTRender: 1 # Final colour adjustment LUT.
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
memcardFilters:
- "SLKA-25334"
- "SLKA-25241" # Underground 2 save grants extra money.
SLKA-25335:
name: "Shadow the Hedgehog"
region: "NTSC-K"
@@ -30986,7 +31241,7 @@ SLKA-25341:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLKA-25342:
name: "Ryu ga Gotoku"
region: "NTSC-K"
@@ -31308,6 +31563,11 @@ SLKA-25411:
region: "NTSC-K"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLKA-25411"
- "SLKA-25185" # Carbon save grants extra money.
- "SLKA-25334" # Most Wanted save grants extra money.
- "SLKA-25241" # UG2 saves can be detected, but the code is unused.
SLKA-25412:
name: "Sengoku Basara 2 - Heroes"
region: "NTSC-K"
@@ -31424,6 +31684,13 @@ SLKA-25446:
recommendedBlendingLevel: 3 # Improves car reflections.
halfPixelOffset: 2 # Fixes blurriness.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLKA-25446"
# Checks from Carbon are left in the code, unused.
- "SLKA-25185"
- "SLKA-25334"
- "SLAJ-25075"
- "SLKA-25241"
SLKA-25447:
name: "FIFA 09"
region: "NTSC-K"
@@ -31619,6 +31886,9 @@ SLPM-55003:
halfPixelOffset: 2 # Fixes blurriness.
cpuCLUTRender: 1 # Final colour adjustment LUT.
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
memcardFilters:
- "SLPM-55003"
- "SLPM-65766" # Underground 2 save grants extra money.
SLPM-55004:
name: "バーンアウト リベンジ [EA:SY! 1980]"
name-sort: "ばーんあうと りべんじ [EA:SY! 1980])"
@@ -31872,6 +32142,9 @@ SLPM-55046:
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
halfPixelOffset: 2 # Fixes depth of field alignment.
nativeScaling: 1 # Fixes depth of field.
memcardFilters:
- "SLPM-55046"
- "SLPM-66498" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
SLPM-55047:
name: "Sugar+Spice ~あの子のステキな何もかも~"
name-sort: "しゅがーすぱいす あのこのすてきななにもかも"
@@ -31946,6 +32219,13 @@ SLPM-55061:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLPM-55061"
# Most Wanted save grants extra money.
- "SLPM-66232"
- "SLPM-66562"
- "SLAJ-25075" # Original wrong Black Edition serial.
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
SLPM-55062:
name: "実況パワフルメジャーリーグ 3"
name-sort: "じっきょうぱわふるめじゃーりーぐ 3"
@@ -32048,7 +32328,6 @@ SLPM-55081:
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting characters.
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
SLPM-55082:
name: "真・三國無双5 Special [ディスク 1]"
@@ -32264,6 +32543,13 @@ SLPM-55127:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLPM-55127"
# Checks from Carbon are left in the code, unused.
- "SLPM-66232"
- "SLPM-66562"
- "SLAJ-25075"
- "SLPM-65766"
SLPM-55128:
name: "ラグビー08 [英語版] [EA:SY! 1980]"
name-sort: "らぐびー08 [えいごばん] [EA:SY! 1980]"
@@ -32401,6 +32687,16 @@ SLPM-55151:
region: "NTSC-J"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLPM-55151"
# Carbon save grants extra money.
- "SLPM-55061"
- "SLPM-66617"
- "SLPM-66869"
# Most Wanted save grants extra money.
- "SLPM-66232"
- "SLPM-66562"
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
SLPM-55152:
name: "スロッターUPコア11 巨人の星Ⅳ 青春群像編"
name-sort: "すろったーUPこあ11 きょじんのほし4 せいしゅんぐんぞうへん"
@@ -32842,6 +33138,13 @@ SLPM-55244:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLPM-55244"
# Checks from Carbon are left in the code, unused.
- "SLPM-66232"
- "SLPM-66562"
- "SLAJ-25075"
- "SLPM-65766"
SLPM-55245:
name: "ひまわり -Pebble in the Sky-"
name-sort: "ひまわり -Pebble in the Sky-"
@@ -34510,7 +34813,6 @@ SLPM-61120:
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting characters.
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
SLPM-61121:
name: "KAIDO ~峠の伝説~ [体験版]"
@@ -41214,7 +41516,7 @@ SLPM-65413:
- "SLPM-65413"
gsHWFixes:
halfPixelOffset: 4 # Mostly aligns post processing.
nativeScaling: 2 # Fixes post processing smoothness and position.
nativeScaling: 1 # Fixes post processing smoothness and position.
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
SLPM-65414:
@@ -44547,7 +44849,6 @@ SLPM-65999:
eeClampMode: 2 # Fixes wrong color on some characters and breakable objects.
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting characters.
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
SLPM-66000:
name: "コンフリクトデルタⅡ 湾岸戦争1991"
@@ -46063,7 +46364,8 @@ SLPM-66232:
- "SLAJ-25075"
- "SLPM-66232"
- "SLPM-66562"
- "SLPM-65766"
- "SLPM-65766" # Underground 2 save grants extra money.
# Unconfirmed UG2 serials.
- "SLPM-66051"
- "SLPM-66960"
SLPM-66233:
@@ -48094,7 +48396,8 @@ SLPM-66562:
- "SLAJ-25075"
- "SLPM-66232"
- "SLPM-66562"
- "SLPM-65766"
- "SLPM-65766" # Underground 2 save grants extra money.
# Unconfirmed UG2 serials.
- "SLPM-66051"
- "SLPM-66960"
SLPM-66563:
@@ -48135,7 +48438,7 @@ SLPM-66567:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLPM-66568:
name: "ブラザー イン アームズ ロード トゥ ヒル サーティ [UBISOFT BEST]"
name-sort: "ぶらざー いん あーむず ろーど とぅ ひる さーてぃ [UBISOFT BEST]"
@@ -48443,6 +48746,13 @@ SLPM-66617:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLPM-66617"
# Most Wanted save grants extra money.
- "SLPM-66232"
- "SLPM-66562"
- "SLAJ-25075" # Original wrong Black Edition serial.
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
SLPM-66618:
name: "夢見師 [初回限定版]"
name-sort: "ゆめみし [しょかいげんていばん]"
@@ -49971,6 +50281,13 @@ SLPM-66869:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLPM-66869"
# Most Wanted save grants extra money.
- "SLPM-66232"
- "SLPM-66562"
- "SLAJ-25075" # Original wrong Black Edition serial.
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
SLPM-66870:
name: "星色のおくりもの [初回スペシャル限定版]"
name-sort: "ほしいろのおくりもの [しょかいすぺしゃるげんていばん]"
@@ -50039,6 +50356,9 @@ SLPM-66881:
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
halfPixelOffset: 2 # Fixes depth of field alignment.
nativeScaling: 1 # Fixes depth of field.
memcardFilters:
- "SLPM-66881"
- "SLPM-66498" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
SLPM-66882:
name: "はかれなはーと ~君がために輝きを~ [限定版・サントラボイスCD付]"
name-sort: "はかれなはーと きみがためにかがやきを [げんていばん さんとらぼいすCDつき]"
@@ -50319,6 +50639,16 @@ SLPM-66932:
region: "NTSC-J"
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLPM-66932"
# Carbon save grants extra money.
- "SLPM-55061"
- "SLPM-66617"
- "SLPM-66869"
# Most Wanted save grants extra money.
- "SLPM-66232"
- "SLPM-66562"
- "SLPM-65766" # UG2 saves can be detected, but the code is unused.
SLPM-66933:
name: "君が主で執事が俺で~お仕え日記~ [初回限定版]"
name-sort: "きみがあるじでしつじがおれで おつかえにっき [しょかいげんていばん]"
@@ -54359,6 +54689,7 @@ SLPS-20485:
region: "NTSC-J"
gsHWFixes:
alignSprite: 1 # Fixes vertical lines.
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
SLPS-20486:
name: "太鼓の達人 ドカッ!と大盛り七代目 [ソフト単体]"
name-sort: "たいこのたつじん どかっ!とおおもりななだいめ [そふとたんたい]"
@@ -54366,6 +54697,7 @@ SLPS-20486:
region: "NTSC-J"
gsHWFixes:
alignSprite: 1 # Fixes vertical lines.
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
SLPS-20487:
name: "パチスロキング! 科学忍者隊ガッチャマン"
name-sort: "ぱちすろきんぐ! かがくにんじゃたいがっちゃまん"
@@ -57943,6 +58275,7 @@ SLPS-25586:
halfPixelOffset: 4 # Fixes post positioning.
nativeScaling: 1 # Fixes post effects.
autoFlush: 2 # Fixes post lighting.
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
SLPS-25587:
name: "シュガシュガルーン 恋もおしゃれもピックアップ!"
name-sort: "しゅがしゅがるーん こいもおしゃれもぴっくあっぷ!"
@@ -60976,6 +61309,7 @@ SLPS-73252:
halfPixelOffset: 4 # Fixes post positioning.
nativeScaling: 1 # Fixes post effects.
autoFlush: 2 # Fixes post lighting.
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
SLPS-73253:
name: "るろうに剣心-明治剣客浪漫譚- 炎上!京都輪廻 [PlayStation2 the Best]"
name-sort: "るろうにけんしん めいじけんかくろまんたん えんじょう きょうとりんね [PlayStation2 the Best]"
@@ -62650,7 +62984,6 @@ SLUS-20329:
region: "NTSC-U"
compat: 5
gsHWFixes:
mergeSprite: 1 # Fixes lighting.
halfPixelOffset: 1 # Fixes vertical lines
SLUS-20330:
name: "NBA 2K2 - Sega Sports"
@@ -64556,7 +64889,7 @@ SLUS-20694:
- "SLUS-20710"
gsHWFixes:
halfPixelOffset: 4 # Mostly aligns post processing.
nativeScaling: 2 # Fixes post processing smoothness and position.
nativeScaling: 1 # Fixes post processing smoothness and position.
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
bilinearUpscale: 2 # Gets rid of center vertical line when upscaling.
SLUS-20695:
@@ -67351,6 +67684,9 @@ SLUS-21182:
skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it.
halfPixelOffset: 2 # Fixes depth of field alignment.
nativeScaling: 1 # Fixes depth of field.
memcardFilters:
- "SLUS-21182"
- "SLUS-21039" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early.
SLUS-21183:
name: "Teen Titans"
region: "NTSC-U"
@@ -67869,6 +68205,9 @@ SLUS-21257:
halfPixelOffset: 2 # Fixes blurriness.
cpuCLUTRender: 1 # Final colour adjustment LUT.
gpuTargetCLUT: 1 # Fixes sun penetrating bridges (along with HPO special).
memcardFilters:
- "SLUS-21257"
- "SLUS-21065" # Underground 2 save grants extra money.
SLUS-21258:
name: ".hack//G.U. Vol.1 - Rebirth"
region: "NTSC-U"
@@ -67955,7 +68294,7 @@ SLUS-21267:
memcardFilters:
- "SLUS-21267"
- "SLUS-21351"
- "SLUS-21065"
- "SLUS-21065" # Underground 2 save grants extra money.
SLUS-21268:
name: "24 - The Game"
region: "NTSC-U"
@@ -67988,7 +68327,7 @@ SLUS-21271:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLUS-21272:
name: "Super Monkey Ball Adventure"
region: "NTSC-U"
@@ -68244,7 +68583,7 @@ SLUS-21306:
region: "NTSC-U"
compat: 5
gsHWFixes:
mergeSprite: 1 # Fixes alignment on fire effects.
halfPixelOffset: 4 # Fixes alignment on fire effects.
SLUS-21307:
name: "Ice Age 2 - The Meltdown"
region: "NTSC-U"
@@ -68544,7 +68883,7 @@ SLUS-21351:
memcardFilters:
- "SLUS-21267"
- "SLUS-21351"
- "SLUS-21065"
- "SLUS-21065" # Underground 2 save grants extra money.
SLUS-21352:
name: "Dai Senryaku VII Exceed"
region: "NTSC-U"
@@ -68706,7 +69045,6 @@ SLUS-21373:
gsHWFixes:
halfPixelOffset: 4 # Fix effects upscaling.
nativeScaling: 2 # Fixes post effects.
mergeSprite: 1 # Align sprite fixes FMVs but not garbage in-game, so needs merge sprite instead.
texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost.
SLUS-21374:
name: "Marvel - Ultimate Alliance"
@@ -68805,6 +69143,7 @@ SLUS-21386:
halfPixelOffset: 4 # Fixes post positioning.
nativeScaling: 1 # Fixes post effects.
autoFlush: 2 # Fixes post lighting.
getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine.
SLUS-21387:
name: "Warship Gunner 2"
region: "NTSC-U"
@@ -68886,7 +69225,7 @@ SLUS-21399:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLUS-21400:
name: "Monster House"
region: "NTSC-U"
@@ -69212,7 +69551,6 @@ SLUS-21449:
compat: 5
gsHWFixes:
recommendedBlendingLevel: 4 # Fixes car reflections.
mergeSprite: 1 # Fixes bluriness.
halfPixelOffset: 4 # Fixes bluriness.
nativeScaling: 2 # Fixes post lighting.
autoFlush: 1 # Fixes post alignment.
@@ -69488,14 +69826,29 @@ SLUS-21493:
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLUS-21493"
# Most Wanted save grants extra money.
- "SLUS-21267"
- "SLUS-21351" # Black Edition detection is bugged by default, can be fixed with a patch.
- "SLAJ-25075" # Original wrong Black Edition serial.
- "SLUS-21065" # UG2 saves can be detected, but the code is unused.
SLUS-21494:
name: "Need for Speed - Carbon [Collector's Edition]"
region: "NTSC-U"
compat: 5
clampModes:
eeClampMode: 3 # Fixes game hang after opening intro.
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes car headlights.
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLUS-21494"
# Most Wanted save grants extra money.
- "SLUS-21267"
- "SLUS-21351" # Black Edition detection is bugged by default, can be fixed with a patch.
- "SLAJ-25075" # Original wrong Black Edition serial.
- "SLUS-21065" # UG2 saves can be detected, but the code is unused.
SLUS-21495:
name: "Sudoku, Carol Vorderman's"
region: "NTSC-U"
@@ -70253,6 +70606,15 @@ SLUS-21658:
compat: 5
gsHWFixes:
halfPixelOffset: 2 # Fixes depth line.
memcardFilters:
- "SLUS-21658"
# Carbon save grants extra money.
- "SLUS-21493"
- "SLUS-21494"
# Most Wanted save grants extra money.
- "SLUS-21267"
- "SLUS-21351"
- "SLUS-21065" # UG2 saves can be detected, but the code is unused.
SLUS-21660:
name: "Disney Princess - Enchanted Journey"
region: "NTSC-U"
@@ -71031,6 +71393,13 @@ SLUS-21801:
gpuTargetCLUT: 1 # Fixes sun penetration.
nativeScaling: 2 # Fixes post alignment.
getSkipCount: "GSC_NFSUndercover"
memcardFilters:
- "SLUS-21801"
# Checks from Carbon are left in the code, unused.
- "SLUS-21267"
- "SLUS-21351"
- "SLAJ-25075"
- "SLUS-21065"
SLUS-21802:
name: "Naked Brothers Band - The Video Game"
region: "NTSC-U"
@@ -72687,7 +73056,7 @@ SLUS-29185:
cpuSpriteRenderBW: 2 # Fixes some bad textures.
cpuCLUTRender: 1 # Fixes the rest of the bad textures.
roundSprite: 1 # Reduces misaligned bloom.
mergeSprite: 1 # Removes bloom explosion around electrical lights and other light sources such as moon/sun.
nativeScaling: 1 # Fixes post processing smoothness and position.
SLUS-29188:
name: "Steambot Chronicles [Regular Demo]"
region: "NTSC-U"

View File

@@ -387,7 +387,7 @@
03000000380700006652000000000000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
03000000380700005032000000000000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000380700005082000000000000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
03000000380700008031000000000000,Mad Catz FightStick Alpha PS3 ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000380700008031000000000000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000003807000038b7000000000000,Mad Catz Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,
03000000380700008433000000000000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000380700008483000000000000,Mad Catz Fightstick TE S PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
@@ -480,6 +480,7 @@
030000008916000000fd000000000000,Onza TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,start:b11,x:b3,y:b4,platform:Windows,
03000000362800000100000000000000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,
03000000120c0000f60e000000000000,P4 Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
03000000790000002201000000000000,PC Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
@@ -665,7 +666,7 @@
03000000952e00002577000000000000,Scuf PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
03000000a30c00002500000000000000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Windows,
03000000a30c00002400000000000000,Sega Mega Drive Mini 6B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,
03000000d804000086e6000000000000,Sega Multi Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
03000000d804000086e6000000000000,Sega Multi Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:a2,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
0300000000050000289b000000000000,Sega Saturn Adapter,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
0300000000f000000800000000000000,Sega Saturn Controller,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b7,righttrigger:b3,start:b0,x:b5,y:b6,platform:Windows,
03000000730700000601000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,
@@ -967,6 +968,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700005082000000010000,Mad Catz PS4 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
03000000380700008483000000010000,Mad Catz PS4 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
0300000049190000020400001b010000,Manba One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000790000000600000007010000,Marvo GT-004,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
030000008f0e00001330000011010000,Mayflash Controller Adapter,a:b2,b:b4,back:b16,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b12,lefttrigger:b16,leftx:a0,lefty:a2,rightshoulder:b14,rightx:a6~,righty:a4,start:b18,x:b0,y:b6,platform:Mac OS X,
03000000790000004318000000010000,Mayflash GameCube Adapter,a:b4,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X,
@@ -998,7 +1000,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000550900001472000025050000,NVIDIA Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X,
030000004b120000014d000000010000,Nyko Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000790000001c18000000010000,PB Tails Choc,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
030000006f0e00000901000002010000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
030000008f0e00000300000000000000,Piranha Xtreme PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000d620000011a7000000020000,PowerA Core Plus Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
@@ -1070,6 +1071,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000457500002211000000010000,SZMY Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000e40a00000307000001000000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,
03000000e40a00000207000001000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,
03000000790000001c18000000010000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000790000001c18000003100000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000591c00002400000021000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X,
03000000591c00002600000021000000,THEGamepad,a:b2,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Mac OS X,
@@ -1380,6 +1382,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000380700005032000011010000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000380700005082000011010000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,
03000000380700008031000011010000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000380700008081000011010000,Mad Catz FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000380700008034000011010000,Mad Catz Fightstick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000380700008084000011010000,Mad Catz Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
03000000380700008433000011010000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
@@ -1519,6 +1523,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c62400001a54000001010000,PowerA Xbox One Mini Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000d62000000240000001010000,PowerA Xbox One Spectra Infinity,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000d62000000520000050010000,PowerA Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000d62000000b20000001010000,PowerA Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,

View File

@@ -146,15 +146,11 @@ if(MSVC AND NOT USE_CLANG_CL)
endif()
if(MSVC)
# Disable RTTI
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
# Disable Exceptions
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
else()
add_compile_options(-pipe -fvisibility=hidden -pthread)
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>"
"$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>"
)
endif()

View File

@@ -17,7 +17,7 @@ find_package(ZLIB REQUIRED) # v1.3, but Mac uses the SDK version.
find_package(Zstd 1.5.5 REQUIRED)
find_package(LZ4 REQUIRED)
find_package(WebP REQUIRED) # v1.3.2, spews an error on Linux because no pkg-config.
find_package(SDL2 2.30.4 REQUIRED)
find_package(SDL3 3.2.6 REQUIRED)
find_package(Freetype 2.11.1 REQUIRED)
if(USE_VULKAN)
@@ -117,6 +117,9 @@ add_subdirectory(3rdparty/demangler EXCLUDE_FROM_ALL)
# Symbol table parser.
add_subdirectory(3rdparty/ccc EXCLUDE_FROM_ALL)
# The docking system for the debugger.
find_package(KDDockWidgets-qt6 REQUIRED)
# Architecture-specific.
if(_M_X86)
add_subdirectory(3rdparty/zydis EXCLUDE_FROM_ALL)

View File

@@ -288,8 +288,14 @@ std::string Path::RealPath(const std::string_view path)
{
// Resolve non-absolute paths first.
std::vector<std::string_view> components;
// We need to keep the full combined path in scope
// as SplitNativePath() returns string_views to it.
std::string buf;
if (!IsAbsolute(path))
components = Path::SplitNativePath(Path::Combine(FileSystem::GetWorkingDirectory(), path));
{
buf = Path::Combine(FileSystem::GetWorkingDirectory(), path);
components = Path::SplitNativePath(buf);
}
else
components = Path::SplitNativePath(path);
@@ -968,7 +974,7 @@ std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error*
{
#ifdef _WIN32
const std::wstring wfilename = GetWin32Path(filename);
const std::wstring wmode = GetWin32Path(mode);
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
if (!wfilename.empty() && !wmode.empty())
{
std::FILE* fp;
@@ -1060,7 +1066,7 @@ std::FILE* FileSystem::OpenSharedCFile(const char* filename, const char* mode, F
{
#ifdef _WIN32
const std::wstring wfilename = GetWin32Path(filename);
const std::wstring wmode = GetWin32Path(mode);
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
if (wfilename.empty() || wmode.empty())
return nullptr;
@@ -1354,8 +1360,11 @@ static u32 TranslateWin32Attributes(u32 Win32Attributes)
}
static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path, const char* path, const char* pattern,
u32 flags, FileSystem::FindResultsArray* results, std::vector<std::string>& visited)
u32 flags, FileSystem::FindResultsArray* results, std::vector<std::string>& visited, ProgressCallback* cancel)
{
if (cancel && cancel->IsCancelled())
return 0;
std::string search_dir;
if (path)
{
@@ -1427,11 +1436,11 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
if (parent_path)
{
const std::string recurse_dir = fmt::format("{}\\{}", parent_path, path);
nFiles += RecursiveFindFiles(origin_path, recurse_dir.c_str(), utf8_filename.c_str(), pattern, flags, results, visited);
nFiles += RecursiveFindFiles(origin_path, recurse_dir.c_str(), utf8_filename.c_str(), pattern, flags, results, visited, cancel);
}
else
{
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results, visited);
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results, visited, cancel);
}
}
}
@@ -1494,7 +1503,7 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
return nFiles;
}
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results)
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel)
{
// has a path
if (path[0] == '\0')
@@ -1514,7 +1523,7 @@ bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, Fin
}
// enter the recursive function
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) == 0)
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited, cancel) == 0)
return false;
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)
@@ -2046,8 +2055,11 @@ bool FileSystem::DeleteSymbolicLink(const char* path, Error* error)
static_assert(sizeof(off_t) == sizeof(s64));
static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern,
u32 Flags, FileSystem::FindResultsArray* pResults, std::vector<std::string>& visited)
u32 Flags, FileSystem::FindResultsArray* pResults, std::vector<std::string>& visited, ProgressCallback* cancel)
{
if (cancel && cancel->IsCancelled())
return 0;
std::string tempStr;
if (Path)
{
@@ -2118,11 +2130,11 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
if (ParentPath)
{
const std::string recursive_dir = fmt::format("{}/{}", ParentPath, Path);
nFiles += RecursiveFindFiles(OriginPath, recursive_dir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults, visited);
nFiles += RecursiveFindFiles(OriginPath, recursive_dir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults, visited, cancel);
}
else
{
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults, visited);
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults, visited, cancel);
}
}
}
@@ -2177,7 +2189,7 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
return nFiles;
}
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results)
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel)
{
// has a path
if (path[0] == '\0')
@@ -2197,7 +2209,7 @@ bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, Fin
}
// enter the recursive function
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) == 0)
if (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited, cancel) == 0)
return false;
if (flags & FILESYSTEM_FIND_SORT_BY_NAME)

View File

@@ -67,7 +67,7 @@ namespace FileSystem
std::vector<std::string> GetRootDirectoryList();
/// Search for files
bool FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results);
bool FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel = nullptr);
/// Stat file
bool StatFile(const char* path, struct stat* st);

View File

@@ -119,6 +119,12 @@ namespace x86Emitter
xImplSimd_DestRegSSE VPD;
};
struct xImplSimd_PBlend
{
xImplSimd_DestRegImmSSE W;
xImplSimd_DestRegSSE VB;
};
// --------------------------------------------------------------------------------------
// xImplSimd_PMove
// --------------------------------------------------------------------------------------

View File

@@ -500,6 +500,7 @@ namespace x86Emitter
extern const xImplSimd_MovHL_RtoR xMOVLH;
extern const xImplSimd_MovHL_RtoR xMOVHL;
extern const xImplSimd_PBlend xPBLEND;
extern const xImplSimd_Blend xBLEND;
extern const xImplSimd_PMove xPMOVSX;
extern const xImplSimd_PMove xPMOVZX;

View File

@@ -556,12 +556,18 @@ namespace x86Emitter
const xImplSimd_MovHL_RtoR xMOVLH = {0x16};
const xImplSimd_MovHL_RtoR xMOVHL = {0x12};
const xImplSimd_PBlend xPBLEND =
{
{0x66, 0x0e3a}, // W
{0x66, 0x1038}, // VB
};
const xImplSimd_Blend xBLEND =
{
{0x66, 0x0c3a}, // PS
{0x66, 0x0d3a}, // PD
{0x66, 0x1438}, // VPS
{0x66, 0x1538}, // VPD
{
{0x66, 0x0c3a}, // PS
{0x66, 0x0d3a}, // PD
{0x66, 0x1438}, // VPS
{0x66, 0x1538}, // VPD
};
const xImplSimd_PMove xPMOVSX = {0x2038};

View File

@@ -4,7 +4,7 @@
<ItemDefinitionGroup>
<Link>
<AdditionalLibraryDirectories>$(DepsLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL2.lib;zlib.lib;zstd.lib</AdditionalDependencies>
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL3.lib;zlib.lib;zstd.lib;kddockwidgets-qt62.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
@@ -15,10 +15,11 @@
<DepsDLLs Include="$(DepsBinDir)libsharpyuv.dll" />
<DepsDLLs Include="$(DepsBinDir)libwebp.dll" />
<DepsDLLs Include="$(DepsBinDir)lz4.dll" />
<DepsDLLs Include="$(DepsBinDir)SDL2.dll" />
<DepsDLLs Include="$(DepsBinDir)SDL3.dll" />
<DepsDLLs Include="$(DepsBinDir)shaderc_shared.dll" />
<DepsDLLs Include="$(DepsBinDir)zlib1.dll" />
<DepsDLLs Include="$(DepsBinDir)zstd.dll" />
<DepsDLLs Include="$(DepsBinDir)kddockwidgets-qt62.dll" />
</ItemGroup>
<Target Name="DepsCopyDLLs"
AfterTargets="Build"
@@ -31,4 +32,4 @@
SkipUnchangedFiles="true"
/>
</Target>
</Project>
</Project>

View File

@@ -30,6 +30,9 @@
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(QtToolOutDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(QtIncludeDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(QtIncludeDir)\QtCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(QtIncludeDir)\QtGui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(QtIncludeDir)\QtWidgets;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(QtLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

View File

@@ -169,11 +169,25 @@ void Host::SetDefaultUISettings(SettingsInterface& si)
// nothing
}
bool Host::LocaleCircleConfirm()
{
// not running any UI, so no settings requests will come in
return false;
}
std::unique_ptr<ProgressCallback> Host::CreateHostProgressCallback()
{
return ProgressCallback::CreateNullProgressCallback();
}
void Host::ReportInfoAsync(const std::string_view title, const std::string_view message)
{
if (!title.empty() && !message.empty())
INFO_LOG("ReportInfoAsync: {}: {}", title, message);
else if (!message.empty())
INFO_LOG("ReportInfoAsync: {}", message);
}
void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
{
if (!title.empty() && !message.empty())
@@ -517,17 +531,17 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
s_settings_interface.SetBoolValue("EmuCore/GS", "dump", true);
if (str.find("rt") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "save", true);
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveRT", true);
if (str.find("f") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "savef", true);
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveFrame", true);
if (str.find("tex") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "savet", true);
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveTexture", true);
if (str.find("z") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "savez", true);
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveDepth", true);
if (str.find("a") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "savea", true);
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveAlpha", true);
if (str.find("i") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "savei", true);
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveInfo", true);
continue;
}
else if (CHECK_ARG_PARAM("-dumprange"))
@@ -550,9 +564,9 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
{
by = std::max(1, StringUtil::FromChars<int>(split[2]).value_or(1));
}
s_settings_interface.SetIntValue("EmuCore/GS", "saven", start);
s_settings_interface.SetIntValue("EmuCore/GS", "savel", num);
s_settings_interface.SetIntValue("EmuCore/GS", "saveb", by);
s_settings_interface.SetIntValue("EmuCore/GS", "SaveDrawStart", start);
s_settings_interface.SetIntValue("EmuCore/GS", "SaveDrawCount", num);
s_settings_interface.SetIntValue("EmuCore/GS", "SaveDrawBy", by);
continue;
}
else if (CHECK_ARG_PARAM("-dumprangef"))
@@ -575,9 +589,9 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
{
by = std::max(1, StringUtil::FromChars<int>(split[2]).value_or(1));
}
s_settings_interface.SetIntValue("EmuCore/GS", "savenf", start);
s_settings_interface.SetIntValue("EmuCore/GS", "savelf", num);
s_settings_interface.SetIntValue("EmuCore/GS", "savebf", by);
s_settings_interface.SetIntValue("EmuCore/GS", "SaveFrameStart", start);
s_settings_interface.SetIntValue("EmuCore/GS", "SaveFrameCount", num);
s_settings_interface.SetIntValue("EmuCore/GS", "SaveFrameBy", by);
continue;
}
else if (CHECK_ARG_PARAM("-dumpdirhw"))

View File

@@ -86,6 +86,9 @@ target_sources(pcsx2-qt PRIVATE
Settings/DebugAnalysisSettingsWidget.cpp
Settings/DebugAnalysisSettingsWidget.h
Settings/DebugAnalysisSettingsWidget.ui
Settings/DebugUserInterfaceSettingsWidget.cpp
Settings/DebugUserInterfaceSettingsWidget.h
Settings/DebugUserInterfaceSettingsWidget.ui
Settings/DebugSettingsWidget.cpp
Settings/DebugSettingsWidget.h
Settings/DebugSettingsWidget.ui
@@ -158,37 +161,66 @@ target_sources(pcsx2-qt PRIVATE
Debugger/AnalysisOptionsDialog.cpp
Debugger/AnalysisOptionsDialog.h
Debugger/AnalysisOptionsDialog.ui
Debugger/CpuWidget.cpp
Debugger/CpuWidget.h
Debugger/CpuWidget.ui
Debugger/DebuggerSettingsManager.cpp
Debugger/DebuggerSettingsManager.h
Debugger/DebuggerEvents.h
Debugger/DebuggerWidget.cpp
Debugger/DebuggerWidget.h
Debugger/DebuggerWindow.cpp
Debugger/DebuggerWindow.h
Debugger/DebuggerWindow.ui
Debugger/DisassemblyWidget.cpp
Debugger/DisassemblyWidget.h
Debugger/DisassemblyWidget.ui
Debugger/MemorySearchWidget.cpp
Debugger/MemorySearchWidget.h
Debugger/MemorySearchWidget.ui
Debugger/MemoryViewWidget.cpp
Debugger/MemoryViewWidget.h
Debugger/MemoryViewWidget.ui
Debugger/JsonValueWrapper.h
Debugger/RegisterWidget.cpp
Debugger/RegisterWidget.h
Debugger/RegisterWidget.ui
Debugger/BreakpointDialog.cpp
Debugger/BreakpointDialog.h
Debugger/BreakpointDialog.ui
Debugger/Models/BreakpointModel.cpp
Debugger/Models/BreakpointModel.h
Debugger/Models/ThreadModel.cpp
Debugger/Models/ThreadModel.h
Debugger/Models/StackModel.cpp
Debugger/Models/StackModel.h
Debugger/Models/SavedAddressesModel.cpp
Debugger/Models/SavedAddressesModel.h
Debugger/StackModel.cpp
Debugger/StackModel.h
Debugger/StackWidget.cpp
Debugger/StackWidget.h
Debugger/ThreadModel.cpp
Debugger/ThreadModel.h
Debugger/ThreadWidget.cpp
Debugger/ThreadWidget.h
Debugger/Breakpoints/BreakpointDialog.cpp
Debugger/Breakpoints/BreakpointDialog.h
Debugger/Breakpoints/BreakpointDialog.ui
Debugger/Breakpoints/BreakpointModel.cpp
Debugger/Breakpoints/BreakpointModel.h
Debugger/Breakpoints/BreakpointWidget.cpp
Debugger/Breakpoints/BreakpointWidget.h
Debugger/Breakpoints/BreakpointWidget.ui
Debugger/Docking/DockLayout.cpp
Debugger/Docking/DockLayout.h
Debugger/Docking/DockManager.cpp
Debugger/Docking/DockManager.h
Debugger/Docking/DockTables.cpp
Debugger/Docking/DockTables.h
Debugger/Docking/DockUtils.cpp
Debugger/Docking/DockUtils.h
Debugger/Docking/DockViews.cpp
Debugger/Docking/DockViews.h
Debugger/Docking/DropIndicators.cpp
Debugger/Docking/DropIndicators.h
Debugger/Docking/LayoutEditorDialog.cpp
Debugger/Docking/LayoutEditorDialog.h
Debugger/Docking/LayoutEditorDialog.ui
Debugger/Docking/NoLayoutsWidget.cpp
Debugger/Docking/NoLayoutsWidget.h
Debugger/Docking/NoLayoutsWidget.ui
Debugger/Memory/MemorySearchWidget.cpp
Debugger/Memory/MemorySearchWidget.h
Debugger/Memory/MemorySearchWidget.ui
Debugger/Memory/MemoryViewWidget.cpp
Debugger/Memory/MemoryViewWidget.h
Debugger/Memory/MemoryViewWidget.ui
Debugger/Memory/SavedAddressesModel.cpp
Debugger/Memory/SavedAddressesModel.h
Debugger/Memory/SavedAddressesWidget.cpp
Debugger/Memory/SavedAddressesWidget.h
Debugger/Memory/SavedAddressesWidget.ui
Debugger/SymbolTree/NewSymbolDialogs.cpp
Debugger/SymbolTree/NewSymbolDialogs.h
Debugger/SymbolTree/NewSymbolDialog.ui
@@ -232,6 +264,7 @@ target_link_libraries(pcsx2-qt PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
KDAB::kddockwidgets
)
# Our Qt builds may have exceptions on, so force them off.

View File

@@ -5,9 +5,9 @@
#include "ui_BreakpointDialog.h"
#include "DebugTools/Breakpoints.h"
#include "BreakpointModel.h"
#include "Models/BreakpointModel.h"
#include "DebugTools/Breakpoints.h"
#include <QtWidgets/QDialog>

View File

@@ -212,7 +212,7 @@
</size>
</property>
<property name="text">
<string>1</string>
<string>4</string>
</property>
</widget>
</item>

View File

@@ -3,21 +3,48 @@
#include "BreakpointModel.h"
#include "QtHost.h"
#include "QtUtils.h"
#include "Debugger/DebuggerSettingsManager.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/DisassemblyManager.h"
#include "common/Console.h"
#include "QtHost.h"
#include "QtUtils.h"
#include <QtWidgets/QMessageBox>
#include <algorithm>
std::map<BreakPointCpu, BreakpointModel*> BreakpointModel::s_instances;
BreakpointModel::BreakpointModel(DebugInterface& cpu, QObject* parent)
: QAbstractTableModel(parent)
, m_cpu(cpu)
{
if (m_cpu.getCpuType() == BREAKPOINT_EE)
{
connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) {
if (title.isEmpty())
return;
if (rowCount() == 0)
DebuggerSettingsManager::loadGameSettings(this);
});
DebuggerSettingsManager::loadGameSettings(this);
}
connect(this, &BreakpointModel::dataChanged, this, &BreakpointModel::refreshData);
}
BreakpointModel* BreakpointModel::getInstance(DebugInterface& cpu)
{
auto iterator = s_instances.find(cpu.getCpuType());
if (iterator == s_instances.end())
iterator = s_instances.emplace(cpu.getCpuType(), new BreakpointModel(cpu)).first;
return iterator->second;
}
int BreakpointModel::rowCount(const QModelIndex&) const
@@ -32,10 +59,14 @@ int BreakpointModel::columnCount(const QModelIndex&) const
QVariant BreakpointModel::data(const QModelIndex& index, int role) const
{
size_t row = static_cast<size_t>(index.row());
if (!index.isValid() || row >= m_breakpoints.size())
return QVariant();
const BreakpointMemcheck& bp_mc = m_breakpoints[row];
if (role == Qt::DisplayRole)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
{
switch (index.column())
@@ -87,8 +118,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
}
else if (role == BreakpointModel::DataRole)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
{
switch (index.column())
@@ -133,8 +162,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
}
else if (role == BreakpointModel::ExportRole)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
{
switch (index.column())
@@ -181,8 +208,6 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
{
if (index.column() == 0)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
{
return bp->enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked;
@@ -273,12 +298,14 @@ Qt::ItemFlags BreakpointModel::flags(const QModelIndex& index) const
bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
std::string error;
size_t row = static_cast<size_t>(index.row());
if (!index.isValid() || row >= m_breakpoints.size())
return false;
const BreakpointMemcheck& bp_mc = m_breakpoints[row];
if (role == Qt::CheckStateRole && index.column() == BreakpointColumns::ENABLED)
{
auto bp_mc = m_breakpoints.at(index.row());
if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
{
Host::RunOnCPUThread([cpu = this->m_cpu.getCpuType(), bp = *bp, enabled = value.toBool()] {
@@ -297,8 +324,6 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
}
else if (role == Qt::EditRole && index.column() == BreakpointColumns::CONDITION)
{
auto bp_mc = m_breakpoints.at(index.row());
if (auto* bp = std::get_if<BreakPoint>(&bp_mc))
{
const QString condValue = value.toString();
@@ -316,6 +341,7 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
{
PostfixExpression expr;
std::string error;
if (!m_cpu.initExpression(condValue.toLocal8Bit().constData(), expr, error))
{
QMessageBox::warning(nullptr, "Condition Error", QString::fromStdString(error));
@@ -349,6 +375,7 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
{
PostfixExpression expr;
std::string error;
if (!m_cpu.initExpression(condValue.toLocal8Bit().constData(), expr, error))
{
QMessageBox::warning(nullptr, "Condition Error", QString::fromStdString(error));

View File

@@ -34,8 +34,7 @@ public:
ExportRole = Qt::UserRole + 1,
};
static constexpr QHeaderView::ResizeMode HeaderResizeModes[BreakpointColumns::COLUMN_COUNT] =
{
static constexpr QHeaderView::ResizeMode HeaderResizeModes[BreakpointColumns::COLUMN_COUNT] = {
QHeaderView::ResizeMode::ResizeToContents,
QHeaderView::ResizeMode::ResizeToContents,
QHeaderView::ResizeMode::ResizeToContents,
@@ -45,7 +44,7 @@ public:
QHeaderView::ResizeMode::ResizeToContents,
};
explicit BreakpointModel(DebugInterface& cpu, QObject* parent = nullptr);
static BreakpointModel* getInstance(DebugInterface& cpu);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -63,6 +62,10 @@ public:
void clear();
private:
BreakpointModel(DebugInterface& cpu, QObject* parent = nullptr);
DebugInterface& m_cpu;
std::vector<BreakpointMemcheck> m_breakpoints;
static std::map<BreakPointCpu, BreakpointModel*> s_instances;
};

View File

@@ -0,0 +1,176 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "BreakpointWidget.h"
#include "QtUtils.h"
#include "Debugger/DebuggerSettingsManager.h"
#include "BreakpointDialog.h"
#include "BreakpointModel.h"
#include <QtGui/QClipboard>
BreakpointWidget::BreakpointWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, DISALLOW_MULTIPLE_INSTANCES)
, m_model(BreakpointModel::getInstance(cpu()))
{
m_ui.setupUi(this);
m_ui.breakpointList->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &BreakpointWidget::openContextMenu);
connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &BreakpointWidget::onDoubleClicked);
m_ui.breakpointList->setModel(m_model);
for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes)
{
m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode);
i++;
}
}
void BreakpointWidget::onDoubleClicked(const QModelIndex& index)
{
if (index.isValid() && index.column() == BreakpointModel::OFFSET)
goToInDisassembler(m_model->data(index, BreakpointModel::DataRole).toUInt(), true);
}
void BreakpointWidget::openContextMenu(QPoint pos)
{
QMenu* menu = new QMenu(m_ui.breakpointList);
menu->setAttribute(Qt::WA_DeleteOnClose);
if (cpu().isAlive())
{
QAction* newAction = menu->addAction(tr("New"));
connect(newAction, &QAction::triggered, this, &BreakpointWidget::contextNew);
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (selModel->hasSelection())
{
QAction* editAction = menu->addAction(tr("Edit"));
connect(editAction, &QAction::triggered, this, &BreakpointWidget::contextEdit);
if (selModel->selectedIndexes().count() == 1)
{
QAction* copyAction = menu->addAction(tr("Copy"));
connect(copyAction, &QAction::triggered, this, &BreakpointWidget::contextCopy);
}
QAction* deleteAction = menu->addAction(tr("Delete"));
connect(deleteAction, &QAction::triggered, this, &BreakpointWidget::contextDelete);
}
}
menu->addSeparator();
if (m_model->rowCount() > 0)
{
QAction* actionExport = menu->addAction(tr("Copy all as CSV"));
connect(actionExport, &QAction::triggered, [this]() {
// It's important to use the Export Role here to allow pasting to be translation agnostic
QGuiApplication::clipboard()->setText(
QtUtils::AbstractItemModelToCSV(m_model, BreakpointModel::ExportRole, true));
});
}
if (cpu().isAlive())
{
QAction* actionImport = menu->addAction(tr("Paste from CSV"));
connect(actionImport, &QAction::triggered, this, &BreakpointWidget::contextPasteCSV);
if (cpu().getCpuType() == BREAKPOINT_EE)
{
QAction* actionLoad = menu->addAction(tr("Load from Settings"));
connect(actionLoad, &QAction::triggered, [this]() {
m_model->clear();
DebuggerSettingsManager::loadGameSettings(m_model);
});
QAction* actionSave = menu->addAction(tr("Save to Settings"));
connect(actionSave, &QAction::triggered, this, &BreakpointWidget::saveBreakpointsToDebuggerSettings);
}
}
menu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos));
}
void BreakpointWidget::contextCopy()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
QGuiApplication::clipboard()->setText(m_model->data(selModel->currentIndex()).toString());
}
void BreakpointWidget::contextDelete()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
QModelIndexList rows = selModel->selectedIndexes();
std::sort(rows.begin(), rows.end(), [](const QModelIndex& a, const QModelIndex& b) {
return a.row() > b.row();
});
for (const QModelIndex& index : rows)
{
m_model->removeRows(index.row(), 1);
}
}
void BreakpointWidget::contextNew()
{
BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), *m_model);
bpDialog->setAttribute(Qt::WA_DeleteOnClose);
bpDialog->show();
}
void BreakpointWidget::contextEdit()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
const int selectedRow = selModel->selectedIndexes().first().row();
auto bpObject = m_model->at(selectedRow);
BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), *m_model, bpObject, selectedRow);
bpDialog->setAttribute(Qt::WA_DeleteOnClose);
bpDialog->show();
}
void BreakpointWidget::contextPasteCSV()
{
QString csv = QGuiApplication::clipboard()->text();
// Skip header
csv = csv.mid(csv.indexOf('\n') + 1);
for (const QString& line : csv.split('\n'))
{
QStringList fields;
// In order to handle text with commas in them we must wrap values in quotes to mark
// where a value starts and end so that text commas aren't identified as delimiters.
// So matches each quote pair, parse it out, and removes the quotes to get the value.
QRegularExpression eachQuotePair(R"("([^"]|\\.)*")");
QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line);
while (it.hasNext())
{
QRegularExpressionMatch match = it.next();
QString matchedValue = match.captured(0);
fields << matchedValue.mid(1, matchedValue.length() - 2);
}
m_model->loadBreakpointFromFieldList(fields);
}
}
void BreakpointWidget::saveBreakpointsToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(m_model);
}

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "ui_BreakpointWidget.h"
#include "BreakpointModel.h"
#include "Debugger/DebuggerWidget.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/DisassemblyManager.h"
#include <QtWidgets/QMenu>
#include <QtWidgets/QTabBar>
#include <QtGui/QPainter>
class BreakpointWidget : public DebuggerWidget
{
Q_OBJECT
public:
BreakpointWidget(const DebuggerWidgetParameters& parameters);
void onDoubleClicked(const QModelIndex& index);
void openContextMenu(QPoint pos);
void contextCopy();
void contextDelete();
void contextNew();
void contextEdit();
void contextPasteCSV();
void saveBreakpointsToDebuggerSettings();
private:
Ui::BreakpointWidget m_ui;
BreakpointModel* m_model;
};

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BreakpointWidget</class>
<widget class="QWidget" name="BreakpointWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="breakpointList"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,732 +0,0 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "CpuWidget.h"
#include "DisassemblyWidget.h"
#include "BreakpointDialog.h"
#include "Models/BreakpointModel.h"
#include "Models/ThreadModel.h"
#include "Models/SavedAddressesModel.h"
#include "Debugger/DebuggerSettingsManager.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/MipsStackWalk.h"
#include "QtUtils.h"
#include "common/Console.h"
#include <QtGui/QClipboard>
#include <QtWidgets/QMessageBox>
#include <QtConcurrent/QtConcurrent>
#include <QtCore/QFutureWatcher>
#include <QtCore/QRegularExpression>
#include <QtCore/QRegularExpressionMatchIterator>
#include <QtCore/QStringList>
#include <QtWidgets/QScrollBar>
using namespace QtUtils;
using namespace MipsStackWalk;
CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
: m_cpu(cpu)
, m_bpModel(cpu)
, m_threadModel(cpu)
, m_stackModel(cpu)
, m_savedAddressesModel(cpu)
{
m_ui.setupUi(this);
connect(g_emu_thread, &EmuThread::onVMPaused, this, &CpuWidget::onVMPaused);
connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) {
if (title.isEmpty())
return;
// Don't overwrite users BPs/Saved Addresses unless they have a clean state.
if (m_bpModel.rowCount() == 0)
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
if (m_savedAddressesModel.rowCount() == 0)
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
});
connect(m_ui.registerWidget, &RegisterWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress);
connect(m_ui.memoryviewWidget, &MemoryViewWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress);
connect(m_ui.memoryviewWidget, &MemoryViewWidget::addToSavedAddresses, this, &CpuWidget::addAddressToSavedAddressesList);
connect(m_ui.registerWidget, &RegisterWidget::gotoInMemory, this, &CpuWidget::onGotoInMemory);
connect(m_ui.disassemblyWidget, &DisassemblyWidget::gotoInMemory, this, &CpuWidget::onGotoInMemory);
connect(m_ui.memoryviewWidget, &MemoryViewWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);
connect(m_ui.registerWidget, &RegisterWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);
connect(m_ui.disassemblyWidget, &DisassemblyWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);
connect(m_ui.disassemblyWidget, &DisassemblyWidget::breakpointsChanged, this, &CpuWidget::updateBreakpoints);
connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &CpuWidget::onBPListContextMenu);
connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &CpuWidget::onBPListDoubleClicked);
m_ui.breakpointList->setModel(&m_bpModel);
for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes)
{
m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode);
i++;
}
connect(&m_bpModel, &BreakpointModel::dataChanged, this, &CpuWidget::updateBreakpoints);
connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu);
connect(m_ui.threadList, &QTableView::doubleClicked, this, &CpuWidget::onThreadListDoubleClick);
m_threadProxyModel.setSourceModel(&m_threadModel);
m_threadProxyModel.setSortRole(Qt::UserRole);
m_ui.threadList->setModel(&m_threadProxyModel);
m_ui.threadList->setSortingEnabled(true);
m_ui.threadList->sortByColumn(ThreadModel::ThreadColumns::ID, Qt::SortOrder::AscendingOrder);
for (std::size_t i = 0; auto mode : ThreadModel::HeaderResizeModes)
{
m_ui.threadList->horizontalHeader()->setSectionResizeMode(i, mode);
i++;
}
connect(m_ui.stackList, &QTableView::customContextMenuRequested, this, &CpuWidget::onStackListContextMenu);
connect(m_ui.stackList, &QTableView::doubleClicked, this, &CpuWidget::onStackListDoubleClick);
m_ui.stackList->setModel(&m_stackModel);
for (std::size_t i = 0; auto mode : StackModel::HeaderResizeModes)
{
m_ui.stackList->horizontalHeader()->setSectionResizeMode(i, mode);
i++;
}
m_ui.disassemblyWidget->SetCpu(&cpu);
m_ui.registerWidget->SetCpu(&cpu);
m_ui.memoryviewWidget->SetCpu(&cpu);
this->repaint();
m_ui.savedAddressesList->setModel(&m_savedAddressesModel);
m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.savedAddressesList, &QTableView::customContextMenuRequested, this, &CpuWidget::onSavedAddressesListContextMenu);
for (std::size_t i = 0; auto mode : SavedAddressesModel::HeaderResizeModes)
{
m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode);
}
QTableView* savedAddressesTableView = m_ui.savedAddressesList;
connect(m_ui.savedAddressesList->model(), &QAbstractItemModel::dataChanged, [savedAddressesTableView](const QModelIndex& topLeft) {
savedAddressesTableView->resizeColumnToContents(topLeft.column());
});
setupSymbolTrees();
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
connect(m_ui.memorySearchWidget, &MemorySearchWidget::addAddressToSavedAddressesList, this, &CpuWidget::addAddressToSavedAddressesList);
connect(m_ui.memorySearchWidget, &MemorySearchWidget::goToAddressInDisassemblyView,
[this](u32 address) { m_ui.disassemblyWidget->gotoAddress(address, true); });
connect(m_ui.memorySearchWidget, &MemorySearchWidget::goToAddressInMemoryView, m_ui.memoryviewWidget, &MemoryViewWidget::gotoAddress);
connect(m_ui.memorySearchWidget, &MemorySearchWidget::switchToMemoryViewTab,
[this]() { m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); });
m_ui.memorySearchWidget->setCpu(&m_cpu);
m_refreshDebuggerTimer.setInterval(1000);
connect(&m_refreshDebuggerTimer, &QTimer::timeout, this, &CpuWidget::refreshDebugger);
m_refreshDebuggerTimer.start();
}
CpuWidget::~CpuWidget() = default;
void CpuWidget::setupSymbolTrees()
{
m_ui.tabFunctions->setLayout(new QVBoxLayout());
m_ui.tabGlobalVariables->setLayout(new QVBoxLayout());
m_ui.tabLocalVariables->setLayout(new QVBoxLayout());
m_ui.tabParameterVariables->setLayout(new QVBoxLayout());
m_ui.tabFunctions->layout()->setContentsMargins(0, 0, 0, 0);
m_ui.tabGlobalVariables->layout()->setContentsMargins(0, 0, 0, 0);
m_ui.tabLocalVariables->layout()->setContentsMargins(0, 0, 0, 0);
m_ui.tabParameterVariables->layout()->setContentsMargins(0, 0, 0, 0);
m_function_tree = new FunctionTreeWidget(m_cpu);
m_global_variable_tree = new GlobalVariableTreeWidget(m_cpu);
m_local_variable_tree = new LocalVariableTreeWidget(m_cpu);
m_parameter_variable_tree = new ParameterVariableTreeWidget(m_cpu);
m_function_tree->updateModel();
m_global_variable_tree->updateModel();
m_local_variable_tree->updateModel();
m_parameter_variable_tree->updateModel();
m_ui.tabFunctions->layout()->addWidget(m_function_tree);
m_ui.tabGlobalVariables->layout()->addWidget(m_global_variable_tree);
m_ui.tabLocalVariables->layout()->addWidget(m_local_variable_tree);
m_ui.tabParameterVariables->layout()->addWidget(m_parameter_variable_tree);
connect(m_function_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus);
connect(m_global_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus);
connect(m_local_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus);
connect(m_parameter_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus);
connect(m_function_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory);
connect(m_global_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory);
connect(m_local_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory);
connect(m_parameter_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory);
connect(m_function_tree, &SymbolTreeWidget::nameColumnClicked, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus);
connect(m_function_tree, &SymbolTreeWidget::locationColumnClicked, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus);
}
void CpuWidget::refreshDebugger()
{
if (!m_cpu.isAlive())
return;
m_ui.registerWidget->update();
m_ui.disassemblyWidget->update();
m_ui.memoryviewWidget->update();
m_ui.memorySearchWidget->update();
m_function_tree->updateModel();
m_global_variable_tree->updateModel();
m_local_variable_tree->updateModel();
m_parameter_variable_tree->updateModel();
}
void CpuWidget::reloadCPUWidgets()
{
updateThreads();
updateStackFrames();
m_ui.registerWidget->update();
m_ui.disassemblyWidget->update();
m_ui.memoryviewWidget->update();
m_function_tree->updateModel();
m_global_variable_tree->updateModel();
m_local_variable_tree->updateModel();
m_parameter_variable_tree->updateModel();
}
void CpuWidget::paintEvent(QPaintEvent* event)
{
m_ui.registerWidget->update();
m_ui.disassemblyWidget->update();
m_ui.memoryviewWidget->update();
m_ui.memorySearchWidget->update();
}
// The cpu shouldn't be alive when these are called
// But make sure it isn't just in case
void CpuWidget::onStepInto()
{
if (!m_cpu.isAlive() || !m_cpu.isCpuPaused())
return;
// Allow the cpu to skip this pc if it is a breakpoint
CBreakPoints::SetSkipFirst(m_cpu.getCpuType(), m_cpu.getPC());
const u32 pc = m_cpu.getPC();
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(&m_cpu, pc);
u32 bpAddr = pc + 0x4; // Default to the next instruction
if (info.isBranch)
{
if (!info.isConditional)
{
bpAddr = info.branchTarget;
}
else
{
if (info.conditionMet)
{
bpAddr = info.branchTarget;
}
else
{
bpAddr = pc + (2 * 4); // Skip branch delay slot
}
}
}
if (info.isSyscall)
bpAddr = info.branchTarget; // Syscalls are always taken
Host::RunOnCPUThread([cpu = &m_cpu, bpAddr] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
cpu->resumeCpu();
});
this->repaint();
}
void CpuWidget::onStepOut()
{
if (!m_cpu.isAlive() || !m_cpu.isCpuPaused())
return;
// Allow the cpu to skip this pc if it is a breakpoint
CBreakPoints::SetSkipFirst(m_cpu.getCpuType(), m_cpu.getPC());
if (m_stackModel.rowCount() < 2)
return;
Host::RunOnCPUThread([cpu = &m_cpu, stackModel = &m_stackModel] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), stackModel->data(stackModel->index(1, StackModel::PC), Qt::UserRole).toUInt(), true);
cpu->resumeCpu();
});
this->repaint();
}
void CpuWidget::onStepOver()
{
if (!m_cpu.isAlive() || !m_cpu.isCpuPaused())
return;
const u32 pc = m_cpu.getPC();
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(&m_cpu, pc);
u32 bpAddr = pc + 0x4; // Default to the next instruction
if (info.isBranch)
{
if (!info.isConditional)
{
if (info.isLinkedBranch) // jal, jalr
{
// it's a function call with a delay slot - skip that too
bpAddr += 4;
}
else // j, ...
{
// in case of absolute branches, set the breakpoint at the branch target
bpAddr = info.branchTarget;
}
}
else // beq, ...
{
if (info.conditionMet)
{
bpAddr = info.branchTarget;
}
else
{
bpAddr = pc + (2 * 4); // Skip branch delay slot
}
}
}
Host::RunOnCPUThread([cpu = &m_cpu, bpAddr] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
cpu->resumeCpu();
});
this->repaint();
}
void CpuWidget::onVMPaused()
{
// Stops us from telling the disassembly dialog to jump somwhere because breakpoint code paused the core.
if (CBreakPoints::GetCorePaused())
{
CBreakPoints::SetCorePaused(false);
}
else
{
m_ui.disassemblyWidget->gotoProgramCounterOnPause();
}
reloadCPUWidgets();
this->repaint();
}
void CpuWidget::updateBreakpoints()
{
m_bpModel.refreshData();
}
void CpuWidget::onBPListDoubleClicked(const QModelIndex& index)
{
if (index.isValid())
{
if (index.column() == BreakpointModel::OFFSET)
{
m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_bpModel.data(index, BreakpointModel::DataRole).toUInt());
}
}
}
void CpuWidget::onBPListContextMenu(QPoint pos)
{
QMenu* contextMenu = new QMenu(tr("Breakpoint List Context Menu"), m_ui.breakpointList);
if (m_cpu.isAlive())
{
QAction* newAction = new QAction(tr("New"), m_ui.breakpointList);
connect(newAction, &QAction::triggered, this, &CpuWidget::contextBPListNew);
contextMenu->addAction(newAction);
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (selModel->hasSelection())
{
QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList);
connect(editAction, &QAction::triggered, this, &CpuWidget::contextBPListEdit);
contextMenu->addAction(editAction);
if (selModel->selectedIndexes().count() == 1)
{
QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList);
connect(copyAction, &QAction::triggered, this, &CpuWidget::contextBPListCopy);
contextMenu->addAction(copyAction);
}
QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList);
connect(deleteAction, &QAction::triggered, this, &CpuWidget::contextBPListDelete);
contextMenu->addAction(deleteAction);
}
}
contextMenu->addSeparator();
if (m_bpModel.rowCount() > 0)
{
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.breakpointList);
connect(actionExport, &QAction::triggered, [this]() {
// It's important to use the Export Role here to allow pasting to be translation agnostic
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.breakpointList->model(), BreakpointModel::ExportRole, true));
});
contextMenu->addAction(actionExport);
}
if (m_cpu.isAlive())
{
QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList);
connect(actionImport, &QAction::triggered, this, &CpuWidget::contextBPListPasteCSV);
contextMenu->addAction(actionImport);
QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.breakpointList);
connect(actionLoad, &QAction::triggered, [this]() {
m_bpModel.clear();
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
});
contextMenu->addAction(actionLoad);
QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.breakpointList);
connect(actionSave, &QAction::triggered, this, &CpuWidget::saveBreakpointsToDebuggerSettings);
contextMenu->addAction(actionSave);
}
contextMenu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos));
}
void CpuWidget::onGotoInMemory(u32 address)
{
m_ui.memoryviewWidget->gotoAddress(address);
m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory);
}
void CpuWidget::contextBPListCopy()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
QGuiApplication::clipboard()->setText(m_bpModel.data(selModel->currentIndex()).toString());
}
void CpuWidget::contextBPListDelete()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
QModelIndexList rows = selModel->selectedIndexes();
std::sort(rows.begin(), rows.end(), [](const QModelIndex& a, const QModelIndex& b) {
return a.row() > b.row();
});
for (const QModelIndex& index : rows)
{
m_bpModel.removeRows(index.row(), 1);
}
}
void CpuWidget::contextBPListNew()
{
BreakpointDialog* bpDialog = new BreakpointDialog(this, &m_cpu, m_bpModel);
bpDialog->show();
}
void CpuWidget::contextBPListEdit()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
const int selectedRow = selModel->selectedIndexes().first().row();
auto bpObject = m_bpModel.at(selectedRow);
BreakpointDialog* bpDialog = new BreakpointDialog(this, &m_cpu, m_bpModel, bpObject, selectedRow);
bpDialog->show();
}
void CpuWidget::contextBPListPasteCSV()
{
QString csv = QGuiApplication::clipboard()->text();
// Skip header
csv = csv.mid(csv.indexOf('\n') + 1);
for (const QString& line : csv.split('\n'))
{
QStringList fields;
// In order to handle text with commas in them we must wrap values in quotes to mark
// where a value starts and end so that text commas aren't identified as delimiters.
// So matches each quote pair, parse it out, and removes the quotes to get the value.
QRegularExpression eachQuotePair(R"("([^"]|\\.)*")");
QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line);
while (it.hasNext())
{
QRegularExpressionMatch match = it.next();
QString matchedValue = match.captured(0);
fields << matchedValue.mid(1, matchedValue.length() - 2);
}
m_bpModel.loadBreakpointFromFieldList(fields);
}
}
void CpuWidget::onSavedAddressesListContextMenu(QPoint pos)
{
QMenu* contextMenu = new QMenu("Saved Addresses List Context Menu", m_ui.savedAddressesList);
QAction* newAction = new QAction(tr("New"), m_ui.savedAddressesList);
connect(newAction, &QAction::triggered, this, &CpuWidget::contextSavedAddressesListNew);
contextMenu->addAction(newAction);
const QModelIndex indexAtPos = m_ui.savedAddressesList->indexAt(pos);
const bool isIndexValid = indexAtPos.isValid();
if (isIndexValid)
{
if (m_cpu.isAlive())
{
QAction* goToAddressMemViewAction = new QAction(tr("Go to in Memory View"), m_ui.savedAddressesList);
connect(goToAddressMemViewAction, &QAction::triggered, this, [this, indexAtPos]() {
const QModelIndex rowAddressIndex = m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex());
m_ui.memoryviewWidget->gotoAddress(m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt());
m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory);
});
contextMenu->addAction(goToAddressMemViewAction);
QAction* goToAddressDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.savedAddressesList);
connect(goToAddressDisassemblyAction, &QAction::triggered, this, [this, indexAtPos]() {
const QModelIndex rowAddressIndex = m_ui.savedAddressesList->model()->index(indexAtPos.row(), 0, QModelIndex());
m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.savedAddressesList->model()->data(rowAddressIndex, Qt::UserRole).toUInt());
});
contextMenu->addAction(goToAddressDisassemblyAction);
}
QAction* copyAction = new QAction(indexAtPos.column() == 0 ? tr("Copy Address") : tr("Copy Text"), m_ui.savedAddressesList);
connect(copyAction, &QAction::triggered, [this, indexAtPos]() {
QGuiApplication::clipboard()->setText(m_ui.savedAddressesList->model()->data(indexAtPos, Qt::DisplayRole).toString());
});
contextMenu->addAction(copyAction);
}
if (m_ui.savedAddressesList->model()->rowCount() > 0)
{
QAction* actionExportCSV = new QAction(tr("Copy all as CSV"), m_ui.savedAddressesList);
connect(actionExportCSV, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.savedAddressesList->model(), Qt::DisplayRole, true));
});
contextMenu->addAction(actionExportCSV);
}
QAction* actionImportCSV = new QAction(tr("Paste from CSV"), m_ui.savedAddressesList);
connect(actionImportCSV, &QAction::triggered, this, &CpuWidget::contextSavedAddressesListPasteCSV);
contextMenu->addAction(actionImportCSV);
if (m_cpu.isAlive())
{
QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.savedAddressesList);
connect(actionLoad, &QAction::triggered, [this]() {
m_savedAddressesModel.clear();
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
});
contextMenu->addAction(actionLoad);
QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.savedAddressesList);
connect(actionSave, &QAction::triggered, this, &CpuWidget::saveSavedAddressesToDebuggerSettings);
contextMenu->addAction(actionSave);
}
if (isIndexValid)
{
QAction* deleteAction = new QAction(tr("Delete"), m_ui.savedAddressesList);
connect(deleteAction, &QAction::triggered, this, [this, indexAtPos]() {
m_ui.savedAddressesList->model()->removeRows(indexAtPos.row(), 1);
});
contextMenu->addAction(deleteAction);
}
contextMenu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos));
}
void CpuWidget::contextSavedAddressesListPasteCSV()
{
QString csv = QGuiApplication::clipboard()->text();
// Skip header
csv = csv.mid(csv.indexOf('\n') + 1);
for (const QString& line : csv.split('\n'))
{
QStringList fields;
// In order to handle text with commas in them we must wrap values in quotes to mark
// where a value starts and end so that text commas aren't identified as delimiters.
// So matches each quote pair, parse it out, and removes the quotes to get the value.
QRegularExpression eachQuotePair(R"("([^"]|\\.)*")");
QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line);
while (it.hasNext())
{
QRegularExpressionMatch match = it.next();
QString matchedValue = match.captured(0);
fields << matchedValue.mid(1, matchedValue.length() - 2);
}
m_savedAddressesModel.loadSavedAddressFromFieldList(fields);
}
}
void CpuWidget::contextSavedAddressesListNew()
{
qobject_cast<SavedAddressesModel*>(m_ui.savedAddressesList->model())->addRow();
const u32 rowCount = m_ui.savedAddressesList->model()->rowCount();
m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 0));
}
void CpuWidget::addAddressToSavedAddressesList(u32 address)
{
qobject_cast<SavedAddressesModel*>(m_ui.savedAddressesList->model())->addRow();
const u32 rowCount = m_ui.savedAddressesList->model()->rowCount();
const QModelIndex addressIndex = m_ui.savedAddressesList->model()->index(rowCount - 1, 0);
m_ui.tabWidget->setCurrentWidget(m_ui.tab_savedaddresses);
m_ui.savedAddressesList->model()->setData(addressIndex, address, Qt::UserRole);
m_ui.savedAddressesList->edit(m_ui.savedAddressesList->model()->index(rowCount - 1, 1));
}
void CpuWidget::updateThreads()
{
m_threadModel.refreshData();
}
void CpuWidget::onThreadListContextMenu(QPoint pos)
{
if (!m_ui.threadList->selectionModel()->hasSelection())
return;
QMenu* contextMenu = new QMenu(tr("Thread List Context Menu"), m_ui.threadList);
QAction* actionCopy = new QAction(tr("Copy"), m_ui.threadList);
connect(actionCopy, &QAction::triggered, [this]() {
const auto* selModel = m_ui.threadList->selectionModel();
if (!selModel->hasSelection())
return;
QGuiApplication::clipboard()->setText(m_ui.threadList->model()->data(selModel->currentIndex()).toString());
});
contextMenu->addAction(actionCopy);
contextMenu->addSeparator();
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.threadList);
connect(actionExport, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.threadList->model()));
});
contextMenu->addAction(actionExport);
contextMenu->popup(m_ui.threadList->viewport()->mapToGlobal(pos));
}
void CpuWidget::onThreadListDoubleClick(const QModelIndex& index)
{
switch (index.column())
{
case ThreadModel::ThreadColumns::ENTRY:
m_ui.memoryviewWidget->gotoAddress(m_ui.threadList->model()->data(index, Qt::UserRole).toUInt());
m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory);
break;
default: // Default to PC
m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.threadList->model()->data(m_ui.threadList->model()->index(index.row(), ThreadModel::ThreadColumns::PC), Qt::UserRole).toUInt());
break;
}
}
void CpuWidget::updateStackFrames()
{
m_stackModel.refreshData();
}
void CpuWidget::onStackListContextMenu(QPoint pos)
{
if (!m_ui.stackList->selectionModel()->hasSelection())
return;
QMenu* contextMenu = new QMenu(tr("Stack List Context Menu"), m_ui.stackList);
QAction* actionCopy = new QAction(tr("Copy"), m_ui.stackList);
connect(actionCopy, &QAction::triggered, [this]() {
const auto* selModel = m_ui.stackList->selectionModel();
if (!selModel->hasSelection())
return;
QGuiApplication::clipboard()->setText(m_ui.stackList->model()->data(selModel->currentIndex()).toString());
});
contextMenu->addAction(actionCopy);
contextMenu->addSeparator();
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.stackList);
connect(actionExport, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.stackList->model()));
});
contextMenu->addAction(actionExport);
contextMenu->popup(m_ui.stackList->viewport()->mapToGlobal(pos));
}
void CpuWidget::onStackListDoubleClick(const QModelIndex& index)
{
switch (index.column())
{
case StackModel::StackModel::ENTRY:
case StackModel::StackModel::ENTRY_LABEL:
m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::ENTRY), Qt::UserRole).toUInt());
break;
case StackModel::StackModel::SP:
m_ui.memoryviewWidget->gotoAddress(m_ui.stackList->model()->data(index, Qt::UserRole).toUInt());
m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory);
break;
default: // Default to PC
m_ui.disassemblyWidget->gotoAddressAndSetFocus(m_ui.stackList->model()->data(m_ui.stackList->model()->index(index.row(), StackModel::StackColumns::PC), Qt::UserRole).toUInt());
break;
}
}
void CpuWidget::saveBreakpointsToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(&m_bpModel);
}
void CpuWidget::saveSavedAddressesToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(&m_savedAddressesModel);
}

View File

@@ -1,97 +0,0 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "ui_CpuWidget.h"
#include "DebugTools/DebugInterface.h"
#include "Models/BreakpointModel.h"
#include "Models/ThreadModel.h"
#include "Models/StackModel.h"
#include "Models/SavedAddressesModel.h"
#include "Debugger/SymbolTree/SymbolTreeWidgets.h"
#include "QtHost.h"
#include <QtWidgets/QWidget>
#include <QtWidgets/QTableWidget>
#include <QtCore/QSortFilterProxyModel>
#include <QtCore/QTimer>
#include <vector>
using namespace MipsStackWalk;
class CpuWidget final : public QWidget
{
Q_OBJECT
public:
CpuWidget(QWidget* parent, DebugInterface& cpu);
~CpuWidget();
public slots:
void paintEvent(QPaintEvent* event);
void onStepInto();
void onStepOver();
void onStepOut();
void onVMPaused();
void updateBreakpoints();
void onBPListDoubleClicked(const QModelIndex& index);
void onBPListContextMenu(QPoint pos);
void onGotoInMemory(u32 address);
void contextBPListCopy();
void contextBPListDelete();
void contextBPListNew();
void contextBPListEdit();
void contextBPListPasteCSV();
void onSavedAddressesListContextMenu(QPoint pos);
void contextSavedAddressesListPasteCSV();
void contextSavedAddressesListNew();
void addAddressToSavedAddressesList(u32 address);
void updateThreads();
void onThreadListDoubleClick(const QModelIndex& index);
void onThreadListContextMenu(QPoint pos);
void updateStackFrames();
void onStackListContextMenu(QPoint pos);
void onStackListDoubleClick(const QModelIndex& index);
void refreshDebugger();
void reloadCPUWidgets();
void saveBreakpointsToDebuggerSettings();
void saveSavedAddressesToDebuggerSettings();
private:
void setupSymbolTrees();
std::vector<QTableWidget*> m_registerTableViews;
QMenu* m_stacklistContextMenu = 0;
QMenu* m_funclistContextMenu = 0;
QMenu* m_moduleTreeContextMenu = 0;
QTimer m_refreshDebuggerTimer;
Ui::CpuWidget m_ui;
DebugInterface& m_cpu;
BreakpointModel m_bpModel;
ThreadModel m_threadModel;
QSortFilterProxyModel m_threadProxyModel;
StackModel m_stackModel;
SavedAddressesModel m_savedAddressesModel;
FunctionTreeWidget* m_function_tree = nullptr;
GlobalVariableTreeWidget* m_global_variable_tree = nullptr;
LocalVariableTreeWidget* m_local_variable_tree = nullptr;
ParameterVariableTreeWidget* m_parameter_variable_tree = nullptr;
};

View File

@@ -1,445 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CpuWidget</class>
<widget class="QWidget" name="CpuWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>668</width>
<height>563</height>
</rect>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="verticalSplitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QSplitter" name="horizontalSplitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QTabWidget" name="tabWidgetRegFunc">
<property name="minimumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabRegisters">
<attribute name="title">
<string>Registers</string>
</attribute>
<layout class="QHBoxLayout" name="registerLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="RegisterWidget" name="registerWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>100</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabFunctions">
<attribute name="title">
<string>Functions</string>
</attribute>
</widget>
<widget class="QWidget" name="tabMemorySearch">
<attribute name="title">
<string>Memory Search</string>
</attribute>
<layout class="QHBoxLayout" name="memorySearchLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="MemorySearchWidget" name="memorySearchWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="DisassemblyWidget" name="disassemblyWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QTabWidget" name="tabWidget">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="tabPosition">
<enum>QTabWidget::North</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_memory">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title">
<string>Memory</string>
</attribute>
<layout class="QHBoxLayout" name="memoryLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="MemoryViewWidget" name="memoryviewWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>100</height>
</size>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_breakpoints">
<attribute name="title">
<string>Breakpoints</string>
</attribute>
<layout class="QHBoxLayout" name="breakpointsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="breakpointList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_threads">
<attribute name="title">
<string>Threads</string>
</attribute>
<layout class="QHBoxLayout" name="threadsLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="threadList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_callstack">
<attribute name="title">
<string>Active Call Stack</string>
</attribute>
<layout class="QHBoxLayout" name="callStackLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="stackList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_savedaddresses">
<attribute name="title">
<string>Saved Addresses</string>
</attribute>
<layout class="QHBoxLayout" name="savedAddressesLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="savedAddressesList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabGlobalVariables">
<attribute name="title">
<string>Globals</string>
</attribute>
</widget>
<widget class="QWidget" name="tabLocalVariables">
<attribute name="title">
<string>Locals</string>
</attribute>
</widget>
<widget class="QWidget" name="tabParameterVariables">
<attribute name="title">
<string>Parameters</string>
</attribute>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DisassemblyWidget</class>
<extends>QWidget</extends>
<header>pcsx2-qt/Debugger/DisassemblyWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>RegisterWidget</class>
<extends>QWidget</extends>
<header>pcsx2-qt/Debugger/RegisterWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MemoryViewWidget</class>
<extends>QWidget</extends>
<header>pcsx2-qt/Debugger/MemoryViewWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MemorySearchWidget</class>
<extends>QWidget</extends>
<header>pcsx2-qt/Debugger/MemorySearchWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include <common/Pcsx2Types.h>
#include <QtCore/qttranslation.h>
namespace DebuggerEvents
{
struct Event
{
virtual ~Event() = default;
};
// Sent when a debugger widget is first created, and subsequently broadcast
// to all debugger widgets at regular intervals.
struct Refresh : Event
{
};
// Go to the address in a disassembly or memory view and switch to that tab.
struct GoToAddress : Event
{
enum Filter
{
NONE,
DISASSEMBLER,
MEMORY_VIEW
};
u32 address = 0;
// Prevent the memory view from handling events for jumping to functions
// and vice versa.
Filter filter = NONE;
bool switch_to_tab = true;
static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Go to in");
};
// The state of the VM has changed and widgets should be updated to reflect
// the new state (e.g. the VM has been paused).
struct VMUpdate : Event
{
};
// Add the address to the saved addresses list and switch to that tab.
struct AddToSavedAddresses : Event
{
u32 address = 0;
bool switch_to_tab = true;
static constexpr const char* ACTION_PREFIX = QT_TRANSLATE_NOOP("DebuggerEvents", "Add to");
};
} // namespace DebuggerEvents

View File

@@ -10,12 +10,12 @@
#include "common/Console.h"
#include "VMManager.h"
#include "Models/BreakpointModel.h"
std::mutex DebuggerSettingsManager::writeLock;
const QString DebuggerSettingsManager::settingsFileVersion = "0.00";
QJsonObject DebuggerSettingsManager::loadGameSettingsJSON() {
QJsonObject DebuggerSettingsManager::loadGameSettingsJSON()
{
std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly))
@@ -134,7 +134,7 @@ void DebuggerSettingsManager::saveGameSettings(QAbstractTableModel* abstractTabl
{
const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
if (path.empty())
return;
return;
const std::lock_guard<std::mutex> lock(writeLock);
QJsonObject loadedSettings = loadGameSettingsJSON();

View File

@@ -6,8 +6,8 @@
#include <mutex>
#include "Models/BreakpointModel.h"
#include "Models/SavedAddressesModel.h"
#include "Breakpoints/BreakpointModel.h"
#include "Memory/SavedAddressesModel.h"
class DebuggerSettingsManager final
{

View File

@@ -0,0 +1,312 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DebuggerWidget.h"
#include "Debugger/DebuggerWindow.h"
#include "Debugger/JsonValueWrapper.h"
#include "Debugger/Docking/DockManager.h"
#include "Debugger/Docking/DockTables.h"
#include "DebugTools/DebugInterface.h"
#include "common/Assertions.h"
DebuggerWidget::DebuggerWidget(const DebuggerWidgetParameters& parameters, u32 flags)
: QWidget(parameters.parent)
, m_unique_name(parameters.unique_name)
, m_cpu(parameters.cpu)
, m_cpu_override(parameters.cpu_override)
, m_flags(flags)
{
updateStyleSheet();
}
DebugInterface& DebuggerWidget::cpu() const
{
if (m_cpu_override.has_value())
return DebugInterface::get(*m_cpu_override);
pxAssertRel(m_cpu, "DebuggerWidget::cpu called on object with null cpu.");
return *m_cpu;
}
QString DebuggerWidget::uniqueName() const
{
return m_unique_name;
}
QString DebuggerWidget::displayName() const
{
QString name = displayNameWithoutSuffix();
// If there are multiple debugger widgets of the same name, append a number
// to the display name.
if (m_display_name_suffix_number.has_value())
name = tr("%1 #%2").arg(name).arg(*m_display_name_suffix_number);
if (m_cpu_override)
name = tr("%1 (%2)").arg(name).arg(DebugInterface::cpuName(*m_cpu_override));
return name;
}
QString DebuggerWidget::displayNameWithoutSuffix() const
{
return m_translated_display_name;
}
QString DebuggerWidget::customDisplayName() const
{
return m_custom_display_name;
}
bool DebuggerWidget::setCustomDisplayName(QString display_name)
{
if (display_name.size() > DockUtils::MAX_DOCK_WIDGET_NAME_SIZE)
return false;
m_custom_display_name = display_name;
return true;
}
bool DebuggerWidget::isPrimary() const
{
return m_is_primary;
}
void DebuggerWidget::setPrimary(bool is_primary)
{
m_is_primary = is_primary;
}
bool DebuggerWidget::setCpu(DebugInterface& new_cpu)
{
BreakPointCpu before = cpu().getCpuType();
m_cpu = &new_cpu;
BreakPointCpu after = cpu().getCpuType();
return before == after;
}
std::optional<BreakPointCpu> DebuggerWidget::cpuOverride() const
{
return m_cpu_override;
}
bool DebuggerWidget::setCpuOverride(std::optional<BreakPointCpu> new_cpu)
{
BreakPointCpu before = cpu().getCpuType();
m_cpu_override = new_cpu;
BreakPointCpu after = cpu().getCpuType();
return before == after;
}
bool DebuggerWidget::handleEvent(const DebuggerEvents::Event& event)
{
auto [begin, end] = m_event_handlers.equal_range(typeid(event).name());
for (auto handler = begin; handler != end; handler++)
if (handler->second(event))
return true;
return false;
}
bool DebuggerWidget::acceptsEventType(const char* event_type)
{
auto [begin, end] = m_event_handlers.equal_range(event_type);
return begin != end;
}
void DebuggerWidget::toJson(JsonValueWrapper& json)
{
std::string custom_display_name_str = m_custom_display_name.toStdString();
rapidjson::Value custom_display_name;
custom_display_name.SetString(custom_display_name_str.c_str(), custom_display_name_str.size(), json.allocator());
json.value().AddMember("customDisplayName", custom_display_name, json.allocator());
json.value().AddMember("isPrimary", m_is_primary, json.allocator());
}
bool DebuggerWidget::fromJson(const JsonValueWrapper& json)
{
auto custom_display_name = json.value().FindMember("customDisplayName");
if (custom_display_name != json.value().MemberEnd() && custom_display_name->value.IsString())
{
m_custom_display_name = QString(custom_display_name->value.GetString());
m_custom_display_name.truncate(DockUtils::MAX_DOCK_WIDGET_NAME_SIZE);
}
auto is_primary = json.value().FindMember("isPrimary");
if (is_primary != json.value().MemberEnd() && is_primary->value.IsBool())
m_is_primary = is_primary->value.GetBool();
return true;
}
void DebuggerWidget::switchToThisTab()
{
g_debugger_window->dockManager().switchToDebuggerWidget(this);
}
bool DebuggerWidget::supportsMultipleInstances()
{
return !(m_flags & DISALLOW_MULTIPLE_INSTANCES);
}
void DebuggerWidget::retranslateDisplayName()
{
if (!m_custom_display_name.isEmpty())
{
m_translated_display_name = m_custom_display_name;
}
else
{
auto description = DockTables::DEBUGGER_WIDGETS.find(metaObject()->className());
if (description != DockTables::DEBUGGER_WIDGETS.end())
m_translated_display_name = QCoreApplication::translate("DebuggerWidget", description->second.display_name);
else
m_translated_display_name = QString();
}
}
std::optional<int> DebuggerWidget::displayNameSuffixNumber() const
{
return m_display_name_suffix_number;
}
void DebuggerWidget::setDisplayNameSuffixNumber(std::optional<int> suffix_number)
{
m_display_name_suffix_number = suffix_number;
}
void DebuggerWidget::updateStyleSheet()
{
QString stylesheet;
if (m_flags & MONOSPACE_FONT)
{
// Easiest way to handle cross platform monospace fonts
// There are issues related to TabWidget -> Children font inheritance otherwise
#if defined(WIN32)
stylesheet += QStringLiteral("font-family: 'Lucida Console';");
#elif defined(__APPLE__)
stylesheet += QStringLiteral("font-family: 'Monaco';");
#else
stylesheet += QStringLiteral("font-family: 'Monospace';");
#endif
}
// HACK: Make the font size smaller without applying a stylesheet to the
// whole window (which would impact performance).
if (g_debugger_window)
stylesheet += QString("font-size: %1pt;").arg(g_debugger_window->fontSize());
setStyleSheet(stylesheet);
}
void DebuggerWidget::goToInDisassembler(u32 address, bool switch_to_tab)
{
DebuggerEvents::GoToAddress event;
event.address = address;
event.filter = DebuggerEvents::GoToAddress::DISASSEMBLER;
event.switch_to_tab = switch_to_tab;
DebuggerWidget::sendEvent(std::move(event));
}
void DebuggerWidget::goToInMemoryView(u32 address, bool switch_to_tab)
{
DebuggerEvents::GoToAddress event;
event.address = address;
event.filter = DebuggerEvents::GoToAddress::MEMORY_VIEW;
event.switch_to_tab = switch_to_tab;
DebuggerWidget::sendEvent(std::move(event));
}
void DebuggerWidget::sendEventImplementation(const DebuggerEvents::Event& event)
{
if (!g_debugger_window)
return;
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
if (widget->isPrimary() && widget->handleEvent(event))
return;
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
if (!widget->isPrimary() && widget->handleEvent(event))
return;
}
void DebuggerWidget::broadcastEventImplementation(const DebuggerEvents::Event& event)
{
if (!g_debugger_window)
return;
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
widget->handleEvent(event);
}
std::vector<QAction*> DebuggerWidget::createEventActionsImplementation(
QMenu* menu,
u32 max_top_level_actions,
bool skip_self,
const char* event_type,
const char* action_prefix,
std::function<const DebuggerEvents::Event*()> event_func)
{
if (!g_debugger_window)
return {};
std::vector<DebuggerWidget*> receivers;
for (const auto& [unique_name, widget] : g_debugger_window->dockManager().debuggerWidgets())
if ((!skip_self || widget != this) && widget->acceptsEventType(event_type))
receivers.emplace_back(widget);
std::sort(receivers.begin(), receivers.end(), [&](const DebuggerWidget* lhs, const DebuggerWidget* rhs) {
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
});
QMenu* submenu = nullptr;
if (receivers.size() > max_top_level_actions)
{
QString title_format = QCoreApplication::translate("DebuggerEvent", "%1...");
submenu = new QMenu(title_format.arg(QCoreApplication::translate("DebuggerEvent", action_prefix)), menu);
}
std::vector<QAction*> actions;
for (size_t i = 0; i < receivers.size(); i++)
{
DebuggerWidget* receiver = receivers[i];
QAction* action;
if (!submenu || i + 1 < max_top_level_actions)
{
QString title_format = QCoreApplication::translate("DebuggerEvent", "%1 %2");
QString event_title = QCoreApplication::translate("DebuggerEvent", action_prefix);
QString title = title_format.arg(event_title).arg(receiver->displayName());
action = new QAction(title, menu);
menu->addAction(action);
}
else
{
action = new QAction(receiver->displayName(), submenu);
submenu->addAction(action);
}
connect(action, &QAction::triggered, receiver, [receiver, event_func]() {
const DebuggerEvents::Event* event = event_func();
if (event)
receiver->handleEvent(*event);
});
actions.emplace_back(action);
}
if (submenu)
menu->addMenu(submenu);
return actions;
}

View File

@@ -0,0 +1,198 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "QtHost.h"
#include "Debugger/DebuggerEvents.h"
#include "DebugTools/DebugInterface.h"
#include <QtWidgets/QWidget>
class JsonValueWrapper;
// Container for variables to be passed to the constructor of DebuggerWidget.
struct DebuggerWidgetParameters
{
QString unique_name;
DebugInterface* cpu = nullptr;
std::optional<BreakPointCpu> cpu_override;
QWidget* parent = nullptr;
};
// The base class for the contents of the dock widgets in the debugger.
class DebuggerWidget : public QWidget
{
Q_OBJECT
public:
QString uniqueName() const;
// Get the translated name that should be displayed for this widget.
QString displayName() const;
QString displayNameWithoutSuffix() const;
QString customDisplayName() const;
bool setCustomDisplayName(QString display_name);
bool isPrimary() const;
void setPrimary(bool is_primary);
// Get the effective debug interface associated with this particular widget
// if it's set, otherwise return the one associated with the layout that
// contains this widget.
DebugInterface& cpu() const;
// Set the debug interface associated with the layout. If false is returned,
// we have to recreate the object.
bool setCpu(DebugInterface& new_cpu);
// Get the CPU associated with this particular widget.
std::optional<BreakPointCpu> cpuOverride() const;
// Set the CPU associated with the individual dock widget. If false is
// returned, we have to recreate the object.
bool setCpuOverride(std::optional<BreakPointCpu> new_cpu);
// Send each open debugger widget an event in turn, until one handles it.
template <typename Event>
static void sendEvent(Event event)
{
if (!QtHost::IsOnUIThread())
{
QtHost::RunOnUIThread([event = std::move(event)]() {
DebuggerWidget::sendEventImplementation(event);
});
return;
}
sendEventImplementation(event);
}
// Send all open debugger widgets an event.
template <typename Event>
static void broadcastEvent(Event event)
{
if (!QtHost::IsOnUIThread())
{
QtHost::RunOnUIThread([event = std::move(event)]() {
DebuggerWidget::broadcastEventImplementation(event);
});
return;
}
broadcastEventImplementation(event);
}
// Register a handler callback for the specified type of event.
template <typename Event>
void receiveEvent(std::function<bool(const Event&)> callback)
{
m_event_handlers.emplace(
typeid(Event).name(),
[callback](const DebuggerEvents::Event& event) -> bool {
return callback(static_cast<const Event&>(event));
});
}
// Register a handler member function for the specified type of event.
template <typename Event, typename SubClass>
void receiveEvent(bool (SubClass::*function)(const Event& event))
{
m_event_handlers.emplace(
typeid(Event).name(),
[this, function](const DebuggerEvents::Event& event) -> bool {
return (*static_cast<SubClass*>(this).*function)(static_cast<const Event&>(event));
});
}
// Call the handler callback for the specified event.
bool handleEvent(const DebuggerEvents::Event& event);
// Check if this debugger widget can receive the specified type of event.
bool acceptsEventType(const char* event_type);
// Generates context menu actions to send an event to each debugger widget
// that can receive it. A submenu is generated if the number of possible
// receivers exceeds max_top_level_actions. If skip_self is true, actions
// are only generated if the sender and receiver aren't the same object.
template <typename Event>
std::vector<QAction*> createEventActions(
QMenu* menu,
std::function<std::optional<Event>()> event_func,
bool skip_self = true,
u32 max_top_level_actions = 5)
{
return createEventActionsImplementation(
menu, max_top_level_actions, skip_self, typeid(Event).name(), Event::ACTION_PREFIX,
[event_func]() -> DebuggerEvents::Event* {
static std::optional<Event> event;
event = event_func();
if (!event.has_value())
return nullptr;
return static_cast<DebuggerEvents::Event*>(&(*event));
});
}
virtual void toJson(JsonValueWrapper& json);
virtual bool fromJson(const JsonValueWrapper& json);
void switchToThisTab();
bool supportsMultipleInstances();
void retranslateDisplayName();
std::optional<int> displayNameSuffixNumber() const;
void setDisplayNameSuffixNumber(std::optional<int> suffix_number);
void updateStyleSheet();
static void goToInDisassembler(u32 address, bool switch_to_tab);
static void goToInMemoryView(u32 address, bool switch_to_tab);
protected:
enum Flags
{
NO_DEBUGGER_FLAGS = 0,
// Prevent the user from opening multiple dock widgets of this type.
DISALLOW_MULTIPLE_INSTANCES = 1 << 0,
// Apply a stylesheet that gives all the text a monospace font.
MONOSPACE_FONT = 1 << 1
};
DebuggerWidget(const DebuggerWidgetParameters& parameters, u32 flags);
private:
static void sendEventImplementation(const DebuggerEvents::Event& event);
static void broadcastEventImplementation(const DebuggerEvents::Event& event);
std::vector<QAction*> createEventActionsImplementation(
QMenu* menu,
u32 max_top_level_actions,
bool skip_self,
const char* event_type,
const char* action_prefix,
std::function<const DebuggerEvents::Event*()> event_func);
QString m_unique_name;
// A user-defined name, or an empty string if no name was specified so that
// the default names can be retranslated on the fly.
QString m_custom_display_name;
QString m_translated_display_name;
std::optional<int> m_display_name_suffix_number;
// Primary debugger widgets will be chosen to handle events first. For
// example, clicking on an address to go to it in the primary memory view.
bool m_is_primary = false;
DebugInterface* m_cpu;
std::optional<BreakPointCpu> m_cpu_override;
u32 m_flags;
std::multimap<std::string, std::function<bool(const DebuggerEvents::Event&)>> m_event_handlers;
};

View File

@@ -3,109 +3,366 @@
#include "DebuggerWindow.h"
#include "Debugger/DebuggerWidget.h"
#include "Debugger/Docking/DockManager.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/MIPSAnalyst.h"
#include "DebugTools/MipsStackWalk.h"
#include "DebugTools/SymbolImporter.h"
#include "VMManager.h"
#include "QtHost.h"
#include "MainWindow.h"
#include "AnalysisOptionsDialog.h"
#include <QtWidgets/QMessageBox>
DebuggerWindow* g_debugger_window = nullptr;
DebuggerWindow::DebuggerWindow(QWidget* parent)
: QMainWindow(parent)
: KDDockWidgets::QtWidgets::MainWindow(QStringLiteral("DebuggerWindow"), {}, parent)
, m_dock_manager(new DockManager(this))
{
m_ui.setupUi(this);
// Easiest way to handle cross platform monospace fonts
// There are issues related to TabWidget -> Children font inheritance otherwise
#if defined(WIN32)
m_ui.cpuTabs->setStyleSheet(QStringLiteral("font: 8pt 'Lucida Console'"));
#elif defined(__APPLE__)
m_ui.cpuTabs->setStyleSheet(QStringLiteral("font: 10pt 'Monaco'"));
#else
m_ui.cpuTabs->setStyleSheet(QStringLiteral("font: 8pt 'Monospace'"));
#endif
g_debugger_window = this;
setupDefaultToolBarState();
setupFonts();
restoreWindowGeometry();
m_dock_manager->loadLayouts();
connect(m_ui.actionAnalyse, &QAction::triggered, this, &DebuggerWindow::onAnalyse);
connect(m_ui.actionSettings, &QAction::triggered, this, &DebuggerWindow::onSettings);
connect(m_ui.actionGameSettings, &QAction::triggered, this, &DebuggerWindow::onGameSettings);
connect(m_ui.actionClose, &QAction::triggered, this, &DebuggerWindow::close);
connect(m_ui.actionOnTop, &QAction::triggered, this, [this](bool checked) {
if (checked)
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
else
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
show();
});
connect(m_ui.actionRun, &QAction::triggered, this, &DebuggerWindow::onRunPause);
connect(m_ui.actionStepInto, &QAction::triggered, this, &DebuggerWindow::onStepInto);
connect(m_ui.actionStepOver, &QAction::triggered, this, &DebuggerWindow::onStepOver);
connect(m_ui.actionStepOut, &QAction::triggered, this, &DebuggerWindow::onStepOut);
connect(m_ui.actionAnalyse, &QAction::triggered, this, &DebuggerWindow::onAnalyse);
connect(m_ui.actionOnTop, &QAction::triggered, [this] { this->setWindowFlags(this->windowFlags() ^ Qt::WindowStaysOnTopHint); this->show(); });
connect(g_emu_thread, &EmuThread::onVMPaused, this, &DebuggerWindow::onVMStateChanged);
connect(g_emu_thread, &EmuThread::onVMResumed, this, &DebuggerWindow::onVMStateChanged);
connect(m_ui.actionShutDown, &QAction::triggered, [this]() {
if (currentCPU() && currentCPU()->isAlive())
g_emu_thread->shutdownVM(false);
});
onVMStateChanged(); // If we missed a state change while we weren't loaded
connect(m_ui.actionReset, &QAction::triggered, [this]() {
if (currentCPU() && currentCPU()->isAlive())
g_emu_thread->resetVM();
});
// We can't do this in the designer, but we want to right align the actionOnTop action in the toolbar
QWidget* spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_ui.toolBar->insertWidget(m_ui.actionAnalyse, spacer);
connect(m_ui.menuTools, &QMenu::aboutToShow, this, [this]() {
m_dock_manager->createToolsMenu(m_ui.menuTools);
});
m_cpuWidget_r5900 = new CpuWidget(this, r5900Debug);
m_cpuWidget_r3000 = new CpuWidget(this, r3000Debug);
connect(m_ui.menuWindows, &QMenu::aboutToShow, this, [this]() {
m_dock_manager->createWindowsMenu(m_ui.menuWindows);
});
m_ui.cpuTabs->addTab(m_cpuWidget_r5900, "R5900");
m_ui.cpuTabs->addTab(m_cpuWidget_r3000, "R3000");
}
connect(m_ui.actionResetAllLayouts, &QAction::triggered, this, [this]() {
QString text = tr("Are you sure you want to reset all layouts?");
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
return;
DebuggerWindow::~DebuggerWindow() = default;
m_dock_manager->resetAllLayouts();
});
// There is no straightforward way to set the tab text to bold in Qt
// Sorry colour blind people, but this is the best we can do for now
void DebuggerWindow::setTabActiveStyle(BreakPointCpu enabledCpu)
{
m_ui.cpuTabs->tabBar()->setTabTextColor(m_ui.cpuTabs->indexOf(m_cpuWidget_r5900), (enabledCpu == BREAKPOINT_EE) ? Qt::red : this->palette().text().color());
m_ui.cpuTabs->tabBar()->setTabTextColor(m_ui.cpuTabs->indexOf(m_cpuWidget_r3000), (enabledCpu == BREAKPOINT_IOP) ? Qt::red : this->palette().text().color());
}
connect(m_ui.actionResetDefaultLayouts, &QAction::triggered, this, [this]() {
QString text = tr("Are you sure you want to reset the default layouts?");
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
return;
void DebuggerWindow::onVMStateChanged()
{
if (!QtHost::IsVMPaused())
m_dock_manager->resetDefaultLayouts();
});
connect(g_emu_thread, &EmuThread::onVMPaused, this, []() {
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
});
connect(g_emu_thread, &EmuThread::onVMStarting, this, &DebuggerWindow::onVMStarting);
connect(g_emu_thread, &EmuThread::onVMPaused, this, &DebuggerWindow::onVMPaused);
connect(g_emu_thread, &EmuThread::onVMResumed, this, &DebuggerWindow::onVMResumed);
connect(g_emu_thread, &EmuThread::onVMStopped, this, &DebuggerWindow::onVMStopped);
if (QtHost::IsVMValid())
{
m_ui.actionRun->setText(tr("Pause"));
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("pause-line")));
m_ui.actionStepInto->setEnabled(false);
m_ui.actionStepOver->setEnabled(false);
m_ui.actionStepOut->setEnabled(false);
setTabActiveStyle(BREAKPOINT_IOP_AND_EE);
onVMStarting();
if (QtHost::IsVMPaused())
onVMPaused();
else
onVMResumed();
}
else
{
m_ui.actionRun->setText(tr("Run"));
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("play-line")));
m_ui.actionStepInto->setEnabled(true);
m_ui.actionStepOver->setEnabled(true);
m_ui.actionStepOut->setEnabled(true);
// Switch to the CPU tab that triggered the breakpoint
// Also bold the tab text to indicate that a breakpoint was triggered
if (CBreakPoints::GetBreakpointTriggered())
{
const BreakPointCpu triggeredCpu = CBreakPoints::GetBreakpointTriggeredCpu();
setTabActiveStyle(triggeredCpu);
switch (triggeredCpu)
{
case BREAKPOINT_EE:
m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r5900);
break;
case BREAKPOINT_IOP:
m_ui.cpuTabs->setCurrentWidget(m_cpuWidget_r3000);
break;
default:
break;
}
Host::RunOnCPUThread([] {
CBreakPoints::ClearTemporaryBreakPoints();
CBreakPoints::SetBreakpointTriggered(false, BREAKPOINT_IOP_AND_EE);
// Our current PC is on a breakpoint.
// When we run the core again, we want to skip this breakpoint and run
CBreakPoints::SetSkipFirst(BREAKPOINT_EE, r5900Debug.getPC());
CBreakPoints::SetSkipFirst(BREAKPOINT_IOP, r3000Debug.getPC());
});
}
onVMStopped();
}
return;
m_dock_manager->switchToLayout(0);
QMenuBar* menu_bar = menuBar();
setMenuWidget(m_dock_manager->createLayoutSwitcher(menu_bar));
Host::RunOnCPUThread([]() {
R5900SymbolImporter.OnDebuggerOpened();
});
QTimer* refresh_timer = new QTimer(this);
connect(refresh_timer, &QTimer::timeout, this, []() {
DebuggerWidget::broadcastEvent(DebuggerEvents::Refresh());
});
refresh_timer->start(1000);
}
DebuggerWindow* DebuggerWindow::getInstance()
{
if (!g_debugger_window)
createInstance();
return g_debugger_window;
}
DebuggerWindow* DebuggerWindow::createInstance()
{
// Setup KDDockWidgets.
DockManager::configureDockingSystem();
if (g_debugger_window)
destroyInstance();
return new DebuggerWindow(nullptr);
}
void DebuggerWindow::destroyInstance()
{
if (g_debugger_window)
g_debugger_window->close();
}
bool DebuggerWindow::shouldShowOnStartup()
{
return Host::GetBaseBoolSettingValue("Debugger/UserInterface", "ShowOnStartup", false);
}
DockManager& DebuggerWindow::dockManager()
{
return *m_dock_manager;
}
void DebuggerWindow::setupDefaultToolBarState()
{
// Hiding all the toolbars lets us save the default state of the window with
// all the toolbars hidden. The DockManager will show the appropriate ones
// later anyway.
for (QToolBar* toolbar : findChildren<QToolBar*>())
toolbar->hide();
m_default_toolbar_state = saveState();
for (QToolBar* toolbar : findChildren<QToolBar*>())
connect(toolbar, &QToolBar::topLevelChanged, m_dock_manager, &DockManager::updateToolBarLockState);
}
void DebuggerWindow::clearToolBarState()
{
restoreState(m_default_toolbar_state);
}
void DebuggerWindow::setupFonts()
{
m_font_size = Host::GetBaseIntSettingValue("Debugger/UserInterface", "FontSize", DEFAULT_FONT_SIZE);
if (m_font_size < MINIMUM_FONT_SIZE || m_font_size > MAXIMUM_FONT_SIZE)
m_font_size = DEFAULT_FONT_SIZE;
m_ui.actionIncreaseFontSize->setShortcuts(QKeySequence::ZoomIn);
connect(m_ui.actionIncreaseFontSize, &QAction::triggered, this, [this]() {
if (m_font_size >= MAXIMUM_FONT_SIZE)
return;
m_font_size++;
updateFontActions();
updateStyleSheets();
saveFontSize();
});
m_ui.actionDecreaseFontSize->setShortcut(QKeySequence::ZoomOut);
connect(m_ui.actionDecreaseFontSize, &QAction::triggered, this, [this]() {
if (m_font_size <= MINIMUM_FONT_SIZE)
return;
m_font_size--;
updateFontActions();
updateStyleSheets();
saveFontSize();
});
connect(m_ui.actionResetFontSize, &QAction::triggered, this, [this]() {
m_font_size = DEFAULT_FONT_SIZE;
updateFontActions();
updateStyleSheets();
saveFontSize();
});
updateFontActions();
updateStyleSheets();
}
void DebuggerWindow::updateFontActions()
{
m_ui.actionIncreaseFontSize->setEnabled(m_font_size < MAXIMUM_FONT_SIZE);
m_ui.actionDecreaseFontSize->setEnabled(m_font_size > MINIMUM_FONT_SIZE);
m_ui.actionResetFontSize->setEnabled(m_font_size != DEFAULT_FONT_SIZE);
}
void DebuggerWindow::saveFontSize()
{
Host::SetBaseIntSettingValue("Debugger/UserInterface", "FontSize", m_font_size);
Host::CommitBaseSettingChanges();
}
int DebuggerWindow::fontSize()
{
return m_font_size;
}
void DebuggerWindow::updateStyleSheets()
{
// TODO: Migrate away from stylesheets to improve performance.
if (m_font_size != DEFAULT_FONT_SIZE)
{
int size = m_font_size + QApplication::font().pointSize() - DEFAULT_FONT_SIZE;
setStyleSheet(QString("* { font-size: %1pt; } QTabBar { font-size: %2pt; }").arg(size).arg(size + 1));
}
else
{
setStyleSheet(QString());
}
dockManager().updateStyleSheets();
}
void DebuggerWindow::saveWindowGeometry()
{
std::string old_geometry = Host::GetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry");
std::string geometry;
if (shouldSaveWindowGeometry())
geometry = saveGeometry().toBase64().toStdString();
if (geometry != old_geometry)
{
Host::SetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry", geometry.c_str());
Host::CommitBaseSettingChanges();
}
}
void DebuggerWindow::restoreWindowGeometry()
{
if (!shouldSaveWindowGeometry())
return;
std::string geometry = Host::GetBaseStringSettingValue("Debugger/UserInterface", "WindowGeometry");
restoreGeometry(QByteArray::fromBase64(QByteArray::fromStdString(geometry)));
}
bool DebuggerWindow::shouldSaveWindowGeometry()
{
return Host::GetBaseBoolSettingValue("Debugger/UserInterface", "SaveWindowGeometry", true);
}
void DebuggerWindow::onVMStarting()
{
m_ui.actionRun->setEnabled(true);
m_ui.actionStepInto->setEnabled(true);
m_ui.actionStepOver->setEnabled(true);
m_ui.actionStepOut->setEnabled(true);
m_ui.actionAnalyse->setEnabled(true);
m_ui.actionGameSettings->setEnabled(true);
m_ui.actionShutDown->setEnabled(true);
m_ui.actionReset->setEnabled(true);
}
void DebuggerWindow::onVMPaused()
{
m_ui.actionRun->setText(tr("Run"));
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("play-line")));
m_ui.actionStepInto->setEnabled(true);
m_ui.actionStepOver->setEnabled(true);
m_ui.actionStepOut->setEnabled(true);
// Switch to the CPU tab that triggered the breakpoint.
// Also blink the tab text to indicate that a breakpoint was triggered.
if (CBreakPoints::GetBreakpointTriggered())
{
const BreakPointCpu triggeredCpu = CBreakPoints::GetBreakpointTriggeredCpu();
m_dock_manager->switchToLayoutWithCPU(triggeredCpu, true);
Host::RunOnCPUThread([] {
CBreakPoints::ClearTemporaryBreakPoints();
CBreakPoints::SetBreakpointTriggered(false, BREAKPOINT_IOP_AND_EE);
// Our current PC is on a breakpoint.
// When we run the core again, we want to skip this breakpoint and run.
CBreakPoints::SetSkipFirst(BREAKPOINT_EE, r5900Debug.getPC());
CBreakPoints::SetSkipFirst(BREAKPOINT_IOP, r3000Debug.getPC());
});
}
}
void DebuggerWindow::onVMResumed()
{
m_ui.actionRun->setText(tr("Pause"));
m_ui.actionRun->setIcon(QIcon::fromTheme(QStringLiteral("pause-line")));
m_ui.actionStepInto->setEnabled(false);
m_ui.actionStepOver->setEnabled(false);
m_ui.actionStepOut->setEnabled(false);
}
void DebuggerWindow::onVMStopped()
{
m_ui.actionRun->setEnabled(false);
m_ui.actionStepInto->setEnabled(false);
m_ui.actionStepOver->setEnabled(false);
m_ui.actionStepOut->setEnabled(false);
m_ui.actionAnalyse->setEnabled(false);
m_ui.actionGameSettings->setEnabled(false);
m_ui.actionShutDown->setEnabled(false);
m_ui.actionReset->setEnabled(false);
}
void DebuggerWindow::onAnalyse()
{
AnalysisOptionsDialog* dialog = new AnalysisOptionsDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
void DebuggerWindow::onSettings()
{
g_main_window->doSettings("Debug");
}
void DebuggerWindow::onGameSettings()
{
g_main_window->doGameSettings("Debug");
}
void DebuggerWindow::onRunPause()
@@ -115,40 +372,162 @@ void DebuggerWindow::onRunPause()
void DebuggerWindow::onStepInto()
{
CpuWidget* currentCpu = static_cast<CpuWidget*>(m_ui.cpuTabs->currentWidget());
currentCpu->onStepInto();
DebugInterface* cpu = currentCPU();
if (!cpu)
return;
if (!cpu->isAlive() || !cpu->isCpuPaused())
return;
// Allow the cpu to skip this pc if it is a breakpoint
CBreakPoints::SetSkipFirst(cpu->getCpuType(), cpu->getPC());
const u32 pc = cpu->getPC();
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(cpu, pc);
u32 bpAddr = pc + 0x4; // Default to the next instruction
if (info.isBranch)
{
if (!info.isConditional)
{
bpAddr = info.branchTarget;
}
else
{
if (info.conditionMet)
{
bpAddr = info.branchTarget;
}
else
{
bpAddr = pc + (2 * 4); // Skip branch delay slot
}
}
}
if (info.isSyscall)
bpAddr = info.branchTarget; // Syscalls are always taken
Host::RunOnCPUThread([cpu, bpAddr] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
cpu->resumeCpu();
});
repaint();
}
void DebuggerWindow::onStepOver()
{
CpuWidget* currentCpu = static_cast<CpuWidget*>(m_ui.cpuTabs->currentWidget());
currentCpu->onStepOver();
DebugInterface* cpu = currentCPU();
if (!cpu)
return;
if (!cpu->isAlive() || !cpu->isCpuPaused())
return;
const u32 pc = cpu->getPC();
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(cpu, pc);
u32 bpAddr = pc + 0x4; // Default to the next instruction
if (info.isBranch)
{
if (!info.isConditional)
{
if (info.isLinkedBranch) // jal, jalr
{
// it's a function call with a delay slot - skip that too
bpAddr += 4;
}
else // j, ...
{
// in case of absolute branches, set the breakpoint at the branch target
bpAddr = info.branchTarget;
}
}
else // beq, ...
{
if (info.conditionMet)
{
bpAddr = info.branchTarget;
}
else
{
bpAddr = pc + (2 * 4); // Skip branch delay slot
}
}
}
Host::RunOnCPUThread([cpu, bpAddr] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
cpu->resumeCpu();
});
this->repaint();
}
void DebuggerWindow::onStepOut()
{
CpuWidget* currentCpu = static_cast<CpuWidget*>(m_ui.cpuTabs->currentWidget());
currentCpu->onStepOut();
}
DebugInterface* cpu = currentCPU();
if (!cpu)
return;
void DebuggerWindow::onAnalyse()
{
AnalysisOptionsDialog* dialog = new AnalysisOptionsDialog(this);
dialog->show();
}
if (!cpu->isAlive() || !cpu->isCpuPaused())
return;
void DebuggerWindow::showEvent(QShowEvent* event)
{
Host::RunOnCPUThread([]() {
R5900SymbolImporter.OnDebuggerOpened();
// Allow the cpu to skip this pc if it is a breakpoint
CBreakPoints::SetSkipFirst(cpu->getCpuType(), cpu->getPC());
std::vector<MipsStackWalk::StackFrame> stack_frames;
for (const auto& thread : cpu->GetThreadList())
{
if (thread->Status() == ThreadStatus::THS_RUN)
{
stack_frames = MipsStackWalk::Walk(
cpu,
cpu->getPC(),
cpu->getRegister(0, 31),
cpu->getRegister(0, 29),
thread->EntryPoint(),
thread->StackTop());
break;
}
}
if (stack_frames.size() < 2)
return;
u32 breakpoint_pc = stack_frames.at(1).pc;
Host::RunOnCPUThread([cpu, breakpoint_pc] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), breakpoint_pc, true);
cpu->resumeCpu();
});
QMainWindow::showEvent(event);
this->repaint();
}
void DebuggerWindow::hideEvent(QHideEvent* event)
void DebuggerWindow::closeEvent(QCloseEvent* event)
{
dockManager().saveCurrentLayout();
saveWindowGeometry();
Host::RunOnCPUThread([]() {
R5900SymbolImporter.OnDebuggerClosed();
});
QMainWindow::hideEvent(event);
KDDockWidgets::QtWidgets::MainWindow::closeEvent(event);
g_debugger_window = nullptr;
deleteLater();
}
DebugInterface* DebuggerWindow::currentCPU()
{
std::optional<BreakPointCpu> maybe_cpu = m_dock_manager->cpu();
if (!maybe_cpu.has_value())
return nullptr;
return &DebugInterface::get(*maybe_cpu);
}

View File

@@ -3,39 +3,70 @@
#pragma once
#include "CpuWidget.h"
#include "ui_DebuggerWindow.h"
class DebuggerWindow : public QMainWindow
#include "DebugTools/DebugInterface.h"
#include <kddockwidgets/MainWindow.h>
class DockManager;
class DebuggerWindow : public KDDockWidgets::QtWidgets::MainWindow
{
Q_OBJECT
public:
DebuggerWindow(QWidget* parent);
~DebuggerWindow();
static DebuggerWindow* getInstance();
static DebuggerWindow* createInstance();
static void destroyInstance();
static bool shouldShowOnStartup();
DockManager& dockManager();
void setupDefaultToolBarState();
void clearToolBarState();
void setupFonts();
void updateFontActions();
void saveFontSize();
int fontSize();
void updateStyleSheets();
void saveWindowGeometry();
void restoreWindowGeometry();
bool shouldSaveWindowGeometry();
public slots:
void onVMStateChanged();
void onVMStarting();
void onVMPaused();
void onVMResumed();
void onVMStopped();
void onAnalyse();
void onSettings();
void onGameSettings();
void onRunPause();
void onStepInto();
void onStepOver();
void onStepOut();
void onAnalyse();
protected:
void showEvent(QShowEvent* event);
void hideEvent(QHideEvent *event);
void closeEvent(QCloseEvent* event);
private:
DebugInterface* currentCPU();
Ui::DebuggerWindow m_ui;
QAction* m_actionRunPause;
QAction* m_actionStepInto;
QAction* m_actionStepOver;
QAction* m_actionStepOut;
CpuWidget* m_cpuWidget_r5900;
CpuWidget* m_cpuWidget_r3000;
DockManager* m_dock_manager;
void setTabActiveStyle(BreakPointCpu toggledCPU);
QByteArray m_default_toolbar_state;
int m_font_size;
static const constexpr int DEFAULT_FONT_SIZE = 10;
static const constexpr int MINIMUM_FONT_SIZE = 5;
static const constexpr int MAXIMUM_FONT_SIZE = 30;
};
extern DebuggerWindow* g_debugger_window;

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
<width>1000</width>
<height>750</height>
</rect>
</property>
<property name="windowTitle">
@@ -18,31 +18,74 @@
<normalon>:/icons/AppIcon64.png</normalon>
</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTabWidget" name="cpuTabs"/>
</item>
</layout>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionAnalyse"/>
<addaction name="actionSettings"/>
<addaction name="actionGameSettings"/>
<addaction name="separator"/>
<addaction name="actionClose"/>
</widget>
<widget class="QMenu" name="menuDebug">
<property name="title">
<string>Debug</string>
</property>
<addaction name="actionRun"/>
<addaction name="actionStepInto"/>
<addaction name="actionStepOver"/>
<addaction name="actionStepOut"/>
</widget>
<widget class="QMenu" name="menuWindows">
<property name="title">
<string>Windows</string>
</property>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<addaction name="actionOnTop"/>
<addaction name="separator"/>
<addaction name="actionIncreaseFontSize"/>
<addaction name="actionDecreaseFontSize"/>
<addaction name="actionResetFontSize"/>
</widget>
<widget class="QMenu" name="menuLayouts">
<property name="title">
<string>Layouts</string>
</property>
<addaction name="actionResetAllLayouts"/>
<addaction name="actionResetDefaultLayouts"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Tools</string>
</property>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
<addaction name="menuDebug"/>
<addaction name="menuTools"/>
<addaction name="menuWindows"/>
<addaction name="menuLayouts"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::PreventContextMenu</enum>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
<widget class="QToolBar" name="toolBarDebug">
<property name="windowTitle">
<string>Debug</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
</property>
<property name="floatable">
<bool>false</bool>
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
@@ -54,8 +97,57 @@
<addaction name="actionStepInto"/>
<addaction name="actionStepOver"/>
<addaction name="actionStepOut"/>
</widget>
<widget class="QToolBar" name="toolBarFile">
<property name="windowTitle">
<string>File</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionAnalyse"/>
<addaction name="actionSettings"/>
<addaction name="actionGameSettings"/>
</widget>
<widget class="QToolBar" name="toolBarSystem">
<property name="windowTitle">
<string>System</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionShutDown"/>
<addaction name="actionReset"/>
</widget>
<widget class="QToolBar" name="toolBarView">
<property name="windowTitle">
<string>View</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionOnTop"/>
<addaction name="actionIncreaseFontSize"/>
<addaction name="actionDecreaseFontSize"/>
<addaction name="actionResetFontSize"/>
</widget>
<action name="actionRun">
<property name="icon">
@@ -120,6 +212,112 @@
<string>Analyze</string>
</property>
</action>
<action name="actionResetAllLayouts">
<property name="text">
<string>Reset All Layouts</string>
</property>
</action>
<action name="actionResetDefaultLayouts">
<property name="text">
<string>Reset Default Layouts</string>
</property>
</action>
<action name="actionResetSplitterPositions">
<property name="text">
<string>Reset Splitter Positions</string>
</property>
</action>
<action name="actionShutDown">
<property name="icon">
<iconset theme="shut-down-line"/>
</property>
<property name="text">
<string>Shut Down</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionReset">
<property name="icon">
<iconset theme="restart-line"/>
</property>
<property name="text">
<string>Reset</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionClose">
<property name="icon">
<iconset theme="close-line"/>
</property>
<property name="text">
<string>Close</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionIncreaseFontSize">
<property name="icon">
<iconset theme="zoom-in-line"/>
</property>
<property name="text">
<string>Increase Font Size</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionDecreaseFontSize">
<property name="icon">
<iconset theme="zoom-out-line"/>
</property>
<property name="text">
<string>Decrease Font Size</string>
</property>
<property name="shortcut">
<string>Ctrl+-</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionResetFontSize">
<property name="icon">
<iconset theme="refresh-line"/>
</property>
<property name="text">
<string>Reset Font Size</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionSettings">
<property name="icon">
<iconset theme="settings-3-line"/>
</property>
<property name="text">
<string>Settings</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionGameSettings">
<property name="icon">
<iconset theme="file-settings-line"/>
</property>
<property name="text">
<string>Game Settings</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@@ -3,6 +3,9 @@
#include "DisassemblyWidget.h"
#include "Debugger/Breakpoints/BreakpointModel.h"
#include "Debugger/JsonValueWrapper.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/DisassemblyManager.h"
#include "DebugTools/Breakpoints.h"
@@ -10,6 +13,7 @@
#include "QtUtils.h"
#include "QtHost.h"
#include <QtCore/QPointer>
#include <QtGui/QMouseEvent>
#include <QtWidgets/QMenu>
#include <QtGui/QClipboard>
@@ -19,16 +23,72 @@
using namespace QtUtils;
DisassemblyWidget::DisassemblyWidget(QWidget* parent)
: QWidget(parent)
DisassemblyWidget::DisassemblyWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, MONOSPACE_FONT)
{
ui.setupUi(this);
m_ui.setupUi(this);
connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::customMenuRequested);
m_disassemblyManager.setCpu(&cpu());
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::openContextMenu);
connect(g_emu_thread, &EmuThread::onVMPaused, this, &DisassemblyWidget::gotoProgramCounterOnPause);
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
update();
return true;
});
receiveEvent<DebuggerEvents::GoToAddress>([this](const DebuggerEvents::GoToAddress& event) -> bool {
if (event.filter != DebuggerEvents::GoToAddress::NONE &&
event.filter != DebuggerEvents::GoToAddress::DISASSEMBLER)
return false;
gotoAddress(event.address, event.switch_to_tab);
if (event.switch_to_tab)
switchToThisTab();
return true;
});
}
DisassemblyWidget::~DisassemblyWidget() = default;
void DisassemblyWidget::toJson(JsonValueWrapper& json)
{
DebuggerWidget::toJson(json);
json.value().AddMember("startAddress", m_visibleStart, json.allocator());
json.value().AddMember("goToPCOnPause", m_goToProgramCounterOnPause, json.allocator());
json.value().AddMember("showInstructionBytes", m_showInstructionBytes, json.allocator());
}
bool DisassemblyWidget::fromJson(const JsonValueWrapper& json)
{
if (!DebuggerWidget::fromJson(json))
return false;
auto start_address = json.value().FindMember("startAddress");
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
m_visibleStart = start_address->value.GetUint() & ~3;
auto go_to_pc_on_pause = json.value().FindMember("goToPCOnPause");
if (go_to_pc_on_pause != json.value().MemberEnd() && go_to_pc_on_pause->value.IsBool())
m_goToProgramCounterOnPause = go_to_pc_on_pause->value.GetBool();
auto show_instruction_bytes = json.value().FindMember("showInstructionBytes");
if (show_instruction_bytes != json.value().MemberEnd() && show_instruction_bytes->value.IsBool())
m_showInstructionBytes = show_instruction_bytes->value.GetBool();
repaint();
return true;
}
void DisassemblyWidget::contextCopyAddress()
{
QGuiApplication::clipboard()->setText(FetchSelectionInfo(SelectionInfo::ADDRESS));
@@ -46,7 +106,7 @@ void DisassemblyWidget::contextCopyInstructionText()
void DisassemblyWidget::contextAssembleInstruction()
{
if (!m_cpu->isCpuPaused())
if (!cpu().isCpuPaused())
{
QMessageBox::warning(this, tr("Assemble Error"), tr("Unable to change assembly while core is running"));
return;
@@ -63,7 +123,7 @@ void DisassemblyWidget::contextAssembleInstruction()
u32 encodedInstruction;
std::string errorText;
bool valid = MipsAssembleOpcode(instruction.toLocal8Bit().constData(), m_cpu, m_selectedAddressStart, encodedInstruction, errorText);
bool valid = MipsAssembleOpcode(instruction.toLocal8Bit().constData(), &cpu(), m_selectedAddressStart, encodedInstruction, errorText);
if (!valid)
{
@@ -72,32 +132,32 @@ void DisassemblyWidget::contextAssembleInstruction()
}
else
{
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu, val = encodedInstruction] {
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = &cpu(), val = encodedInstruction] {
for (u32 i = start; i <= end; i += 4)
{
this->m_nopedInstructions.insert({i, cpu->read32(i)});
cpu->write32(i, val);
}
emit VMUpdate();
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
});
}
}
void DisassemblyWidget::contextNoopInstruction()
{
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu] {
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = &cpu()] {
for (u32 i = start; i <= end; i += 4)
{
this->m_nopedInstructions.insert({i, cpu->read32(i)});
cpu->write32(i, 0x00);
}
emit VMUpdate();
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
});
}
void DisassemblyWidget::contextRestoreInstruction()
{
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu] {
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = &cpu()] {
for (u32 i = start; i <= end; i += 4)
{
if (this->m_nopedInstructions.find(i) != this->m_nopedInstructions.end())
@@ -106,14 +166,14 @@ void DisassemblyWidget::contextRestoreInstruction()
this->m_nopedInstructions.erase(i);
}
}
emit VMUpdate();
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
});
}
void DisassemblyWidget::contextRunToCursor()
{
const u32 selectedAddressStart = m_selectedAddressStart;
Host::RunOnCPUThread([cpu = m_cpu, selectedAddressStart] {
Host::RunOnCPUThread([cpu = &cpu(), selectedAddressStart] {
CBreakPoints::AddBreakPoint(cpu->getCpuType(), selectedAddressStart, true);
cpu->resumeCpu();
});
@@ -121,28 +181,13 @@ void DisassemblyWidget::contextRunToCursor()
void DisassemblyWidget::contextJumpToCursor()
{
m_cpu->setPc(m_selectedAddressStart);
cpu().setPc(m_selectedAddressStart);
this->repaint();
}
void DisassemblyWidget::contextToggleBreakpoint()
{
if (!m_cpu->isAlive())
return;
const u32 selectedAddressStart = m_selectedAddressStart;
const BreakPointCpu cpuType = m_cpu->getCpuType();
if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddressStart))
{
Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddressStart); });
}
else
{
Host::RunOnCPUThread([cpuType, selectedAddressStart] { CBreakPoints::AddBreakPoint(cpuType, selectedAddressStart); });
}
breakpointsChanged();
this->repaint();
toggleBreakpoint(m_selectedAddressStart);
}
void DisassemblyWidget::contextFollowBranch()
@@ -171,7 +216,7 @@ void DisassemblyWidget::contextGoToAddress()
u64 address = 0;
std::string error;
if (!m_cpu->evaluateExpression(targetString.toStdString().c_str(), address, error))
if (!cpu().evaluateExpression(targetString.toStdString().c_str(), address, error))
{
QMessageBox::warning(this, tr("Cannot Go To"), QString::fromStdString(error));
return;
@@ -182,7 +227,8 @@ void DisassemblyWidget::contextGoToAddress()
void DisassemblyWidget::contextAddFunction()
{
NewFunctionDialog* dialog = new NewFunctionDialog(*m_cpu, this);
NewFunctionDialog* dialog = new NewFunctionDialog(cpu(), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setName(QString("func_%1").arg(m_selectedAddressStart, 8, 16, QChar('0')));
dialog->setAddress(m_selectedAddressStart);
if (m_selectedAddressEnd != m_selectedAddressStart)
@@ -193,13 +239,13 @@ void DisassemblyWidget::contextAddFunction()
void DisassemblyWidget::contextCopyFunctionName()
{
std::string name = m_cpu->GetSymbolGuardian().FunctionStartingAtAddress(m_selectedAddressStart).name;
std::string name = cpu().GetSymbolGuardian().FunctionStartingAtAddress(m_selectedAddressStart).name;
QGuiApplication::clipboard()->setText(QString::fromStdString(name));
}
void DisassemblyWidget::contextRemoveFunction()
{
m_cpu->GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
ccc::Function* curFunc = database.functions.symbol_overlapping_address(m_selectedAddressStart);
if (!curFunc)
return;
@@ -215,7 +261,7 @@ void DisassemblyWidget::contextRemoveFunction()
void DisassemblyWidget::contextRenameFunction()
{
const FunctionInfo curFunc = m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart);
const FunctionInfo curFunc = cpu().GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart);
if (!curFunc.address.valid())
{
@@ -236,28 +282,28 @@ void DisassemblyWidget::contextRenameFunction()
return;
}
m_cpu->GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
database.functions.rename_symbol(curFunc.handle, newName.toStdString());
});
}
void DisassemblyWidget::contextStubFunction()
{
FunctionInfo function = m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart);
FunctionInfo function = cpu().GetSymbolGuardian().FunctionOverlappingAddress(m_selectedAddressStart);
u32 address = function.address.valid() ? function.address.value : m_selectedAddressStart;
Host::RunOnCPUThread([this, address, cpu = m_cpu] {
Host::RunOnCPUThread([this, address, cpu = &cpu()] {
this->m_stubbedFunctions.insert({address, {cpu->read32(address), cpu->read32(address + 4)}});
cpu->write32(address, 0x03E00008); // jr ra
cpu->write32(address + 4, 0x00000000); // nop
emit VMUpdate();
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
});
}
void DisassemblyWidget::contextRestoreFunction()
{
u32 address = m_selectedAddressStart;
m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
const ccc::Function* function = database.functions.symbol_overlapping_address(m_selectedAddressStart);
if (function)
address = function->address().value;
@@ -266,12 +312,12 @@ void DisassemblyWidget::contextRestoreFunction()
auto stub = m_stubbedFunctions.find(address);
if (stub != m_stubbedFunctions.end())
{
Host::RunOnCPUThread([this, address, cpu = m_cpu, stub] {
Host::RunOnCPUThread([this, address, cpu = &cpu(), stub] {
auto [first_instruction, second_instruction] = stub->second;
cpu->write32(address, first_instruction);
cpu->write32(address + 4, second_instruction);
this->m_stubbedFunctions.erase(address);
emit VMUpdate();
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
});
}
else
@@ -280,18 +326,12 @@ void DisassemblyWidget::contextRestoreFunction()
}
}
void DisassemblyWidget::contextShowOpcode()
void DisassemblyWidget::contextShowInstructionBytes()
{
m_showInstructionOpcode = !m_showInstructionOpcode;
m_showInstructionBytes = !m_showInstructionBytes;
this->repaint();
}
void DisassemblyWidget::SetCpu(DebugInterface* cpu)
{
m_cpu = cpu;
m_disassemblyManager.setCpu(cpu);
}
QString DisassemblyWidget::GetLineDisasm(u32 address)
{
DisassemblyLineInfo lineInfo;
@@ -322,7 +362,7 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event)
bool inSelectionBlock = false;
bool alternate = m_visibleStart % 8;
const u32 curPC = m_cpu->getPC(); // Get the PC here, because it'll change when we are drawing and make it seem like there are two PCs
const u32 curPC = cpu().getPC(); // Get the PC here, because it'll change when we are drawing and make it seem like there are two PCs
for (u32 i = 0; i <= m_visibleRows; i++)
{
@@ -347,7 +387,7 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event)
// Breakpoint marker
bool enabled;
if (CBreakPoints::IsAddressBreakPoint(m_cpu->getCpuType(), rowAddress, &enabled) && !CBreakPoints::IsTempBreakPoint(m_cpu->getCpuType(), rowAddress))
if (CBreakPoints::IsAddressBreakPoint(cpu().getCpuType(), rowAddress, &enabled) && !CBreakPoints::IsTempBreakPoint(cpu().getCpuType(), rowAddress))
{
if (enabled)
{
@@ -371,7 +411,7 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event)
s32 branchCount = 0;
for (const auto& branchLine : branchLines)
{
if (branchCount == (m_showInstructionOpcode ? 3 : 5))
if (branchCount == (m_showInstructionBytes ? 3 : 5))
break;
const int winBottom = this->height();
@@ -506,21 +546,7 @@ void DisassemblyWidget::mousePressEvent(QMouseEvent* event)
void DisassemblyWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
if (!m_cpu->isAlive())
return;
const u32 selectedAddress = (static_cast<int>(event->position().y()) / m_rowHeight * 4) + m_visibleStart;
const BreakPointCpu cpuType = m_cpu->getCpuType();
if (CBreakPoints::IsAddressBreakPoint(cpuType, selectedAddress))
{
Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::RemoveBreakPoint(cpuType, selectedAddress); });
}
else
{
Host::RunOnCPUThread([cpuType, selectedAddress] { CBreakPoints::AddBreakPoint(cpuType, selectedAddress); });
}
breakpointsChanged();
this->repaint();
toggleBreakpoint((static_cast<int>(event->position().y()) / m_rowHeight * 4) + m_visibleStart);
}
void DisassemblyWidget::wheelEvent(QWheelEvent* event)
@@ -598,115 +624,139 @@ void DisassemblyWidget::keyPressEvent(QKeyEvent* event)
contextFollowBranch();
break;
case Qt::Key_Left:
gotoAddressAndSetFocus(m_cpu->getPC());
gotoAddressAndSetFocus(cpu().getPC());
break;
case Qt::Key_O:
m_showInstructionOpcode = !m_showInstructionOpcode;
case Qt::Key_I:
m_showInstructionBytes = !m_showInstructionBytes;
break;
}
this->repaint();
}
void DisassemblyWidget::customMenuRequested(QPoint pos)
void DisassemblyWidget::openContextMenu(QPoint pos)
{
if (!m_cpu->isAlive())
if (!cpu().isAlive())
return;
QMenu* contextMenu = new QMenu(this);
QMenu* menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction* action = 0;
contextMenu->addAction(action = new QAction(tr("Copy Address"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress);
contextMenu->addAction(action = new QAction(tr("Copy Instruction Hex"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex);
contextMenu->addAction(action = new QAction(tr("&Copy Instruction Text"), this));
action->setShortcut(QKeySequence(Qt::Key_C));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText);
if (m_cpu->GetSymbolGuardian().FunctionExistsWithStartingAddress(m_selectedAddressStart))
QAction* copy_address_action = menu->addAction(tr("Copy Address"));
connect(copy_address_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress);
QAction* copy_instruction_hex_action = menu->addAction(tr("Copy Instruction Hex"));
connect(copy_instruction_hex_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex);
QAction* copy_instruction_text_action = menu->addAction(tr("&Copy Instruction Text"));
copy_instruction_text_action->setShortcut(QKeySequence(Qt::Key_C));
connect(copy_instruction_text_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText);
if (cpu().GetSymbolGuardian().FunctionExistsWithStartingAddress(m_selectedAddressStart))
{
contextMenu->addAction(action = new QAction(tr("Copy Function Name"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyFunctionName);
QAction* copy_function_name_action = menu->addAction(tr("Copy Function Name"));
connect(copy_function_name_action, &QAction::triggered, this, &DisassemblyWidget::contextCopyFunctionName);
}
contextMenu->addSeparator();
menu->addSeparator();
if (AddressCanRestore(m_selectedAddressStart, m_selectedAddressEnd))
{
contextMenu->addAction(action = new QAction(tr("Restore Instruction(s)"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreInstruction);
QAction* restore_instruction_action = menu->addAction(tr("Restore Instruction(s)"));
connect(restore_instruction_action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreInstruction);
}
contextMenu->addAction(action = new QAction(tr("Asse&mble new Instruction(s)"), this));
action->setShortcut(QKeySequence(Qt::Key_M));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction);
contextMenu->addAction(action = new QAction(tr("NOP Instruction(s)"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction);
contextMenu->addSeparator();
contextMenu->addAction(action = new QAction(tr("Run to Cursor"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor);
contextMenu->addAction(action = new QAction(tr("&Jump to Cursor"), this));
action->setShortcut(QKeySequence(Qt::Key_J));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor);
contextMenu->addAction(action = new QAction(tr("Toggle &Breakpoint"), this));
action->setShortcut(QKeySequence(Qt::Key_B));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint);
contextMenu->addAction(action = new QAction(tr("Follow Branch"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch);
contextMenu->addSeparator();
contextMenu->addAction(action = new QAction(tr("&Go to Address"), this));
action->setShortcut(QKeySequence(Qt::Key_G));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress);
contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this));
connect(action, &QAction::triggered, this, [this]() { gotoInMemory(m_selectedAddressStart); });
contextMenu->addAction(action = new QAction(tr("Go to PC on Pause"), this));
action->setCheckable(true);
action->setChecked(m_goToProgramCounterOnPause);
connect(action, &QAction::triggered, this, [this](bool value) { m_goToProgramCounterOnPause = value; });
QAction* assemble_new_instruction = menu->addAction(tr("Asse&mble new Instruction(s)"));
assemble_new_instruction->setShortcut(QKeySequence(Qt::Key_M));
connect(assemble_new_instruction, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction);
QAction* nop_instruction_action = menu->addAction(tr("NOP Instruction(s)"));
connect(nop_instruction_action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction);
menu->addSeparator();
QAction* run_to_cursor_action = menu->addAction(tr("Run to Cursor"));
connect(run_to_cursor_action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor);
QAction* jump_to_cursor_action = menu->addAction(tr("&Jump to Cursor"));
jump_to_cursor_action->setShortcut(QKeySequence(Qt::Key_J));
connect(jump_to_cursor_action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor);
QAction* toggle_breakpoint_action = menu->addAction(tr("Toggle &Breakpoint"));
toggle_breakpoint_action->setShortcut(QKeySequence(Qt::Key_B));
connect(toggle_breakpoint_action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint);
QAction* follow_branch_action = menu->addAction(tr("Follow Branch"));
connect(follow_branch_action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch);
menu->addSeparator();
QAction* go_to_address_action = menu->addAction(tr("&Go to Address"));
go_to_address_action->setShortcut(QKeySequence(Qt::Key_G));
connect(go_to_address_action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress);
createEventActions<DebuggerEvents::GoToAddress>(menu, [this]() {
DebuggerEvents::GoToAddress event;
event.address = m_selectedAddressStart;
return std::optional(event);
});
QAction* go_to_pc_on_pause = menu->addAction(tr("Go to PC on Pause"));
go_to_pc_on_pause->setCheckable(true);
go_to_pc_on_pause->setChecked(m_goToProgramCounterOnPause);
connect(go_to_pc_on_pause, &QAction::triggered, this,
[this](bool value) { m_goToProgramCounterOnPause = value; });
menu->addSeparator();
QAction* add_function_action = menu->addAction(tr("Add Function"));
connect(add_function_action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction);
QAction* rename_function_action = menu->addAction(tr("Rename Function"));
connect(rename_function_action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction);
QAction* remove_function_action = menu->addAction(tr("Remove Function"));
menu->addAction(remove_function_action);
connect(remove_function_action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction);
contextMenu->addSeparator();
contextMenu->addAction(action = new QAction(tr("Add Function"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction);
contextMenu->addAction(action = new QAction(tr("Rename Function"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction);
contextMenu->addAction(action = new QAction(tr("Remove Function"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction);
if (FunctionCanRestore(m_selectedAddressStart))
{
contextMenu->addAction(action = new QAction(tr("Restore Function"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreFunction);
QAction* restore_action = menu->addAction(tr("Restore Function"));
connect(restore_action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreFunction);
}
else
{
contextMenu->addAction(action = new QAction(tr("Stub (NOP) Function"), this));
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction);
QAction* stub_action = menu->addAction(tr("Stub (NOP) Function"));
connect(stub_action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction);
}
contextMenu->addSeparator();
contextMenu->addAction(action = new QAction(tr("Show &Opcode"), this));
action->setShortcut(QKeySequence(Qt::Key_O));
action->setCheckable(true);
action->setChecked(m_showInstructionOpcode);
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextShowOpcode);
menu->addSeparator();
contextMenu->setAttribute(Qt::WA_DeleteOnClose);
contextMenu->popup(this->mapToGlobal(pos));
QAction* show_instruction_bytes_action = menu->addAction(tr("Show &Instruction Bytes"));
show_instruction_bytes_action->setShortcut(QKeySequence(Qt::Key_I));
show_instruction_bytes_action->setCheckable(true);
show_instruction_bytes_action->setChecked(m_showInstructionBytes);
connect(show_instruction_bytes_action, &QAction::triggered, this, &DisassemblyWidget::contextShowInstructionBytes);
menu->popup(this->mapToGlobal(pos));
}
inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFont font, u32 pc, bool selected)
{
DisassemblyLineInfo line;
if (!m_cpu->isValidAddress(address))
if (!cpu().isValidAddress(address))
return tr("%1 NOT VALID ADDRESS").arg(address, 8, 16, QChar('0')).toUpper();
// Todo? support non symbol view?
m_disassemblyManager.getLine(address, true, line);
const bool isConditional = line.info.isConditional && m_cpu->getPC() == address;
const bool isConditional = line.info.isConditional && cpu().getPC() == address;
const bool isConditionalMet = line.info.conditionMet;
const bool isCurrentPC = m_cpu->getPC() == address;
const bool isCurrentPC = cpu().getPC() == address;
FunctionInfo function = m_cpu->GetSymbolGuardian().FunctionStartingAtAddress(address);
SymbolInfo symbol = m_cpu->GetSymbolGuardian().SymbolStartingAtAddress(address);
const bool showOpcode = m_showInstructionOpcode && m_cpu->isAlive();
FunctionInfo function = cpu().GetSymbolGuardian().FunctionStartingAtAddress(address);
SymbolInfo symbol = cpu().GetSymbolGuardian().SymbolStartingAtAddress(address);
const bool showOpcode = m_showInstructionBytes && cpu().isAlive();
QString lineString;
if (showOpcode)
@@ -739,7 +789,7 @@ inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFon
if (showOpcode)
{
const u32 opcode = m_cpu->read32(address);
const u32 opcode = cpu().read32(address);
lineString = lineString.arg(QtUtils::FilledQStringFromValue(opcode, 16));
}
@@ -754,16 +804,8 @@ inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFon
QColor DisassemblyWidget::GetAddressFunctionColor(u32 address)
{
// This is an attempt to figure out if the current palette is dark or light
// We calculate the luminance of the alternateBase colour
// and swap between our darker and lighter function colours
std::array<QColor, 6> colors;
const QColor base = this->palette().alternateBase().color();
const auto Y = (base.redF() * 0.33) + (0.5 * base.greenF()) + (0.16 * base.blueF());
if (Y > 0.5)
if (QtUtils::IsLightTheme(palette()))
{
colors = {
QColor::fromRgba(0xFFFA3434),
@@ -789,7 +831,7 @@ QColor DisassemblyWidget::GetAddressFunctionColor(u32 address)
// Use the address to pick the colour since the value of the handle may
// change from run to run.
ccc::Address function_address =
m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(address).address;
cpu().GetSymbolGuardian().FunctionOverlappingAddress(address).address;
if (!function_address.valid())
return palette().text().color();
@@ -817,7 +859,7 @@ QString DisassemblyWidget::FetchSelectionInfo(SelectionInfo selInfo)
}
else // INSTRUCTIONHEX
{
infoBlock += FilledQStringFromValue(m_cpu->read32(i), 16);
infoBlock += FilledQStringFromValue(cpu().read32(i), 16);
}
}
return infoBlock;
@@ -831,7 +873,7 @@ void DisassemblyWidget::gotoAddressAndSetFocus(u32 address)
void DisassemblyWidget::gotoProgramCounterOnPause()
{
if (m_goToProgramCounterOnPause)
gotoAddress(m_cpu->getPC(), false);
gotoAddress(cpu().getPC(), false);
}
void DisassemblyWidget::gotoAddress(u32 address, bool should_set_focus)
@@ -847,6 +889,27 @@ void DisassemblyWidget::gotoAddress(u32 address, bool should_set_focus)
this->setFocus();
}
void DisassemblyWidget::toggleBreakpoint(u32 address)
{
if (!cpu().isAlive())
return;
QPointer<DisassemblyWidget> disassembly_widget(this);
Host::RunOnCPUThread([cpu = &cpu(), address, disassembly_widget] {
if (!CBreakPoints::IsAddressBreakPoint(cpu->getCpuType(), address))
CBreakPoints::AddBreakPoint(cpu->getCpuType(), address);
else
CBreakPoints::RemoveBreakPoint(cpu->getCpuType(), address);
QtHost::RunOnUIThread([cpu, disassembly_widget]() {
BreakpointModel::getInstance(*cpu)->refreshData();
if (disassembly_widget)
disassembly_widget->repaint();
});
});
}
bool DisassemblyWidget::AddressCanRestore(u32 start, u32 end)
{
for (u32 i = start; i <= end; i += 4)
@@ -861,7 +924,7 @@ bool DisassemblyWidget::AddressCanRestore(u32 start, u32 end)
bool DisassemblyWidget::FunctionCanRestore(u32 address)
{
FunctionInfo function = m_cpu->GetSymbolGuardian().FunctionOverlappingAddress(address);
FunctionInfo function = cpu().GetSymbolGuardian().FunctionOverlappingAddress(address);
if (function.address.valid())
address = function.address.value;

View File

@@ -5,36 +5,36 @@
#include "ui_DisassemblyWidget.h"
#include "pcsx2/DebugTools/DebugInterface.h"
#include "DebuggerWidget.h"
#include "pcsx2/DebugTools/DisassemblyManager.h"
#include <QtWidgets/QWidget>
#include <QtWidgets/QMenu>
#include <QtGui/QPainter>
class DisassemblyWidget final : public QWidget
class DisassemblyWidget final : public DebuggerWidget
{
Q_OBJECT
public:
DisassemblyWidget(QWidget* parent);
DisassemblyWidget(const DebuggerWidgetParameters& parameters);
~DisassemblyWidget();
// Required because our constructor needs to take no extra arguments.
void SetCpu(DebugInterface* cpu);
void toJson(JsonValueWrapper& json) override;
bool fromJson(const JsonValueWrapper& json) override;
// Required for the breakpoint list (ugh wtf)
QString GetLineDisasm(u32 address);
protected:
void paintEvent(QPaintEvent* event);
void mousePressEvent(QMouseEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event);
void wheelEvent(QWheelEvent* event);
void keyPressEvent(QKeyEvent* event);
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
public slots:
void customMenuRequested(QPoint pos);
void openContextMenu(QPoint pos);
// Context menu actions
// When called, m_selectedAddressStart will be the 'selected' instruction
@@ -56,23 +56,18 @@ public slots:
void contextRemoveFunction();
void contextStubFunction();
void contextRestoreFunction();
void contextShowOpcode();
void contextShowInstructionBytes();
void gotoAddressAndSetFocus(u32 address);
void gotoProgramCounterOnPause();
void gotoAddress(u32 address, bool should_set_focus);
void setDemangle(bool demangle) { m_demangleFunctions = demangle; };
signals:
void gotoInMemory(u32 address);
void breakpointsChanged();
void VMUpdate();
void toggleBreakpoint(u32 address);
private:
Ui::DisassemblyWidget ui;
Ui::DisassemblyWidget m_ui;
DebugInterface* m_cpu;
u32 m_visibleStart = 0x00336318; // The address of the first opcode shown(row 0)
u32 m_visibleStart = 0x100000; // The address of the first instruction shown.
u32 m_visibleRows;
u32 m_selectedAddressStart = 0;
u32 m_selectedAddressEnd = 0;
@@ -81,8 +76,7 @@ private:
std::map<u32, u32> m_nopedInstructions;
std::map<u32, std::tuple<u32, u32>> m_stubbedFunctions;
bool m_demangleFunctions = true;
bool m_showInstructionOpcode = true;
bool m_showInstructionBytes = true;
bool m_goToProgramCounterOnPause = true;
DisassemblyManager m_disassemblyManager;

View File

@@ -0,0 +1,918 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DockLayout.h"
#include "Debugger/DebuggerWidget.h"
#include "Debugger/DebuggerWindow.h"
#include "Debugger/JsonValueWrapper.h"
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include <kddockwidgets/Config.h>
#include <kddockwidgets/DockWidget.h>
#include <kddockwidgets/LayoutSaver.h>
#include <kddockwidgets/core/DockRegistry.h>
#include <kddockwidgets/core/DockWidget.h>
#include <kddockwidgets/core/Group.h>
#include <kddockwidgets/core/Layout.h>
#include <kddockwidgets/core/ViewFactory.h>
#include <kddockwidgets/qtwidgets/Group.h>
#include <kddockwidgets/qtwidgets/MainWindow.h>
#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
const char* DEBUGGER_LAYOUT_FILE_FORMAT = "PCSX2 Debugger User Interface Layout";
// Increment this whenever there is a breaking change to the JSON format.
const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR = 1;
// Increment this whenever there is a non-breaking change to the JSON format.
const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR = 0;
DockLayout::DockLayout(
QString name,
BreakPointCpu cpu,
bool is_default,
const std::string& base_name,
DockLayout::Index index)
: m_name(name)
, m_cpu(cpu)
, m_is_default(is_default)
, m_base_layout(base_name)
{
reset();
save(index);
}
DockLayout::DockLayout(
QString name,
BreakPointCpu cpu,
bool is_default,
DockLayout::Index index)
: m_name(name)
, m_cpu(cpu)
, m_is_default(is_default)
{
save(index);
}
DockLayout::DockLayout(
QString name,
BreakPointCpu cpu,
bool is_default,
const DockLayout& layout_to_clone,
DockLayout::Index index)
: m_name(name)
, m_cpu(cpu)
, m_is_default(is_default)
, m_next_unique_name(layout_to_clone.m_next_unique_name)
, m_base_layout(layout_to_clone.m_base_layout)
, m_toolbars(layout_to_clone.m_toolbars)
, m_geometry(layout_to_clone.m_geometry)
{
for (const auto& [unique_name, widget_to_clone] : layout_to_clone.m_widgets)
{
auto widget_description = DockTables::DEBUGGER_WIDGETS.find(widget_to_clone->metaObject()->className());
if (widget_description == DockTables::DEBUGGER_WIDGETS.end())
continue;
DebuggerWidgetParameters parameters;
parameters.unique_name = unique_name;
parameters.cpu = &DebugInterface::get(cpu);
parameters.cpu_override = widget_to_clone->cpuOverride();
DebuggerWidget* new_widget = widget_description->second.create_widget(parameters);
new_widget->setCustomDisplayName(widget_to_clone->customDisplayName());
new_widget->setPrimary(widget_to_clone->isPrimary());
m_widgets.emplace(unique_name, new_widget);
}
save(index);
}
DockLayout::DockLayout(
const std::string& path,
DockLayout::LoadResult& result,
DockLayout::Index& index_last_session,
DockLayout::Index index)
{
load(path, result, index_last_session);
}
DockLayout::~DockLayout()
{
for (auto& [unique_name, widget] : m_widgets)
{
pxAssert(widget.get());
delete widget;
}
}
const QString& DockLayout::name() const
{
return m_name;
}
void DockLayout::setName(QString name)
{
m_name = std::move(name);
}
BreakPointCpu DockLayout::cpu() const
{
return m_cpu;
}
bool DockLayout::isDefault() const
{
return m_is_default;
}
void DockLayout::setCpu(BreakPointCpu cpu)
{
m_cpu = cpu;
for (auto& [unique_name, widget] : m_widgets)
{
pxAssert(widget.get());
if (!widget->setCpu(DebugInterface::get(cpu)))
recreateDebuggerWidget(unique_name);
}
}
void DockLayout::freeze()
{
pxAssert(m_is_active);
m_is_active = false;
if (g_debugger_window)
m_toolbars = g_debugger_window->saveState();
// Store the geometry of all the dock widgets as JSON.
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
m_geometry = saver.serializeLayout();
// Delete the dock widgets.
for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets())
{
// Make sure the dock widget releases ownership of its content.
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock->view());
view->setWidget(new QWidget());
delete dock;
}
}
void DockLayout::thaw()
{
pxAssert(!m_is_active);
m_is_active = true;
if (!g_debugger_window)
return;
// Restore the state of the toolbars.
if (m_toolbars.isEmpty())
{
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
if (base_layout)
{
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
if (base_layout->toolbars.contains(toolbar->objectName().toStdString()))
toolbar->show();
}
}
else
{
g_debugger_window->restoreState(m_toolbars);
}
if (m_geometry.isEmpty())
{
// This is a newly created layout with no geometry information.
setupDefaultLayout();
}
else
{
// Create all the dock widgets.
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
if (!saver.restoreLayout(m_geometry))
{
// We've failed to restore the geometry, so just tear down whatever
// dock widgets may exist and then setup the default layout.
for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets())
{
// Make sure the dock widget releases ownership of its content.
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock->view());
view->setWidget(new QWidget());
delete dock;
}
setupDefaultLayout();
}
}
// Check that all the dock widgets have been restored correctly.
std::vector<QString> orphaned_debugger_widgets;
for (auto& [unique_name, widget] : m_widgets)
{
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
if (!controller || !view)
{
Console.Error("Debugger: Failed to restore dock widget '%s'.", unique_name.toStdString().c_str());
orphaned_debugger_widgets.emplace_back(unique_name);
}
}
// Delete any debugger widgets that haven't been restored correctly.
for (const QString& unique_name : orphaned_debugger_widgets)
{
auto widget_iterator = m_widgets.find(unique_name);
pxAssert(widget_iterator != m_widgets.end());
setPrimaryDebuggerWidget(widget_iterator->second.get(), false);
delete widget_iterator->second.get();
m_widgets.erase(widget_iterator);
}
updateDockWidgetTitles();
}
bool DockLayout::canReset()
{
return DockTables::defaultLayout(m_base_layout) != nullptr;
}
void DockLayout::reset()
{
pxAssert(!m_is_active);
for (auto& [unique_name, widget] : m_widgets)
{
pxAssert(widget.get());
delete widget;
}
m_next_unique_name = 0;
m_toolbars.clear();
m_widgets.clear();
m_geometry.clear();
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
if (!base_layout)
return;
for (size_t i = 0; i < base_layout->widgets.size(); i++)
{
auto iterator = DockTables::DEBUGGER_WIDGETS.find(base_layout->widgets[i].type);
pxAssertRel(iterator != DockTables::DEBUGGER_WIDGETS.end(), "Invalid default layout.");
const DockTables::DebuggerWidgetDescription& dock_description = iterator->second;
DebuggerWidgetParameters parameters;
parameters.unique_name = generateNewUniqueName(base_layout->widgets[i].type.c_str());
parameters.cpu = &DebugInterface::get(m_cpu);
if (parameters.unique_name.isEmpty())
continue;
DebuggerWidget* widget = dock_description.create_widget(parameters);
widget->setPrimary(true);
m_widgets.emplace(parameters.unique_name, widget);
}
}
KDDockWidgets::Core::DockWidget* DockLayout::createDockWidget(const QString& name)
{
pxAssert(m_is_active);
pxAssert(KDDockWidgets::LayoutSaver::restoreInProgress());
auto widget_iterator = m_widgets.find(name);
if (widget_iterator == m_widgets.end())
return nullptr;
DebuggerWidget* widget = widget_iterator->second;
if (!widget)
return nullptr;
pxAssert(widget->uniqueName() == name);
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
KDDockWidgets::Config::self().viewFactory()->createDockWidget(name));
view->setWidget(widget);
return view->asController<KDDockWidgets::Core::DockWidget>();
}
void DockLayout::updateDockWidgetTitles()
{
if (!m_is_active)
return;
// Translate default debugger widget names.
for (auto& [unique_name, widget] : m_widgets)
widget->retranslateDisplayName();
// Determine if any widgets have duplicate display names.
std::map<QString, std::vector<DebuggerWidget*>> display_name_to_widgets;
for (auto& [unique_name, widget] : m_widgets)
display_name_to_widgets[widget->displayNameWithoutSuffix()].emplace_back(widget.get());
for (auto& [display_name, widgets] : display_name_to_widgets)
{
std::sort(widgets.begin(), widgets.end(),
[&](const DebuggerWidget* lhs, const DebuggerWidget* rhs) {
return lhs->uniqueName() < rhs->uniqueName();
});
for (size_t i = 0; i < widgets.size(); i++)
{
std::optional<int> suffix_number;
if (widgets.size() != 1)
suffix_number = static_cast<int>(i + 1);
widgets[i]->setDisplayNameSuffixNumber(suffix_number);
}
}
// Propagate the new names from the debugger widgets to the dock widgets.
for (auto& [unique_name, widget] : m_widgets)
{
auto [controller, view] = DockUtils::dockWidgetFromName(widget->uniqueName());
if (!controller)
continue;
controller->setTitle(widget->displayName());
}
}
const std::map<QString, QPointer<DebuggerWidget>>& DockLayout::debuggerWidgets()
{
return m_widgets;
}
bool DockLayout::hasDebuggerWidget(const QString& unique_name)
{
return m_widgets.find(unique_name) != m_widgets.end();
}
size_t DockLayout::countDebuggerWidgetsOfType(const char* type)
{
size_t count = 0;
for (const auto& [unique_name, widget] : m_widgets)
{
if (strcmp(widget->metaObject()->className(), type) == 0)
count++;
}
return count;
}
void DockLayout::createDebuggerWidget(const std::string& type)
{
pxAssert(m_is_active);
if (!g_debugger_window)
return;
auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(type);
pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end());
const DockTables::DebuggerWidgetDescription& description = description_iterator->second;
DebuggerWidgetParameters parameters;
parameters.unique_name = generateNewUniqueName(type.c_str());
parameters.cpu = &DebugInterface::get(m_cpu);
if (parameters.unique_name.isEmpty())
return;
DebuggerWidget* widget = description.create_widget(parameters);
m_widgets.emplace(parameters.unique_name, widget);
setPrimaryDebuggerWidget(widget, countDebuggerWidgetsOfType(type.c_str()) == 0);
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName()));
view->setWidget(widget);
KDDockWidgets::Core::DockWidget* controller = view->asController<KDDockWidgets::Core::DockWidget>();
pxAssert(controller);
DockUtils::insertDockWidgetAtPreferredLocation(controller, description.preferred_location, g_debugger_window);
updateDockWidgetTitles();
}
void DockLayout::recreateDebuggerWidget(const QString& unique_name)
{
if (!g_debugger_window)
return;
auto debugger_widget_iterator = m_widgets.find(unique_name);
pxAssert(debugger_widget_iterator != m_widgets.end());
DebuggerWidget* old_debugger_widget = debugger_widget_iterator->second;
auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(old_debugger_widget->metaObject()->className());
pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end());
const DockTables::DebuggerWidgetDescription& description = description_iterator->second;
DebuggerWidgetParameters parameters;
parameters.unique_name = old_debugger_widget->uniqueName();
parameters.cpu = &DebugInterface::get(m_cpu);
parameters.cpu_override = old_debugger_widget->cpuOverride();
DebuggerWidget* new_debugger_widget = description.create_widget(parameters);
new_debugger_widget->setCustomDisplayName(old_debugger_widget->customDisplayName());
new_debugger_widget->setPrimary(old_debugger_widget->isPrimary());
debugger_widget_iterator->second = new_debugger_widget;
if (m_is_active)
{
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
if (view)
view->setWidget(new_debugger_widget);
}
delete old_debugger_widget;
}
void DockLayout::destroyDebuggerWidget(const QString& unique_name)
{
pxAssert(m_is_active);
if (!g_debugger_window)
return;
auto debugger_widget_iterator = m_widgets.find(unique_name);
if (debugger_widget_iterator == m_widgets.end())
return;
setPrimaryDebuggerWidget(debugger_widget_iterator->second.get(), false);
delete debugger_widget_iterator->second.get();
m_widgets.erase(debugger_widget_iterator);
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
if (!controller)
return;
controller->deleteLater();
updateDockWidgetTitles();
}
void DockLayout::setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary)
{
bool present = false;
for (auto& [unique_name, test_widget] : m_widgets)
{
if (test_widget.get() == widget)
{
present = true;
break;
}
}
if (!present)
return;
if (is_primary)
{
// Set the passed widget as the primary widget.
for (auto& [unique_name, test_widget] : m_widgets)
{
if (strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0)
{
test_widget->setPrimary(test_widget.get() == widget);
}
}
}
else if (widget->isPrimary())
{
// Set an arbitrary widget as the primary widget.
bool next = true;
for (auto& [unique_name, test_widget] : m_widgets)
{
if (test_widget != widget &&
strcmp(test_widget->metaObject()->className(), widget->metaObject()->className()) == 0)
{
test_widget->setPrimary(next);
next = false;
}
}
// If we haven't set another widget as the primary one we can't make
// this one not the primary one.
if (!next)
widget->setPrimary(false);
}
}
void DockLayout::deleteFile()
{
if (m_layout_file_path.empty())
return;
if (!FileSystem::DeleteFilePath(m_layout_file_path.c_str()))
Console.Error("Debugger: Failed to delete layout file '%s'.", m_layout_file_path.c_str());
}
bool DockLayout::save(DockLayout::Index layout_index)
{
if (!g_debugger_window)
return false;
if (m_is_active)
{
m_toolbars = g_debugger_window->saveState();
// Store the geometry of all the dock widgets as JSON.
KDDockWidgets::LayoutSaver saver(KDDockWidgets::RestoreOption_RelativeToMainWindow);
m_geometry = saver.serializeLayout();
}
// Serialize the layout as JSON.
rapidjson::Document json(rapidjson::kObjectType);
rapidjson::Document geometry;
const char* cpu_name = DebugInterface::cpuName(m_cpu);
const std::string& default_layouts_hash = DockTables::hashDefaultLayouts();
rapidjson::Value format;
format.SetString(DEBUGGER_LAYOUT_FILE_FORMAT, strlen(DEBUGGER_LAYOUT_FILE_FORMAT));
json.AddMember("format", format, json.GetAllocator());
json.AddMember("version_major", DEBUGGER_LAYOUT_FILE_VERSION_MAJOR, json.GetAllocator());
json.AddMember("version_minor", DEBUGGER_LAYOUT_FILE_VERSION_MINOR, json.GetAllocator());
rapidjson::Value version_hash;
version_hash.SetString(default_layouts_hash.c_str(), default_layouts_hash.size());
json.AddMember("version_hash", version_hash, json.GetAllocator());
std::string name_str = m_name.toStdString();
json.AddMember("name", rapidjson::Value().SetString(name_str.c_str(), name_str.size()), json.GetAllocator());
json.AddMember("target", rapidjson::Value().SetString(cpu_name, strlen(cpu_name)), json.GetAllocator());
json.AddMember("index", static_cast<int>(layout_index), json.GetAllocator());
json.AddMember("isDefault", m_is_default, json.GetAllocator());
json.AddMember("nextUniqueName", m_next_unique_name, json.GetAllocator());
if (!m_base_layout.empty())
{
rapidjson::Value base_layout;
base_layout.SetString(m_base_layout.c_str(), m_base_layout.size());
json.AddMember("baseLayout", base_layout, json.GetAllocator());
}
if (!m_toolbars.isEmpty())
{
std::string toolbars_str = m_toolbars.toBase64().toStdString();
rapidjson::Value toolbars;
toolbars.SetString(toolbars_str.data(), toolbars_str.size(), json.GetAllocator());
json.AddMember("toolbars", toolbars, json.GetAllocator());
}
rapidjson::Value widgets(rapidjson::kArrayType);
for (auto& [unique_name, widget] : m_widgets)
{
pxAssert(widget.get());
rapidjson::Value object(rapidjson::kObjectType);
std::string name_str = unique_name.toStdString();
rapidjson::Value name;
name.SetString(name_str.c_str(), name_str.size(), json.GetAllocator());
object.AddMember("uniqueName", name, json.GetAllocator());
const char* type_str = widget->metaObject()->className();
rapidjson::Value type;
type.SetString(type_str, strlen(type_str), json.GetAllocator());
object.AddMember("type", type, json.GetAllocator());
if (widget->cpuOverride().has_value())
{
const char* cpu_name = DebugInterface::cpuName(*widget->cpuOverride());
rapidjson::Value target;
target.SetString(cpu_name, strlen(cpu_name));
object.AddMember("target", target, json.GetAllocator());
}
JsonValueWrapper wrapper(object, json.GetAllocator());
widget->toJson(wrapper);
widgets.PushBack(object, json.GetAllocator());
}
json.AddMember("widgets", widgets, json.GetAllocator());
if (!m_geometry.isEmpty() && !geometry.Parse(m_geometry).HasParseError())
json.AddMember("geometry", geometry, json.GetAllocator());
rapidjson::StringBuffer string_buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(string_buffer);
json.Accept(writer);
std::string safe_name = Path::SanitizeFileName(m_name.toStdString());
// Create a temporary file first so that we don't corrupt an existing file
// in the case that we succeed in opening the file but fail to write our
// data to it.
std::string temp_file_path = Path::Combine(EmuFolders::DebuggerLayouts, safe_name + ".tmp");
if (!FileSystem::WriteStringToFile(temp_file_path.c_str(), string_buffer.GetString()))
{
Console.Error("Debugger: Failed to save temporary layout file '%s'.", temp_file_path.c_str());
FileSystem::DeleteFilePath(temp_file_path.c_str());
return false;
}
// Now move the layout to its final location.
std::string file_path = Path::Combine(EmuFolders::DebuggerLayouts, safe_name + ".json");
if (!FileSystem::RenamePath(temp_file_path.c_str(), file_path.c_str()))
{
Console.Error("Debugger: Failed to move layout file to '%s'.", file_path.c_str());
FileSystem::DeleteFilePath(temp_file_path.c_str());
return false;
}
// If the layout has been renamed we need to delete the old file.
if (file_path != m_layout_file_path)
deleteFile();
m_layout_file_path = std::move(file_path);
return true;
}
void DockLayout::load(
const std::string& path,
LoadResult& result,
DockLayout::Index& index_last_session)
{
pxAssert(!m_is_active);
result = SUCCESS;
std::optional<std::string> text = FileSystem::ReadFileToString(path.c_str());
if (!text.has_value())
{
Console.Error("Debugger: Failed to open layout file '%s'.", path.c_str());
result = FILE_NOT_FOUND;
return;
}
rapidjson::Document json;
if (json.Parse(text->c_str()).HasParseError() || !json.IsObject())
{
Console.Error("Debugger: Failed to parse layout file '%s' as JSON.", path.c_str());
result = INVALID_FORMAT;
return;
}
auto format = json.FindMember("format");
if (format == json.MemberEnd() ||
!format->value.IsString() ||
strcmp(format->value.GetString(), DEBUGGER_LAYOUT_FILE_FORMAT) != 0)
{
Console.Error("Debugger: Layout file '%s' has missing or invalid 'format' property.", path.c_str());
result = INVALID_FORMAT;
return;
}
auto version_major = json.FindMember("version_major");
if (version_major == json.MemberEnd() || !version_major->value.IsInt())
{
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_major' property.", path.c_str());
result = INVALID_FORMAT;
return;
}
if (version_major->value.GetInt() != DEBUGGER_LAYOUT_FILE_VERSION_MAJOR)
{
result = MAJOR_VERSION_MISMATCH;
return;
}
auto version_minor = json.FindMember("version_minor");
if (version_minor == json.MemberEnd() || !version_minor->value.IsInt())
{
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_minor' property.", path.c_str());
result = INVALID_FORMAT;
return;
}
auto version_hash = json.FindMember("version_hash");
if (version_hash == json.MemberEnd() || !version_hash->value.IsString())
{
Console.Error("Debugger: Layout file '%s' has missing or invalid 'version_hash' property.", path.c_str());
result = INVALID_FORMAT;
return;
}
if (strcmp(version_hash->value.GetString(), DockTables::hashDefaultLayouts().c_str()) != 0)
result = DEFAULT_LAYOUT_HASH_MISMATCH;
auto name = json.FindMember("name");
if (name != json.MemberEnd() && name->value.IsString())
m_name = name->value.GetString();
else
m_name = QCoreApplication::translate("DockLayout", "Unnamed");
m_name.truncate(DockUtils::MAX_LAYOUT_NAME_SIZE);
auto target = json.FindMember("target");
m_cpu = BREAKPOINT_EE;
if (target != json.MemberEnd() && target->value.IsString())
{
for (BreakPointCpu cpu : DEBUG_CPUS)
if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0)
m_cpu = cpu;
}
auto index = json.FindMember("index");
if (index != json.MemberEnd() && index->value.IsInt())
index_last_session = index->value.GetInt();
auto is_default = json.FindMember("isDefault");
if (is_default != json.MemberEnd() && is_default->value.IsBool())
m_is_default = is_default->value.GetBool();
auto next_unique_name = json.FindMember("nextUniqueName");
if (next_unique_name != json.MemberBegin() && next_unique_name->value.IsInt())
m_next_unique_name = next_unique_name->value.GetInt();
auto base_layout = json.FindMember("baseLayout");
if (base_layout != json.MemberEnd() && base_layout->value.IsString())
m_base_layout = base_layout->value.GetString();
auto toolbars = json.FindMember("toolbars");
if (toolbars != json.MemberEnd() && toolbars->value.IsString())
m_toolbars = QByteArray::fromBase64(toolbars->value.GetString());
auto widgets = json.FindMember("widgets");
if (widgets != json.MemberEnd() && widgets->value.IsArray())
{
for (rapidjson::Value& object : widgets->value.GetArray())
{
auto unique_name = object.FindMember("uniqueName");
if (unique_name == object.MemberEnd() || !unique_name->value.IsString())
continue;
auto widgets_iterator = m_widgets.find(unique_name->value.GetString());
if (widgets_iterator != m_widgets.end())
continue;
auto type = object.FindMember("type");
if (type == object.MemberEnd() || !type->value.IsString())
continue;
auto description = DockTables::DEBUGGER_WIDGETS.find(type->value.GetString());
if (description == DockTables::DEBUGGER_WIDGETS.end())
continue;
std::optional<BreakPointCpu> cpu_override;
auto target = object.FindMember("target");
if (target != object.MemberEnd() && target->value.IsString())
{
for (BreakPointCpu cpu : DEBUG_CPUS)
if (strcmp(DebugInterface::cpuName(cpu), target->value.GetString()) == 0)
cpu_override = cpu;
}
DebuggerWidgetParameters parameters;
parameters.unique_name = unique_name->value.GetString();
parameters.cpu = &DebugInterface::get(m_cpu);
parameters.cpu_override = cpu_override;
DebuggerWidget* widget = description->second.create_widget(parameters);
JsonValueWrapper wrapper(object, json.GetAllocator());
if (!widget->fromJson(wrapper))
{
delete widget;
continue;
}
m_widgets.emplace(unique_name->value.GetString(), widget);
}
}
auto geometry = json.FindMember("geometry");
if (geometry != json.MemberEnd() && geometry->value.IsObject())
{
rapidjson::StringBuffer string_buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(string_buffer);
geometry->value.Accept(writer);
m_geometry = QByteArray(string_buffer.GetString(), string_buffer.GetSize());
}
m_layout_file_path = path;
validatePrimaryDebuggerWidgets();
}
void DockLayout::validatePrimaryDebuggerWidgets()
{
std::map<std::string, std::vector<DebuggerWidget*>> type_to_widgets;
for (const auto& [unique_name, widget] : m_widgets)
type_to_widgets[widget->metaObject()->className()].emplace_back(widget.get());
for (auto& [type, widgets] : type_to_widgets)
{
u32 primary_widgets = 0;
// Make sure at most one widget is marked as primary.
for (DebuggerWidget* widget : widgets)
{
if (widget->isPrimary())
{
if (primary_widgets != 0)
widget->setPrimary(false);
primary_widgets++;
}
}
// If none of the widgets were marked as primary, just set the first one
// as the primary one.
if (primary_widgets == 0)
widgets[0]->setPrimary(true);
}
}
void DockLayout::setupDefaultLayout()
{
pxAssert(m_is_active);
if (!g_debugger_window)
return;
const DockTables::DefaultDockLayout* base_layout = DockTables::defaultLayout(m_base_layout);
if (!base_layout)
return;
std::vector<KDDockWidgets::QtWidgets::DockWidget*> groups(base_layout->groups.size(), nullptr);
for (const DockTables::DefaultDockWidgetDescription& dock_description : base_layout->widgets)
{
const DockTables::DefaultDockGroupDescription& group =
base_layout->groups[static_cast<u32>(dock_description.group)];
DebuggerWidget* widget = nullptr;
for (auto& [unique_name, test_widget] : m_widgets)
if (test_widget->metaObject()->className() == dock_description.type)
widget = test_widget;
if (!widget)
continue;
auto view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(
KDDockWidgets::Config::self().viewFactory()->createDockWidget(widget->uniqueName()));
view->setWidget(widget);
if (!groups[static_cast<u32>(dock_description.group)])
{
KDDockWidgets::QtWidgets::DockWidget* parent = nullptr;
if (group.parent != DockTables::DefaultDockGroup::ROOT)
parent = groups[static_cast<u32>(group.parent)];
g_debugger_window->addDockWidget(view, group.location, parent);
groups[static_cast<u32>(dock_description.group)] = view;
}
else
{
groups[static_cast<u32>(dock_description.group)]->addDockWidgetAsTab(view);
}
}
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
group->setCurrentTabIndex(0);
}
QString DockLayout::generateNewUniqueName(const char* type)
{
QString name;
do
{
if (m_next_unique_name == INT_MAX)
return QString();
// Produce unique names that will lexicographically sort in the order
// they were allocated. This ensures the #1, #2, etc suffixes for dock
// widgets with conflicting names will be assigned in the correct order.
name = QStringLiteral("%1-%2").arg(m_next_unique_name, 16, 10, QLatin1Char('0')).arg(type);
m_next_unique_name++;
} while (hasDebuggerWidget(name));
return name;
}

View File

@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "Debugger/Docking/DockTables.h"
#include "DebugTools/DebugInterface.h"
#include <kddockwidgets/MainWindow.h>
#include <kddockwidgets/DockWidget.h>
#include <QtCore/QPointer>
class DebuggerWidget;
class DebuggerWindow;
extern const char* DEBUGGER_LAYOUT_FILE_FORMAT;
// Increment this whenever there is a breaking change to the JSON format.
extern const u32 DEBUGGER_LAYOUT_FILE_VERSION_MAJOR;
// Increment this whenever there is a non-breaking change to the JSON format.
extern const u32 DEBUGGER_LAYOUT_FILE_VERSION_MINOR;
class DockLayout
{
public:
using Index = size_t;
static const constexpr Index INVALID_INDEX = SIZE_MAX;
enum LoadResult
{
SUCCESS,
FILE_NOT_FOUND,
INVALID_FORMAT,
MAJOR_VERSION_MISMATCH,
DEFAULT_LAYOUT_HASH_MISMATCH,
CONFLICTING_NAME
};
// Create a layout based on a default layout.
DockLayout(
QString name,
BreakPointCpu cpu,
bool is_default,
const std::string& base_name,
DockLayout::Index index);
// Create a new blank layout.
DockLayout(
QString name,
BreakPointCpu cpu,
bool is_default,
DockLayout::Index index);
// Clone an existing layout.
DockLayout(
QString name,
BreakPointCpu cpu,
bool is_default,
const DockLayout& layout_to_clone,
DockLayout::Index index);
// Load a layout from a file.
DockLayout(
const std::string& path,
LoadResult& result,
DockLayout::Index& index_last_session,
DockLayout::Index index);
~DockLayout();
DockLayout(const DockLayout& rhs) = delete;
DockLayout& operator=(const DockLayout& rhs) = delete;
DockLayout(DockLayout&& rhs) = default;
DockLayout& operator=(DockLayout&&) = default;
const QString& name() const;
void setName(QString name);
BreakPointCpu cpu() const;
void setCpu(BreakPointCpu cpu);
bool isDefault() const;
// Tear down and save the state of all the dock widgets from this layout.
void freeze();
// Restore the state of all the dock widgets from this layout.
void thaw();
bool canReset();
void reset();
KDDockWidgets::Core::DockWidget* createDockWidget(const QString& name);
void updateDockWidgetTitles();
const std::map<QString, QPointer<DebuggerWidget>>& debuggerWidgets();
bool hasDebuggerWidget(const QString& unique_name);
size_t countDebuggerWidgetsOfType(const char* type);
void createDebuggerWidget(const std::string& type);
void recreateDebuggerWidget(const QString& unique_name);
void destroyDebuggerWidget(const QString& unique_name);
void setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary);
void deleteFile();
bool save(DockLayout::Index layout_index);
private:
void load(
const std::string& path,
DockLayout::LoadResult& result,
DockLayout::Index& index_last_session);
// Make sure there is only a single primary debugger widget of each type.
void validatePrimaryDebuggerWidgets();
void setupDefaultLayout();
QString generateNewUniqueName(const char* type);
// The name displayed in the user interface. Also used to determine the
// file name for the layout file.
QString m_name;
// The default target for dock widgets in this layout. This can be
// overriden on a per-widget basis.
BreakPointCpu m_cpu;
// Is this one of the default layouts?
bool m_is_default = false;
// A counter used to generate new unique names for dock widgets.
int m_next_unique_name = 0;
// The name of the default layout which this layout was based on. This will
// be used if the m_geometry variable above is empty.
std::string m_base_layout;
// The state of all the toolbars, saved and restored using
// QMainWindow::saveState and QMainWindow::restoreState respectively.
QByteArray m_toolbars;
// All the dock widgets currently open in this layout. If this is the active
// layout then these will be owned by the docking system, otherwise they
// won't be and will need to be cleaned up separately.
std::map<QString, QPointer<DebuggerWidget>> m_widgets;
// The geometry of all the dock widgets, converted to JSON by the
// LayoutSaver class from KDDockWidgets.
QByteArray m_geometry;
// The absolute file path of the corresponding layout file as it currently
// exists exists on disk, or empty if no such file exists.
std::string m_layout_file_path;
// If this layout is the currently selected layout this will be true,
// otherwise it will be false.
bool m_is_active = false;
};

View File

@@ -0,0 +1,967 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DockManager.h"
#include "Debugger/DebuggerWidget.h"
#include "Debugger/DebuggerWindow.h"
#include "Debugger/Docking/DockTables.h"
#include "Debugger/Docking/DockViews.h"
#include "Debugger/Docking/DropIndicators.h"
#include "Debugger/Docking/LayoutEditorDialog.h"
#include "Debugger/Docking/NoLayoutsWidget.h"
#include "common/Assertions.h"
#include "common/FileSystem.h"
#include "common/StringUtil.h"
#include "common/Path.h"
#include <kddockwidgets/Config.h>
#include <kddockwidgets/core/Group.h>
#include <kddockwidgets/core/Stack.h>
#include <kddockwidgets/core/indicators/SegmentedDropIndicatorOverlay.h>
#include <kddockwidgets/qtwidgets/Stack.h>
#include <QtCore/QTimer>
#include <QtCore/QtTranslation>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QPushButton>
DockManager::DockManager(QObject* parent)
: QObject(parent)
{
QTimer* autosave_timer = new QTimer(this);
connect(autosave_timer, &QTimer::timeout, this, &DockManager::saveCurrentLayout);
autosave_timer->start(60 * 1000);
m_blink_timer = new QTimer(this);
connect(m_blink_timer, &QTimer::timeout, this, &DockManager::layoutSwitcherUpdateBlink);
}
void DockManager::configureDockingSystem()
{
std::string indicator_style = Host::GetBaseStringSettingValue(
"Debugger/UserInterface", "DropIndicatorStyle", "Classic");
if (indicator_style == "Segmented" || indicator_style == "Minimalistic")
{
KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Segmented;
DockSegmentedDropIndicatorOverlay::s_indicator_style = indicator_style;
}
else
{
KDDockWidgets::Core::ViewFactory::s_dropIndicatorType = KDDockWidgets::DropIndicatorType::Classic;
}
static bool done = false;
if (done)
return;
KDDockWidgets::initFrontend(KDDockWidgets::FrontendType::QtWidgets);
KDDockWidgets::Config& config = KDDockWidgets::Config::self();
config.setFlags(
KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible |
KDDockWidgets::Config::Flag_AlwaysShowTabs |
KDDockWidgets::Config::Flag_AllowReorderTabs |
KDDockWidgets::Config::Flag_TitleBarIsFocusable);
// We set this flag regardless of whether or not the windowing system
// supports compositing since it's only used by the built-in docking
// indicator, and we only fall back to that if compositing is disabled.
config.setInternalFlags(KDDockWidgets::Config::InternalFlag_DisableTranslucency);
config.setDockWidgetFactoryFunc(&DockManager::dockWidgetFactory);
config.setViewFactory(new DockViewFactory());
config.setDragAboutToStartFunc(&DockManager::dragAboutToStart);
config.setStartDragDistance(std::max(QApplication::startDragDistance(), 32));
done = true;
}
bool DockManager::deleteLayout(DockLayout::Index layout_index)
{
pxAssertRel(layout_index != DockLayout::INVALID_INDEX,
"DockManager::deleteLayout called with INVALID_INDEX.");
if (layout_index == m_current_layout)
{
DockLayout::Index other_layout = DockLayout::INVALID_INDEX;
if (layout_index + 1 < m_layouts.size())
other_layout = layout_index + 1;
else if (layout_index > 0)
other_layout = layout_index - 1;
switchToLayout(other_layout);
}
m_layouts.at(layout_index).deleteFile();
m_layouts.erase(m_layouts.begin() + layout_index);
// All the layouts after the one being deleted have been shifted over by
// one, so adjust the current layout index accordingly.
if (m_current_layout > layout_index && m_current_layout != DockLayout::INVALID_INDEX)
m_current_layout--;
if (m_layouts.empty() && g_debugger_window)
{
NoLayoutsWidget* widget = new NoLayoutsWidget;
connect(widget->createDefaultLayoutsButton(), &QPushButton::clicked, this, &DockManager::resetAllLayouts);
KDDockWidgets::QtWidgets::DockWidget* dock = new KDDockWidgets::QtWidgets::DockWidget("placeholder");
dock->setTitle(tr("No Layouts"));
dock->setWidget(widget);
g_debugger_window->addDockWidget(dock, KDDockWidgets::Location_OnTop);
}
return true;
}
void DockManager::switchToLayout(DockLayout::Index layout_index, bool blink_tab)
{
if (layout_index != m_current_layout)
{
if (m_current_layout != DockLayout::INVALID_INDEX)
{
DockLayout& layout = m_layouts.at(m_current_layout);
layout.freeze();
layout.save(m_current_layout);
}
// Clear out the existing positions of toolbars so they don't affect
// where new toolbars appear for other layouts.
if (g_debugger_window)
g_debugger_window->clearToolBarState();
updateToolBarLockState();
m_current_layout = layout_index;
if (m_current_layout != DockLayout::INVALID_INDEX)
{
DockLayout& layout = m_layouts.at(m_current_layout);
layout.thaw();
int tab_index = static_cast<int>(layout_index);
if (m_switcher && tab_index >= 0 && tab_index < m_plus_tab_index)
{
m_ignore_current_tab_changed = true;
m_switcher->setCurrentIndex(tab_index);
m_ignore_current_tab_changed = false;
}
}
}
if (blink_tab)
layoutSwitcherStartBlink();
}
bool DockManager::switchToLayoutWithCPU(BreakPointCpu cpu, bool blink_tab)
{
// Don't interrupt the user if the current layout already has the right CPU.
if (m_current_layout != DockLayout::INVALID_INDEX && m_layouts.at(m_current_layout).cpu() == cpu)
{
switchToLayout(m_current_layout, blink_tab);
return true;
}
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
{
if (m_layouts[i].cpu() == cpu)
{
switchToLayout(i, blink_tab);
return true;
}
}
return false;
}
void DockManager::loadLayouts()
{
m_layouts.clear();
// Load the layouts.
FileSystem::FindResultsArray files;
FileSystem::FindFiles(
EmuFolders::DebuggerLayouts.c_str(),
"*.json",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES,
&files);
bool needs_reset = false;
bool order_changed = false;
std::vector<DockLayout::Index> indices_last_session;
for (const FILESYSTEM_FIND_DATA& ffd : files)
{
DockLayout::LoadResult result;
DockLayout::Index index_last_session = DockLayout::INVALID_INDEX;
DockLayout::Index index =
createLayout(ffd.FileName, result, index_last_session);
DockLayout& layout = m_layouts.at(index);
// Try to make sure the layout has a unique name.
const QString& name = layout.name();
QString new_name = name;
if (result == DockLayout::SUCCESS || result == DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
{
for (int i = 2; hasNameConflict(new_name, index) && i < 100; i++)
{
if (i == 99)
{
result = DockLayout::CONFLICTING_NAME;
break;
}
new_name = QString("%1 #%2").arg(name).arg(i);
}
}
needs_reset |= result != DockLayout::SUCCESS;
if (result != DockLayout::SUCCESS && result != DockLayout::DEFAULT_LAYOUT_HASH_MISMATCH)
{
deleteLayout(index);
// Only delete the file if we've identified that it's actually a
// layout file.
if (result == DockLayout::MAJOR_VERSION_MISMATCH || result == DockLayout::CONFLICTING_NAME)
FileSystem::DeleteFilePath(ffd.FileName.c_str());
continue;
}
if (new_name != name)
{
layout.setName(new_name);
layout.save(index);
}
if (index_last_session != index)
order_changed = true;
indices_last_session.emplace_back(index_last_session);
}
// Make sure the layouts remain in the same order they were in previously.
std::vector<DockLayout*> layout_pointers;
for (DockLayout& layout : m_layouts)
layout_pointers.emplace_back(&layout);
std::sort(layout_pointers.begin(), layout_pointers.end(),
[this, &indices_last_session](const DockLayout* lhs, const DockLayout* rhs) {
size_t lhs_index = lhs - m_layouts.data();
size_t rhs_index = rhs - m_layouts.data();
DockLayout::Index lhs_index_last_session = indices_last_session.at(lhs_index);
DockLayout::Index rhs_index_last_session = indices_last_session.at(rhs_index);
return lhs_index_last_session < rhs_index_last_session;
});
std::vector<DockLayout> sorted_layouts;
for (size_t i = 0; i < layout_pointers.size(); i++)
sorted_layouts.emplace_back(std::move(*layout_pointers[i]));
m_layouts = std::move(sorted_layouts);
if (m_layouts.empty() || needs_reset)
resetDefaultLayouts();
else
updateLayoutSwitcher();
// Make sure the indices in the existing layout files match up with the
// indices of any new layouts.
if (order_changed)
saveLayouts();
}
bool DockManager::saveLayouts()
{
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
if (!m_layouts[i].save(i))
return false;
return true;
}
bool DockManager::saveCurrentLayout()
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return true;
return m_layouts.at(m_current_layout).save(m_current_layout);
}
void DockManager::resetAllLayouts()
{
switchToLayout(DockLayout::INVALID_INDEX);
for (DockLayout& layout : m_layouts)
layout.deleteFile();
m_layouts.clear();
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
createLayout(tr(layout.name.c_str()), layout.cpu, true, layout.name);
switchToLayout(0);
updateLayoutSwitcher();
saveLayouts();
}
void DockManager::resetDefaultLayouts()
{
switchToLayout(DockLayout::INVALID_INDEX);
std::vector<DockLayout> old_layouts = std::move(m_layouts);
m_layouts = std::vector<DockLayout>();
for (const DockTables::DefaultDockLayout& layout : DockTables::DEFAULT_DOCK_LAYOUTS)
createLayout(tr(layout.name.c_str()), layout.cpu, true, layout.name);
for (DockLayout& layout : old_layouts)
if (!layout.isDefault())
m_layouts.emplace_back(std::move(layout));
else
layout.deleteFile();
switchToLayout(0);
updateLayoutSwitcher();
saveLayouts();
}
void DockManager::createToolsMenu(QMenu* menu)
{
menu->clear();
if (m_current_layout == DockLayout::INVALID_INDEX || !g_debugger_window)
return;
for (QToolBar* widget : g_debugger_window->findChildren<QToolBar*>())
{
QAction* action = menu->addAction(widget->windowTitle());
action->setText(widget->windowTitle());
action->setCheckable(true);
action->setChecked(widget->isVisible());
connect(action, &QAction::triggered, this, [widget]() {
widget->setVisible(!widget->isVisible());
});
menu->addAction(action);
}
}
void DockManager::createWindowsMenu(QMenu* menu)
{
menu->clear();
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
DockLayout& layout = m_layouts.at(m_current_layout);
// Create a menu that allows for multiple dock widgets of the same type to
// be opened.
QMenu* add_another_menu = menu->addMenu(tr("Add Another..."));
std::vector<DebuggerWidget*> add_another_widgets;
std::set<std::string> add_another_types;
for (const auto& [unique_name, widget] : layout.debuggerWidgets())
{
std::string type = widget->metaObject()->className();
if (widget->supportsMultipleInstances() && !add_another_types.contains(type))
{
add_another_widgets.emplace_back(widget);
add_another_types.emplace(type);
}
}
std::sort(add_another_widgets.begin(), add_another_widgets.end(),
[](const DebuggerWidget* lhs, const DebuggerWidget* rhs) {
if (lhs->displayNameWithoutSuffix() == rhs->displayNameWithoutSuffix())
return lhs->displayNameSuffixNumber() < rhs->displayNameSuffixNumber();
return lhs->displayNameWithoutSuffix() < rhs->displayNameWithoutSuffix();
});
for (DebuggerWidget* widget : add_another_widgets)
{
const char* type = widget->metaObject()->className();
const auto description_iterator = DockTables::DEBUGGER_WIDGETS.find(type);
pxAssert(description_iterator != DockTables::DEBUGGER_WIDGETS.end());
QAction* action = add_another_menu->addAction(description_iterator->second.display_name);
connect(action, &QAction::triggered, this, [this, type]() {
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_layouts.at(m_current_layout).createDebuggerWidget(type);
});
}
if (add_another_widgets.empty())
add_another_menu->setDisabled(true);
menu->addSeparator();
struct DebuggerWidgetToggle
{
QString display_name;
std::optional<int> suffix_number;
QAction* action;
};
std::vector<DebuggerWidgetToggle> toggles;
std::set<std::string> toggle_types;
// Create a menu item for each open debugger widget.
for (const auto& [unique_name, widget] : layout.debuggerWidgets())
{
QAction* action = new QAction(menu);
action->setText(widget->displayName());
action->setCheckable(true);
action->setChecked(true);
connect(action, &QAction::triggered, this, [this, unique_name]() {
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_layouts.at(m_current_layout).destroyDebuggerWidget(unique_name);
});
DebuggerWidgetToggle& toggle = toggles.emplace_back();
toggle.display_name = widget->displayNameWithoutSuffix();
toggle.suffix_number = widget->displayNameSuffixNumber();
toggle.action = action;
toggle_types.emplace(widget->metaObject()->className());
}
// Create menu items to open debugger widgets without any open instances.
for (const auto& [type, desc] : DockTables::DEBUGGER_WIDGETS)
{
if (!toggle_types.contains(type))
{
QString display_name = QCoreApplication::translate("DebuggerWidget", desc.display_name);
QAction* action = new QAction(menu);
action->setText(display_name);
action->setCheckable(true);
action->setChecked(false);
connect(action, &QAction::triggered, this, [this, type]() {
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_layouts.at(m_current_layout).createDebuggerWidget(type);
});
DebuggerWidgetToggle& toggle = toggles.emplace_back();
toggle.display_name = display_name;
toggle.suffix_number = std::nullopt;
toggle.action = action;
}
}
std::sort(toggles.begin(), toggles.end(),
[](const DebuggerWidgetToggle& lhs, const DebuggerWidgetToggle& rhs) {
if (lhs.display_name == rhs.display_name)
return lhs.suffix_number < rhs.suffix_number;
return lhs.display_name < rhs.display_name;
});
for (const DebuggerWidgetToggle& toggle : toggles)
menu->addAction(toggle.action);
}
QWidget* DockManager::createLayoutSwitcher(QWidget* menu_bar)
{
QWidget* container = new QWidget;
QHBoxLayout* layout = new QHBoxLayout;
layout->setContentsMargins(0, 2, 2, 0);
container->setLayout(layout);
QWidget* menu_wrapper = new QWidget;
menu_wrapper->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
layout->addWidget(menu_wrapper);
QHBoxLayout* menu_layout = new QHBoxLayout;
menu_layout->setContentsMargins(0, 4, 0, 4);
menu_wrapper->setLayout(menu_layout);
menu_layout->addWidget(menu_bar);
m_switcher = new QTabBar;
m_switcher->setContentsMargins(0, 0, 0, 0);
m_switcher->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
m_switcher->setContextMenuPolicy(Qt::CustomContextMenu);
m_switcher->setMovable(true);
layout->addWidget(m_switcher);
updateLayoutSwitcher();
connect(m_switcher, &QTabBar::tabMoved, this, &DockManager::layoutSwitcherTabMoved);
connect(m_switcher, &QTabBar::customContextMenuRequested, this, &DockManager::layoutSwitcherContextMenu);
QWidget* spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
layout->addWidget(spacer);
bool layout_locked = Host::GetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", true);
QPushButton* lock_layout_toggle = new QPushButton;
lock_layout_toggle->setCheckable(true);
lock_layout_toggle->setChecked(layout_locked);
lock_layout_toggle->setFlat(true);
connect(lock_layout_toggle, &QPushButton::toggled, this, [this, lock_layout_toggle](bool checked) {
setLayoutLocked(checked, lock_layout_toggle, true);
});
layout->addWidget(lock_layout_toggle);
setLayoutLocked(layout_locked, lock_layout_toggle, false);
return container;
}
void DockManager::updateLayoutSwitcher()
{
if (!m_switcher)
return;
disconnect(m_tab_connection);
for (int i = m_switcher->count(); i > 0; i--)
m_switcher->removeTab(i - 1);
for (DockLayout& layout : m_layouts)
{
const char* cpu_name = DebugInterface::cpuName(layout.cpu());
QString tab_name = QString("%1 (%2)").arg(layout.name()).arg(cpu_name);
m_switcher->addTab(tab_name);
}
m_plus_tab_index = m_switcher->addTab("+");
m_current_tab_index = m_current_layout;
if (m_current_layout != DockLayout::INVALID_INDEX)
m_switcher->setCurrentIndex(m_current_layout);
// If we don't have any layouts, the currently selected tab will never be
// changed, so we respond to all clicks instead.
if (!m_layouts.empty())
m_tab_connection = connect(m_switcher, &QTabBar::currentChanged, this, &DockManager::layoutSwitcherTabChanged);
else
m_tab_connection = connect(m_switcher, &QTabBar::tabBarClicked, this, &DockManager::layoutSwitcherTabChanged);
layoutSwitcherStopBlink();
}
void DockManager::layoutSwitcherTabChanged(int index)
{
// Prevent recursion.
if (m_ignore_current_tab_changed)
return;
if (index == m_plus_tab_index)
{
if (m_current_tab_index >= 0 && m_current_tab_index < m_plus_tab_index)
{
m_ignore_current_tab_changed = true;
m_switcher->setCurrentIndex(m_current_tab_index);
m_ignore_current_tab_changed = false;
}
auto name_validator = [this](const QString& name) {
return !hasNameConflict(name, DockLayout::INVALID_INDEX);
};
bool can_clone_current_layout = m_current_layout != DockLayout::INVALID_INDEX;
QPointer<LayoutEditorDialog> dialog = new LayoutEditorDialog(
name_validator, can_clone_current_layout, g_debugger_window);
if (dialog->exec() == QDialog::Accepted && name_validator(dialog->name()))
{
DockLayout::Index new_layout = DockLayout::INVALID_INDEX;
const auto [mode, index] = dialog->initialState();
switch (mode)
{
case LayoutEditorDialog::DEFAULT_LAYOUT:
{
const DockTables::DefaultDockLayout& default_layout = DockTables::DEFAULT_DOCK_LAYOUTS.at(index);
new_layout = createLayout(dialog->name(), dialog->cpu(), false, default_layout.name);
break;
}
case LayoutEditorDialog::BLANK_LAYOUT:
{
new_layout = createLayout(dialog->name(), dialog->cpu(), false);
break;
}
case LayoutEditorDialog::CLONE_LAYOUT:
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
DockLayout::Index old_layout = m_current_layout;
// Freeze the current layout so we can copy the geometry.
switchToLayout(DockLayout::INVALID_INDEX);
new_layout = createLayout(dialog->name(), dialog->cpu(), false, m_layouts.at(old_layout));
break;
}
}
updateLayoutSwitcher();
switchToLayout(new_layout);
}
delete dialog.get();
}
else
{
DockLayout::Index layout_index = static_cast<DockLayout::Index>(index);
if (layout_index < 0 || layout_index >= m_layouts.size())
return;
switchToLayout(layout_index);
m_current_tab_index = index;
}
}
void DockManager::layoutSwitcherTabMoved(int from, int to)
{
DockLayout::Index from_index = static_cast<DockLayout::Index>(from);
DockLayout::Index to_index = static_cast<DockLayout::Index>(to);
if (from_index >= m_layouts.size() || to_index >= m_layouts.size())
{
// This happens when the user tries to move a layout to the right of the
// plus button.
updateLayoutSwitcher();
return;
}
DockLayout& from_layout = m_layouts[from_index];
DockLayout& to_layout = m_layouts[to_index];
std::swap(from_layout, to_layout);
from_layout.save(from_index);
to_layout.save(to_index);
if (from_index == m_current_layout)
m_current_layout = to_index;
else if (to_index == m_current_layout)
m_current_layout = from_index;
}
void DockManager::layoutSwitcherContextMenu(QPoint pos)
{
DockLayout::Index layout_index = static_cast<DockLayout::Index>(m_switcher->tabAt(pos));
if (layout_index >= m_layouts.size())
return;
DockLayout& layout = m_layouts[layout_index];
QMenu* menu = new QMenu(m_switcher);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction* edit_action = menu->addAction(tr("Edit Layout"));
connect(edit_action, &QAction::triggered, [this, layout_index]() {
if (layout_index >= m_layouts.size())
return;
DockLayout& layout = m_layouts[layout_index];
auto name_validator = [this, layout_index](const QString& name) {
return !hasNameConflict(name, layout_index);
};
QPointer<LayoutEditorDialog> dialog = new LayoutEditorDialog(
layout.name(), layout.cpu(), name_validator, g_debugger_window);
if (dialog->exec() != QDialog::Accepted || !name_validator(dialog->name()))
return;
layout.setName(dialog->name());
layout.setCpu(dialog->cpu());
layout.save(layout_index);
delete dialog.get();
updateLayoutSwitcher();
});
QAction* reset_action = menu->addAction(tr("Reset Layout"));
reset_action->setEnabled(layout.canReset());
reset_action->connect(reset_action, &QAction::triggered, [this, layout_index]() {
if (layout_index >= m_layouts.size())
return;
DockLayout& layout = m_layouts[layout_index];
if (!layout.canReset())
return;
QString text = tr("Are you sure you want to reset layout '%1'?").arg(layout.name());
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
return;
bool current_layout = layout_index == m_current_layout;
if (current_layout)
switchToLayout(DockLayout::INVALID_INDEX);
layout.reset();
layout.save(layout_index);
if (current_layout)
switchToLayout(layout_index);
});
QAction* delete_action = menu->addAction(tr("Delete Layout"));
connect(delete_action, &QAction::triggered, [this, layout_index]() {
if (layout_index >= m_layouts.size())
return;
DockLayout& layout = m_layouts[layout_index];
QString text = tr("Are you sure you want to delete layout '%1'?").arg(layout.name());
if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
return;
deleteLayout(layout_index);
updateLayoutSwitcher();
});
menu->popup(m_switcher->mapToGlobal(pos));
}
void DockManager::layoutSwitcherStartBlink()
{
if (!m_switcher)
return;
layoutSwitcherStopBlink();
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_blink_tab = m_current_layout;
m_blink_stage = 0;
m_blink_timer->start(500);
layoutSwitcherUpdateBlink();
}
void DockManager::layoutSwitcherUpdateBlink()
{
if (!m_switcher)
return;
if (m_blink_tab < m_switcher->count())
{
if (m_blink_stage % 2 == 0)
m_switcher->setTabTextColor(m_blink_tab, Qt::red);
else
m_switcher->setTabTextColor(m_blink_tab, m_switcher->palette().text().color());
}
m_blink_stage++;
if (m_blink_stage > 7)
m_blink_timer->stop();
}
void DockManager::layoutSwitcherStopBlink()
{
if (m_blink_timer->isActive())
{
if (m_blink_tab < m_switcher->count())
m_switcher->setTabTextColor(m_blink_tab, m_switcher->palette().text().color());
m_blink_timer->stop();
}
}
bool DockManager::hasNameConflict(const QString& name, DockLayout::Index layout_index)
{
std::string safe_name = Path::SanitizeFileName(name.toStdString());
for (DockLayout::Index i = 0; i < m_layouts.size(); i++)
{
std::string other_safe_name = Path::SanitizeFileName(m_layouts[i].name().toStdString());
if (i != layout_index && StringUtil::compareNoCase(other_safe_name, safe_name))
return true;
}
return false;
}
void DockManager::updateDockWidgetTitles()
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_layouts.at(m_current_layout).updateDockWidgetTitles();
}
const std::map<QString, QPointer<DebuggerWidget>>& DockManager::debuggerWidgets()
{
static std::map<QString, QPointer<DebuggerWidget>> dummy;
if (m_current_layout == DockLayout::INVALID_INDEX)
return dummy;
return m_layouts.at(m_current_layout).debuggerWidgets();
}
size_t DockManager::countDebuggerWidgetsOfType(const char* type)
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return 0;
return m_layouts.at(m_current_layout).countDebuggerWidgetsOfType(type);
}
void DockManager::recreateDebuggerWidget(const QString& unique_name)
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_layouts.at(m_current_layout).recreateDebuggerWidget(unique_name);
}
void DockManager::destroyDebuggerWidget(const QString& unique_name)
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_layouts.at(m_current_layout).destroyDebuggerWidget(unique_name);
}
void DockManager::setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary)
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
m_layouts.at(m_current_layout).setPrimaryDebuggerWidget(widget, is_primary);
}
void DockManager::switchToDebuggerWidget(DebuggerWidget* widget)
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return;
for (const auto& [unique_name, test_widget] : m_layouts.at(m_current_layout).debuggerWidgets())
{
if (widget == test_widget)
{
auto [controller, view] = DockUtils::dockWidgetFromName(unique_name);
controller->setAsCurrentTab();
break;
}
}
}
void DockManager::updateStyleSheets()
{
for (DockLayout& layout : m_layouts)
for (const auto& [unique_name, widget] : layout.debuggerWidgets())
widget->updateStyleSheet();
}
bool DockManager::isLayoutLocked()
{
return m_layout_locked;
}
void DockManager::setLayoutLocked(bool locked, QPushButton* lock_layout_toggle, bool write_back)
{
m_layout_locked = locked;
if (lock_layout_toggle)
{
if (m_layout_locked)
{
lock_layout_toggle->setText(tr("Layout Locked"));
lock_layout_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-lock")));
}
else
{
lock_layout_toggle->setText(tr("Layout Unlocked"));
lock_layout_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-unlock")));
}
}
updateToolBarLockState();
for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
{
auto stack = static_cast<KDDockWidgets::QtWidgets::Stack*>(group->stack()->view());
stack->setTabsClosable(!m_layout_locked);
// HACK: Make sure the sizes of the tabs get updated.
if (stack->tabBar()->count() > 0)
stack->tabBar()->setTabText(0, stack->tabBar()->tabText(0));
}
if (write_back)
{
Host::SetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", m_layout_locked);
Host::CommitBaseSettingChanges();
}
}
void DockManager::updateToolBarLockState()
{
if (!g_debugger_window)
return;
for (QToolBar* toolbar : g_debugger_window->findChildren<QToolBar*>())
toolbar->setMovable(!m_layout_locked || toolbar->isFloating());
}
std::optional<BreakPointCpu> DockManager::cpu()
{
if (m_current_layout == DockLayout::INVALID_INDEX)
return std::nullopt;
return m_layouts.at(m_current_layout).cpu();
}
KDDockWidgets::Core::DockWidget* DockManager::dockWidgetFactory(const QString& name)
{
if (!g_debugger_window)
return nullptr;
DockManager& manager = g_debugger_window->dockManager();
if (manager.m_current_layout == DockLayout::INVALID_INDEX)
return nullptr;
return manager.m_layouts.at(manager.m_current_layout).createDockWidget(name);
}
bool DockManager::dragAboutToStart(KDDockWidgets::Core::Draggable* draggable)
{
bool locked = true;
if (g_debugger_window)
locked = g_debugger_window->dockManager().isLayoutLocked();
KDDockWidgets::Config::self().setDropIndicatorsInhibited(locked);
if (draggable->isInProgrammaticDrag())
return true;
// Allow floating windows to be dragged around even if the layout is locked.
if (draggable->isWindow())
return true;
if (!g_debugger_window)
return false;
return !locked;
}

View File

@@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "Debugger/Docking/DockLayout.h"
#include <kddockwidgets/MainWindow.h>
#include <kddockwidgets/DockWidget.h>
#include <kddockwidgets/core/DockRegistry.h>
#include <kddockwidgets/core/DockWidget.h>
#include <kddockwidgets/core/Draggable_p.h>
#include <QtCore/QPointer>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTabBar>
class DockManager : public QObject
{
Q_OBJECT
public:
DockManager(QObject* parent = nullptr);
DockManager(const DockManager& rhs) = delete;
DockManager& operator=(const DockManager& rhs) = delete;
DockManager(DockManager&& rhs) = delete;
DockManager& operator=(DockManager&&) = delete;
// This needs to be called before any KDDockWidgets objects are created
// including the debugger window itself.
static void configureDockingSystem();
template <typename... Args>
DockLayout::Index createLayout(Args&&... args)
{
DockLayout::Index layout_index = m_layouts.size();
if (m_layouts.empty())
{
// Delete the placeholder created in DockManager::deleteLayout.
for (KDDockWidgets::Core::DockWidget* dock : KDDockWidgets::DockRegistry::self()->dockwidgets())
delete dock;
}
m_layouts.emplace_back(std::forward<Args>(args)..., layout_index);
return layout_index;
}
bool deleteLayout(DockLayout::Index layout_index);
void switchToLayout(DockLayout::Index layout_index, bool blink_tab = false);
bool switchToLayoutWithCPU(BreakPointCpu cpu, bool blink_tab = false);
void loadLayouts();
bool saveLayouts();
bool saveCurrentLayout();
QString currentLayoutName();
bool canResetCurrentLayout();
void resetCurrentLayout();
void resetDefaultLayouts();
void resetAllLayouts();
void createToolsMenu(QMenu* menu);
void createWindowsMenu(QMenu* menu);
QWidget* createLayoutSwitcher(QWidget* menu_bar);
void updateLayoutSwitcher();
void layoutSwitcherTabChanged(int index);
void layoutSwitcherTabMoved(int from, int to);
void layoutSwitcherContextMenu(QPoint pos);
void layoutSwitcherStartBlink();
void layoutSwitcherUpdateBlink();
void layoutSwitcherStopBlink();
bool hasNameConflict(const QString& name, DockLayout::Index layout_index);
void updateDockWidgetTitles();
const std::map<QString, QPointer<DebuggerWidget>>& debuggerWidgets();
size_t countDebuggerWidgetsOfType(const char* type);
void recreateDebuggerWidget(const QString& unique_name);
void destroyDebuggerWidget(const QString& unique_name);
void setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary);
void switchToDebuggerWidget(DebuggerWidget* widget);
void updateStyleSheets();
bool isLayoutLocked();
void setLayoutLocked(bool locked, QPushButton* lock_layout_toggle, bool write_back);
void updateToolBarLockState();
std::optional<BreakPointCpu> cpu();
private:
static KDDockWidgets::Core::DockWidget* dockWidgetFactory(const QString& name);
static bool dragAboutToStart(KDDockWidgets::Core::Draggable* draggable);
std::vector<DockLayout> m_layouts;
DockLayout::Index m_current_layout = DockLayout::INVALID_INDEX;
QTabBar* m_switcher = nullptr;
int m_plus_tab_index = -1;
int m_current_tab_index = -1;
bool m_ignore_current_tab_changed = false;
QMetaObject::Connection m_tab_connection;
bool m_layout_locked = true;
QTimer* m_blink_timer = nullptr;
int m_blink_tab = 0;
int m_blink_stage = 0;
};

View File

@@ -0,0 +1,226 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DockTables.h"
#include "Debugger/DebuggerEvents.h"
#include "Debugger/DisassemblyWidget.h"
#include "Debugger/RegisterWidget.h"
#include "Debugger/StackWidget.h"
#include "Debugger/ThreadWidget.h"
#include "Debugger/Breakpoints/BreakpointWidget.h"
#include "Debugger/Memory/MemorySearchWidget.h"
#include "Debugger/Memory/MemoryViewWidget.h"
#include "Debugger/Memory/SavedAddressesWidget.h"
#include "Debugger/SymbolTree/SymbolTreeWidgets.h"
#include "common/MD5Digest.h"
#include "fmt/format.h"
using namespace DockUtils;
#define DEBUGGER_WIDGET(type, display_name, preferred_location) \
{ \
#type, \
{ \
[](const DebuggerWidgetParameters& parameters) -> DebuggerWidget* { \
DebuggerWidget* widget = new type(parameters); \
widget->handleEvent(DebuggerEvents::Refresh()); \
return widget; \
}, \
display_name, \
preferred_location \
} \
}
const std::map<std::string, DockTables::DebuggerWidgetDescription> DockTables::DEBUGGER_WIDGETS = {
DEBUGGER_WIDGET(BreakpointWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Breakpoints"), BOTTOM_MIDDLE),
DEBUGGER_WIDGET(DisassemblyWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Disassembly"), TOP_RIGHT),
DEBUGGER_WIDGET(FunctionTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Functions"), TOP_LEFT),
DEBUGGER_WIDGET(GlobalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Globals"), BOTTOM_MIDDLE),
DEBUGGER_WIDGET(LocalVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Locals"), BOTTOM_MIDDLE),
DEBUGGER_WIDGET(MemorySearchWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory Search"), TOP_LEFT),
DEBUGGER_WIDGET(MemoryViewWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Memory"), BOTTOM_MIDDLE),
DEBUGGER_WIDGET(ParameterVariableTreeWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Parameters"), BOTTOM_MIDDLE),
DEBUGGER_WIDGET(RegisterWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Registers"), TOP_LEFT),
DEBUGGER_WIDGET(SavedAddressesWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Saved Addresses"), BOTTOM_MIDDLE),
DEBUGGER_WIDGET(StackWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Stack"), BOTTOM_MIDDLE),
DEBUGGER_WIDGET(ThreadWidget, QT_TRANSLATE_NOOP("DebuggerWidget", "Threads"), BOTTOM_MIDDLE),
};
#undef DEBUGGER_WIDGET
const std::vector<DockTables::DefaultDockLayout> DockTables::DEFAULT_DOCK_LAYOUTS = {
{
.name = QT_TRANSLATE_NOOP("DebuggerLayout", "R5900"),
.cpu = BREAKPOINT_EE,
.groups = {
/* [DefaultDockGroup::TOP_RIGHT] = */ {KDDockWidgets::Location_OnRight, DefaultDockGroup::ROOT},
/* [DefaultDockGroup::BOTTOM] = */ {KDDockWidgets::Location_OnBottom, DefaultDockGroup::TOP_RIGHT},
/* [DefaultDockGroup::TOP_LEFT] = */ {KDDockWidgets::Location_OnLeft, DefaultDockGroup::TOP_RIGHT},
},
.widgets = {
/* DefaultDockGroup::TOP_RIGHT */
{"DisassemblyWidget", DefaultDockGroup::TOP_RIGHT},
/* DefaultDockGroup::BOTTOM */
{"MemoryViewWidget", DefaultDockGroup::BOTTOM},
{"BreakpointWidget", DefaultDockGroup::BOTTOM},
{"ThreadWidget", DefaultDockGroup::BOTTOM},
{"StackWidget", DefaultDockGroup::BOTTOM},
{"SavedAddressesWidget", DefaultDockGroup::BOTTOM},
{"GlobalVariableTreeWidget", DefaultDockGroup::BOTTOM},
{"LocalVariableTreeWidget", DefaultDockGroup::BOTTOM},
{"ParameterVariableTreeWidget", DefaultDockGroup::BOTTOM},
/* DefaultDockGroup::TOP_LEFT */
{"RegisterWidget", DefaultDockGroup::TOP_LEFT},
{"FunctionTreeWidget", DefaultDockGroup::TOP_LEFT},
{"MemorySearchWidget", DefaultDockGroup::TOP_LEFT},
},
.toolbars = {
"toolBarDebug",
"toolBarFile",
},
},
{
.name = QT_TRANSLATE_NOOP("DebuggerLayout", "R3000"),
.cpu = BREAKPOINT_IOP,
.groups = {
/* [DefaultDockGroup::TOP_RIGHT] = */ {KDDockWidgets::Location_OnRight, DefaultDockGroup::ROOT},
/* [DefaultDockGroup::BOTTOM] = */ {KDDockWidgets::Location_OnBottom, DefaultDockGroup::TOP_RIGHT},
/* [DefaultDockGroup::TOP_LEFT] = */ {KDDockWidgets::Location_OnLeft, DefaultDockGroup::TOP_RIGHT},
},
.widgets = {
/* DefaultDockGroup::TOP_RIGHT */
{"DisassemblyWidget", DefaultDockGroup::TOP_RIGHT},
/* DefaultDockGroup::BOTTOM */
{"MemoryViewWidget", DefaultDockGroup::BOTTOM},
{"BreakpointWidget", DefaultDockGroup::BOTTOM},
{"ThreadWidget", DefaultDockGroup::BOTTOM},
{"StackWidget", DefaultDockGroup::BOTTOM},
{"SavedAddressesWidget", DefaultDockGroup::BOTTOM},
{"GlobalVariableTreeWidget", DefaultDockGroup::BOTTOM},
{"LocalVariableTreeWidget", DefaultDockGroup::BOTTOM},
{"ParameterVariableTreeWidget", DefaultDockGroup::BOTTOM},
/* DefaultDockGroup::TOP_LEFT */
{"RegisterWidget", DefaultDockGroup::TOP_LEFT},
{"FunctionTreeWidget", DefaultDockGroup::TOP_LEFT},
{"MemorySearchWidget", DefaultDockGroup::TOP_LEFT},
},
.toolbars = {
"toolBarDebug",
"toolBarFile",
},
},
};
const DockTables::DefaultDockLayout* DockTables::defaultLayout(const std::string& name)
{
for (const DockTables::DefaultDockLayout& default_layout : DockTables::DEFAULT_DOCK_LAYOUTS)
if (default_layout.name == name)
return &default_layout;
return nullptr;
}
const std::string& DockTables::hashDefaultLayouts()
{
static std::string hash;
if (!hash.empty())
return hash;
MD5Digest md5;
u32 hash_version = 1;
md5.Update(&hash_version, sizeof(hash_version));
u32 layout_count = static_cast<u32>(DEFAULT_DOCK_LAYOUTS.size());
md5.Update(&layout_count, sizeof(layout_count));
for (const DefaultDockLayout& layout : DEFAULT_DOCK_LAYOUTS)
hashDefaultLayout(layout, md5);
u8 digest[16];
md5.Final(digest);
hash = fmt::format(
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]);
return hash;
}
void DockTables::hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5)
{
u32 layout_name_size = static_cast<u32>(layout.name.size());
md5.Update(&layout_name_size, sizeof(layout_name_size));
md5.Update(layout.name.data(), layout_name_size);
const char* cpu_name = DebugInterface::cpuName(layout.cpu);
u32 cpu_name_size = static_cast<u32>(strlen(cpu_name));
md5.Update(&cpu_name_size, sizeof(cpu_name_size));
md5.Update(cpu_name, cpu_name_size);
u32 group_count = static_cast<u32>(layout.groups.size());
md5.Update(&group_count, sizeof(group_count));
for (const DefaultDockGroupDescription& group : layout.groups)
hashDefaultGroup(group, md5);
u32 widget_count = static_cast<u32>(layout.widgets.size());
md5.Update(&widget_count, sizeof(widget_count));
for (const DefaultDockWidgetDescription& widget : layout.widgets)
hashDefaultDockWidget(widget, md5);
u32 toolbar_count = static_cast<u32>(layout.toolbars.size());
md5.Update(&toolbar_count, sizeof(toolbar_count));
for (const std::string& toolbar : layout.toolbars)
{
u32 toolbar_size = toolbar.size();
md5.Update(&toolbar_size, sizeof(toolbar_size));
md5.Update(toolbar.data(), toolbar.size());
}
}
void DockTables::hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5)
{
// This is inline here so that it's obvious that changing it will affect the
// result of the hash.
const char* location = "";
switch (group.location)
{
case KDDockWidgets::Location_None:
location = "none";
break;
case KDDockWidgets::Location_OnLeft:
location = "left";
break;
case KDDockWidgets::Location_OnTop:
location = "top";
break;
case KDDockWidgets::Location_OnRight:
location = "right";
break;
case KDDockWidgets::Location_OnBottom:
location = "bottom";
break;
}
u32 location_size = static_cast<u32>(strlen(location));
md5.Update(&location_size, sizeof(location_size));
md5.Update(location, location_size);
u32 parent = static_cast<u32>(group.parent);
md5.Update(&parent, sizeof(parent));
}
void DockTables::hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5)
{
u32 type_size = static_cast<u32>(widget.type.size());
md5.Update(&type_size, sizeof(type_size));
md5.Update(widget.type.data(), type_size);
u32 group = static_cast<u32>(widget.group);
md5.Update(&group, sizeof(group));
}

View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "DockUtils.h"
#include "DebugTools/DebugInterface.h"
#include <kddockwidgets/KDDockWidgets.h>
class MD5Digest;
class DebuggerWidget;
struct DebuggerWidgetParameters;
namespace DockTables
{
struct DebuggerWidgetDescription
{
DebuggerWidget* (*create_widget)(const DebuggerWidgetParameters& parameters);
// The untranslated string displayed as the dock widget tab text.
const char* display_name;
// This is used to determine which group dock widgets of this type are
// added to when they're opened from the Windows menu.
DockUtils::PreferredLocation preferred_location;
};
extern const std::map<std::string, DebuggerWidgetDescription> DEBUGGER_WIDGETS;
enum class DefaultDockGroup
{
ROOT = -1,
TOP_RIGHT = 0,
BOTTOM = 1,
TOP_LEFT = 2
};
struct DefaultDockGroupDescription
{
KDDockWidgets::Location location;
DefaultDockGroup parent;
};
extern const std::vector<DefaultDockGroupDescription> DEFAULT_DOCK_GROUPS;
struct DefaultDockWidgetDescription
{
std::string type;
DefaultDockGroup group;
};
struct DefaultDockLayout
{
std::string name;
BreakPointCpu cpu;
std::vector<DefaultDockGroupDescription> groups;
std::vector<DefaultDockWidgetDescription> widgets;
std::set<std::string> toolbars;
};
extern const std::vector<DefaultDockLayout> DEFAULT_DOCK_LAYOUTS;
const DefaultDockLayout* defaultLayout(const std::string& name);
// This is used to determine if the user has updated and we need to recreate
// the default layouts.
const std::string& hashDefaultLayouts();
void hashDefaultLayout(const DefaultDockLayout& layout, MD5Digest& md5);
void hashDefaultGroup(const DefaultDockGroupDescription& group, MD5Digest& md5);
void hashDefaultDockWidget(const DefaultDockWidgetDescription& widget, MD5Digest& md5);
} // namespace DockTables

View File

@@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DockUtils.h"
#include <kddockwidgets/Config.h>
#include <kddockwidgets/core/DockRegistry.h>
#include <kddockwidgets/core/Group.h>
#include <kddockwidgets/qtwidgets/DockWidget.h>
#include <kddockwidgets/qtwidgets/Group.h>
DockUtils::DockWidgetPair DockUtils::dockWidgetFromName(const QString& unique_name)
{
KDDockWidgets::Vector<QString> names{unique_name};
KDDockWidgets::Vector<KDDockWidgets::Core::DockWidget*> dock_widgets =
KDDockWidgets::DockRegistry::self()->dockWidgets(names);
if (dock_widgets.size() != 1 || !dock_widgets[0])
return {};
return {dock_widgets[0], static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_widgets[0]->view())};
}
void DockUtils::insertDockWidgetAtPreferredLocation(
KDDockWidgets::Core::DockWidget* dock_widget,
PreferredLocation location,
KDDockWidgets::QtWidgets::MainWindow* window)
{
int width = window->width();
int height = window->height();
int half_width = width / 2;
int half_height = height / 2;
QPoint preferred_location;
switch (location)
{
case DockUtils::TOP_LEFT:
preferred_location = {0, 0};
break;
case DockUtils::TOP_MIDDLE:
preferred_location = {half_width, 0};
break;
case DockUtils::TOP_RIGHT:
preferred_location = {width, 0};
break;
case DockUtils::MIDDLE_LEFT:
preferred_location = {0, half_height};
break;
case DockUtils::MIDDLE_MIDDLE:
preferred_location = {half_width, half_height};
break;
case DockUtils::MIDDLE_RIGHT:
preferred_location = {width, half_height};
break;
case DockUtils::BOTTOM_LEFT:
preferred_location = {0, height};
break;
case DockUtils::BOTTOM_MIDDLE:
preferred_location = {half_width, height};
break;
case DockUtils::BOTTOM_RIGHT:
preferred_location = {width, height};
break;
}
// Find the dock group which is closest to the preferred location.
KDDockWidgets::Core::Group* best_group = nullptr;
int best_distance_squared = 0;
for (KDDockWidgets::Core::Group* group_controller : KDDockWidgets::DockRegistry::self()->groups())
{
if (group_controller->isFloating())
continue;
auto group = static_cast<KDDockWidgets::QtWidgets::Group*>(group_controller->view());
QPoint local_midpoint = group->pos() + QPoint(group->width() / 2, group->height() / 2);
QPoint midpoint = group->mapTo(window, local_midpoint);
QPoint delta = midpoint - preferred_location;
int distance_squared = delta.x() * delta.x() + delta.y() * delta.y();
if (!best_group || distance_squared < best_distance_squared)
{
best_group = group_controller;
best_distance_squared = distance_squared;
}
}
if (best_group && best_group->dockWidgetCount() > 0)
{
KDDockWidgets::Core::DockWidget* other_dock_widget = best_group->dockWidgetAt(0);
other_dock_widget->addDockWidgetAsTab(dock_widget);
}
else
{
auto dock_view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_widget->view());
window->addDockWidget(dock_view, KDDockWidgets::Location_OnTop);
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include <kddockwidgets/KDDockWidgets.h>
#include <kddockwidgets/core/DockWidget.h>
#include <kddockwidgets/qtwidgets/MainWindow.h>
namespace DockUtils
{
inline const constexpr int MAX_LAYOUT_NAME_SIZE = 40;
inline const constexpr int MAX_DOCK_WIDGET_NAME_SIZE = 40;
struct DockWidgetPair
{
KDDockWidgets::Core::DockWidget* controller = nullptr;
KDDockWidgets::QtWidgets::DockWidget* view = nullptr;
};
DockWidgetPair dockWidgetFromName(const QString& unique_name);
enum PreferredLocation
{
TOP_LEFT,
TOP_MIDDLE,
TOP_RIGHT,
MIDDLE_LEFT,
MIDDLE_MIDDLE,
MIDDLE_RIGHT,
BOTTOM_LEFT,
BOTTOM_MIDDLE,
BOTTOM_RIGHT
};
void insertDockWidgetAtPreferredLocation(
KDDockWidgets::Core::DockWidget* dock_widget,
PreferredLocation location,
KDDockWidgets::QtWidgets::MainWindow* window);
} // namespace DockUtils

View File

@@ -0,0 +1,303 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DockViews.h"
#include "QtUtils.h"
#include "Debugger/DebuggerWidget.h"
#include "Debugger/DebuggerWindow.h"
#include "Debugger/Docking/DockManager.h"
#include "Debugger/Docking/DropIndicators.h"
#include "DebugTools/DebugInterface.h"
#include <kddockwidgets/Config.h>
#include <kddockwidgets/core/TabBar.h>
#include <kddockwidgets/qtwidgets/views/DockWidget.h>
#include <QtGui/QActionGroup>
#include <QtGui/QPalette>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QMenu>
KDDockWidgets::Core::View* DockViewFactory::createDockWidget(
const QString& unique_name,
KDDockWidgets::DockWidgetOptions options,
KDDockWidgets::LayoutSaverOptions layout_saver_options,
Qt::WindowFlags window_flags) const
{
return new DockWidget(unique_name, options, layout_saver_options, window_flags);
}
KDDockWidgets::Core::View* DockViewFactory::createTitleBar(
KDDockWidgets::Core::TitleBar* controller,
KDDockWidgets::Core::View* parent) const
{
return new DockTitleBar(controller, parent);
}
KDDockWidgets::Core::View* DockViewFactory::createStack(
KDDockWidgets::Core::Stack* controller,
KDDockWidgets::Core::View* parent) const
{
return new DockStack(controller, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
}
KDDockWidgets::Core::View* DockViewFactory::createTabBar(
KDDockWidgets::Core::TabBar* tabBar,
KDDockWidgets::Core::View* parent) const
{
return new DockTabBar(tabBar, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
}
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockViewFactory::createClassicIndicatorWindow(
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
KDDockWidgets::Core::View* parent) const
{
return new DockDropIndicatorProxy(classic_indicators);
}
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockViewFactory::createFallbackClassicIndicatorWindow(
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
KDDockWidgets::Core::View* parent) const
{
return KDDockWidgets::QtWidgets::ViewFactory::createClassicIndicatorWindow(classic_indicators, parent);
}
KDDockWidgets::Core::View* DockViewFactory::createSegmentedDropIndicatorOverlayView(
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller,
KDDockWidgets::Core::View* parent) const
{
return new DockSegmentedDropIndicatorOverlay(controller, KDDockWidgets::QtCommon::View_qt::asQWidget(parent));
}
// *****************************************************************************
DockWidget::DockWidget(
const QString& unique_name,
KDDockWidgets::DockWidgetOptions options,
KDDockWidgets::LayoutSaverOptions layout_saver_options,
Qt::WindowFlags window_flags)
: KDDockWidgets::QtWidgets::DockWidget(unique_name, options, layout_saver_options, window_flags)
{
connect(this, &DockWidget::isOpenChanged, this, &DockWidget::openStateChanged);
}
void DockWidget::openStateChanged(bool open)
{
// The LayoutSaver class will close a bunch of dock widgets. We only want to
// delete the dock widgets when they're being closed by the user.
if (KDDockWidgets::LayoutSaver::restoreInProgress())
return;
if (!open && g_debugger_window)
g_debugger_window->dockManager().destroyDebuggerWidget(uniqueName());
}
// *****************************************************************************
DockTitleBar::DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent)
: KDDockWidgets::QtWidgets::TitleBar(controller, parent)
{
}
void DockTitleBar::mouseDoubleClickEvent(QMouseEvent* event)
{
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
KDDockWidgets::QtWidgets::TitleBar::mouseDoubleClickEvent(event);
else
event->ignore();
}
// *****************************************************************************
DockStack::DockStack(KDDockWidgets::Core::Stack* controller, QWidget* parent)
: KDDockWidgets::QtWidgets::Stack(controller, parent)
{
}
void DockStack::init()
{
KDDockWidgets::QtWidgets::Stack::init();
if (g_debugger_window)
{
bool locked = g_debugger_window->dockManager().isLayoutLocked();
setTabsClosable(!locked);
}
}
void DockStack::mouseDoubleClickEvent(QMouseEvent* ev)
{
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
KDDockWidgets::QtWidgets::Stack::mouseDoubleClickEvent(ev);
else
ev->ignore();
}
// *****************************************************************************
DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent)
: KDDockWidgets::QtWidgets::TabBar(controller, parent)
{
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::openContextMenu);
}
void DockTabBar::openContextMenu(QPoint pos)
{
if (!g_debugger_window)
return;
int tab_index = tabAt(pos);
// Filter out the placeholder widget displayed when there are no layouts.
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
if (!widget)
return;
size_t dock_widgets_of_type = g_debugger_window->dockManager().countDebuggerWidgetsOfType(
widget->metaObject()->className());
QMenu* menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction* rename_action = menu->addAction(tr("Rename"));
connect(rename_action, &QAction::triggered, this, [this, tab_index]() {
if (!g_debugger_window)
return;
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
if (!widget)
return;
bool ok;
QString new_name = QInputDialog::getText(
this, tr("Rename Window"), tr("New name:"), QLineEdit::Normal, widget->displayNameWithoutSuffix(), &ok);
if (!ok)
return;
if (!widget->setCustomDisplayName(new_name))
{
QMessageBox::warning(this, tr("Invalid Name"), tr("The specified name is too long."));
return;
}
g_debugger_window->dockManager().updateDockWidgetTitles();
});
QAction* reset_name_action = menu->addAction(tr("Reset Name"));
reset_name_action->setEnabled(!widget->customDisplayName().isEmpty());
connect(reset_name_action, &QAction::triggered, this, [this, tab_index] {
if (!g_debugger_window)
return;
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
if (!widget)
return;
widget->setCustomDisplayName(QString());
g_debugger_window->dockManager().updateDockWidgetTitles();
});
QAction* primary_action = menu->addAction(tr("Primary"));
primary_action->setCheckable(true);
primary_action->setChecked(widget->isPrimary());
primary_action->setEnabled(dock_widgets_of_type > 1);
connect(primary_action, &QAction::triggered, this, [this, tab_index](bool checked) {
if (!g_debugger_window)
return;
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
if (!widget)
return;
g_debugger_window->dockManager().setPrimaryDebuggerWidget(widget, checked);
});
QMenu* set_target_menu = menu->addMenu(tr("Set Target"));
QActionGroup* set_target_group = new QActionGroup(menu);
set_target_group->setExclusive(true);
for (BreakPointCpu cpu : DEBUG_CPUS)
{
const char* long_cpu_name = DebugInterface::longCpuName(cpu);
const char* cpu_name = DebugInterface::cpuName(cpu);
QString text = QString("%1 (%2)").arg(long_cpu_name).arg(cpu_name);
QAction* cpu_action = set_target_menu->addAction(text);
cpu_action->setCheckable(true);
cpu_action->setChecked(widget->cpuOverride().has_value() && *widget->cpuOverride() == cpu);
connect(cpu_action, &QAction::triggered, this, [this, tab_index, cpu]() {
setCpuOverrideForTab(tab_index, cpu);
});
set_target_group->addAction(cpu_action);
}
set_target_menu->addSeparator();
QAction* inherit_action = set_target_menu->addAction(tr("Inherit From Layout"));
inherit_action->setCheckable(true);
inherit_action->setChecked(!widget->cpuOverride().has_value());
connect(inherit_action, &QAction::triggered, this, [this, tab_index]() {
setCpuOverrideForTab(tab_index, std::nullopt);
});
set_target_group->addAction(inherit_action);
QAction* close_action = menu->addAction(tr("Close"));
connect(close_action, &QAction::triggered, this, [this, tab_index]() {
if (!g_debugger_window)
return;
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
if (!widget)
return;
g_debugger_window->dockManager().destroyDebuggerWidget(widget->uniqueName());
});
menu->popup(mapToGlobal(pos));
}
void DockTabBar::setCpuOverrideForTab(int tab_index, std::optional<BreakPointCpu> cpu_override)
{
if (!g_debugger_window)
return;
auto [widget, controller, view] = widgetsFromTabIndex(tab_index);
if (!widget)
return;
if (!widget->setCpuOverride(cpu_override))
g_debugger_window->dockManager().recreateDebuggerWidget(view->uniqueName());
g_debugger_window->dockManager().updateDockWidgetTitles();
}
DockTabBar::WidgetsFromTabIndexResult DockTabBar::widgetsFromTabIndex(int tab_index)
{
KDDockWidgets::Core::TabBar* tab_bar_controller = asController<KDDockWidgets::Core::TabBar>();
if (!tab_bar_controller)
return {};
KDDockWidgets::Core::DockWidget* dock_controller = tab_bar_controller->dockWidgetAt(tab_index);
if (!dock_controller)
return {};
auto dock_view = static_cast<KDDockWidgets::QtWidgets::DockWidget*>(dock_controller->view());
DebuggerWidget* widget = qobject_cast<DebuggerWidget*>(dock_view->widget());
if (!widget)
return {};
return {widget, dock_controller, dock_view};
}
void DockTabBar::mouseDoubleClickEvent(QMouseEvent* event)
{
if (g_debugger_window && !g_debugger_window->dockManager().isLayoutLocked())
KDDockWidgets::QtWidgets::TabBar::mouseDoubleClickEvent(event);
else
event->ignore();
}

View File

@@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "DebugTools/DebugInterface.h"
#include <kddockwidgets/qtwidgets/ViewFactory.h>
#include <kddockwidgets/qtwidgets/views/DockWidget.h>
#include <kddockwidgets/qtwidgets/views/Stack.h>
#include <kddockwidgets/qtwidgets/views/TitleBar.h>
#include <kddockwidgets/qtwidgets/views/TabBar.h>
class DebuggerWidget;
class DockManager;
class DockViewFactory : public KDDockWidgets::QtWidgets::ViewFactory
{
Q_OBJECT
public:
KDDockWidgets::Core::View* createDockWidget(
const QString& unique_name,
KDDockWidgets::DockWidgetOptions options = {},
KDDockWidgets::LayoutSaverOptions layout_saver_options = {},
Qt::WindowFlags window_flags = {}) const override;
KDDockWidgets::Core::View* createTitleBar(
KDDockWidgets::Core::TitleBar* controller,
KDDockWidgets::Core::View* parent) const override;
KDDockWidgets::Core::View* createStack(
KDDockWidgets::Core::Stack* controller,
KDDockWidgets::Core::View* parent) const override;
KDDockWidgets::Core::View* createTabBar(
KDDockWidgets::Core::TabBar* tabBar,
KDDockWidgets::Core::View* parent) const override;
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* createClassicIndicatorWindow(
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
KDDockWidgets::Core::View* parent) const override;
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* createFallbackClassicIndicatorWindow(
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators,
KDDockWidgets::Core::View* parent) const;
KDDockWidgets::Core::View* createSegmentedDropIndicatorOverlayView(
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller,
KDDockWidgets::Core::View* parent) const override;
};
class DockWidget : public KDDockWidgets::QtWidgets::DockWidget
{
Q_OBJECT
public:
DockWidget(
const QString& unique_name,
KDDockWidgets::DockWidgetOptions options,
KDDockWidgets::LayoutSaverOptions layout_saver_options,
Qt::WindowFlags window_flags);
protected:
void openStateChanged(bool open);
};
class DockTitleBar : public KDDockWidgets::QtWidgets::TitleBar
{
Q_OBJECT
public:
DockTitleBar(KDDockWidgets::Core::TitleBar* controller, KDDockWidgets::Core::View* parent = nullptr);
protected:
void mouseDoubleClickEvent(QMouseEvent* event) override;
};
class DockStack : public KDDockWidgets::QtWidgets::Stack
{
Q_OBJECT
public:
DockStack(KDDockWidgets::Core::Stack* controller, QWidget* parent = nullptr);
void init() override;
protected:
void mouseDoubleClickEvent(QMouseEvent* event) override;
};
class DockTabBar : public KDDockWidgets::QtWidgets::TabBar
{
Q_OBJECT
public:
DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent = nullptr);
protected:
void openContextMenu(QPoint pos);
struct WidgetsFromTabIndexResult
{
DebuggerWidget* debugger_widget = nullptr;
KDDockWidgets::Core::DockWidget* controller = nullptr;
KDDockWidgets::QtWidgets::DockWidget* view = nullptr;
};
void setCpuOverrideForTab(int tab_index, std::optional<BreakPointCpu> cpu_override);
WidgetsFromTabIndexResult widgetsFromTabIndex(int tab_index);
void mouseDoubleClickEvent(QMouseEvent* event) override;
};

View File

@@ -0,0 +1,571 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DropIndicators.h"
#include "QtUtils.h"
#include "Debugger/Docking/DockViews.h"
#include "common/Assertions.h"
#include <kddockwidgets/Config.h>
#include <kddockwidgets/core/Group.h>
#include <kddockwidgets/core/indicators/SegmentedDropIndicatorOverlay.h>
#include <kddockwidgets/qtwidgets/ViewFactory.h>
#include <QtGui/QPainter>
static std::pair<QColor, QColor> pickNiceColours(const QPalette& palette, bool hovered)
{
QColor fill = palette.highlight().color();
QColor outline = palette.highlight().color();
if (QtUtils::IsLightTheme(palette))
{
fill = fill.darker(200);
outline = outline.darker(200);
}
else
{
fill = fill.lighter(200);
outline = outline.lighter(200);
}
fill.setAlpha(200);
outline.setAlpha(255);
if (!hovered)
{
fill.setAlpha(fill.alpha() / 2);
outline.setAlpha(outline.alpha() / 2);
}
return {fill, outline};
}
// *****************************************************************************
DockDropIndicatorProxy::DockDropIndicatorProxy(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
: m_classic_indicators(classic_indicators)
{
recreateWindowIfNecessary();
}
DockDropIndicatorProxy::~DockDropIndicatorProxy()
{
delete m_window;
delete m_fallback_window;
}
void DockDropIndicatorProxy::setObjectName(const QString& name)
{
window()->setObjectName(name);
}
KDDockWidgets::DropLocation DockDropIndicatorProxy::hover(QPoint globalPos)
{
return window()->hover(globalPos);
}
QPoint DockDropIndicatorProxy::posForIndicator(KDDockWidgets::DropLocation loc) const
{
return window()->posForIndicator(loc);
}
void DockDropIndicatorProxy::updatePositions()
{
// Check if a compositor is running whenever a drag starts.
recreateWindowIfNecessary();
window()->updatePositions();
}
void DockDropIndicatorProxy::raise()
{
window()->raise();
}
void DockDropIndicatorProxy::setVisible(bool visible)
{
window()->setVisible(visible);
}
void DockDropIndicatorProxy::resize(QSize size)
{
window()->resize(size);
}
void DockDropIndicatorProxy::setGeometry(QRect rect)
{
window()->setGeometry(rect);
}
bool DockDropIndicatorProxy::isWindow() const
{
return window()->isWindow();
}
void DockDropIndicatorProxy::updateIndicatorVisibility()
{
window()->updateIndicatorVisibility();
}
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window()
{
if (!m_supports_compositing)
{
pxAssert(m_fallback_window);
return m_fallback_window;
}
pxAssert(m_window);
return m_window;
}
const KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* DockDropIndicatorProxy::window() const
{
if (!m_supports_compositing)
{
pxAssert(m_fallback_window);
return m_fallback_window;
}
pxAssert(m_window);
return m_window;
}
void DockDropIndicatorProxy::recreateWindowIfNecessary()
{
bool supports_compositing = QtUtils::IsCompositorManagerRunning();
if (supports_compositing == m_supports_compositing && (m_window || m_fallback_window))
return;
m_supports_compositing = supports_compositing;
DockViewFactory* factory = static_cast<DockViewFactory*>(KDDockWidgets::Config::self().viewFactory());
if (supports_compositing)
{
if (!m_window)
m_window = new DockDropIndicatorWindow(m_classic_indicators);
QWidget* old_window = dynamic_cast<QWidget*>(m_fallback_window);
if (old_window)
{
m_window->setObjectName(old_window->objectName());
m_window->setVisible(old_window->isVisible());
m_window->setGeometry(old_window->geometry());
}
delete m_fallback_window;
m_fallback_window = nullptr;
}
else
{
if (!m_fallback_window)
m_fallback_window = factory->createFallbackClassicIndicatorWindow(m_classic_indicators, nullptr);
QWidget* old_window = dynamic_cast<QWidget*>(m_window);
if (old_window)
{
m_window->setObjectName(old_window->objectName());
m_window->setVisible(old_window->isVisible());
m_window->setGeometry(old_window->geometry());
}
delete m_window;
m_window = nullptr;
}
}
// *****************************************************************************
static const constexpr int IND_LEFT = 0;
static const constexpr int IND_TOP = 1;
static const constexpr int IND_RIGHT = 2;
static const constexpr int IND_BOTTOM = 3;
static const constexpr int IND_CENTER = 4;
static const constexpr int IND_OUTER_LEFT = 5;
static const constexpr int IND_OUTER_TOP = 6;
static const constexpr int IND_OUTER_RIGHT = 7;
static const constexpr int IND_OUTER_BOTTOM = 8;
static const constexpr int INDICATOR_SIZE = 40;
static const constexpr int INDICATOR_MARGIN = 10;
static bool isWayland()
{
return KDDockWidgets::Core::Platform::instance()->displayType() ==
KDDockWidgets::Core::Platform::DisplayType::Wayland;
}
static QWidget* parentForIndicatorWindow(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
{
if (isWayland())
return KDDockWidgets::QtCommon::View_qt::asQWidget(classic_indicators->view());
return nullptr;
}
static Qt::WindowFlags flagsForIndicatorWindow()
{
if (isWayland())
return Qt::Widget;
return Qt::Tool | Qt::BypassWindowManagerHint;
}
DockDropIndicatorWindow::DockDropIndicatorWindow(
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators)
: QWidget(parentForIndicatorWindow(classic_indicators), flagsForIndicatorWindow())
, m_classic_indicators(classic_indicators)
, m_indicators({
/* [IND_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Left, this),
/* [IND_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Top, this),
/* [IND_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Right, this),
/* [IND_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Bottom, this),
/* [IND_CENTER] = */ new DockDropIndicator(KDDockWidgets::DropLocation_Center, this),
/* [IND_OUTER_LEFT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterLeft, this),
/* [IND_OUTER_TOP] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterTop, this),
/* [IND_OUTER_RIGHT] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterRight, this),
/* [IND_OUTER_BOTTOM] = */ new DockDropIndicator(KDDockWidgets::DropLocation_OutterBottom, this),
})
{
setWindowFlag(Qt::FramelessWindowHint, true);
if (KDDockWidgets::Config::self().flags() & KDDockWidgets::Config::Flag_KeepAboveIfNotUtilityWindow)
setWindowFlag(Qt::WindowStaysOnTopHint, true);
setAttribute(Qt::WA_TranslucentBackground);
}
void DockDropIndicatorWindow::setObjectName(const QString& name)
{
QWidget::setObjectName(name);
}
KDDockWidgets::DropLocation DockDropIndicatorWindow::hover(QPoint globalPos)
{
KDDockWidgets::DropLocation result = KDDockWidgets::DropLocation_None;
for (DockDropIndicator* indicator : m_indicators)
{
if (indicator->isVisible())
{
bool hovered = indicator->rect().contains(indicator->mapFromGlobal(globalPos));
if (hovered != indicator->hovered)
{
indicator->hovered = hovered;
indicator->update();
}
if (hovered)
result = indicator->location;
}
}
return result;
}
QPoint DockDropIndicatorWindow::posForIndicator(KDDockWidgets::DropLocation loc) const
{
for (DockDropIndicator* indicator : m_indicators)
if (indicator->location == loc)
return indicator->mapToGlobal(indicator->rect().center());
return QPoint();
}
void DockDropIndicatorWindow::updatePositions()
{
DockDropIndicator* left = m_indicators[IND_LEFT];
DockDropIndicator* top = m_indicators[IND_TOP];
DockDropIndicator* right = m_indicators[IND_RIGHT];
DockDropIndicator* bottom = m_indicators[IND_BOTTOM];
DockDropIndicator* center = m_indicators[IND_CENTER];
DockDropIndicator* outer_left = m_indicators[IND_OUTER_LEFT];
DockDropIndicator* outer_top = m_indicators[IND_OUTER_TOP];
DockDropIndicator* outer_right = m_indicators[IND_OUTER_RIGHT];
DockDropIndicator* outer_bottom = m_indicators[IND_OUTER_BOTTOM];
QRect r = rect();
int half_indicator_width = INDICATOR_SIZE / 2;
outer_left->move(r.x() + INDICATOR_MARGIN, r.center().y() - half_indicator_width);
outer_bottom->move(r.center().x() - half_indicator_width, r.y() + height() - INDICATOR_SIZE - INDICATOR_MARGIN);
outer_top->move(r.center().x() - half_indicator_width, r.y() + INDICATOR_MARGIN);
outer_right->move(r.x() + width() - INDICATOR_SIZE - INDICATOR_MARGIN, r.center().y() - half_indicator_width);
KDDockWidgets::Core::Group* hovered_group = m_classic_indicators->hoveredGroup();
if (hovered_group)
{
QRect hoveredRect = hovered_group->view()->geometry();
center->move(r.topLeft() + hoveredRect.center() - QPoint(half_indicator_width, half_indicator_width));
top->move(center->pos() - QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN));
right->move(center->pos() + QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0));
bottom->move(center->pos() + QPoint(0, INDICATOR_SIZE + INDICATOR_MARGIN));
left->move(center->pos() - QPoint(INDICATOR_SIZE + INDICATOR_MARGIN, 0));
}
}
void DockDropIndicatorWindow::raise()
{
QWidget::raise();
}
void DockDropIndicatorWindow::setVisible(bool is)
{
QWidget::setVisible(is);
}
void DockDropIndicatorWindow::resize(QSize size)
{
QWidget::resize(size);
}
void DockDropIndicatorWindow::setGeometry(QRect rect)
{
QWidget::setGeometry(rect);
}
bool DockDropIndicatorWindow::isWindow() const
{
return QWidget::isWindow();
}
void DockDropIndicatorWindow::updateIndicatorVisibility()
{
for (DockDropIndicator* indicator : m_indicators)
indicator->setVisible(m_classic_indicators->dropIndicatorVisible(indicator->location));
}
void DockDropIndicatorWindow::resizeEvent(QResizeEvent* ev)
{
QWidget::resizeEvent(ev);
updatePositions();
}
// *****************************************************************************
DockDropIndicator::DockDropIndicator(KDDockWidgets::DropLocation loc, QWidget* parent)
: QWidget(parent)
, location(loc)
{
setFixedSize(INDICATOR_SIZE, INDICATOR_SIZE);
setVisible(true);
}
void DockDropIndicator::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
auto [fill, outline] = pickNiceColours(palette(), hovered);
painter.setBrush(fill);
QPen pen;
pen.setColor(outline);
pen.setWidth(2);
painter.setPen(pen);
painter.drawRect(rect());
QRectF rf = rect().toRectF();
QRectF outer = rf.marginsRemoved(QMarginsF(4.f, 4.f, 4.f, 4.f));
QPointF icon_position;
switch (location)
{
case KDDockWidgets::DropLocation_Left:
case KDDockWidgets::DropLocation_OutterLeft:
outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, outer.width() / 2.f, 0.f));
icon_position = rf.marginsRemoved(QMarginsF(rf.width() / 2.f, 0.f, 0.f, 0.f)).center();
break;
case KDDockWidgets::DropLocation_Top:
case KDDockWidgets::DropLocation_OutterTop:
outer = outer.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, outer.width() / 2.f));
icon_position = rf.marginsRemoved(QMarginsF(0.f, rf.width() / 2.f, 0.f, 0.f)).center();
break;
case KDDockWidgets::DropLocation_Right:
case KDDockWidgets::DropLocation_OutterRight:
outer = outer.marginsRemoved(QMarginsF(outer.width() / 2.f, 0.f, 0.f, 0.f));
icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, rf.width() / 2.f, 0.f)).center();
break;
case KDDockWidgets::DropLocation_Bottom:
case KDDockWidgets::DropLocation_OutterBottom:
outer = outer.marginsRemoved(QMarginsF(0.f, outer.width() / 2.f, 0.f, 0.f));
icon_position = rf.marginsRemoved(QMarginsF(0.f, 0.f, 0.f, rf.width() / 2.f)).center();
break;
default:
{
}
}
painter.drawRect(outer);
float arrow_size = INDICATOR_SIZE / 10.f;
QPolygonF arrow;
switch (location)
{
case KDDockWidgets::DropLocation_Left:
arrow = {
icon_position + QPointF(-arrow_size, 0.f),
icon_position + QPointF(arrow_size, arrow_size * 2.f),
icon_position + QPointF(arrow_size, -arrow_size * 2.f),
};
break;
case KDDockWidgets::DropLocation_Top:
arrow = {
icon_position + QPointF(0.f, -arrow_size),
icon_position + QPointF(arrow_size * 2.f, arrow_size),
icon_position + QPointF(-arrow_size * 2.f, arrow_size),
};
break;
case KDDockWidgets::DropLocation_Right:
arrow = {
icon_position + QPointF(arrow_size, 0.f),
icon_position + QPointF(-arrow_size, arrow_size * 2.f),
icon_position + QPointF(-arrow_size, -arrow_size * 2.f),
};
break;
case KDDockWidgets::DropLocation_Bottom:
arrow = {
icon_position + QPointF(0.f, arrow_size),
icon_position + QPointF(arrow_size * 2.f, -arrow_size),
icon_position + QPointF(-arrow_size * 2.f, -arrow_size),
};
break;
default:
{
}
}
painter.drawPolygon(arrow);
}
// *****************************************************************************
std::string DockSegmentedDropIndicatorOverlay::s_indicator_style;
DockSegmentedDropIndicatorOverlay::DockSegmentedDropIndicatorOverlay(
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, QWidget* parent)
: KDDockWidgets::QtWidgets::SegmentedDropIndicatorOverlay(controller, parent)
{
}
void DockSegmentedDropIndicatorOverlay::paintEvent(QPaintEvent* event)
{
if (s_indicator_style == "Minimalistic")
drawMinimalistic();
else
drawSegmented();
}
void DockSegmentedDropIndicatorOverlay::drawSegmented()
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller =
asController<KDDockWidgets::Core::SegmentedDropIndicatorOverlay>();
const std::unordered_map<KDDockWidgets::DropLocation, QPolygon>& segments = controller->segments();
for (KDDockWidgets::DropLocation location :
{KDDockWidgets::DropLocation_Left,
KDDockWidgets::DropLocation_Top,
KDDockWidgets::DropLocation_Right,
KDDockWidgets::DropLocation_Bottom,
KDDockWidgets::DropLocation_Center,
KDDockWidgets::DropLocation_OutterLeft,
KDDockWidgets::DropLocation_OutterTop,
KDDockWidgets::DropLocation_OutterRight,
KDDockWidgets::DropLocation_OutterBottom})
{
auto segment = segments.find(location);
if (segment == segments.end() || segment->second.size() < 2)
continue;
bool hovered = segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill);
auto [fill, outline] = pickNiceColours(palette(), hovered);
painter.setBrush(fill);
QPen pen(outline);
pen.setWidth(1);
painter.setPen(pen);
int margin = KDDockWidgets::Core::SegmentedDropIndicatorOverlay::s_segmentGirth * 2;
// Make sure the rectangles don't intersect with each other.
QRect rect;
switch (location)
{
case KDDockWidgets::DropLocation_Top:
case KDDockWidgets::DropLocation_Bottom:
case KDDockWidgets::DropLocation_OutterTop:
case KDDockWidgets::DropLocation_OutterBottom:
{
rect = segment->second.boundingRect().marginsRemoved(QMargins(margin, 4, margin, 4));
break;
}
case KDDockWidgets::DropLocation_Left:
case KDDockWidgets::DropLocation_Right:
case KDDockWidgets::DropLocation_OutterLeft:
case KDDockWidgets::DropLocation_OutterRight:
{
rect = segment->second.boundingRect().marginsRemoved(QMargins(4, margin, 4, margin));
break;
}
default:
{
rect = segment->second.boundingRect().marginsRemoved(QMargins(4, 4, 4, 4));
break;
}
}
painter.drawRect(rect);
}
}
void DockSegmentedDropIndicatorOverlay::drawMinimalistic()
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller =
asController<KDDockWidgets::Core::SegmentedDropIndicatorOverlay>();
const std::unordered_map<KDDockWidgets::DropLocation, QPolygon>& segments = controller->segments();
for (KDDockWidgets::DropLocation location :
{KDDockWidgets::DropLocation_Left,
KDDockWidgets::DropLocation_Top,
KDDockWidgets::DropLocation_Right,
KDDockWidgets::DropLocation_Bottom,
KDDockWidgets::DropLocation_Center,
KDDockWidgets::DropLocation_OutterLeft,
KDDockWidgets::DropLocation_OutterTop,
KDDockWidgets::DropLocation_OutterRight,
KDDockWidgets::DropLocation_OutterBottom})
{
auto segment = segments.find(location);
if (segment == segments.end() || segment->second.size() < 2)
continue;
if (!segment->second.containsPoint(controller->hoveredPt(), Qt::OddEvenFill))
continue;
auto [fill, outline] = pickNiceColours(palette(), true);
painter.setBrush(fill);
QPen pen(outline);
pen.setWidth(1);
painter.setPen(pen);
painter.drawRect(segment->second.boundingRect());
}
}

View File

@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include <kddockwidgets/core/indicators/ClassicDropIndicatorOverlay.h>
#include <kddockwidgets/core/views/ClassicIndicatorWindowViewInterface.h>
#include <kddockwidgets/qtwidgets/views/SegmentedDropIndicatorOverlay.h>
class DockDropIndicator;
// This switches between our custom drop indicators and KDDockWidget's built-in
// ones on the fly depending on whether or not we have a windowing system that
// supports compositing.
class DockDropIndicatorProxy : public KDDockWidgets::Core::ClassicIndicatorWindowViewInterface
{
public:
DockDropIndicatorProxy(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators);
~DockDropIndicatorProxy();
void setObjectName(const QString&) override;
KDDockWidgets::DropLocation hover(QPoint globalPos) override;
QPoint posForIndicator(KDDockWidgets::DropLocation) const override;
void updatePositions() override;
void raise() override;
void setVisible(bool visible) override;
void resize(QSize size) override;
void setGeometry(QRect rect) override;
bool isWindow() const override;
void updateIndicatorVisibility() override;
private:
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* window();
const KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* window() const;
void recreateWindowIfNecessary();
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* m_window = nullptr;
KDDockWidgets::Core::ClassicIndicatorWindowViewInterface* m_fallback_window = nullptr;
bool m_supports_compositing = true;
KDDockWidgets::Core::ClassicDropIndicatorOverlay* m_classic_indicators = nullptr;
};
// Our default custom drop indicator implementation. This fits in with PCSX2's
// themes a lot better, but doesn't support windowing systems where compositing
// is disabled (it would show a black screen).
class DockDropIndicatorWindow : public QWidget, public KDDockWidgets::Core::ClassicIndicatorWindowViewInterface
{
Q_OBJECT
public:
DockDropIndicatorWindow(
KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators);
void setObjectName(const QString& name) override;
KDDockWidgets::DropLocation hover(QPoint globalPos) override;
QPoint posForIndicator(KDDockWidgets::DropLocation loc) const override;
void updatePositions() override;
void raise() override;
void setVisible(bool visible) override;
void resize(QSize size) override;
void setGeometry(QRect rect) override;
bool isWindow() const override;
void updateIndicatorVisibility() override;
protected:
void resizeEvent(QResizeEvent* ev) override;
private:
KDDockWidgets::Core::ClassicDropIndicatorOverlay* m_classic_indicators;
std::vector<DockDropIndicator*> m_indicators;
};
class DockDropIndicator : public QWidget
{
Q_OBJECT
public:
DockDropIndicator(KDDockWidgets::DropLocation loc, QWidget* parent = nullptr);
KDDockWidgets::DropLocation location;
bool hovered = false;
protected:
void paintEvent(QPaintEvent* event) override;
};
// An alternative drop indicator design that can be enabled from the settings
// menu. For this one we don't need to worry about whether compositing is
// supported since it doesn't create its own window.
class DockSegmentedDropIndicatorOverlay : public KDDockWidgets::QtWidgets::SegmentedDropIndicatorOverlay
{
Q_OBJECT
public:
DockSegmentedDropIndicatorOverlay(
KDDockWidgets::Core::SegmentedDropIndicatorOverlay* controller, QWidget* parent = nullptr);
static std::string s_indicator_style;
protected:
void paintEvent(QPaintEvent* event) override;
private:
void drawSegmented();
void drawMinimalistic();
};

View File

@@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "LayoutEditorDialog.h"
#include "Debugger/Docking/DockTables.h"
#include <QtWidgets/QPushButton>
Q_DECLARE_METATYPE(LayoutEditorDialog::InitialState);
LayoutEditorDialog::LayoutEditorDialog(NameValidator name_validator, bool can_clone_current_layout, QWidget* parent)
: QDialog(parent)
, m_name_validator(name_validator)
{
m_ui.setupUi(this);
setWindowTitle(tr("New Layout"));
setupInputWidgets(BREAKPOINT_EE, can_clone_current_layout);
onNameChanged();
}
LayoutEditorDialog::LayoutEditorDialog(
const QString& name, BreakPointCpu cpu, NameValidator name_validator, QWidget* parent)
: QDialog(parent)
, m_name_validator(name_validator)
{
m_ui.setupUi(this);
setWindowTitle(tr("Edit Layout"));
m_ui.nameEditor->setText(name);
setupInputWidgets(cpu, {});
m_ui.initialStateLabel->hide();
m_ui.initialStateEditor->hide();
onNameChanged();
}
QString LayoutEditorDialog::name()
{
return m_ui.nameEditor->text();
}
BreakPointCpu LayoutEditorDialog::cpu()
{
return static_cast<BreakPointCpu>(m_ui.cpuEditor->currentData().toInt());
}
LayoutEditorDialog::InitialState LayoutEditorDialog::initialState()
{
return m_ui.initialStateEditor->currentData().value<InitialState>();
}
void LayoutEditorDialog::setupInputWidgets(BreakPointCpu cpu, bool can_clone_current_layout)
{
connect(m_ui.nameEditor, &QLineEdit::textChanged, this, &LayoutEditorDialog::onNameChanged);
for (BreakPointCpu cpu : DEBUG_CPUS)
{
const char* long_cpu_name = DebugInterface::longCpuName(cpu);
const char* cpu_name = DebugInterface::cpuName(cpu);
QString text = QString("%1 (%2)").arg(long_cpu_name).arg(cpu_name);
m_ui.cpuEditor->addItem(text, cpu);
}
for (int i = 0; i < m_ui.cpuEditor->count(); i++)
if (m_ui.cpuEditor->itemData(i).toInt() == cpu)
m_ui.cpuEditor->setCurrentIndex(i);
for (size_t i = 0; i < DockTables::DEFAULT_DOCK_LAYOUTS.size(); i++)
m_ui.initialStateEditor->addItem(
tr("Create Default \"%1\" Layout").arg(tr(DockTables::DEFAULT_DOCK_LAYOUTS[i].name.c_str())),
QVariant::fromValue(InitialState(DEFAULT_LAYOUT, i)));
m_ui.initialStateEditor->addItem(tr("Create Blank Layout"), QVariant::fromValue(InitialState(BLANK_LAYOUT, 0)));
if (can_clone_current_layout)
m_ui.initialStateEditor->addItem(tr("Clone Current Layout"), QVariant::fromValue(InitialState(CLONE_LAYOUT, 0)));
m_ui.initialStateEditor->setCurrentIndex(0);
}
void LayoutEditorDialog::onNameChanged()
{
QString error_message;
if (m_ui.nameEditor->text().isEmpty())
{
error_message = tr("Name is empty.");
}
else if (m_ui.nameEditor->text().size() > DockUtils::MAX_LAYOUT_NAME_SIZE)
{
error_message = tr("Name too long.");
}
else if (!m_name_validator(m_ui.nameEditor->text()))
{
error_message = tr("A layout with that name already exists.");
}
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(error_message.isEmpty());
m_ui.errorMessage->setText(error_message);
}

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "ui_LayoutEditorDialog.h"
#include "DebugTools/DebugInterface.h"
#include <QtWidgets/QDialog>
class LayoutEditorDialog : public QDialog
{
Q_OBJECT
public:
using NameValidator = std::function<bool(const QString&)>;
enum CreationMode
{
DEFAULT_LAYOUT,
BLANK_LAYOUT,
CLONE_LAYOUT,
};
// Bundles together a creation mode and inital state.
using InitialState = std::pair<CreationMode, size_t>;
// Create a "New Layout" dialog.
LayoutEditorDialog(NameValidator name_validator, bool can_clone_current_layout, QWidget* parent = nullptr);
// Create a "Edit Layout" dialog.
LayoutEditorDialog(const QString& name, BreakPointCpu cpu, NameValidator name_validator, QWidget* parent = nullptr);
QString name();
BreakPointCpu cpu();
InitialState initialState();
private:
void setupInputWidgets(BreakPointCpu cpu, bool can_clone_current_layout);
void onNameChanged();
Ui::LayoutEditorDialog m_ui;
NameValidator m_name_validator;
};

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LayoutEditorDialog</class>
<widget class="QDialog" name="LayoutEditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>150</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="cpuLabel">
<property name="text">
<string>Target</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="initialStateLabel">
<property name="text">
<string>Initial State</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cpuEditor"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameEditor"/>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="initialStateEditor"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="errorMessage">
<property name="styleSheet">
<string notr="true">color: red</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>LayoutEditorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>LayoutEditorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "NoLayoutsWidget.h"
NoLayoutsWidget::NoLayoutsWidget(QWidget* parent)
: QWidget(parent)
{
m_ui.setupUi(this);
}
QPushButton* NoLayoutsWidget::createDefaultLayoutsButton()
{
return m_ui.createDefaultLayoutsButton;
}

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "ui_NoLayoutsWidget.h"
#include <QtWidgets/QPushButton>
class NoLayoutsWidget : public QWidget
{
Q_OBJECT
public:
NoLayoutsWidget(QWidget* parent = nullptr);
QPushButton* createDefaultLayoutsButton();
private:
Ui::NoLayoutsWidget m_ui;
};

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NoLayoutsWidget</class>
<widget class="QWidget" name="NoLayoutsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="topSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>There are no layouts.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="leftSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="createDefaultLayoutsButton">
<property name="text">
<string>Create Default Layouts</string>
</property>
</widget>
</item>
<item>
<spacer name="rightSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="bottomSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "rapidjson/document.h"
// Container for a JSON value. This exists solely so that we can forward declare
// it to avoid pulling in rapidjson for the entire debugger.
class JsonValueWrapper
{
public:
JsonValueWrapper(
rapidjson::Value& value,
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& allocator)
: m_value(value)
, m_allocator(allocator)
{
}
rapidjson::Value& value()
{
return m_value;
}
const rapidjson::Value& value() const
{
return m_value;
}
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& allocator()
{
return m_allocator;
}
private:
rapidjson::Value& m_value;
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& m_allocator;
};

View File

@@ -23,8 +23,8 @@ using SearchResult = MemorySearchWidget::SearchResult;
using namespace QtUtils;
MemorySearchWidget::MemorySearchWidget(QWidget* parent)
: QWidget(parent)
MemorySearchWidget::MemorySearchWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, MONOSPACE_FONT)
{
m_ui.setupUi(this);
this->repaint();
@@ -32,10 +32,8 @@ MemorySearchWidget::MemorySearchWidget(QWidget* parent)
m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.btnSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked);
connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &MemorySearchWidget::onSearchButtonClicked);
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item)
{
emit switchToMemoryViewTab();
emit goToAddressInMemoryView(item->text().toUInt(nullptr, 16));
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [](QListWidgetItem* item) {
goToInMemoryView(item->text().toUInt(nullptr, 16), true);
});
connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &MemorySearchWidget::onSearchResultsListScroll);
connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &MemorySearchWidget::onListSearchResultsContextMenu);
@@ -46,21 +44,11 @@ MemorySearchWidget::MemorySearchWidget(QWidget* parent)
m_resultsLoadTimer.setInterval(100);
m_resultsLoadTimer.setSingleShot(true);
connect(&m_resultsLoadTimer, &QTimer::timeout, this, &MemorySearchWidget::loadSearchResults);
}
void MemorySearchWidget::setCpu(DebugInterface* cpu)
{
m_cpu = cpu;
}
void MemorySearchWidget::contextSearchResultGoToDisassembly()
{
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
if (!selModel->hasSelection())
return;
u32 selectedAddress = m_ui.listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt();
emit goToAddressInDisassemblyView(selectedAddress);
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
update();
return true;
});
}
void MemorySearchWidget::contextRemoveSearchResult()
@@ -92,44 +80,47 @@ void MemorySearchWidget::contextCopySearchResultAddress()
void MemorySearchWidget::onListSearchResultsContextMenu(QPoint pos)
{
QMenu* contextMenu = new QMenu(tr("Search Results List Context Menu"), m_ui.listSearchResults);
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
const auto listSearchResults = m_ui.listSearchResults;
const QItemSelectionModel* selection_model = m_ui.listSearchResults->selectionModel();
const QListWidget* list_search_results = m_ui.listSearchResults;
if (selModel->hasSelection())
QMenu* menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
if (selection_model->hasSelection())
{
QAction* copyAddressAction = new QAction(tr("Copy Address"), m_ui.listSearchResults);
connect(copyAddressAction, &QAction::triggered, this, &MemorySearchWidget::contextCopySearchResultAddress);
contextMenu->addAction(copyAddressAction);
connect(menu->addAction(tr("Copy Address")), &QAction::triggered,
this, &MemorySearchWidget::contextCopySearchResultAddress);
QAction* goToDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.listSearchResults);
connect(goToDisassemblyAction, &QAction::triggered, this, &MemorySearchWidget::contextSearchResultGoToDisassembly);
contextMenu->addAction(goToDisassemblyAction);
QAction* addToSavedAddressesAction = new QAction(tr("Add to Saved Memory Addresses"), m_ui.listSearchResults);
connect(addToSavedAddressesAction, &QAction::triggered, this, [this, listSearchResults]() {
u32 selectedAddress = listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt();
emit addAddressToSavedAddressesList(selectedAddress);
createEventActions<DebuggerEvents::GoToAddress>(menu, [list_search_results]() {
u32 selected_address = list_search_results->selectedItems().first()->data(Qt::UserRole).toUInt();
DebuggerEvents::GoToAddress event;
event.address = selected_address;
return std::optional(event);
});
contextMenu->addAction(addToSavedAddressesAction);
QAction* removeResultAction = new QAction(tr("Remove Result"), m_ui.listSearchResults);
connect(removeResultAction, &QAction::triggered, this, &MemorySearchWidget::contextRemoveSearchResult);
contextMenu->addAction(removeResultAction);
createEventActions<DebuggerEvents::AddToSavedAddresses>(menu, [list_search_results]() {
u32 selected_address = list_search_results->selectedItems().first()->data(Qt::UserRole).toUInt();
DebuggerEvents::AddToSavedAddresses event;
event.address = selected_address;
return std::optional(event);
});
connect(menu->addAction(tr("Remove Result")), &QAction::triggered,
this, &MemorySearchWidget::contextRemoveSearchResult);
}
contextMenu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos));
menu->popup(m_ui.listSearchResults->viewport()->mapToGlobal(pos));
}
template<typename T>
template <typename T>
T readValueAtAddress(DebugInterface* cpu, u32 addr);
template<>
template <>
float readValueAtAddress<float>(DebugInterface* cpu, u32 addr)
{
return std::bit_cast<float>(cpu->read32(addr));
}
template<>
template <>
double readValueAtAddress<double>(DebugInterface* cpu, u32 addr)
{
return std::bit_cast<double>(cpu->read64(addr));
@@ -230,7 +221,7 @@ template <typename T>
bool handleSearchComparison(SearchComparison searchComparison, u32 searchAddress, const SearchResult* priorResult, T searchValue, T readValue)
{
const bool isNotOperator = searchComparison == SearchComparison::NotEquals || searchComparison == SearchComparison::NotChanged;
switch (searchComparison)
switch (searchComparison)
{
case SearchComparison::Equals:
case SearchComparison::NotEquals:
@@ -302,7 +293,7 @@ void searchWorker(DebugInterface* cpu, std::vector<SearchResult>& searchResults,
{
if (!cpu->isValidAddress(addr))
continue;
T readValue = readValueAtAddress<T>(cpu, addr);
if (handleSearchComparison(searchComparison, addr, nullptr, searchValue, readValue))
{
@@ -315,7 +306,7 @@ void searchWorker(DebugInterface* cpu, std::vector<SearchResult>& searchResults,
auto removeIt = std::remove_if(searchResults.begin(), searchResults.end(), [cpu, searchType, searchComparison, searchValue](SearchResult& searchResult) -> bool {
const u32 addr = searchResult.getAddress();
if (!cpu->isValidAddress(addr))
return true;
return true;
const auto readValue = readValueAtAddress<T>(cpu, addr);
@@ -415,7 +406,7 @@ static void searchWorkerByteArray(DebugInterface* cpu, SearchType searchType, Se
}
else
{
auto removeIt = std::remove_if(searchResults.begin(), searchResults.end(), [ searchComparison, searchType, searchValue, cpu ](SearchResult& searchResult) -> bool {
auto removeIt = std::remove_if(searchResults.begin(), searchResults.end(), [searchComparison, searchType, searchValue, cpu](SearchResult& searchResult) -> bool {
const u32 addr = searchResult.getAddress();
if (!cpu->isValidAddress(addr))
return true;
@@ -453,7 +444,7 @@ std::vector<SearchResult> startWorker(DebugInterface* cpu, const SearchType type
isSigned ? searchWorker<s32>(cpu, searchResults, type, comparison, start, end, value.toInt(nullptr, base)) : searchWorker<u32>(cpu, searchResults, type, comparison, start, end, value.toUInt(nullptr, base));
break;
case SearchType::Int64Type:
isSigned ? searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toLong(nullptr, base)) : searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toULongLong(nullptr, base));
isSigned ? searchWorker<s64>(cpu, searchResults, type, comparison, start, end, value.toLongLong(nullptr, base)) : searchWorker<u64>(cpu, searchResults, type, comparison, start, end, value.toULongLong(nullptr, base));
break;
case SearchType::FloatType:
searchWorker<float>(cpu, searchResults, type, comparison, start, end, value.toFloat());
@@ -476,7 +467,7 @@ std::vector<SearchResult> startWorker(DebugInterface* cpu, const SearchType type
void MemorySearchWidget::onSearchButtonClicked()
{
if (!m_cpu->isAlive())
if (!cpu().isAlive())
return;
const SearchType searchType = getCurrentSearchType();
@@ -510,9 +501,9 @@ void MemorySearchWidget::onSearchButtonClicked()
const bool isFilterSearch = sender() == m_ui.btnFilterSearch;
unsigned long long value;
if(searchComparison != SearchComparison::UnknownValue)
if (searchComparison != SearchComparison::UnknownValue)
{
if(doesSearchComparisonTakeInput(searchComparison))
if (doesSearchComparisonTakeInput(searchComparison))
{
switch (searchType)
{
@@ -565,16 +556,32 @@ void MemorySearchWidget::onSearchButtonClicked()
}
}
if (!isFilterSearch && (searchComparison == SearchComparison::Changed || searchComparison == SearchComparison::ChangedBy
|| searchComparison == SearchComparison::Decreased || searchComparison == SearchComparison::DecreasedBy
|| searchComparison == SearchComparison::Increased || searchComparison == SearchComparison::IncreasedBy
|| searchComparison == SearchComparison::NotChanged))
if (!isFilterSearch &&
(searchComparison == SearchComparison::Changed ||
searchComparison == SearchComparison::ChangedBy ||
searchComparison == SearchComparison::Decreased ||
searchComparison == SearchComparison::DecreasedBy ||
searchComparison == SearchComparison::Increased ||
searchComparison == SearchComparison::IncreasedBy ||
searchComparison == SearchComparison::NotChanged))
{
QMessageBox::critical(this, tr("Debugger"), tr("This search comparison can only be used with filter searches."));
return;
}
}
if (!isFilterSearch && (searchComparison == SearchComparison::Changed ||
searchComparison == SearchComparison::ChangedBy ||
searchComparison == SearchComparison::Decreased ||
searchComparison == SearchComparison::DecreasedBy ||
searchComparison == SearchComparison::Increased ||
searchComparison == SearchComparison::IncreasedBy ||
searchComparison == SearchComparison::NotChanged))
{
QMessageBox::critical(this, tr("Debugger"), tr("This search comparison can only be used with filter searches."));
return;
}
QFutureWatcher<std::vector<SearchResult>>* workerWatcher = new QFutureWatcher<std::vector<SearchResult>>();
auto onSearchFinished = [this, workerWatcher] {
m_ui.btnSearch->setDisabled(false);
@@ -597,7 +604,7 @@ void MemorySearchWidget::onSearchButtonClicked()
m_searchResults.clear();
}
QFuture<std::vector<SearchResult>> workerFuture = QtConcurrent::run(startWorker, m_cpu, searchType, searchComparison, std::move(m_searchResults), searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
QFuture<std::vector<SearchResult>> workerFuture = QtConcurrent::run(startWorker, &cpu(), searchType, searchComparison, std::move(m_searchResults), searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
workerWatcher->setFuture(workerFuture);
connect(workerWatcher, &QFutureWatcher<std::vector<SearchResult>>::finished, onSearchFinished);
m_searchResults.clear();
@@ -649,7 +656,8 @@ SearchComparison MemorySearchWidget::getCurrentSearchComparison()
bool MemorySearchWidget::doesSearchComparisonTakeInput(const SearchComparison comparison)
{
switch (comparison) {
switch (comparison)
{
case SearchComparison::Equals:
case SearchComparison::NotEquals:
case SearchComparison::GreaterThan:
@@ -708,7 +716,7 @@ void MemorySearchWidget::updateSearchComparisonSelections()
std::vector<SearchComparison> MemorySearchWidget::getValidSearchComparisonsForState(SearchType type, std::vector<SearchResult>& existingResults)
{
const bool hasResults = existingResults.size() > 0;
std::vector<SearchComparison> comparisons = { SearchComparison::Equals };
std::vector<SearchComparison> comparisons = {SearchComparison::Equals};
if (type == SearchType::ArrayType || type == SearchType::StringType)
{
@@ -736,8 +744,8 @@ std::vector<SearchComparison> MemorySearchWidget::getValidSearchComparisonsForSt
comparisons.push_back(SearchComparison::ChangedBy);
comparisons.push_back(SearchComparison::NotChanged);
}
if(!hasResults)
if (!hasResults)
{
comparisons.push_back(SearchComparison::UnknownValue);
}

View File

@@ -5,22 +5,23 @@
#include "ui_MemorySearchWidget.h"
#include "Debugger/DebuggerWidget.h"
#include "DebugTools/DebugInterface.h"
#include <QtWidgets/QWidget>
#include <QtCore/QTimer>
#include <QtCore/QMap>
class MemorySearchWidget final : public QWidget
class MemorySearchWidget final : public DebuggerWidget
{
Q_OBJECT
Q_OBJECT
public:
MemorySearchWidget(QWidget* parent);
~MemorySearchWidget() = default;
void setCpu(DebugInterface* cpu);
MemorySearchWidget(const DebuggerWidgetParameters& parameters);
~MemorySearchWidget() = default;
enum class SearchType
enum class SearchType
{
ByteType,
Int16Type,
@@ -77,9 +78,11 @@ public:
{
return labelToEnumMap.value(comparisonLabel, SearchComparison::Invalid);
}
QString enumToLabel(SearchComparison comparison) {
QString enumToLabel(SearchComparison comparison)
{
return enumToLabelMap.value(comparison, "");
}
private:
QMap<SearchComparison, QString> enumToLabelMap;
QMap<QString, SearchComparison> labelToEnumMap;
@@ -100,7 +103,9 @@ public:
public:
SearchResult() {}
SearchResult(u32 address, const QVariant& value, SearchType type)
: address(address), value(value), type(type)
: address(address)
, value(value)
, type(type)
{
}
bool isIntegerValue() const { return type == SearchType::ByteType || type == SearchType::Int16Type || type == SearchType::Int32Type || type == SearchType::Int64Type; }
@@ -111,7 +116,7 @@ public:
SearchType getType() const { return type; }
QByteArray getArrayValue() const { return isArrayValue() ? value.toByteArray() : QByteArray(); }
template<typename T>
template <typename T>
T getValue() const
{
return value.value<T>();
@@ -124,29 +129,21 @@ public slots:
void onSearchTypeChanged(int newIndex);
void onSearchComparisonChanged(int newIndex);
void loadSearchResults();
void contextSearchResultGoToDisassembly();
void contextRemoveSearchResult();
void contextCopySearchResultAddress();
void onListSearchResultsContextMenu(QPoint pos);
signals:
void addAddressToSavedAddressesList(u32 address);
void goToAddressInDisassemblyView(u32 address);
void goToAddressInMemoryView(u32 address);
void switchToMemoryViewTab();
private:
std::vector<SearchResult> m_searchResults;
SearchComparisonLabelMap m_searchComparisonLabelMap;
Ui::MemorySearchWidget m_ui;
DebugInterface* m_cpu;
QTimer m_resultsLoadTimer;
u32 m_initialResultsLoadLimit = 20000;
u32 m_numResultsAddedPerLoad = 10000;
void updateSearchComparisonSelections();
std::vector<SearchComparison> getValidSearchComparisonsForState(SearchType type, std::vector<SearchResult> &existingResults);
std::vector<SearchComparison> getValidSearchComparisonsForState(SearchType type, std::vector<SearchResult>& existingResults);
SearchType getCurrentSearchType();
SearchComparison getCurrentSearchComparison();
bool doesSearchComparisonTakeInput(SearchComparison comparison);

View File

@@ -10,11 +10,6 @@
<height>300</height>
</rect>
</property>
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="windowTitle">
<string/>
</property>

View File

@@ -2,15 +2,17 @@
// SPDX-License-Identifier: GPL-3.0+
#include "MemoryViewWidget.h"
#include "common/Console.h"
#include "Debugger/JsonValueWrapper.h"
#include "QtHost.h"
#include "QtUtils.h"
#include <QtGui/QMouseEvent>
#include <QtCore/QObject>
#include <QtGui/QActionGroup>
#include <QtGui/QClipboard>
#include <QtGui/QMouseEvent>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#include <QtGui/QClipboard>
using namespace QtUtils;
@@ -41,7 +43,7 @@ void MemoryViewTable::UpdateSelectedAddress(u32 selected, bool page)
}
}
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height)
void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu)
{
rowHeight = painter.fontMetrics().height() + 2;
const s32 charWidth = painter.fontMetrics().averageCharWidth();
@@ -106,7 +108,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
{
case MemoryViewType::BYTE:
{
const u8 val = static_cast<u8>(m_cpu->read8(thisSegmentsStart, valid));
const u8 val = static_cast<u8>(cpu.read8(thisSegmentsStart, valid));
if (penDefault && val == 0)
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "??");
@@ -114,7 +116,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
}
case MemoryViewType::BYTEHW:
{
const u16 val = convertEndian<u16>(static_cast<u16>(m_cpu->read16(thisSegmentsStart, valid)));
const u16 val = convertEndian<u16>(static_cast<u16>(cpu.read16(thisSegmentsStart, valid)));
if (penDefault && val == 0)
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????");
@@ -122,7 +124,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
}
case MemoryViewType::WORD:
{
const u32 val = convertEndian<u32>(m_cpu->read32(thisSegmentsStart, valid));
const u32 val = convertEndian<u32>(cpu.read32(thisSegmentsStart, valid));
if (penDefault && val == 0)
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????");
@@ -130,7 +132,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
}
case MemoryViewType::DWORD:
{
const u64 val = convertEndian<u64>(m_cpu->read64(thisSegmentsStart, valid));
const u64 val = convertEndian<u64>(cpu.read64(thisSegmentsStart, valid));
if (penDefault && val == 0)
painter.setPen(QColor::fromRgb(145, 145, 155)); // ZERO BYTE COLOUR
painter.drawText(valX, y + (rowHeight * i), valid ? FilledQStringFromValue(val, 16) : "????????????????");
@@ -153,7 +155,7 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
painter.setPen(palette.text().color());
bool valid;
const u8 value = m_cpu->read8(currentRowAddress + j, valid);
const u8 value = cpu.read8(currentRowAddress + j, valid);
if (valid)
{
QChar curChar = QChar::fromLatin1(value);
@@ -173,6 +175,10 @@ void MemoryViewTable::DrawTable(QPainter& painter, const QPalette& palette, s32
void MemoryViewTable::SelectAt(QPoint pos)
{
// Check if SelectAt was called before DrawTable.
if (rowHeight == 0)
return;
const u32 selectedRow = (pos.y() - 2) / (rowHeight);
const s32 x = pos.x();
const s32 avgSegmentWidth = segmentXAxis[1] - segmentXAxis[0];
@@ -212,54 +218,54 @@ void MemoryViewTable::SelectAt(QPoint pos)
}
}
u128 MemoryViewTable::GetSelectedSegment()
u128 MemoryViewTable::GetSelectedSegment(DebugInterface& cpu)
{
u128 val;
switch (displayType)
{
case MemoryViewType::BYTE:
val.lo = m_cpu->read8(selectedAddress);
val.lo = cpu.read8(selectedAddress);
break;
case MemoryViewType::BYTEHW:
val.lo = convertEndian(static_cast<u16>(m_cpu->read16(selectedAddress & ~1)));
val.lo = convertEndian(static_cast<u16>(cpu.read16(selectedAddress & ~1)));
break;
case MemoryViewType::WORD:
val.lo = convertEndian(m_cpu->read32(selectedAddress & ~3));
val.lo = convertEndian(cpu.read32(selectedAddress & ~3));
break;
case MemoryViewType::DWORD:
val._u64[0] = convertEndian(m_cpu->read64(selectedAddress & ~7));
val._u64[0] = convertEndian(cpu.read64(selectedAddress & ~7));
break;
}
return val;
}
void MemoryViewTable::InsertIntoSelectedHexView(u8 value)
void MemoryViewTable::InsertIntoSelectedHexView(u8 value, DebugInterface& cpu)
{
const u8 mask = selectedNibbleHI ? 0x0f : 0xf0;
u8 curVal = m_cpu->read8(selectedAddress) & mask;
u8 curVal = cpu.read8(selectedAddress) & mask;
u8 newVal = value << (selectedNibbleHI ? 4 : 0);
curVal |= newVal;
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, val = curVal] {
cpu->write8(address, val);
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, val = curVal] {
cpu.write8(address, val);
QtHost::RunOnUIThread([this] { parent->update(); });
});
}
void MemoryViewTable::InsertAtCurrentSelection(const QString& text)
void MemoryViewTable::InsertAtCurrentSelection(const QString& text, DebugInterface& cpu)
{
if (!m_cpu->isValidAddress(selectedAddress))
if (!cpu.isValidAddress(selectedAddress))
return;
// If pasting into the hex view, also decode the input as hex bytes.
// This approach prevents one from pasting on a nibble boundary, but that is almost always
// user error, and we don't have an undo function in this view, so best to stay conservative.
QByteArray input = selectedText ? text.toUtf8() : QByteArray::fromHex(text.toUtf8());
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, inBytes = input] {
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, inBytes = input] {
u32 currAddr = address;
for (int i = 0; i < inBytes.size(); i++)
{
cpu->write8(currAddr, inBytes[i]);
cpu.write8(currAddr, inBytes[i]);
currAddr = nextAddress(currAddr);
QtHost::RunOnUIThread([this] { parent->update(); });
}
@@ -339,9 +345,9 @@ void MemoryViewTable::BackwardSelection()
// We need both key and keychar because `key` is easy to use, but is case insensitive
bool MemoryViewTable::KeyPress(int key, QChar keychar)
bool MemoryViewTable::KeyPress(int key, QChar keychar, DebugInterface& cpu)
{
if (!m_cpu->isValidAddress(selectedAddress))
if (!cpu.isValidAddress(selectedAddress))
return false;
bool pressHandled = false;
@@ -352,8 +358,8 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
{
if (keyCharIsText || (!keychar.isNonCharacter() && keychar.category() != QChar::Other_Control))
{
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu, val = keychar.toLatin1()] {
cpu->write8(address, val);
Host::RunOnCPUThread([this, address = selectedAddress, &cpu, val = keychar.toLatin1()] {
cpu.write8(address, val);
QtHost::RunOnUIThread([this] { UpdateSelectedAddress(selectedAddress + 1); parent->update(); });
});
pressHandled = true;
@@ -363,8 +369,8 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
{
case Qt::Key::Key_Backspace:
case Qt::Key::Key_Escape:
Host::RunOnCPUThread([this, address = selectedAddress, cpu = m_cpu] {
cpu->write8(address, 0);
Host::RunOnCPUThread([this, address = selectedAddress, &cpu] {
cpu.write8(address, 0);
QtHost::RunOnUIThread([this] {BackwardSelection(); parent->update(); });
});
pressHandled = true;
@@ -391,7 +397,7 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
const u8 keyPressed = static_cast<u8>(QString(QChar(key)).toInt(&pressHandled, 16));
if (pressHandled)
{
InsertIntoSelectedHexView(keyPressed);
InsertIntoSelectedHexView(keyPressed, cpu);
ForwardSelection();
}
}
@@ -400,7 +406,7 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
{
case Qt::Key::Key_Backspace:
case Qt::Key::Key_Escape:
InsertIntoSelectedHexView(0);
InsertIntoSelectedHexView(0, cpu);
BackwardSelection();
pressHandled = true;
break;
@@ -447,123 +453,174 @@ bool MemoryViewTable::KeyPress(int key, QChar keychar)
/*
MemoryViewWidget
*/
MemoryViewWidget::MemoryViewWidget(QWidget* parent)
: QWidget(parent)
MemoryViewWidget::MemoryViewWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, MONOSPACE_FONT)
, m_table(this)
{
ui.setupUi(this);
this->setFocusPolicy(Qt::FocusPolicy::ClickFocus);
connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::customMenuRequested);
setFocusPolicy(Qt::FocusPolicy::ClickFocus);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &MemoryViewWidget::customContextMenuRequested, this, &MemoryViewWidget::openContextMenu);
m_table.UpdateStartAddress(0x100000);
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
update();
return true;
});
receiveEvent<DebuggerEvents::GoToAddress>([this](const DebuggerEvents::GoToAddress& event) -> bool {
if (event.filter != DebuggerEvents::GoToAddress::NONE &&
event.filter != DebuggerEvents::GoToAddress::MEMORY_VIEW)
return false;
gotoAddress(event.address);
if (event.switch_to_tab)
switchToThisTab();
return true;
});
}
MemoryViewWidget::~MemoryViewWidget() = default;
void MemoryViewWidget::SetCpu(DebugInterface* cpu)
void MemoryViewWidget::toJson(JsonValueWrapper& json)
{
m_cpu = cpu;
m_table.SetCpu(cpu);
m_table.UpdateStartAddress(0x480000);
DebuggerWidget::toJson(json);
json.value().AddMember("startAddress", m_table.startAddress, json.allocator());
json.value().AddMember("viewType", static_cast<int>(m_table.GetViewType()), json.allocator());
json.value().AddMember("littleEndian", m_table.GetLittleEndian(), json.allocator());
}
bool MemoryViewWidget::fromJson(const JsonValueWrapper& json)
{
if (!DebuggerWidget::fromJson(json))
return false;
auto start_address = json.value().FindMember("startAddress");
if (start_address != json.value().MemberEnd() && start_address->value.IsUint())
m_table.UpdateStartAddress(start_address->value.GetUint());
auto view_type = json.value().FindMember("viewType");
if (view_type != json.value().MemberEnd() && view_type->value.IsInt())
{
MemoryViewType type = static_cast<MemoryViewType>(view_type->value.GetInt());
if (type == MemoryViewType::BYTE ||
type == MemoryViewType::BYTEHW ||
type == MemoryViewType::WORD ||
type == MemoryViewType::DWORD)
m_table.SetViewType(type);
}
auto little_endian = json.value().FindMember("littleEndian");
if (little_endian != json.value().MemberEnd() && little_endian->value.IsBool())
m_table.SetLittleEndian(little_endian->value.GetBool());
repaint();
return true;
}
void MemoryViewWidget::paintEvent(QPaintEvent* event)
{
if (!m_cpu->isAlive())
return;
QPainter painter(this);
m_table.DrawTable(painter, this->palette(), this->height());
painter.fillRect(rect(), palette().window());
if (!cpu().isAlive())
return;
m_table.DrawTable(painter, this->palette(), this->height(), cpu());
}
void MemoryViewWidget::mousePressEvent(QMouseEvent* event)
{
if (!m_cpu->isAlive())
if (!cpu().isAlive())
return;
m_table.SelectAt(event->pos());
repaint();
}
void MemoryViewWidget::customMenuRequested(QPoint pos)
void MemoryViewWidget::openContextMenu(QPoint pos)
{
if (!m_cpu->isAlive())
if (!cpu().isAlive())
return;
if (!m_contextMenu)
{
m_contextMenu = new QMenu(this);
QMenu* menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction* action = new QAction(tr("Copy Address"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper()); });
QAction* copy_action = menu->addAction(tr("Copy Address"));
connect(copy_action, &QAction::triggered, this, [this]() {
QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper());
});
action = new QAction(tr("Go to in Disassembly"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { emit gotoInDisasm(m_table.selectedAddress); });
createEventActions<DebuggerEvents::GoToAddress>(menu, [this]() {
DebuggerEvents::GoToAddress event;
event.address = m_table.selectedAddress;
return std::optional(event);
});
action = new QAction(tr("Go to address"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { contextGoToAddress(); });
QAction* go_to_address_action = menu->addAction(tr("Go to address"));
connect(go_to_address_action, &QAction::triggered, this, [this]() { contextGoToAddress(); });
m_contextMenu->addSeparator();
menu->addSeparator();
m_actionLittleEndian = new QAction(tr("Show as Little Endian"));
m_actionLittleEndian->setCheckable(true);
m_contextMenu->addAction(m_actionLittleEndian);
connect(m_actionLittleEndian, &QAction::triggered, this, [this]() { m_table.SetLittleEndian(m_actionLittleEndian->isChecked()); });
QAction* endian_action = menu->addAction(tr("Show as Little Endian"));
endian_action->setCheckable(true);
endian_action->setChecked(m_table.GetLittleEndian());
connect(endian_action, &QAction::triggered, this, [this, endian_action]() {
m_table.SetLittleEndian(endian_action->isChecked());
});
// View Types
m_actionBYTE = new QAction(tr("Show as 1 byte"));
m_actionBYTE->setCheckable(true);
m_contextMenu->addAction(m_actionBYTE);
connect(m_actionBYTE, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTE); });
const MemoryViewType current_view_type = m_table.GetViewType();
m_actionBYTEHW = new QAction(tr("Show as 2 bytes"));
m_actionBYTEHW->setCheckable(true);
m_contextMenu->addAction(m_actionBYTEHW);
connect(m_actionBYTEHW, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTEHW); });
// View Types
QActionGroup* view_type_group = new QActionGroup(menu);
view_type_group->setExclusive(true);
m_actionWORD = new QAction(tr("Show as 4 bytes"));
m_actionWORD->setCheckable(true);
m_contextMenu->addAction(m_actionWORD);
connect(m_actionWORD, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::WORD); });
QAction* byte_action = menu->addAction(tr("Show as 1 byte"));
byte_action->setCheckable(true);
byte_action->setChecked(current_view_type == MemoryViewType::BYTE);
connect(byte_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTE); });
view_type_group->addAction(byte_action);
m_actionDWORD = new QAction(tr("Show as 8 bytes"));
m_actionDWORD->setCheckable(true);
m_contextMenu->addAction(m_actionDWORD);
connect(m_actionDWORD, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::DWORD); });
QAction* bytehw_action = menu->addAction(tr("Show as 2 bytes"));
bytehw_action->setCheckable(true);
bytehw_action->setChecked(current_view_type == MemoryViewType::BYTEHW);
connect(bytehw_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::BYTEHW); });
view_type_group->addAction(bytehw_action);
m_contextMenu->addSeparator();
QAction* word_action = menu->addAction(tr("Show as 4 bytes"));
word_action->setCheckable(true);
word_action->setChecked(current_view_type == MemoryViewType::WORD);
connect(word_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::WORD); });
view_type_group->addAction(word_action);
action = new QAction((tr("Add to Saved Memory Addresses")));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { emit addToSavedAddresses(m_table.selectedAddress); });
QAction* dword_action = menu->addAction(tr("Show as 8 bytes"));
dword_action->setCheckable(true);
dword_action->setChecked(current_view_type == MemoryViewType::DWORD);
connect(dword_action, &QAction::triggered, this, [this]() { m_table.SetViewType(MemoryViewType::DWORD); });
view_type_group->addAction(dword_action);
action = new QAction(tr("Copy Byte"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { contextCopyByte(); });
menu->addSeparator();
action = new QAction(tr("Copy Segment"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { contextCopySegment(); });
createEventActions<DebuggerEvents::AddToSavedAddresses>(menu, [this]() {
DebuggerEvents::AddToSavedAddresses event;
event.address = m_table.selectedAddress;
return std::optional(event);
});
action = new QAction(tr("Copy Character"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { contextCopyCharacter(); });
connect(menu->addAction(tr("Copy Byte")), &QAction::triggered, this, &MemoryViewWidget::contextCopyByte);
connect(menu->addAction(tr("Copy Segment")), &QAction::triggered, this, &MemoryViewWidget::contextCopySegment);
connect(menu->addAction(tr("Copy Character")), &QAction::triggered, this, &MemoryViewWidget::contextCopyCharacter);
connect(menu->addAction(tr("Paste")), &QAction::triggered, this, &MemoryViewWidget::contextPaste);
action = new QAction(tr("Paste"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { contextPaste(); });
}
m_actionLittleEndian->setChecked(m_table.GetLittleEndian());
const MemoryViewType currentViewType = m_table.GetViewType();
m_actionBYTE->setChecked(currentViewType == MemoryViewType::BYTE);
m_actionBYTEHW->setChecked(currentViewType == MemoryViewType::BYTEHW);
m_actionWORD->setChecked(currentViewType == MemoryViewType::WORD);
m_actionDWORD->setChecked(currentViewType == MemoryViewType::DWORD);
m_contextMenu->popup(this->mapToGlobal(pos));
menu->popup(this->mapToGlobal(pos));
this->repaint();
return;
@@ -571,22 +628,22 @@ void MemoryViewWidget::customMenuRequested(QPoint pos)
void MemoryViewWidget::contextCopyByte()
{
QApplication::clipboard()->setText(QString::number(m_cpu->read8(m_table.selectedAddress), 16).toUpper());
QApplication::clipboard()->setText(QString::number(cpu().read8(m_table.selectedAddress), 16).toUpper());
}
void MemoryViewWidget::contextCopySegment()
{
QApplication::clipboard()->setText(QString::number(m_table.GetSelectedSegment().lo, 16).toUpper());
QApplication::clipboard()->setText(QString::number(m_table.GetSelectedSegment(cpu()).lo, 16).toUpper());
}
void MemoryViewWidget::contextCopyCharacter()
{
QApplication::clipboard()->setText(QChar::fromLatin1(m_cpu->read8(m_table.selectedAddress)).toUpper());
QApplication::clipboard()->setText(QChar::fromLatin1(cpu().read8(m_table.selectedAddress)).toUpper());
}
void MemoryViewWidget::contextPaste()
{
m_table.InsertAtCurrentSelection(QApplication::clipboard()->text());
m_table.InsertAtCurrentSelection(QApplication::clipboard()->text(), cpu());
}
void MemoryViewWidget::contextGoToAddress()
@@ -600,7 +657,7 @@ void MemoryViewWidget::contextGoToAddress()
u64 address = 0;
std::string error;
if (!m_cpu->evaluateExpression(targetString.toStdString().c_str(), address, error))
if (!cpu().evaluateExpression(targetString.toStdString().c_str(), address, error))
{
QMessageBox::warning(this, tr("Cannot Go To"), QString::fromStdString(error));
return;
@@ -628,7 +685,7 @@ void MemoryViewWidget::wheelEvent(QWheelEvent* event)
void MemoryViewWidget::keyPressEvent(QKeyEvent* event)
{
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0'))
if (!m_table.KeyPress(event->key(), event->text().size() ? event->text()[0] : '\0', cpu()))
{
switch (event->key())
{
@@ -644,7 +701,7 @@ void MemoryViewWidget::keyPressEvent(QKeyEvent* event)
}
}
this->repaint();
VMUpdate();
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
}
void MemoryViewWidget::gotoAddress(u32 address)

View File

@@ -3,7 +3,9 @@
#pragma once
#include "ui_RegisterWidget.h"
#include "ui_MemoryViewWidget.h"
#include "Debugger/DebuggerWidget.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/DisassemblyManager.h"
@@ -27,7 +29,6 @@ enum class MemoryViewType
class MemoryViewTable
{
QWidget* parent;
DebugInterface* m_cpu;
MemoryViewType displayType = MemoryViewType::BYTE;
bool littleEndian = true;
u32 rowCount;
@@ -44,7 +45,7 @@ class MemoryViewTable
bool selectedNibbleHI = false;
void InsertIntoSelectedHexView(u8 value);
void InsertIntoSelectedHexView(u8 value, DebugInterface& cpu);
template <class T>
T convertEndian(T in)
@@ -64,24 +65,23 @@ class MemoryViewTable
public:
MemoryViewTable(QWidget* parent)
: parent(parent){};
: parent(parent)
{
}
u32 startAddress;
u32 selectedAddress;
void SetCpu(DebugInterface* cpu)
{
m_cpu = cpu;
}
void UpdateStartAddress(u32 start);
void UpdateSelectedAddress(u32 selected, bool page = false);
void DrawTable(QPainter& painter, const QPalette& palette, s32 height);
void DrawTable(QPainter& painter, const QPalette& palette, s32 height, DebugInterface& cpu);
void SelectAt(QPoint pos);
u128 GetSelectedSegment();
void InsertAtCurrentSelection(const QString& text);
u128 GetSelectedSegment(DebugInterface& cpu);
void InsertAtCurrentSelection(const QString& text, DebugInterface& cpu);
void ForwardSelection();
void BackwardSelection();
// Returns true if the keypress was handled
bool KeyPress(int key, QChar keychar);
bool KeyPress(int key, QChar keychar, DebugInterface& cpu);
MemoryViewType GetViewType()
{
@@ -104,26 +104,26 @@ public:
}
};
class MemoryViewWidget final : public QWidget
class MemoryViewWidget final : public DebuggerWidget
{
Q_OBJECT
public:
MemoryViewWidget(QWidget* parent);
MemoryViewWidget(const DebuggerWidgetParameters& parameters);
~MemoryViewWidget();
void SetCpu(DebugInterface* cpu);
void toJson(JsonValueWrapper& json) override;
bool fromJson(const JsonValueWrapper& json) override;
protected:
void paintEvent(QPaintEvent* event);
void mousePressEvent(QMouseEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event);
void wheelEvent(QWheelEvent* event);
void keyPressEvent(QKeyEvent* event);
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
public slots:
void customMenuRequested(QPoint pos);
void openContextMenu(QPoint pos);
void contextGoToAddress();
void contextCopyByte();
@@ -132,21 +132,8 @@ public slots:
void contextPaste();
void gotoAddress(u32 address);
signals:
void gotoInDisasm(u32 address, bool should_set_focus = true);
void addToSavedAddresses(u32 address);
void VMUpdate();
private:
Ui::RegisterWidget ui;
Ui::MemoryViewWidget ui;
QMenu* m_contextMenu = 0x0;
QAction* m_actionLittleEndian;
QAction* m_actionBYTE;
QAction* m_actionBYTEHW;
QAction* m_actionWORD;
QAction* m_actionDWORD;
DebugInterface* m_cpu;
MemoryViewTable m_table;
};

View File

@@ -6,93 +6,112 @@
#include "common/Console.h"
std::map<BreakPointCpu, SavedAddressesModel*> SavedAddressesModel::s_instances;
SavedAddressesModel::SavedAddressesModel(DebugInterface& cpu, QObject* parent)
: QAbstractTableModel(parent)
, m_cpu(cpu)
{
}
SavedAddressesModel* SavedAddressesModel::getInstance(DebugInterface& cpu)
{
auto iterator = s_instances.find(cpu.getCpuType());
if (iterator == s_instances.end())
iterator = s_instances.emplace(cpu.getCpuType(), new SavedAddressesModel(cpu)).first;
return iterator->second;
}
QVariant SavedAddressesModel::data(const QModelIndex& index, int role) const
{
size_t row = static_cast<size_t>(index.row());
if (!index.isValid() || row >= m_savedAddresses.size())
return false;
const SavedAddress& entry = m_savedAddresses[row];
if (role == Qt::CheckStateRole)
{
return QVariant();
}
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
SavedAddress savedAddress = m_savedAddresses.at(index.row());
switch (index.column())
{
case HeaderColumns::ADDRESS:
return QString::number(savedAddress.address, 16).toUpper();
return QString::number(entry.address, 16).toUpper();
case HeaderColumns::LABEL:
return savedAddress.label;
return entry.label;
case HeaderColumns::DESCRIPTION:
return savedAddress.description;
return entry.description;
}
}
if (role == Qt::UserRole)
{
SavedAddress savedAddress = m_savedAddresses.at(index.row());
switch (index.column())
{
case HeaderColumns::ADDRESS:
return savedAddress.address;
return entry.address;
case HeaderColumns::LABEL:
return savedAddress.label;
return entry.label;
case HeaderColumns::DESCRIPTION:
return savedAddress.description;
return entry.description;
}
}
return QVariant();
}
bool SavedAddressesModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole)
{
size_t row = static_cast<size_t>(index.row());
if (!index.isValid() || row >= m_savedAddresses.size())
return false;
SavedAddress& entry = m_savedAddresses[row];
if (role == Qt::CheckStateRole)
return false;
}
if (role == Qt::EditRole)
{
SavedAddress addressToEdit = m_savedAddresses.at(index.row());
if (index.column() == HeaderColumns::ADDRESS)
{
bool ok = false;
const u32 address = value.toString().toUInt(&ok, 16);
if (ok)
addressToEdit.address = address;
entry.address = address;
else
return false;
}
if (index.column() == HeaderColumns::DESCRIPTION)
addressToEdit.description = value.toString();
entry.description = value.toString();
if (index.column() == HeaderColumns::LABEL)
addressToEdit.label = value.toString();
m_savedAddresses.at(index.row()) = addressToEdit;
entry.label = value.toString();
emit dataChanged(index, index, QList<int>(role));
return true;
}
else if (role == Qt::UserRole)
{
SavedAddress addressToEdit = m_savedAddresses.at(index.row());
if (index.column() == HeaderColumns::ADDRESS)
{
const u32 address = value.toUInt();
addressToEdit.address = address;
entry.address = address;
}
if (index.column() == HeaderColumns::DESCRIPTION)
addressToEdit.description = value.toString();
entry.description = value.toString();
if (index.column() == HeaderColumns::LABEL)
addressToEdit.label = value.toString();
m_savedAddresses.at(index.row()) = addressToEdit;
entry.label = value.toString();
emit dataChanged(index, index, QList<int>(role));
return true;
}
return false;
}
@@ -155,6 +174,7 @@ bool SavedAddressesModel::removeRows(int row, int count, const QModelIndex& pare
{
if (row < 0 || count < 1 || static_cast<size_t>(row + count) > m_savedAddresses.size())
return false;
beginRemoveRows(parent, row, row + count - 1);
m_savedAddresses.erase(m_savedAddresses.begin() + row, m_savedAddresses.begin() + row + count);
endRemoveRows();

View File

@@ -20,7 +20,7 @@ public:
QString description;
};
enum HeaderColumns: int
enum HeaderColumns : int
{
ADDRESS = 0,
LABEL,
@@ -28,14 +28,14 @@ public:
COLUMN_COUNT
};
static constexpr QHeaderView::ResizeMode HeaderResizeModes[HeaderColumns::COLUMN_COUNT] =
{
static constexpr QHeaderView::ResizeMode HeaderResizeModes[HeaderColumns::COLUMN_COUNT] = {
QHeaderView::ResizeMode::ResizeToContents,
QHeaderView::ResizeMode::ResizeToContents,
QHeaderView::ResizeMode::Stretch,
};
explicit SavedAddressesModel(DebugInterface& cpu, QObject* parent = nullptr);
static SavedAddressesModel* getInstance(DebugInterface& cpu);
QVariant data(const QModelIndex& index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -49,6 +49,10 @@ public:
void clear();
private:
SavedAddressesModel(DebugInterface& cpu, QObject* parent = nullptr);
DebugInterface& m_cpu;
std::vector<SavedAddress> m_savedAddresses;
static std::map<BreakPointCpu, SavedAddressesModel*> s_instances;
};

View File

@@ -0,0 +1,166 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "SavedAddressesWidget.h"
#include "QtUtils.h"
#include "Debugger/DebuggerSettingsManager.h"
#include <QtGui/QClipboard>
#include <QtWidgets/QMenu>
SavedAddressesWidget::SavedAddressesWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, DISALLOW_MULTIPLE_INSTANCES)
, m_model(SavedAddressesModel::getInstance(cpu()))
{
m_ui.setupUi(this);
m_ui.savedAddressesList->setModel(m_model);
m_ui.savedAddressesList->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.savedAddressesList, &QTableView::customContextMenuRequested,
this, &SavedAddressesWidget::openContextMenu);
connect(g_emu_thread, &EmuThread::onGameChanged, this, [this](const QString& title) {
if (title.isEmpty())
return;
if (m_model->rowCount() == 0)
DebuggerSettingsManager::loadGameSettings(m_model);
});
DebuggerSettingsManager::loadGameSettings(m_model);
for (std::size_t i = 0; auto mode : SavedAddressesModel::HeaderResizeModes)
{
m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode);
}
QTableView* savedAddressesTableView = m_ui.savedAddressesList;
connect(m_model, &QAbstractItemModel::dataChanged, this, [savedAddressesTableView](const QModelIndex& topLeft) {
savedAddressesTableView->resizeColumnToContents(topLeft.column());
});
receiveEvent<DebuggerEvents::AddToSavedAddresses>([this](const DebuggerEvents::AddToSavedAddresses& event) {
addAddress(event.address);
if (event.switch_to_tab)
switchToThisTab();
return true;
});
}
void SavedAddressesWidget::openContextMenu(QPoint pos)
{
QMenu* menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction* new_action = menu->addAction(tr("New"));
connect(new_action, &QAction::triggered, this, &SavedAddressesWidget::contextNew);
const QModelIndex index_at_pos = m_ui.savedAddressesList->indexAt(pos);
const bool is_index_valid = index_at_pos.isValid();
bool is_cpu_alive = cpu().isAlive();
std::vector<QAction*> go_to_actions = createEventActions<DebuggerEvents::GoToAddress>(
menu, [this, index_at_pos]() {
const QModelIndex rowAddressIndex = m_model->index(index_at_pos.row(), 0, QModelIndex());
DebuggerEvents::GoToAddress event;
event.address = m_model->data(rowAddressIndex, Qt::UserRole).toUInt();
return std::optional(event);
});
for (QAction* go_to_action : go_to_actions)
go_to_action->setEnabled(is_index_valid);
QAction* copy_action = menu->addAction(index_at_pos.column() == 0 ? tr("Copy Address") : tr("Copy Text"));
copy_action->setEnabled(is_index_valid);
connect(copy_action, &QAction::triggered, [this, index_at_pos]() {
QGuiApplication::clipboard()->setText(
m_model->data(index_at_pos, Qt::DisplayRole).toString());
});
if (m_model->rowCount() > 0)
{
QAction* copy_all_as_csv_action = menu->addAction(tr("Copy all as CSV"));
connect(copy_all_as_csv_action, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(
QtUtils::AbstractItemModelToCSV(m_ui.savedAddressesList->model(), Qt::DisplayRole, true));
});
}
QAction* paste_from_csv_action = menu->addAction(tr("Paste from CSV"));
connect(paste_from_csv_action, &QAction::triggered, this, &SavedAddressesWidget::contextPasteCSV);
QAction* load_action = menu->addAction(tr("Load from Settings"));
load_action->setEnabled(is_cpu_alive);
connect(load_action, &QAction::triggered, [this]() {
m_model->clear();
DebuggerSettingsManager::loadGameSettings(m_model);
});
QAction* save_action = menu->addAction(tr("Save to Settings"));
save_action->setEnabled(is_cpu_alive);
connect(save_action, &QAction::triggered, this, &SavedAddressesWidget::saveToDebuggerSettings);
QAction* delete_action = menu->addAction(tr("Delete"));
connect(delete_action, &QAction::triggered, this, [this, index_at_pos]() {
m_model->removeRows(index_at_pos.row(), 1);
});
delete_action->setEnabled(is_index_valid);
menu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos));
}
void SavedAddressesWidget::contextPasteCSV()
{
QString csv = QGuiApplication::clipboard()->text();
// Skip header
csv = csv.mid(csv.indexOf('\n') + 1);
for (const QString& line : csv.split('\n'))
{
QStringList fields;
// In order to handle text with commas in them we must wrap values in quotes to mark
// where a value starts and end so that text commas aren't identified as delimiters.
// So matches each quote pair, parse it out, and removes the quotes to get the value.
QRegularExpression each_quote_pair(R"("([^"]|\\.)*")");
QRegularExpressionMatchIterator it = each_quote_pair.globalMatch(line);
while (it.hasNext())
{
QRegularExpressionMatch match = it.next();
QString matched_value = match.captured(0);
fields << matched_value.mid(1, matched_value.length() - 2);
}
m_model->loadSavedAddressFromFieldList(fields);
}
}
void SavedAddressesWidget::contextNew()
{
m_model->addRow();
const u32 row_count = m_model->rowCount();
m_ui.savedAddressesList->edit(m_model->index(row_count - 1, 0));
}
void SavedAddressesWidget::addAddress(u32 address)
{
m_model->addRow();
u32 row_count = m_model->rowCount();
QModelIndex address_index = m_model->index(row_count - 1, SavedAddressesModel::ADDRESS);
m_model->setData(address_index, address, Qt::UserRole);
QModelIndex label_index = m_model->index(row_count - 1, SavedAddressesModel::LABEL);
if (label_index.isValid())
m_ui.savedAddressesList->edit(label_index);
}
void SavedAddressesWidget::saveToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(m_model);
}

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "ui_SavedAddressesWidget.h"
#include "SavedAddressesModel.h"
#include "Debugger/DebuggerWidget.h"
class SavedAddressesWidget : public DebuggerWidget
{
Q_OBJECT
public:
SavedAddressesWidget(const DebuggerWidgetParameters& parameters);
void openContextMenu(QPoint pos);
void contextPasteCSV();
void contextNew();
void addAddress(u32 address);
void saveToDebuggerSettings();
private:
Ui::SavedAddressesWidget m_ui;
SavedAddressesModel* m_model;
};

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SavedAddressesWidget</class>
<widget class="QWidget" name="SavedAddressesWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Saved Addresses</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="savedAddressesList"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -3,6 +3,8 @@
#include "RegisterWidget.h"
#include "Debugger/JsonValueWrapper.h"
#include "QtUtils.h"
#include <QtGui/QMouseEvent>
#include <QtWidgets/QTabBar>
@@ -13,15 +15,14 @@
#include <QtWidgets/QProxyStyle>
#include <QtWidgets/QMessageBox>
#include <algorithm>
#include <bit>
#define CAT_SHOW_FLOAT (categoryIndex == EECAT_FPR && m_showFPRFloat) || (categoryIndex == EECAT_VU0F && m_showVU0FFloat)
using namespace QtUtils;
RegisterWidget::RegisterWidget(QWidget* parent)
: QWidget(parent)
RegisterWidget::RegisterWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, MONOSPACE_FONT)
{
this->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
@@ -30,21 +31,48 @@ RegisterWidget::RegisterWidget(QWidget* parent)
connect(this, &RegisterWidget::customContextMenuRequested, this, &RegisterWidget::customMenuRequested);
connect(ui.registerTabs, &QTabBar::currentChanged, this, &RegisterWidget::tabCurrentChanged);
};
for (int i = 0; i < cpu().getRegisterCategoryCount(); i++)
{
ui.registerTabs->addTab(cpu().getRegisterCategoryName(i));
}
connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); });
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
update();
return true;
});
}
RegisterWidget::~RegisterWidget()
{
}
void RegisterWidget::SetCpu(DebugInterface* cpu)
void RegisterWidget::toJson(JsonValueWrapper& json)
{
m_cpu = cpu;
for (int i = 0; i < m_cpu->getRegisterCategoryCount(); i++)
{
ui.registerTabs->addTab(m_cpu->getRegisterCategoryName(i));
}
DebuggerWidget::toJson(json);
connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); });
json.value().AddMember("showVU0FFloat", m_showVU0FFloat, json.allocator());
json.value().AddMember("showFPRFloat", m_showFPRFloat, json.allocator());
}
bool RegisterWidget::fromJson(const JsonValueWrapper& json)
{
if (!DebuggerWidget::fromJson(json))
return false;
auto show_vu0f_float = json.value().FindMember("showVU0FFloat");
if (show_vu0f_float != json.value().MemberEnd() && show_vu0f_float->value.IsBool())
m_showVU0FFloat = show_vu0f_float->value.GetBool();
auto show_fpr_float = json.value().FindMember("showFPRFloat");
if (show_fpr_float != json.value().MemberEnd() && show_fpr_float->value.IsBool())
m_showFPRFloat = show_fpr_float->value.GetBool();
repaint();
return true;
}
void RegisterWidget::tabCurrentChanged(int cur)
@@ -54,9 +82,6 @@ void RegisterWidget::tabCurrentChanged(int cur)
void RegisterWidget::paintEvent(QPaintEvent* event)
{
if (!m_cpu)
return;
QPainter painter(this);
painter.setPen(this->palette().text().color());
m_renderStart = QPoint(0, ui.registerTabs->pos().y() + ui.registerTabs->size().height());
@@ -94,9 +119,9 @@ void RegisterWidget::paintEvent(QPaintEvent* event)
// off of that.
// Can probably constexpr the loop out as register names are known during runtime
int safeValueStartX = 0;
for (int i = 0; i < m_cpu->getRegisterCount(categoryIndex); i++)
for (int i = 0; i < cpu().getRegisterCount(categoryIndex); i++)
{
const int registerNameWidth = strlen(m_cpu->getRegisterName(categoryIndex, i));
const int registerNameWidth = strlen(cpu().getRegisterName(categoryIndex, i));
if (safeValueStartX < registerNameWidth)
{
safeValueStartX = registerNameWidth;
@@ -110,7 +135,7 @@ void RegisterWidget::paintEvent(QPaintEvent* event)
// Make it relative to where we start rendering
safeValueStartX += m_renderStart.x();
for (s32 i = 0; i < m_cpu->getRegisterCount(categoryIndex) - m_rowStart; i++)
for (s32 i = 0; i < cpu().getRegisterCount(categoryIndex) - m_rowStart; i++)
{
const s32 registerIndex = i + m_rowStart;
const int yStart = (i * m_rowHeight) + m_renderStart.y();
@@ -120,11 +145,11 @@ void RegisterWidget::paintEvent(QPaintEvent* event)
// Draw register name
painter.setPen(this->palette().text().color());
painter.drawText(m_renderStart.x() + painter.fontMetrics().averageCharWidth(), yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, m_cpu->getRegisterName(categoryIndex, registerIndex));
painter.drawText(m_renderStart.x() + painter.fontMetrics().averageCharWidth(), yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft, cpu().getRegisterName(categoryIndex, registerIndex));
if (m_cpu->getRegisterSize(categoryIndex) == 128)
if (cpu().getRegisterSize(categoryIndex) == 128)
{
const u128 curRegister = m_cpu->getRegister(categoryIndex, registerIndex);
const u128 curRegister = cpu().getRegister(categoryIndex, registerIndex);
int regIndex = 3;
for (int j = 0; j < 4; j++)
@@ -136,7 +161,7 @@ void RegisterWidget::paintEvent(QPaintEvent* event)
if (categoryIndex == EECAT_VU0F && m_showVU0FFloat)
painter.drawText(m_fieldStartX[j], yStart, m_fieldWidth, m_rowHeight, Qt::AlignLeft,
painter.fontMetrics().elidedText(QString::number(std::bit_cast<float>(m_cpu->getRegister(categoryIndex, registerIndex)._u32[regIndex])), Qt::ElideRight, m_fieldWidth - painter.fontMetrics().averageCharWidth()));
painter.fontMetrics().elidedText(QString::number(std::bit_cast<float>(cpu().getRegister(categoryIndex, registerIndex)._u32[regIndex])), Qt::ElideRight, m_fieldWidth - painter.fontMetrics().averageCharWidth()));
else
painter.drawText(m_fieldStartX[j], yStart, m_fieldWidth, m_rowHeight,
Qt::AlignLeft, FilledQStringFromValue(curRegister._u32[regIndex], 16));
@@ -153,13 +178,13 @@ void RegisterWidget::paintEvent(QPaintEvent* event)
if (categoryIndex == EECAT_FPR && m_showFPRFloat)
painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft,
QString("%1").arg(QString::number(std::bit_cast<float>(m_cpu->getRegister(categoryIndex, registerIndex)._u32[0]))).toUpper());
else if (m_cpu->getRegisterSize(categoryIndex) == 64)
QString("%1").arg(QString::number(std::bit_cast<float>(cpu().getRegister(categoryIndex, registerIndex)._u32[0]))).toUpper());
else if (cpu().getRegisterSize(categoryIndex) == 64)
painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft,
FilledQStringFromValue(m_cpu->getRegister(categoryIndex, registerIndex).lo, 16));
FilledQStringFromValue(cpu().getRegister(categoryIndex, registerIndex).lo, 16));
else
painter.drawText(safeValueStartX, yStart, renderSize.width(), m_rowHeight, Qt::AlignLeft,
FilledQStringFromValue(m_cpu->getRegister(categoryIndex, registerIndex)._u32[0], 16));
FilledQStringFromValue(cpu().getRegister(categoryIndex, registerIndex)._u32[0], 16));
}
}
painter.end();
@@ -171,7 +196,7 @@ void RegisterWidget::mousePressEvent(QMouseEvent* event)
m_selectedRow = static_cast<int>(((event->position().y() - m_renderStart.y()) / m_rowHeight)) + m_rowStart;
// For 128 bit types, support selecting segments
if (m_cpu->getRegisterSize(categoryIndex) == 128)
if (cpu().getRegisterSize(categoryIndex) == 128)
{
constexpr auto inRange = [](u32 low, u32 high, u32 val) {
return (low <= val && val <= high);
@@ -190,7 +215,7 @@ void RegisterWidget::mousePressEvent(QMouseEvent* event)
void RegisterWidget::wheelEvent(QWheelEvent* event)
{
if (event->angleDelta().y() < 0 && m_rowEnd < m_cpu->getRegisterCount(ui.registerTabs->currentIndex()))
if (event->angleDelta().y() < 0 && m_rowEnd < cpu().getRegisterCount(ui.registerTabs->currentIndex()))
{
m_rowStart += 1;
}
@@ -204,12 +229,12 @@ void RegisterWidget::wheelEvent(QWheelEvent* event)
void RegisterWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
if (!m_cpu->isAlive())
if (!cpu().isAlive())
return;
if (m_selectedRow > m_rowEnd) // Unsigned underflow; selectedRow will be > m_rowEnd (technically negative)
return;
const int categoryIndex = ui.registerTabs->currentIndex();
if (m_cpu->getRegisterSize(categoryIndex) == 128)
if (cpu().getRegisterSize(categoryIndex) == 128)
contextChangeSegment();
else
contextChangeValue();
@@ -217,85 +242,85 @@ void RegisterWidget::mouseDoubleClickEvent(QMouseEvent* event)
void RegisterWidget::customMenuRequested(QPoint pos)
{
if (!m_cpu->isAlive())
if (!cpu().isAlive())
return;
if (m_selectedRow > m_rowEnd) // Unsigned underflow; selectedRow will be > m_rowEnd (technically negative)
return;
// Unlike the disassembly widget, we need to create a new context menu every time
// we show it. Because some register groups are special
if (!m_contextMenu)
m_contextMenu = new QMenu(this);
else
m_contextMenu->clear();
QMenu* menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
const int categoryIndex = ui.registerTabs->currentIndex();
QAction* action = 0;
if (categoryIndex == EECAT_FPR)
{
m_contextMenu->addAction(action = new QAction(m_showFPRFloat ? tr("View as hex") : tr("View as float")));
connect(action, &QAction::triggered, this, [this]() { m_showFPRFloat = !m_showFPRFloat; });
m_contextMenu->addSeparator();
QAction* action = menu->addAction(tr("Show as Float"));
action->setCheckable(true);
action->setChecked(m_showFPRFloat);
connect(action, &QAction::triggered, this, [this]() {
m_showFPRFloat = !m_showFPRFloat;
repaint();
});
menu->addSeparator();
}
if (categoryIndex == EECAT_VU0F)
{
m_contextMenu->addAction(action = new QAction(m_showVU0FFloat ? tr("View as hex") : tr("View as float")));
connect(action, &QAction::triggered, this, [this]() { m_showVU0FFloat = !m_showVU0FFloat; });
m_contextMenu->addSeparator();
QAction* action = menu->addAction(tr("Show as Float"));
action->setCheckable(true);
action->setChecked(m_showVU0FFloat);
connect(action, &QAction::triggered, this, [this]() {
m_showVU0FFloat = !m_showVU0FFloat;
repaint();
});
menu->addSeparator();
}
if (m_cpu->getRegisterSize(categoryIndex) == 128)
if (cpu().getRegisterSize(categoryIndex) == 128)
{
m_contextMenu->addAction(action = new QAction(tr("Copy Top Half"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyTop);
m_contextMenu->addAction(action = new QAction(tr("Copy Bottom Half"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyBottom);
m_contextMenu->addAction(action = new QAction(tr("Copy Segment"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopySegment);
connect(menu->addAction(tr("Copy Top Half")), &QAction::triggered, this, &RegisterWidget::contextCopyTop);
connect(menu->addAction(tr("Copy Bottom Half")), &QAction::triggered, this, &RegisterWidget::contextCopyBottom);
connect(menu->addAction(tr("Copy Segment")), &QAction::triggered, this, &RegisterWidget::contextCopySegment);
}
else
{
m_contextMenu->addAction(action = new QAction(tr("Copy Value"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextCopyValue);
connect(menu->addAction(tr("Copy Value")), &QAction::triggered, this, &RegisterWidget::contextCopyValue);
}
m_contextMenu->addSeparator();
menu->addSeparator();
if (m_cpu->getRegisterSize(categoryIndex) == 128)
if (cpu().getRegisterSize(categoryIndex) == 128)
{
m_contextMenu->addAction(action = new QAction(tr("Change Top Half"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeTop);
m_contextMenu->addAction(action = new QAction(tr("Change Bottom Half"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeBottom);
m_contextMenu->addAction(action = new QAction(tr("Change Segment"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeSegment);
connect(menu->addAction(tr("Change Top Half")), &QAction::triggered,
this, &RegisterWidget::contextChangeTop);
connect(menu->addAction(tr("Change Bottom Half")), &QAction::triggered,
this, &RegisterWidget::contextChangeBottom);
connect(menu->addAction(tr("Change Segment")), &QAction::triggered,
this, &RegisterWidget::contextChangeSegment);
}
else
{
m_contextMenu->addAction(action = new QAction(tr("Change Value"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextChangeValue);
connect(menu->addAction(tr("Change Value")), &QAction::triggered,
this, &RegisterWidget::contextChangeValue);
}
m_contextMenu->addSeparator();
menu->addSeparator();
m_contextMenu->addAction(action = new QAction(tr("Go to in Disassembly"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoDisasm);
createEventActions<DebuggerEvents::GoToAddress>(menu, [this]() {
return contextCreateGotoEvent();
});
m_contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoMemory);
m_contextMenu->popup(this->mapToGlobal(pos));
menu->popup(this->mapToGlobal(pos));
}
void RegisterWidget::contextCopyValue()
{
const int categoryIndex = ui.registerTabs->currentIndex();
const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow);
const u128 val = cpu().getRegister(categoryIndex, m_selectedRow);
if (CAT_SHOW_FLOAT)
QApplication::clipboard()->setText(QString("%1").arg(QString::number(std::bit_cast<float>(val._u32[0])).toUpper(), 16));
else
@@ -305,21 +330,21 @@ void RegisterWidget::contextCopyValue()
void RegisterWidget::contextCopyTop()
{
const int categoryIndex = ui.registerTabs->currentIndex();
const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow);
const u128 val = cpu().getRegister(categoryIndex, m_selectedRow);
QApplication::clipboard()->setText(FilledQStringFromValue(val.hi, 16));
}
void RegisterWidget::contextCopyBottom()
{
const int categoryIndex = ui.registerTabs->currentIndex();
const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow);
const u128 val = cpu().getRegister(categoryIndex, m_selectedRow);
QApplication::clipboard()->setText(FilledQStringFromValue(val.lo, 16));
}
void RegisterWidget::contextCopySegment()
{
const int categoryIndex = ui.registerTabs->currentIndex();
const u128 val = m_cpu->getRegister(categoryIndex, m_selectedRow);
const u128 val = cpu().getRegister(categoryIndex, m_selectedRow);
if (CAT_SHOW_FLOAT)
QApplication::clipboard()->setText(FilledQStringFromValue(std::bit_cast<float>(val._u32[3 - m_selected128Field]), 10));
else
@@ -330,7 +355,7 @@ bool RegisterWidget::contextFetchNewValue(u64& out, u64 currentValue, bool segme
{
const int categoryIndex = ui.registerTabs->currentIndex();
const bool floatingPoint = CAT_SHOW_FLOAT && segment;
const int regSize = m_cpu->getRegisterSize(categoryIndex);
const int regSize = cpu().getRegisterSize(categoryIndex);
bool ok = false;
QString existingValue("%1");
@@ -341,7 +366,7 @@ bool RegisterWidget::contextFetchNewValue(u64& out, u64 currentValue, bool segme
existingValue = existingValue.arg(std::bit_cast<float>((u32)currentValue));
//: Changing the value in a CPU register (e.g. "Change t0")
QString input = QInputDialog::getText(this, tr("Change %1").arg(m_cpu->getRegisterName(categoryIndex, m_selectedRow)), "",
QString input = QInputDialog::getText(this, tr("Change %1").arg(cpu().getRegisterName(categoryIndex, m_selectedRow)), "",
QLineEdit::Normal, existingValue, &ok);
if (!ok)
@@ -373,76 +398,70 @@ void RegisterWidget::contextChangeValue()
{
const int categoryIndex = ui.registerTabs->currentIndex();
u64 newVal;
if (contextFetchNewValue(newVal, m_cpu->getRegister(categoryIndex, m_selectedRow).lo))
if (contextFetchNewValue(newVal, cpu().getRegister(categoryIndex, m_selectedRow).lo))
{
m_cpu->setRegister(categoryIndex, m_selectedRow, u128::From64(newVal));
VMUpdate();
cpu().setRegister(categoryIndex, m_selectedRow, u128::From64(newVal));
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
}
}
void RegisterWidget::contextChangeTop()
{
u64 newVal;
u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow);
u128 oldVal = cpu().getRegister(ui.registerTabs->currentIndex(), m_selectedRow);
if (contextFetchNewValue(newVal, oldVal.hi))
{
oldVal.hi = newVal;
m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
VMUpdate();
cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
}
}
void RegisterWidget::contextChangeBottom()
{
u64 newVal;
u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow);
u128 oldVal = cpu().getRegister(ui.registerTabs->currentIndex(), m_selectedRow);
if (contextFetchNewValue(newVal, oldVal.lo))
{
oldVal.lo = newVal;
m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
VMUpdate();
cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
}
}
void RegisterWidget::contextChangeSegment()
{
u64 newVal;
u128 oldVal = m_cpu->getRegister(ui.registerTabs->currentIndex(), m_selectedRow);
u128 oldVal = cpu().getRegister(ui.registerTabs->currentIndex(), m_selectedRow);
if (contextFetchNewValue(newVal, oldVal._u32[3 - m_selected128Field], true))
{
oldVal._u32[3 - m_selected128Field] = (u32)newVal;
m_cpu->setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
VMUpdate();
cpu().setRegister(ui.registerTabs->currentIndex(), m_selectedRow, oldVal);
DebuggerWidget::broadcastEvent(DebuggerEvents::VMUpdate());
}
}
void RegisterWidget::contextGotoDisasm()
std::optional<DebuggerEvents::GoToAddress> RegisterWidget::contextCreateGotoEvent()
{
const int categoryIndex = ui.registerTabs->currentIndex();
u128 regVal = m_cpu->getRegister(categoryIndex, m_selectedRow);
u128 regVal = cpu().getRegister(categoryIndex, m_selectedRow);
u32 addr = 0;
if (m_cpu->getRegisterSize(categoryIndex) == 128)
if (cpu().getRegisterSize(categoryIndex) == 128)
addr = regVal._u32[3 - m_selected128Field];
else
addr = regVal._u32[0];
if (m_cpu->isValidAddress(addr))
gotoInDisasm(addr);
else
QMessageBox::warning(this, tr("Invalid target address"), ("This register holds an invalid address."));
}
void RegisterWidget::contextGotoMemory()
{
const int categoryIndex = ui.registerTabs->currentIndex();
u128 regVal = m_cpu->getRegister(categoryIndex, m_selectedRow);
u32 addr = 0;
if (m_cpu->getRegisterSize(categoryIndex) == 128)
addr = regVal._u32[3 - m_selected128Field];
else
addr = regVal._u32[0];
gotoInMemory(addr);
if (!cpu().isValidAddress(addr))
{
QMessageBox::warning(
this,
tr("Invalid target address"),
tr("This register holds an invalid address."));
return std::nullopt;
}
DebuggerEvents::GoToAddress event;
event.address = addr;
return event;
}

View File

@@ -5,29 +5,31 @@
#include "ui_RegisterWidget.h"
#include "DebuggerWidget.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/DisassemblyManager.h"
#include <QtWidgets/QWidget>
#include <QtWidgets/QMenu>
#include <QtWidgets/QTabBar>
#include <QtGui/QPainter>
class RegisterWidget final : public QWidget
class RegisterWidget final : public DebuggerWidget
{
Q_OBJECT
public:
RegisterWidget(QWidget* parent);
RegisterWidget(const DebuggerWidgetParameters& parameters);
~RegisterWidget();
void SetCpu(DebugInterface* cpu);
void toJson(JsonValueWrapper& json) override;
bool fromJson(const JsonValueWrapper& json) override;
protected:
void paintEvent(QPaintEvent* event);
void mousePressEvent(QMouseEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event);
void wheelEvent(QWheelEvent* event);
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
public slots:
void customMenuRequested(QPoint pos);
@@ -40,41 +42,30 @@ public slots:
void contextChangeBottom();
void contextChangeSegment();
void contextGotoDisasm();
void contextGotoMemory();
std::optional<DebuggerEvents::GoToAddress> contextCreateGotoEvent();
void tabCurrentChanged(int cur);
signals:
void gotoInDisasm(u32 address, bool should_set_focus = true);
void gotoInMemory(u32 address);
void VMUpdate();
private:
Ui::RegisterWidget ui;
QMenu* m_contextMenu = 0x0;
// Returns true on success
bool contextFetchNewValue(u64& out, u64 currentValue, bool segment = false);
DebugInterface* m_cpu;
// Used for the height offset the tab bar creates
// because we share a widget
QPoint m_renderStart;
s32 m_rowStart = 0; // Index, 0 -> VF00, 1 -> VF01 etc
s32 m_rowEnd; // Index, what register is the last one drawn
s32 m_rowHeight; // The height of each register row
s32 m_rowStart = 0; // Index, 0 -> VF00, 1 -> VF01 etc
s32 m_rowEnd; // Index, what register is the last one drawn
s32 m_rowHeight; // The height of each register row
// Used for mouse clicks
s32 m_fieldStartX[4]; // Where the register segments start
s32 m_fieldWidth; // How wide the register segments are
s32 m_fieldStartX[4]; // Where the register segments start
s32 m_fieldWidth; // How wide the register segments are
s32 m_selectedRow = 0; // Index
s32 m_selectedRow = 0; // Index
s32 m_selected128Field = 0; // Values are from 0 to 3
// TODO: Save this configuration ??
bool m_showVU0FFloat = false;
bool m_showFPRFloat = false;
};

View File

@@ -7,13 +7,13 @@
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<height>316</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
@@ -25,31 +25,46 @@
<property name="windowTitle">
<string>Register View</string>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>411</width>
<height>301</height>
</rect>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<item alignment="Qt::AlignLeft|Qt::AlignTop">
<widget class="QTabBar" name="registerTabs" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabBar" name="registerTabs" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>289</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>

View File

@@ -24,16 +24,20 @@ int StackModel::columnCount(const QModelIndex&) const
QVariant StackModel::data(const QModelIndex& index, int role) const
{
size_t row = static_cast<size_t>(index.row());
if (row >= m_stackFrames.size())
return QVariant();
const auto& stackFrame = m_stackFrames[row];
if (role == Qt::DisplayRole)
{
const auto& stackFrame = m_stackFrames.at(index.row());
switch (index.column())
{
case StackModel::ENTRY:
return QtUtils::FilledQStringFromValue(stackFrame.entry, 16);
case StackModel::ENTRY_LABEL:
return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(stackFrame.entry).name);
return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(stackFrame.entry).name);
case StackModel::PC:
return QtUtils::FilledQStringFromValue(stackFrame.pc, 16);
case StackModel::PC_OPCODE:
@@ -63,6 +67,7 @@ QVariant StackModel::data(const QModelIndex& index, int role) const
return stackFrame.stackSize;
}
}
return QVariant();
}

View File

@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "StackWidget.h"
#include "QtUtils.h"
#include <QtGui/QClipboard>
#include <QtWidgets/QMenu>
StackWidget::StackWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, NO_DEBUGGER_FLAGS)
, m_model(new StackModel(cpu()))
{
m_ui.setupUi(this);
m_ui.stackList->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.stackList, &QTableView::customContextMenuRequested, this, &StackWidget::openContextMenu);
connect(m_ui.stackList, &QTableView::doubleClicked, this, &StackWidget::onDoubleClick);
m_ui.stackList->setModel(m_model);
for (std::size_t i = 0; auto mode : StackModel::HeaderResizeModes)
{
m_ui.stackList->horizontalHeader()->setSectionResizeMode(i, mode);
i++;
}
receiveEvent<DebuggerEvents::VMUpdate>([this](const DebuggerEvents::VMUpdate& event) -> bool {
m_model->refreshData();
return true;
});
}
void StackWidget::openContextMenu(QPoint pos)
{
if (!m_ui.stackList->selectionModel()->hasSelection())
return;
QMenu* menu = new QMenu(m_ui.stackList);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction* copy_action = menu->addAction(tr("Copy"));
connect(copy_action, &QAction::triggered, [this]() {
const auto* selection_model = m_ui.stackList->selectionModel();
if (!selection_model->hasSelection())
return;
QGuiApplication::clipboard()->setText(m_model->data(selection_model->currentIndex()).toString());
});
menu->addSeparator();
QAction* copy_all_as_csv_action = menu->addAction(tr("Copy all as CSV"));
connect(copy_all_as_csv_action, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.stackList->model()));
});
menu->popup(m_ui.stackList->viewport()->mapToGlobal(pos));
}
void StackWidget::onDoubleClick(const QModelIndex& index)
{
switch (index.column())
{
case StackModel::StackModel::ENTRY:
case StackModel::StackModel::ENTRY_LABEL:
{
QModelIndex entry_index = m_model->index(index.row(), StackModel::StackColumns::ENTRY);
goToInDisassembler(m_model->data(entry_index, Qt::UserRole).toUInt(), true);
break;
}
case StackModel::StackModel::SP:
{
goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true);
break;
}
default: // Default to PC
{
QModelIndex pc_index = m_model->index(index.row(), StackModel::StackColumns::PC);
goToInDisassembler(m_model->data(pc_index, Qt::UserRole).toUInt(), true);
break;
}
}
}

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "ui_StackWidget.h"
#include "StackModel.h"
#include "DebuggerWidget.h"
class StackWidget final : public DebuggerWidget
{
Q_OBJECT
public:
StackWidget(const DebuggerWidgetParameters& parameters);
void openContextMenu(QPoint pos);
void onDoubleClick(const QModelIndex& index);
private:
Ui::StackWidget m_ui;
StackModel* m_model;
};

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StackWidget</class>
<widget class="QWidget" name="StackWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Stack</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="stackList"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -6,12 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>419</width>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string>Symbol Tree</string>
</property>
<layout class="QVBoxLayout" name="vertical_layout">
<property name="spacing">

View File

@@ -3,30 +3,32 @@
#include "SymbolTreeWidgets.h"
#include "Debugger/JsonValueWrapper.h"
#include "Debugger/SymbolTree/NewSymbolDialogs.h"
#include "Debugger/SymbolTree/SymbolTreeDelegates.h"
#include <QtGui/QClipboard>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QScrollBar>
#include "NewSymbolDialogs.h"
#include "SymbolTreeDelegates.h"
static bool testName(const QString& name, const QString& filter);
SymbolTreeWidget::SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, DebugInterface& cpu, QWidget* parent)
: QWidget(parent)
, m_cpu(cpu)
SymbolTreeWidget::SymbolTreeWidget(
u32 flags,
s32 symbol_address_alignment,
const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, MONOSPACE_FONT)
, m_flags(flags)
, m_symbol_address_alignment(symbol_address_alignment)
, m_group_by_module(cpu().getCpuType() == BREAKPOINT_IOP)
{
m_ui.setupUi(this);
setupMenu();
connect(m_ui.refreshButton, &QPushButton::clicked, this, [&]() {
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
m_cpu.GetSymbolGuardian().UpdateFunctionHashes(database, m_cpu);
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().UpdateFunctionHashes(database, cpu());
});
reset();
@@ -42,11 +44,16 @@ SymbolTreeWidget::SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, Debu
});
m_ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.treeView, &QTreeView::customContextMenuRequested, this, &SymbolTreeWidget::openMenu);
connect(m_ui.treeView, &QTreeView::customContextMenuRequested, this, &SymbolTreeWidget::openContextMenu);
connect(m_ui.treeView, &QTreeView::expanded, this, [&]() {
updateVisibleNodes(true);
});
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
updateModel();
return true;
});
}
SymbolTreeWidget::~SymbolTreeWidget() = default;
@@ -58,6 +65,78 @@ void SymbolTreeWidget::resizeEvent(QResizeEvent* event)
updateVisibleNodes(false);
}
void SymbolTreeWidget::toJson(JsonValueWrapper& json)
{
DebuggerWidget::toJson(json);
json.value().AddMember("showSizeColumn", m_show_size_column, json.allocator());
if (m_flags & ALLOW_GROUPING)
{
json.value().AddMember("groupByModule", m_group_by_module, json.allocator());
json.value().AddMember("groupBySection", m_group_by_section, json.allocator());
json.value().AddMember("groupBySourceFile", m_group_by_source_file, json.allocator());
}
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
{
json.value().AddMember("sortByIfTypeIsKnown", m_sort_by_if_type_is_known, json.allocator());
}
}
bool SymbolTreeWidget::fromJson(const JsonValueWrapper& json)
{
if (!DebuggerWidget::fromJson(json))
return false;
bool needs_reset = false;
auto show_size_column = json.value().FindMember("showSizeColumn");
if (show_size_column != json.value().MemberEnd() && show_size_column->value.IsBool())
{
needs_reset |= show_size_column->value.GetBool() != m_show_size_column;
m_show_size_column = show_size_column->value.GetBool();
}
if (m_flags & ALLOW_GROUPING)
{
auto group_by_module = json.value().FindMember("groupByModule");
if (group_by_module != json.value().MemberEnd() && group_by_module->value.IsBool())
{
needs_reset |= group_by_module->value.GetBool() != m_group_by_module;
m_group_by_module = group_by_module->value.GetBool();
}
auto group_by_section = json.value().FindMember("groupBySection");
if (group_by_section != json.value().MemberEnd() && group_by_section->value.IsBool())
{
needs_reset |= group_by_section->value.GetBool() != m_group_by_section;
m_group_by_section = group_by_section->value.GetBool();
}
auto group_by_source_file = json.value().FindMember("groupBySourceFile");
if (group_by_source_file != json.value().MemberEnd() && group_by_source_file->value.IsBool())
{
needs_reset |= group_by_source_file->value.GetBool() != m_group_by_source_file;
m_group_by_source_file = group_by_source_file->value.GetBool();
}
}
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
{
auto sort_by_if_type_is_known = json.value().FindMember("sortByIfTypeIsKnown");
if (sort_by_if_type_is_known != json.value().MemberEnd() && sort_by_if_type_is_known->value.IsBool())
{
needs_reset |= sort_by_if_type_is_known->value.GetBool() != m_sort_by_if_type_is_known;
m_sort_by_if_type_is_known = sort_by_if_type_is_known->value.GetBool();
}
}
if (needs_reset)
reset();
return true;
}
void SymbolTreeWidget::updateModel()
{
if (needsReset())
@@ -71,28 +150,22 @@ void SymbolTreeWidget::reset()
if (!m_model)
setupTree();
m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column || !m_show_size_column->isChecked());
m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column);
SymbolFilters filters;
std::unique_ptr<SymbolTreeNode> root;
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
filters.group_by_module = m_group_by_module && m_group_by_module->isChecked();
filters.group_by_section = m_group_by_section && m_group_by_section->isChecked();
filters.group_by_source_file = m_group_by_source_file && m_group_by_source_file->isChecked();
filters.string = m_ui.filterBox->text();
root = buildTree(filters, database);
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
root = buildTree(database);
});
if (root)
{
root->sortChildrenRecursively(m_sort_by_if_type_is_known && m_sort_by_if_type_is_known->isChecked());
root->sortChildrenRecursively(m_sort_by_if_type_is_known);
m_model->reset(std::move(root));
// Read the initial values for visible nodes.
updateVisibleNodes(true);
if (!filters.string.isEmpty())
if (!m_ui.filterBox->text().isEmpty())
expandGroups(QModelIndex());
}
}
@@ -114,8 +187,8 @@ void SymbolTreeWidget::updateVisibleNodes(bool update_hashes)
// Hash functions for symbols with visible nodes.
if (update_hashes)
{
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
SymbolTreeNode::updateSymbolHashes(nodes, m_cpu, database);
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
SymbolTreeNode::updateSymbolHashes(nodes, cpu(), database);
});
}
@@ -147,16 +220,16 @@ void SymbolTreeWidget::expandGroups(QModelIndex index)
void SymbolTreeWidget::setupTree()
{
m_model = new SymbolTreeModel(m_cpu, this);
m_model = new SymbolTreeModel(cpu(), this);
m_ui.treeView->setModel(m_model);
auto location_delegate = new SymbolTreeLocationDelegate(m_cpu, m_symbol_address_alignment, this);
auto location_delegate = new SymbolTreeLocationDelegate(cpu(), m_symbol_address_alignment, this);
m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::LOCATION, location_delegate);
auto type_delegate = new SymbolTreeTypeDelegate(m_cpu, this);
auto type_delegate = new SymbolTreeTypeDelegate(cpu(), this);
m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::TYPE, type_delegate);
auto value_delegate = new SymbolTreeValueDelegate(m_cpu, this);
auto value_delegate = new SymbolTreeValueDelegate(cpu(), this);
m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::VALUE, value_delegate);
m_ui.treeView->setAlternatingRowColors(true);
@@ -167,9 +240,9 @@ void SymbolTreeWidget::setupTree()
connect(m_ui.treeView, &QTreeView::pressed, this, &SymbolTreeWidget::onTreeViewClicked);
}
std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const SymbolFilters& filters, const ccc::SymbolDatabase& database)
std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const ccc::SymbolDatabase& database)
{
std::vector<SymbolWork> symbols = getSymbols(filters.string, database);
std::vector<SymbolWork> symbols = getSymbols(m_ui.filterBox->text(), database);
auto source_file_comparator = [](const SymbolWork& lhs, const SymbolWork& rhs) -> bool {
if (lhs.source_file)
@@ -194,13 +267,13 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const SymbolFilters&
// Sort all of the symbols so that we can iterate over them in order and
// build a tree.
if (filters.group_by_source_file)
if (m_group_by_source_file)
std::stable_sort(symbols.begin(), symbols.end(), source_file_comparator);
if (filters.group_by_section)
if (m_group_by_section)
std::stable_sort(symbols.begin(), symbols.end(), section_comparator);
if (filters.group_by_module)
if (m_group_by_module)
std::stable_sort(symbols.begin(), symbols.end(), module_comparator);
std::unique_ptr<SymbolTreeNode> root = std::make_unique<SymbolTreeNode>();
@@ -221,23 +294,23 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::buildTree(const SymbolFilters&
{
std::unique_ptr<SymbolTreeNode> node = buildNode(work, database);
if (filters.group_by_source_file)
if (m_group_by_source_file)
{
node = groupBySourceFile(std::move(node), work, source_file_node, source_file_work, filters);
node = groupBySourceFile(std::move(node), work, source_file_node, source_file_work);
if (!node)
continue;
}
if (filters.group_by_section)
if (m_group_by_section)
{
node = groupBySection(std::move(node), work, section_node, section_work, filters);
node = groupBySection(std::move(node), work, section_node, section_work);
if (!node)
continue;
}
if (filters.group_by_module)
if (m_group_by_module)
{
node = groupByModule(std::move(node), work, module_node, module_work, filters);
node = groupByModule(std::move(node), work, module_node, module_work);
if (!node)
continue;
}
@@ -252,14 +325,13 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupBySourceFile(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters)
const SymbolWork*& prev_work)
{
bool group_exists =
prev_group &&
child_work.source_file == prev_work->source_file &&
(!filters.group_by_section || child_work.section == prev_work->section) &&
(!filters.group_by_module || child_work.module_symbol == prev_work->module_symbol);
(!m_group_by_section || child_work.section == prev_work->section) &&
(!m_group_by_module || child_work.module_symbol == prev_work->module_symbol);
if (group_exists)
{
prev_group->emplaceChild(std::move(child));
@@ -296,13 +368,12 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupBySection(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters)
const SymbolWork*& prev_work)
{
bool group_exists =
prev_group &&
child_work.section == prev_work->section &&
(!filters.group_by_module || child_work.module_symbol == prev_work->module_symbol);
(!m_group_by_module || child_work.module_symbol == prev_work->module_symbol);
if (group_exists)
{
prev_group->emplaceChild(std::move(child));
@@ -336,8 +407,7 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupByModule(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters)
const SymbolWork*& prev_work)
{
bool group_exists =
prev_group &&
@@ -378,95 +448,7 @@ std::unique_ptr<SymbolTreeNode> SymbolTreeWidget::groupByModule(
return child;
}
void SymbolTreeWidget::setupMenu()
{
m_context_menu = new QMenu(this);
QAction* copy_name = new QAction(tr("Copy Name"), this);
connect(copy_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyName);
m_context_menu->addAction(copy_name);
if (m_flags & ALLOW_MANGLED_NAME_ACTIONS)
{
QAction* copy_mangled_name = new QAction(tr("Copy Mangled Name"), this);
connect(copy_mangled_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyMangledName);
m_context_menu->addAction(copy_mangled_name);
}
QAction* copy_location = new QAction(tr("Copy Location"), this);
connect(copy_location, &QAction::triggered, this, &SymbolTreeWidget::onCopyLocation);
m_context_menu->addAction(copy_location);
m_context_menu->addSeparator();
m_rename_symbol = new QAction(tr("Rename Symbol"), this);
connect(m_rename_symbol, &QAction::triggered, this, &SymbolTreeWidget::onRenameSymbol);
m_context_menu->addAction(m_rename_symbol);
m_context_menu->addSeparator();
m_go_to_in_disassembly = new QAction(tr("Go to in Disassembly"), this);
connect(m_go_to_in_disassembly, &QAction::triggered, this, &SymbolTreeWidget::onGoToInDisassembly);
m_context_menu->addAction(m_go_to_in_disassembly);
m_m_go_to_in_memory_view = new QAction(tr("Go to in Memory View"), this);
connect(m_m_go_to_in_memory_view, &QAction::triggered, this, &SymbolTreeWidget::onGoToInMemoryView);
m_context_menu->addAction(m_m_go_to_in_memory_view);
m_show_size_column = new QAction(tr("Show Size Column"), this);
m_show_size_column->setCheckable(true);
connect(m_show_size_column, &QAction::triggered, this, &SymbolTreeWidget::reset);
m_context_menu->addAction(m_show_size_column);
if (m_flags & ALLOW_GROUPING)
{
m_context_menu->addSeparator();
m_group_by_module = new QAction(tr("Group by Module"), this);
m_group_by_module->setCheckable(true);
if (m_cpu.getCpuType() == BREAKPOINT_IOP)
m_group_by_module->setChecked(true);
connect(m_group_by_module, &QAction::toggled, this, &SymbolTreeWidget::reset);
m_context_menu->addAction(m_group_by_module);
m_group_by_section = new QAction(tr("Group by Section"), this);
m_group_by_section->setCheckable(true);
connect(m_group_by_section, &QAction::toggled, this, &SymbolTreeWidget::reset);
m_context_menu->addAction(m_group_by_section);
m_group_by_source_file = new QAction(tr("Group by Source File"), this);
m_group_by_source_file->setCheckable(true);
connect(m_group_by_source_file, &QAction::toggled, this, &SymbolTreeWidget::reset);
m_context_menu->addAction(m_group_by_source_file);
}
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
{
m_context_menu->addSeparator();
m_sort_by_if_type_is_known = new QAction(tr("Sort by if type is known"), this);
m_sort_by_if_type_is_known->setCheckable(true);
m_context_menu->addAction(m_sort_by_if_type_is_known);
connect(m_sort_by_if_type_is_known, &QAction::toggled, this, &SymbolTreeWidget::reset);
}
if (m_flags & ALLOW_TYPE_ACTIONS)
{
m_context_menu->addSeparator();
m_reset_children = new QAction(tr("Reset children"), this);
m_context_menu->addAction(m_reset_children);
m_change_type_temporarily = new QAction(tr("Change type temporarily"), this);
m_context_menu->addAction(m_change_type_temporarily);
connect(m_reset_children, &QAction::triggered, this, &SymbolTreeWidget::onResetChildren);
connect(m_change_type_temporarily, &QAction::triggered, this, &SymbolTreeWidget::onChangeTypeTemporarily);
}
}
void SymbolTreeWidget::openMenu(QPoint pos)
void SymbolTreeWidget::openContextMenu(QPoint pos)
{
SymbolTreeNode* node = currentNode();
if (!node)
@@ -476,17 +458,107 @@ void SymbolTreeWidget::openMenu(QPoint pos)
bool node_is_symbol = node->symbol.valid();
bool node_is_memory = node->location.type == SymbolTreeLocation::MEMORY;
m_rename_symbol->setEnabled(node_is_symbol);
m_go_to_in_disassembly->setEnabled(node_is_memory);
m_m_go_to_in_memory_view->setEnabled(node_is_memory);
QMenu* menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
if (m_reset_children)
m_reset_children->setEnabled(node_is_object);
QAction* copy_name = menu->addAction(tr("Copy Name"));
connect(copy_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyName);
if (m_change_type_temporarily)
m_change_type_temporarily->setEnabled(node_is_object);
if (m_flags & ALLOW_MANGLED_NAME_ACTIONS)
{
QAction* copy_mangled_name = menu->addAction(tr("Copy Mangled Name"));
connect(copy_mangled_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyMangledName);
}
m_context_menu->exec(m_ui.treeView->viewport()->mapToGlobal(pos));
QAction* copy_location = menu->addAction(tr("Copy Location"));
connect(copy_location, &QAction::triggered, this, &SymbolTreeWidget::onCopyLocation);
menu->addSeparator();
QAction* rename_symbol = menu->addAction(tr("Rename Symbol"));
rename_symbol->setEnabled(node_is_symbol);
connect(rename_symbol, &QAction::triggered, this, &SymbolTreeWidget::onRenameSymbol);
menu->addSeparator();
std::vector<QAction*> go_to_actions = createEventActions<DebuggerEvents::GoToAddress>(
menu, [this]() -> std::optional<DebuggerEvents::GoToAddress> {
SymbolTreeNode* node = currentNode();
if (!node)
return std::nullopt;
DebuggerEvents::GoToAddress event;
event.address = node->location.address;
return event;
});
for (QAction* action : go_to_actions)
action->setEnabled(node_is_memory);
QAction* show_size_column = menu->addAction(tr("Show Size Column"));
show_size_column->setCheckable(true);
show_size_column->setChecked(m_show_size_column);
connect(show_size_column, &QAction::triggered, this, [this](bool checked) {
m_show_size_column = checked;
m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column);
});
if (m_flags & ALLOW_GROUPING)
{
menu->addSeparator();
QAction* group_by_module = menu->addAction(tr("Group by Module"));
group_by_module->setCheckable(true);
group_by_module->setChecked(m_group_by_module);
connect(group_by_module, &QAction::toggled, this, [this](bool checked) {
m_group_by_module = checked;
reset();
});
QAction* group_by_section = menu->addAction(tr("Group by Section"));
group_by_section->setCheckable(true);
group_by_section->setChecked(m_group_by_section);
connect(group_by_section, &QAction::toggled, this, [this](bool checked) {
m_group_by_section = checked;
reset();
});
QAction* group_by_source_file = menu->addAction(tr("Group by Source File"));
group_by_source_file->setCheckable(true);
group_by_source_file->setChecked(m_group_by_source_file);
connect(group_by_source_file, &QAction::toggled, this, [this](bool checked) {
m_group_by_source_file = checked;
reset();
});
}
if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN)
{
menu->addSeparator();
QAction* sort_by_if_type_is_known = menu->addAction(tr("Sort by if type is known"));
sort_by_if_type_is_known->setCheckable(true);
sort_by_if_type_is_known->setChecked(m_sort_by_if_type_is_known);
connect(sort_by_if_type_is_known, &QAction::toggled, this, [this](bool checked) {
m_sort_by_if_type_is_known = checked;
reset();
});
}
if (m_flags & ALLOW_TYPE_ACTIONS)
{
menu->addSeparator();
QAction* reset_children = menu->addAction(tr("Reset Children"));
reset_children->setEnabled(node_is_object);
connect(reset_children, &QAction::triggered, this, &SymbolTreeWidget::onResetChildren);
QAction* change_type_temporarily = menu->addAction(tr("Change Type Temporarily"));
change_type_temporarily->setEnabled(node_is_object);
connect(change_type_temporarily, &QAction::triggered, this, &SymbolTreeWidget::onChangeTypeTemporarily);
}
menu->popup(m_ui.treeView->viewport()->mapToGlobal(pos));
}
bool SymbolTreeWidget::needsReset() const
@@ -506,7 +578,7 @@ void SymbolTreeWidget::onDeleteButtonPressed()
if (QMessageBox::question(this, tr("Confirm Deletion"), tr("Delete '%1'?").arg(node->name)) != QMessageBox::Yes)
return;
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
node->symbol.destroy_symbol(database, true);
});
@@ -540,7 +612,7 @@ void SymbolTreeWidget::onCopyLocation()
if (!node)
return;
QApplication::clipboard()->setText(node->location.toString(m_cpu));
QApplication::clipboard()->setText(node->location.toString(cpu()));
}
void SymbolTreeWidget::onRenameSymbol()
@@ -553,7 +625,7 @@ void SymbolTreeWidget::onRenameSymbol()
QString label = tr("Name:");
QString text;
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
const ccc::Symbol* symbol = node->symbol.lookup_symbol(database);
if (!symbol || !symbol->address().valid())
return;
@@ -566,29 +638,11 @@ void SymbolTreeWidget::onRenameSymbol()
if (!ok)
return;
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
node->symbol.rename_symbol(name, database);
});
}
void SymbolTreeWidget::onGoToInDisassembly()
{
SymbolTreeNode* node = currentNode();
if (!node)
return;
emit goToInDisassembly(node->location.address);
}
void SymbolTreeWidget::onGoToInMemoryView()
{
SymbolTreeNode* node = currentNode();
if (!node)
return;
emit goToInMemoryView(node->location.address);
}
void SymbolTreeWidget::onResetChildren()
{
if (!m_model)
@@ -634,19 +688,17 @@ void SymbolTreeWidget::onTreeViewClicked(const QModelIndex& index)
if (!index.isValid())
return;
SymbolTreeNode* node = m_model->nodeFromIndex(index);
if (!node)
if ((m_flags & CLICK_TO_GO_TO_IN_DISASSEMBLER) == 0)
return;
switch (index.column())
{
case SymbolTreeModel::NAME:
emit nameColumnClicked(node->location.address);
break;
case SymbolTreeModel::LOCATION:
emit locationColumnClicked(node->location.address);
break;
}
if ((QGuiApplication::mouseButtons() & Qt::LeftButton) == 0)
return;
SymbolTreeNode* node = m_model->nodeFromIndex(index);
if (!node || node->location.type != SymbolTreeLocation::MEMORY)
return;
goToInDisassembler(node->location.address, false);
}
SymbolTreeNode* SymbolTreeWidget::currentNode()
@@ -660,8 +712,11 @@ SymbolTreeNode* SymbolTreeWidget::currentNode()
// *****************************************************************************
FunctionTreeWidget::FunctionTreeWidget(DebugInterface& cpu, QWidget* parent)
: SymbolTreeWidget(ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS, 4, cpu, parent)
FunctionTreeWidget::FunctionTreeWidget(const DebuggerWidgetParameters& parameters)
: SymbolTreeWidget(
ALLOW_GROUPING | ALLOW_MANGLED_NAME_ACTIONS | CLICK_TO_GO_TO_IN_DISASSEMBLER,
4,
parameters)
{
}
@@ -737,15 +792,19 @@ void FunctionTreeWidget::configureColumns()
void FunctionTreeWidget::onNewButtonPressed()
{
NewFunctionDialog* dialog = new NewFunctionDialog(m_cpu, this);
NewFunctionDialog* dialog = new NewFunctionDialog(cpu(), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
if (dialog->exec() == QDialog::Accepted)
reset();
}
// *****************************************************************************
GlobalVariableTreeWidget::GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent)
: SymbolTreeWidget(ALLOW_GROUPING | ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN | ALLOW_TYPE_ACTIONS | ALLOW_MANGLED_NAME_ACTIONS, 1, cpu, parent)
GlobalVariableTreeWidget::GlobalVariableTreeWidget(const DebuggerWidgetParameters& parameters)
: SymbolTreeWidget(
ALLOW_GROUPING | ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN | ALLOW_TYPE_ACTIONS | ALLOW_MANGLED_NAME_ACTIONS,
1,
parameters)
{
}
@@ -876,15 +935,19 @@ void GlobalVariableTreeWidget::configureColumns()
void GlobalVariableTreeWidget::onNewButtonPressed()
{
NewGlobalVariableDialog* dialog = new NewGlobalVariableDialog(m_cpu, this);
NewGlobalVariableDialog* dialog = new NewGlobalVariableDialog(cpu(), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
if (dialog->exec() == QDialog::Accepted)
reset();
}
// *****************************************************************************
LocalVariableTreeWidget::LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent)
: SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent)
LocalVariableTreeWidget::LocalVariableTreeWidget(const DebuggerWidgetParameters& parameters)
: SymbolTreeWidget(
ALLOW_TYPE_ACTIONS,
1,
parameters)
{
}
@@ -895,10 +958,10 @@ bool LocalVariableTreeWidget::needsReset() const
if (!m_function.valid())
return true;
u32 program_counter = m_cpu.getPC();
u32 program_counter = cpu().getPC();
bool left_function = true;
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
const ccc::Function* function = database.functions.symbol_from_handle(m_function);
if (!function || !function->address().valid())
return;
@@ -918,7 +981,7 @@ bool LocalVariableTreeWidget::needsReset() const
std::vector<SymbolTreeWidget::SymbolWork> LocalVariableTreeWidget::getSymbols(
const QString& filter, const ccc::SymbolDatabase& database)
{
u32 program_counter = m_cpu.getPC();
u32 program_counter = cpu().getPC();
const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter);
if (!function || !function->local_variables().has_value())
{
@@ -927,7 +990,7 @@ std::vector<SymbolTreeWidget::SymbolWork> LocalVariableTreeWidget::getSymbols(
}
m_function = function->handle();
m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function);
m_caller_stack_pointer = cpu().getCallerStackPointer(*function);
std::vector<SymbolTreeWidget::SymbolWork> symbols;
@@ -973,7 +1036,7 @@ std::unique_ptr<SymbolTreeNode> LocalVariableTreeWidget::buildNode(
node->live_range = local_variable.live_range;
node->symbol = ccc::MultiSymbolHandle(local_variable);
if (const ccc::GlobalStorage* storage = std::get_if<ccc::GlobalStorage>(&local_variable.storage))
if (std::get_if<ccc::GlobalStorage>(&local_variable.storage))
node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, local_variable.address().value);
else if (const ccc::RegisterStorage* storage = std::get_if<ccc::RegisterStorage>(&local_variable.storage))
node->location = SymbolTreeLocation(SymbolTreeLocation::REGISTER, storage->dbx_register_number);
@@ -1001,15 +1064,19 @@ void LocalVariableTreeWidget::configureColumns()
void LocalVariableTreeWidget::onNewButtonPressed()
{
NewLocalVariableDialog* dialog = new NewLocalVariableDialog(m_cpu, this);
NewLocalVariableDialog* dialog = new NewLocalVariableDialog(cpu(), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
if (dialog->exec() == QDialog::Accepted)
reset();
}
// *****************************************************************************
ParameterVariableTreeWidget::ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent)
: SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent)
ParameterVariableTreeWidget::ParameterVariableTreeWidget(const DebuggerWidgetParameters& parameters)
: SymbolTreeWidget(
ALLOW_TYPE_ACTIONS,
1,
parameters)
{
}
@@ -1020,10 +1087,10 @@ bool ParameterVariableTreeWidget::needsReset() const
if (!m_function.valid())
return true;
u32 program_counter = m_cpu.getPC();
u32 program_counter = cpu().getPC();
bool left_function = true;
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
cpu().GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
const ccc::Function* function = database.functions.symbol_from_handle(m_function);
if (!function || !function->address().valid())
return;
@@ -1045,7 +1112,7 @@ std::vector<SymbolTreeWidget::SymbolWork> ParameterVariableTreeWidget::getSymbol
{
std::vector<SymbolTreeWidget::SymbolWork> symbols;
u32 program_counter = m_cpu.getPC();
u32 program_counter = cpu().getPC();
const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter);
if (!function || !function->parameter_variables().has_value())
{
@@ -1054,7 +1121,7 @@ std::vector<SymbolTreeWidget::SymbolWork> ParameterVariableTreeWidget::getSymbol
}
m_function = function->handle();
m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function);
m_caller_stack_pointer = cpu().getCallerStackPointer(*function);
for (const ccc::ParameterVariableHandle parameter_variable_handle : *function->parameter_variables())
{
@@ -1124,7 +1191,8 @@ void ParameterVariableTreeWidget::configureColumns()
void ParameterVariableTreeWidget::onNewButtonPressed()
{
NewParameterVariableDialog* dialog = new NewParameterVariableDialog(m_cpu, this);
NewParameterVariableDialog* dialog = new NewParameterVariableDialog(cpu(), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
if (dialog->exec() == QDialog::Accepted)
reset();
}

View File

@@ -3,16 +3,14 @@
#pragma once
#include <QtWidgets/QWidget>
#include "SymbolTreeModel.h"
#include "ui_SymbolTreeWidget.h"
struct SymbolFilters;
#include "Debugger/DebuggerWidget.h"
#include "Debugger/SymbolTree/SymbolTreeModel.h"
// A symbol tree widget with its associated refresh button, filter box and
// right-click menu. Supports grouping, sorting and various other settings.
class SymbolTreeWidget : public QWidget
class SymbolTreeWidget : public DebuggerWidget
{
Q_OBJECT
@@ -24,12 +22,6 @@ public:
void updateVisibleNodes(bool update_hashes);
void expandGroups(QModelIndex index);
signals:
void goToInDisassembly(u32 address);
void goToInMemoryView(u32 address);
void nameColumnClicked(u32 address);
void locationColumnClicked(u32 address);
protected:
struct SymbolWork
{
@@ -41,36 +33,38 @@ protected:
const ccc::SourceFile* source_file = nullptr;
};
SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, DebugInterface& cpu, QWidget* parent = nullptr);
SymbolTreeWidget(
u32 flags,
s32 symbol_address_alignment,
const DebuggerWidgetParameters& parameters);
void resizeEvent(QResizeEvent* event) override;
void toJson(JsonValueWrapper& json) override;
bool fromJson(const JsonValueWrapper& json) override;
void setupTree();
std::unique_ptr<SymbolTreeNode> buildTree(const SymbolFilters& filters, const ccc::SymbolDatabase& database);
std::unique_ptr<SymbolTreeNode> buildTree(const ccc::SymbolDatabase& database);
std::unique_ptr<SymbolTreeNode> groupBySourceFile(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters);
const SymbolWork*& prev_work);
std::unique_ptr<SymbolTreeNode> groupBySection(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters);
const SymbolWork*& prev_work);
std::unique_ptr<SymbolTreeNode> groupByModule(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters);
const SymbolWork*& prev_work);
void setupMenu();
void openMenu(QPoint pos);
void openContextMenu(QPoint pos);
virtual bool needsReset() const;
@@ -89,8 +83,6 @@ protected:
void onCopyMangledName();
void onCopyLocation();
void onRenameSymbol();
void onGoToInDisassembly();
void onGoToInMemoryView();
void onResetChildren();
void onChangeTypeTemporarily();
@@ -100,39 +92,33 @@ protected:
Ui::SymbolTreeWidget m_ui;
DebugInterface& m_cpu;
SymbolTreeModel* m_model = nullptr;
QMenu* m_context_menu = nullptr;
QAction* m_rename_symbol = nullptr;
QAction* m_go_to_in_disassembly = nullptr;
QAction* m_m_go_to_in_memory_view = nullptr;
QAction* m_show_size_column = nullptr;
QAction* m_group_by_module = nullptr;
QAction* m_group_by_section = nullptr;
QAction* m_group_by_source_file = nullptr;
QAction* m_sort_by_if_type_is_known = nullptr;
QAction* m_reset_children = nullptr;
QAction* m_change_type_temporarily = nullptr;
enum Flags
{
NO_SYMBOL_TREE_FLAGS = 0,
ALLOW_GROUPING = 1 << 0,
ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN = 1 << 1,
ALLOW_TYPE_ACTIONS = 1 << 2,
ALLOW_MANGLED_NAME_ACTIONS = 1 << 3
ALLOW_MANGLED_NAME_ACTIONS = 1 << 3,
CLICK_TO_GO_TO_IN_DISASSEMBLER = 1 << 4
};
u32 m_flags;
u32 m_symbol_address_alignment;
bool m_show_size_column = false;
bool m_group_by_module = false;
bool m_group_by_section = false;
bool m_group_by_source_file = false;
bool m_sort_by_if_type_is_known = false;
};
class FunctionTreeWidget : public SymbolTreeWidget
{
Q_OBJECT
public:
explicit FunctionTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
explicit FunctionTreeWidget(const DebuggerWidgetParameters& parameters);
virtual ~FunctionTreeWidget();
protected:
@@ -151,7 +137,7 @@ class GlobalVariableTreeWidget : public SymbolTreeWidget
{
Q_OBJECT
public:
explicit GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
explicit GlobalVariableTreeWidget(const DebuggerWidgetParameters& parameters);
virtual ~GlobalVariableTreeWidget();
protected:
@@ -170,7 +156,7 @@ class LocalVariableTreeWidget : public SymbolTreeWidget
{
Q_OBJECT
public:
explicit LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
explicit LocalVariableTreeWidget(const DebuggerWidgetParameters& parameters);
virtual ~LocalVariableTreeWidget();
protected:
@@ -194,7 +180,7 @@ class ParameterVariableTreeWidget : public SymbolTreeWidget
{
Q_OBJECT
public:
explicit ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
explicit ParameterVariableTreeWidget(const DebuggerWidgetParameters& parameters);
virtual ~ParameterVariableTreeWidget();
protected:
@@ -213,11 +199,3 @@ protected:
ccc::FunctionHandle m_function;
std::optional<u32> m_caller_stack_pointer;
};
struct SymbolFilters
{
bool group_by_module = false;
bool group_by_section = false;
bool group_by_source_file = false;
QString string;
};

View File

@@ -23,8 +23,13 @@ int ThreadModel::columnCount(const QModelIndex&) const
QVariant ThreadModel::data(const QModelIndex& index, int role) const
{
const auto threads = m_cpu.GetThreadList();
auto* const thread = threads.at(index.row()).get();
const std::vector<std::unique_ptr<BiosThread>> threads = m_cpu.GetThreadList();
size_t row = static_cast<size_t>(index.row());
if (row >= threads.size())
return QVariant();
const BiosThread* thread = threads[row].get();
if (role == Qt::DisplayRole)
{

View File

@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "ThreadWidget.h"
#include "QtUtils.h"
#include <QtGui/QClipboard>
#include <QtWidgets/QMenu>
ThreadWidget::ThreadWidget(const DebuggerWidgetParameters& parameters)
: DebuggerWidget(parameters, NO_DEBUGGER_FLAGS)
, m_model(new ThreadModel(cpu()))
, m_proxy_model(new QSortFilterProxyModel())
{
m_ui.setupUi(this);
m_ui.threadList->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &ThreadWidget::openContextMenu);
connect(m_ui.threadList, &QTableView::doubleClicked, this, &ThreadWidget::onDoubleClick);
m_proxy_model->setSourceModel(m_model);
m_proxy_model->setSortRole(Qt::UserRole);
m_ui.threadList->setModel(m_proxy_model);
m_ui.threadList->setSortingEnabled(true);
m_ui.threadList->sortByColumn(ThreadModel::ThreadColumns::ID, Qt::SortOrder::AscendingOrder);
for (std::size_t i = 0; auto mode : ThreadModel::HeaderResizeModes)
{
m_ui.threadList->horizontalHeader()->setSectionResizeMode(i, mode);
i++;
}
receiveEvent<DebuggerEvents::VMUpdate>([this](const DebuggerEvents::VMUpdate& event) -> bool {
m_model->refreshData();
return true;
});
}
void ThreadWidget::openContextMenu(QPoint pos)
{
if (!m_ui.threadList->selectionModel()->hasSelection())
return;
QMenu* menu = new QMenu(m_ui.threadList);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction* copy = menu->addAction(tr("Copy"));
connect(copy, &QAction::triggered, [this]() {
const QItemSelectionModel* selection_model = m_ui.threadList->selectionModel();
if (!selection_model->hasSelection())
return;
QGuiApplication::clipboard()->setText(m_model->data(selection_model->currentIndex()).toString());
});
menu->addSeparator();
QAction* copy_all_as_csv = menu->addAction(tr("Copy all as CSV"));
connect(copy_all_as_csv, &QAction::triggered, [this]() {
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.threadList->model()));
});
menu->popup(m_ui.threadList->viewport()->mapToGlobal(pos));
}
void ThreadWidget::onDoubleClick(const QModelIndex& index)
{
switch (index.column())
{
case ThreadModel::ThreadColumns::ENTRY:
{
goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true);
break;
}
default: // Default to PC
{
QModelIndex pc_index = m_model->index(index.row(), ThreadModel::ThreadColumns::PC);
goToInDisassembler(m_model->data(pc_index, Qt::UserRole).toUInt(), true);
break;
}
}
}

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "ui_ThreadWidget.h"
#include "DebuggerWidget.h"
#include "ThreadModel.h"
#include <QtCore/QSortFilterProxyModel>
class ThreadWidget final : public DebuggerWidget
{
Q_OBJECT
public:
ThreadWidget(const DebuggerWidgetParameters& parameters);
void openContextMenu(QPoint pos);
void onDoubleClick(const QModelIndex& index);
private:
Ui::ThreadWidget m_ui;
ThreadModel* m_model;
QSortFilterProxyModel* m_proxy_model;
};

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ThreadWidget</class>
<widget class="QWidget" name="ThreadWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Threads</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="threadList"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -12,6 +12,7 @@
#include "QtHost.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
#include "Debugger/Docking/DockManager.h"
#include "Settings/AchievementLoginDialog.h"
#include "Settings/ControllerSettingsWindow.h"
#include "Settings/GameListSettingsWidget.h"
@@ -99,11 +100,23 @@ static QString s_current_disc_serial;
static quint32 s_current_disc_crc;
static quint32 s_current_running_crc;
static bool s_record_on_start = false;
static QString s_path_to_recording_for_record_on_start;
MainWindow::MainWindow()
{
pxAssert(!g_main_window);
g_main_window = this;
// Native window rendering is broken in wayland.
// Let's work around it by disabling it for every widget besides
// DisplayWidget.
// Additionally, alien widget rendering is much more performant, so we
// should have a nice responsiveness boost in our UI :)
// QTBUG-133919, reported upstream by govanify
QGuiApplication::setAttribute(Qt::AA_NativeWindows, false);
QGuiApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
#if !defined(_WIN32) && !defined(__APPLE__)
s_use_central_widget = DisplayContainer::isRunningOnWayland();
#endif
@@ -608,12 +621,7 @@ void MainWindow::quit()
void MainWindow::destroySubWindows()
{
if (m_debugger_window)
{
m_debugger_window->close();
m_debugger_window->deleteLater();
m_debugger_window = nullptr;
}
DebuggerWindow::destroyInstance();
if (m_controller_settings_window)
{
@@ -729,7 +737,48 @@ void MainWindow::updateAdvancedSettingsVisibility()
void MainWindow::onVideoCaptureToggled(bool checked)
{
if (!s_vm_valid)
{
if (!s_record_on_start)
{
QMessageBox msgbox(this);
msgbox.setIcon(QMessageBox::Question);
msgbox.setWindowIcon(QtHost::GetAppIcon());
msgbox.setWindowTitle(tr("Record On Boot"));
msgbox.setWindowModality(Qt::WindowModal);
msgbox.setText(tr("Did you want to start recording on boot?"));
msgbox.addButton(QMessageBox::Yes);
msgbox.addButton(QMessageBox::No);
msgbox.setDefaultButton(QMessageBox::Yes);
if (msgbox.exec() == QMessageBox::Yes)
{
const QString container(QString::fromStdString(
Host::GetStringSettingValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER)));
const QString filter(tr("%1 Files (*.%2)").arg(container.toUpper()).arg(container));
QString temp(QStringLiteral("%1.%2").arg(QString::fromStdString(GSGetBaseVideoFilename())).arg(container));
temp = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Video Capture"), temp, filter));
s_path_to_recording_for_record_on_start = temp;
if (s_path_to_recording_for_record_on_start.isEmpty())
return;
s_record_on_start = true;
}
}
else
{
QMessageBox msgbox(this);
msgbox.setIcon(QMessageBox::Question);
msgbox.setWindowIcon(QtHost::GetAppIcon());
msgbox.setWindowTitle(tr("Record On Boot"));
msgbox.setWindowModality(Qt::WindowModal);
msgbox.setText(tr("Did you want to cancel recording on boot?"));
msgbox.addButton(QMessageBox::Yes);
msgbox.addButton(QMessageBox::No);
msgbox.setDefaultButton(QMessageBox::Yes);
if (msgbox.exec() == QMessageBox::Yes)
s_record_on_start = false;
}
return;
}
// Reset the checked state, we'll get updated by the GS thread.
QSignalBlocker sb(m_ui.actionVideoCapture);
@@ -741,16 +790,26 @@ void MainWindow::onVideoCaptureToggled(bool checked)
return;
}
const QString container(QString::fromStdString(
Host::GetStringSettingValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER)));
const QString filter(tr("%1 Files (*.%2)").arg(container.toUpper()).arg(container));
if (s_record_on_start && !s_path_to_recording_for_record_on_start.isEmpty())
{
// We can't start recording immediately, this is called before full GS init (specifically the fps amount)
// and GSCapture ends up unhappy.
// TODO: Pass some sort of flag or callback to the GS thread to start recording on frame 0.
Host::AddOSDMessage(tr("Recording will start in a moment").toStdString(), 3.0f);
QTimer::singleShot(2000, []() { g_emu_thread->beginCapture(s_path_to_recording_for_record_on_start); });
}
else
{
const QString container(QString::fromStdString(
Host::GetStringSettingValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER)));
const QString filter(tr("%1 Files (*.%2)").arg(container.toUpper()).arg(container));
QString path(QStringLiteral("%1.%2").arg(QString::fromStdString(GSGetBaseVideoFilename())).arg(container));
path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Video Capture"), path, filter));
if (path.isEmpty())
return;
g_emu_thread->beginCapture(path);
QString path(QStringLiteral("%1.%2").arg(QString::fromStdString(GSGetBaseVideoFilename())).arg(container));
path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Video Capture"), path, filter));
if (path.isEmpty())
return;
g_emu_thread->beginCapture(path);
}
}
void MainWindow::onCaptureStarted(const QString& filename)
@@ -787,12 +846,8 @@ void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
{
// If PauseOnEntry is enabled, we prompt the user to disable Hardcore Mode
// or cancel the action later, so we should keep the debugger around
if (m_debugger_window && !DebugInterface::getPauseOnEntry())
{
m_debugger_window->close();
m_debugger_window->deleteLater();
m_debugger_window = nullptr;
}
if (g_debugger_window && !DebugInterface::getPauseOnEntry())
DebuggerWindow::destroyInstance();
}
}
@@ -895,8 +950,6 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool stoppi
m_ui.actionToolbarSaveState->setEnabled(running);
m_ui.actionViewGameProperties->setEnabled(running);
m_ui.actionVideoCapture->setEnabled(running);
if (!running && m_ui.actionVideoCapture->isChecked())
{
QSignalBlocker sb(m_ui.actionVideoCapture);
@@ -1084,7 +1137,7 @@ bool MainWindow::shouldMouseLock() const
if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false))
return false;
bool windowsHidden = (!m_debugger_window || m_debugger_window->isHidden()) &&
bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
(!m_controller_settings_window || m_controller_settings_window->isHidden()) &&
(!m_settings_window || m_settings_window->isHidden());
@@ -1160,6 +1213,11 @@ void MainWindow::cancelGameListRefresh()
m_game_list_widget->cancelRefresh();
}
void MainWindow::reportInfo(const QString& title, const QString& message)
{
QMessageBox::information(this, title, message);
}
void MainWindow::reportError(const QString& title, const QString& message)
{
QMessageBox::critical(this, title, message);
@@ -1369,7 +1427,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
{
connect(action, &QAction::triggered, [entry]() {
SettingsWindow::openGamePropertiesDialog(entry,
entry->title, entry->serial, entry->crc, entry->type == GameList::EntryType::ELF);
entry->title, entry->serial, entry->crc, entry->type == GameList::EntryType::ELF, nullptr);
});
}
@@ -1415,7 +1473,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
connect(action, &QAction::triggered, [this, entry]() {
DebugInterface::setPauseOnEntry(true);
startGameListEntry(entry);
getDebuggerWindow()->show();
DebuggerWindow::getInstance()->show();
});
}
@@ -1573,41 +1631,7 @@ void MainWindow::onViewSystemDisplayTriggered()
void MainWindow::onViewGamePropertiesActionTriggered()
{
if (!s_vm_valid)
return;
// prefer to use a game list entry, if we have one, that way the summary is populated
if (!s_current_disc_path.isEmpty() || !s_current_elf_override.isEmpty())
{
auto lock = GameList::GetLock();
const QString& path = (s_current_elf_override.isEmpty() ? s_current_disc_path : s_current_elf_override);
const GameList::Entry* entry = GameList::GetEntryForPath(path.toUtf8().constData());
if (entry)
{
SettingsWindow::openGamePropertiesDialog(
entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty());
return;
}
}
// open properties for the current running file (isn't in the game list)
if (s_current_disc_crc == 0)
{
QMessageBox::critical(this, tr("Game Properties"), tr("Game properties is unavailable for the current game."));
return;
}
// can't use serial for ELFs, because they might have a disc set
if (s_current_elf_override.isEmpty())
{
SettingsWindow::openGamePropertiesDialog(
nullptr, s_current_title.toStdString(), s_current_disc_serial.toStdString(), s_current_disc_crc, false);
}
else
{
SettingsWindow::openGamePropertiesDialog(
nullptr, s_current_title.toStdString(), std::string(), s_current_disc_crc, true);
}
doGameSettings(nullptr);
}
void MainWindow::onGitHubRepositoryActionTriggered()
@@ -1753,36 +1777,11 @@ void MainWindow::onCreateMemoryCardOpenRequested()
void MainWindow::updateTheme()
{
// The debugger hates theme changes.
// We have unfortunately to destroy it and recreate it.
const bool debugger_is_open = m_debugger_window ? m_debugger_window->isVisible() : false;
const QSize debugger_size = m_debugger_window ? m_debugger_window->size() : QSize();
const QPoint debugger_pos = m_debugger_window ? m_debugger_window->pos() : QPoint();
if (m_debugger_window)
{
if (QMessageBox::question(this, tr("Theme Change"),
tr("Changing the theme will close the debugger window. Any unsaved data will be lost. Do you want to continue?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
{
return;
}
}
QtHost::UpdateApplicationTheme();
reloadThemeSpecificImages();
if (m_debugger_window)
{
m_debugger_window->deleteLater();
m_debugger_window = nullptr;
getDebuggerWindow(); // populates m_debugger_window
m_debugger_window->resize(debugger_size);
m_debugger_window->move(debugger_pos);
if (debugger_is_open)
{
m_debugger_window->show();
}
}
if (g_debugger_window)
g_debugger_window->updateStyleSheets();
}
void MainWindow::reloadThemeSpecificImages()
@@ -1982,6 +1981,11 @@ void MainWindow::onVMStarted()
updateWindowTitle();
updateStatusBarWidgetVisibility();
updateInputRecordingActions(true);
if (s_record_on_start)
{
m_ui.actionVideoCapture->setChecked(true);
s_record_on_start = false;
}
}
void MainWindow::onVMPaused()
@@ -2592,13 +2596,12 @@ QWidget* MainWindow::getDisplayContainer() const
void MainWindow::setupMouseMoveHandler()
{
auto mouse_cb_fn = [](int x, int y)
{
if(g_main_window)
auto mouse_cb_fn = [](int x, int y) {
if (g_main_window)
g_main_window->checkMousePosition(x, y);
};
if(!Common::AttachMousePositionCb(mouse_cb_fn))
if (!Common::AttachMousePositionCb(mouse_cb_fn))
{
Console.Warning("Unable to setup mouse position cb!");
}
@@ -2708,18 +2711,48 @@ void MainWindow::doSettings(const char* category /* = nullptr */)
dlg->setCategory(category);
}
DebuggerWindow* MainWindow::getDebuggerWindow()
void MainWindow::doGameSettings(const char* category)
{
if (!m_debugger_window)
// Don't pass us (this) as the parent, otherwise the window is always on top of the mainwindow (on windows at least)
m_debugger_window = new DebuggerWindow(nullptr);
if (!s_vm_valid)
return;
return m_debugger_window;
// prefer to use a game list entry, if we have one, that way the summary is populated
if (!s_current_disc_path.isEmpty() || !s_current_elf_override.isEmpty())
{
auto lock = GameList::GetLock();
const QString& path = (s_current_elf_override.isEmpty() ? s_current_disc_path : s_current_elf_override);
const GameList::Entry* entry = GameList::GetEntryForPath(path.toUtf8().constData());
if (entry)
{
SettingsWindow::openGamePropertiesDialog(
entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty(), category);
return;
}
}
// open properties for the current running file (isn't in the game list)
if (s_current_disc_crc == 0)
{
QMessageBox::critical(this, tr("Game Properties"), tr("Game properties is unavailable for the current game."));
return;
}
// can't use serial for ELFs, because they might have a disc set
if (s_current_elf_override.isEmpty())
{
SettingsWindow::openGamePropertiesDialog(
nullptr, s_current_title.toStdString(), s_current_disc_serial.toStdString(), s_current_disc_crc, false, category);
}
else
{
SettingsWindow::openGamePropertiesDialog(
nullptr, s_current_title.toStdString(), std::string(), s_current_disc_crc, true, category);
}
}
void MainWindow::openDebugger()
{
DebuggerWindow* dwnd = getDebuggerWindow();
DebuggerWindow* dwnd = DebuggerWindow::getInstance();
dwnd->isVisible() ? dwnd->activateWindow() : dwnd->show();
}

View File

@@ -103,12 +103,16 @@ public:
/// Rescans a single file. NOTE: Happens on UI thread.
void rescanFile(const std::string& path);
void doSettings(const char* category = nullptr);
void doGameSettings(const char* category = nullptr);
void openDebugger();
void checkMousePosition(int x, int y);
public Q_SLOTS:
void checkForUpdates(bool display_message, bool force_check);
void refreshGameList(bool invalidate_cache);
void cancelGameListRefresh();
void reportInfo(const QString& title, const QString& message);
void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message);
@@ -254,13 +258,10 @@ private:
void updateDisplayWidgetCursor();
SettingsWindow* getSettingsWindow();
void doSettings(const char* category = nullptr);
InputRecordingViewer* getInputRecordingViewer();
void updateInputRecordingActions(bool started);
DebuggerWindow* getDebuggerWindow();
void doControllerSettings(ControllerSettingsWindow::Category category = ControllerSettingsWindow::Category::Count);
QString getDiscDevicePath(const QString& title);
@@ -290,8 +291,6 @@ private:
InputRecordingViewer* m_input_recording_viewer = nullptr;
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
DebuggerWindow* m_debugger_window = nullptr;
QProgressBar* m_status_progress_widget = nullptr;
QLabel* m_status_verbose_widget = nullptr;
QLabel* m_status_renderer_widget = nullptr;

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0+
#include "AutoUpdaterDialog.h"
#include "Debugger/DebuggerWindow.h"
#include "DisplayWidget.h"
#include "GameList/GameListWidget.h"
#include "LogWindow.h"
@@ -1064,12 +1065,12 @@ void EmuThread::updatePerformanceMetrics(bool force)
Q_ARG(const QString&, tr("VPS: %1 ").arg(vfps, 0, 'f', 0)));
m_last_video_fps = vfps;
if (speed != m_last_speed || force)
{
QMetaObject::invokeMethod(g_main_window->getStatusSpeedWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, tr("Speed: %1% ").arg(speed, 0, 'f', 0)));
m_last_speed = speed;
}
if (speed != m_last_speed || force)
{
QMetaObject::invokeMethod(g_main_window->getStatusSpeedWidget(), "setText", Qt::QueuedConnection,
Q_ARG(const QString&, tr("Speed: %1% ").arg(speed, 0, 'f', 0)));
m_last_speed = speed;
}
}
}
}
@@ -1612,6 +1613,18 @@ bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url
return true;
}
void Host::ReportInfoAsync(const std::string_view title, const std::string_view message)
{
if (!title.empty() && !message.empty())
INFO_LOG("ReportInfoAsync: {}: {}", title, message);
else if (!message.empty())
INFO_LOG("ReportInfoAsync: {}", message);
QMetaObject::invokeMethod(g_main_window, "reportInfo", Qt::QueuedConnection,
Q_ARG(const QString&, title.empty() ? QString() : QString::fromUtf8(title.data(), title.size())),
Q_ARG(const QString&, message.empty() ? QString() : QString::fromUtf8(message.data(), message.size())));
}
void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
{
if (!title.empty() && !message.empty())
@@ -1712,57 +1725,58 @@ void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor);
}
namespace {
class QtHostProgressCallback final : public BaseProgressCallback
namespace
{
public:
QtHostProgressCallback();
~QtHostProgressCallback() override;
__fi const std::string& GetName() const { return m_name; }
void PushState() override;
void PopState() override;
bool IsCancelled() const override;
void SetCancellable(bool cancellable) override;
void SetTitle(const char* title) override;
void SetStatusText(const char* text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void DisplayError(const char* message) override;
void DisplayWarning(const char* message) override;
void DisplayInformation(const char* message) override;
void DisplayDebugMessage(const char* message) override;
void ModalError(const char* message) override;
bool ModalConfirmation(const char* message) override;
void ModalInformation(const char* message) override;
void SetCancelled();
private:
struct SharedData
class QtHostProgressCallback final : public BaseProgressCallback
{
QProgressDialog* dialog = nullptr;
QString init_title;
QString init_status_text;
std::atomic_bool cancelled{false};
bool cancellable = true;
bool was_fullscreen = false;
public:
QtHostProgressCallback();
~QtHostProgressCallback() override;
__fi const std::string& GetName() const { return m_name; }
void PushState() override;
void PopState() override;
bool IsCancelled() const override;
void SetCancellable(bool cancellable) override;
void SetTitle(const char* title) override;
void SetStatusText(const char* text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void DisplayError(const char* message) override;
void DisplayWarning(const char* message) override;
void DisplayInformation(const char* message) override;
void DisplayDebugMessage(const char* message) override;
void ModalError(const char* message) override;
bool ModalConfirmation(const char* message) override;
void ModalInformation(const char* message) override;
void SetCancelled();
private:
struct SharedData
{
QProgressDialog* dialog = nullptr;
QString init_title;
QString init_status_text;
std::atomic_bool cancelled{false};
bool cancellable = true;
bool was_fullscreen = false;
};
void EnsureHasData();
static void EnsureDialogVisible(const std::shared_ptr<SharedData>& data);
void Redraw(bool force);
std::string m_name;
std::shared_ptr<SharedData> m_data;
int m_last_progress_percent = -1;
};
void EnsureHasData();
static void EnsureDialogVisible(const std::shared_ptr<SharedData>& data);
void Redraw(bool force);
std::string m_name;
std::shared_ptr<SharedData> m_data;
int m_last_progress_percent = -1;
};
}
} // namespace
QtHostProgressCallback::QtHostProgressCallback()
: BaseProgressCallback()
@@ -1817,7 +1831,7 @@ void QtHostProgressCallback::SetTitle(const char* title)
void QtHostProgressCallback::SetStatusText(const char* text)
{
BaseProgressCallback::SetStatusText(text);
EnsureHasData();
QtHost::RunOnUIThread([data = m_data, text = QString::fromUtf8(text)]() {
if (data->dialog)
@@ -2372,9 +2386,9 @@ int main(int argc, char* argv[])
if (s_start_fullscreen_ui)
g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen);
if (s_boot_and_debug)
if (s_boot_and_debug || DebuggerWindow::shouldShowOnStartup())
{
DebugInterface::setPauseOnEntry(true);
DebugInterface::setPauseOnEntry(s_boot_and_debug);
g_main_window->openDebugger();
}

View File

@@ -114,7 +114,7 @@ public Q_SLOTS:
void endCapture();
void setAudioOutputVolume(int volume, int fast_forward_volume);
void setAudioOutputMuted(bool muted);
Q_SIGNALS:
bool messageConfirmed(const QString& title, const QString& message);
void statusMessage(const QString& message);

View File

@@ -5,6 +5,7 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
#include <QtCore/QtGlobal>
#include <QtCore/QMetaObject>
#include <QtGui/QAction>
#include <QtGui/QGuiApplication>
@@ -25,6 +26,10 @@
#include <QtWidgets/QTableView>
#include <QtWidgets/QTreeView>
#ifdef Q_OS_LINUX
#include <QtGui/private/qtx11extras_p.h>
#endif
#include <algorithm>
#include <array>
#include <map>
@@ -351,4 +356,22 @@ namespace QtUtils
}
return csv;
}
bool IsLightTheme(const QPalette& palette)
{
return palette.text().color().lightnessF() < 0.5;
}
bool IsCompositorManagerRunning()
{
if (qEnvironmentVariableIsSet("PCSX2_NO_COMPOSITING"))
return false;
#ifdef Q_OS_LINUX
if (QX11Info::isPlatformX11() && !QX11Info::isCompositingManagerRunning())
return false;
#endif
return true;
}
} // namespace QtUtils

View File

@@ -97,4 +97,9 @@ namespace QtUtils
/// Converts an abstract item model to a CSV string.
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role = Qt::DisplayRole, bool useQuotes = false);
// Heuristic to check if the current theme is a light or dark theme.
bool IsLightTheme(const QPalette& palette);
bool IsCompositorManagerRunning();
} // namespace QtUtils

Some files were not shown because too many files have changed in this diff Show More