Compare commits

...

130 Commits

Author SHA1 Message Date
Mrlinkwii
e2a60b7280 add KDDockWidgets to Third-Party Licenses 2025-03-14 19:35:34 +01:00
Dakota Smith
fe2d0cb514 Revert "Debugger: Set default breakpoint size to 4"
This reverts commit 92b9390c51.
2025-03-14 08:46:03 -04:00
TellowKrinkle
3362ca25be GHActions:MacOS: Don't use cmake --parallel without specifying job count
That ends up allowing infinite jobs on the makefile backend for some reason
2025-03-14 08:43:16 -04:00
PCSX2 Bot
c919c2a6fd [ci skip] Qt: Update Base Translation. 2025-03-14 00:08:58 -05:00
Silent
5a70075169 GameDB: Add memcard filters for Midnight Club 3 - DUB Edition Remix
Save data from Midnight Club 3 - DUB Edition (non-Remix)
can be imported into Remix.
2025-03-13 10:58:14 +01:00
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
Ty Lamontagne
97c098b1ff QT: Adjust mouse grab help text 2025-02-19 11:18:17 -05:00
Ty
e252cb6643 HostSys: Implement platform Specific mouse lock osx/windows/x11 2025-02-19 11:18:17 -05:00
Gonzalosilvalde
75c0236e1e Qt: Add mouse grab/lock feature when PCSX2 is in focus 2025-02-19 11:18:17 -05:00
KamFretoZ
9c4a98bc25 Qt: Add AMOLED Theme 2025-02-19 10:34:44 -05:00
GovanifY
9cba11cde5 appimage: revert #11667 , switch back to upstream appimagetool 2025-02-19 01:44:18 +01:00
GovanifY
fac5512b04 flatpak: misc additions to the metainfo 2025-02-19 01:44:18 +01:00
GovanifY
ed9bf05971 bin: update icons to be centered 2025-02-18 20:44:43 +01:00
GovanifY
19d0f3bdc5 flatpak: add branding colors 2025-02-18 20:44:43 +01:00
GovanifY
2abe53de43 flatpak: remove wild caps in summary 2025-02-18 20:44:43 +01:00
GovanifY
37a25750d7 flatpak: update default screenshots
This fixes a few issues in our flathub review report
2025-02-18 20:44:43 +01:00
lightningterror
d3e288447f GS/HW: Cleanup gl and vk shaders.
gl: Rename fetch_rt to sample_from_rt to match vk shaders, remove old atst call.

vk: Make sure shuffle values have correct unsigned type.
2025-02-17 21:27:34 +01:00
Ziemas
4a44d2668c SDL Audio: Set app name hint 2025-02-16 16:41:51 -05:00
Ty
752b1420a3 CI: Increase flatpak cron job timeout to 3 hours instead of 1
[ci skip]
2025-02-16 16:17:11 -05:00
PCSX2 Bot
71705fc91f [ci skip] Qt: Update Base Translation. 2025-02-16 22:12:45 +01:00
Ty
645efc7520 CI: Skip macos signing on forks 2025-02-16 12:03:14 -05:00
Ty
b6ee0e5219 CI: Bump our ccache size from 100M to 500M 2025-02-16 12:03:14 -05:00
Ty
7acf32debc CI: Sign and notarize our macos binaries 2025-02-16 12:03:14 -05:00
Ty
13397f68a3 CI: Update the node version for announce-release try #2 2025-02-16 11:39:54 -05:00
Ty
79250722d6 CI: Update the node version for announce-release 2025-02-16 11:30:47 -05:00
Ty
04b8748a8f CI: Update the announce-release script to use node-fetch (#12310) 2025-02-15 15:14:28 -05:00
dependabot[bot]
2dab8053ea CI: Bump deps in announce-release (#12308)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-15 11:03:07 -05:00
refractionpcsx2
f8bab2e465 Build/VS: Fix change in latest VS 2022 update with std::chrono
For more information see: https://learn.microsoft.com/en-us/cpp/overview/what-s-new-for-visual-cpp-in-visual-studio?view=msvc-170#:~:text=Moved%20system_clock%2C%20high_resolution_clock%2C%20and%20chrono_literals%20from%20a%20commonly%20included%20internal%20header%20to%20%3Cchrono%3E.%20If%20you%20see%20compiler%20errors%20that%20types%20like%20system_clock%20or%20user%2Ddefined%20literals%20like%201729ms%20aren%27t%20recognized%2C%20include%20%3Cchrono%3E.
2025-02-14 02:40:24 +00:00
TJnotJT
46221a8500 Tools: Fix typos in GS dump runner help. 2025-02-12 17:40:49 -05:00
TJnotJT
8b0e61af8c Tools: Remove debug preprocessor check around dumping of HW renderer frames. 2025-02-12 17:40:49 -05:00
TJnotJT
2b0a78811a Tools: Add additional options for dumping in GS runner. Other formatting fixes. 2025-02-12 17:40:49 -05:00
TheTechnician27
5798cd7176 Qt: End memcard conversion progress at 100% instead of 99% 2025-02-12 17:40:17 -05:00
JordanTheToaster
5c25637381 GameDB: Knights of the Temple 2 fixes 2025-02-12 17:39:56 -05:00
218 changed files with 17441 additions and 8124 deletions

View File

@@ -10,6 +10,7 @@ jobs:
if: github.repository == 'PCSX2/pcsx2'
name: "Check if release is needed"
runs-on: ubuntu-latest
timeout-minutes: 180
outputs:
PCSX2_RELEASE: ${{ steps.getinfo.outputs.PCSX2_RELEASE }}
FLATHUB_RELEASE: ${{ steps.getinfo.outputs.FLATHUB_RELEASE }}

View File

@@ -55,7 +55,7 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPRESS: true
CCACHE_COMPRESSLEVEL: 9
CCACHE_MAXSIZE: 100M
CCACHE_MAXSIZE: 500M
steps:
- name: Checkout Repository

View File

@@ -25,6 +25,10 @@ on:
required: false
type: boolean
default: false
sign_and_notarize:
required: false
type: boolean
default: false
jobs:
build_macos:
@@ -38,7 +42,9 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPRESS: true
CCACHE_COMPRESSLEVEL: 9
CCACHE_MAXSIZE: 100M
CCACHE_MAXSIZE: 500M
# Only way to use a secret in an if statement
SIGN_KEY: ${{ secrets.APPLE_SIGN_P12_B64 }}
steps:
- name: Checkout Repository
@@ -143,6 +149,38 @@ jobs:
run: make -j$(getconf _NPROCESSORS_ONLN) unittests
- name: Prepare Build Artifacts
run: |
mv build/pcsx2*/PCSX2.app PCSX2.app
- name: Pull the Signing Keys and Notarization Credentials
if: ${{ inputs.sign_and_notarize == true && env.SIGN_KEY }}
run: |
echo "${{ secrets.APPLE_SIGN_P12_B64 }}" | base64 -d > cert.p12
echo "${{ secrets.APPLE_APPSTORECONNECT_CFG }}" | base64 -d > key.json
- name: Sign the Application
if: ${{ inputs.sign_and_notarize == true && env.SIGN_KEY }}
uses: indygreg/apple-code-sign-action@v1.1
with:
input_path: 'PCSX2.app'
p12_file: cert.p12
p12_password: "${{ secrets.APPLE_SIGN_P12_PASS }}"
sign_args: |
--for-notarization
--code-signature-flags=runtime
--entitlements-xml-file=pcsx2/Resources/PCSX2.entitlements
notarize: true
# max_wait_seconds is only present on my fork located at F0bes/apple-code-sign-action@demo4
# If we are timing out we should switch to the newest upstream (if I get it upstreamed)
# or use my fork.
# max_wait_seconds: '2000'
staple: true
# Generated using rcodesign
# Despite what the docs say, I found that this file is required and I had 0 luck
# passing the issuer id, key, etc through arguments.
app_store_connect_api_key_json_file: 'key.json'
- name: Zip Build Artifacts
run: |
TAG="$(git tag --points-at HEAD)"
if [ -z "$TAG" ]; then
@@ -150,7 +188,7 @@ jobs:
else
APPNAME="PCSX2-$TAG"
fi
mv build/pcsx2*/PCSX2.app "$APPNAME.app"
mv PCSX2.app "$APPNAME.app"
tar --options xz:compression-level=9 -cvJf "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" "$APPNAME.app"
mkdir ci-artifacts
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS.tar.xz

View File

@@ -16,4 +16,5 @@ jobs:
with:
jobName: "MacOS Build"
artifactPrefixName: "PCSX2-macos-Qt"
sign_and_notarize: true # If we find that notarization takes a long time we should disable that on PR builds
secrets: inherit

View File

@@ -148,6 +148,7 @@ jobs:
artifactPrefixName: "PCSX2-macos-Qt"
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
sign_and_notarize: true
secrets: inherit
# Upload the Artifacts
@@ -204,7 +205,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 16
node-version: 22
- name: Announce Release
env:

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

@@ -63,9 +63,9 @@ declare -a REMOVE_LIBS=(
set -e
LINUXDEPLOY=./linuxdeploy-x86_64
LINUXDEPLOY_PLUGIN_QT=./linuxdeploy-plugin-qt-x86_64
APPIMAGETOOL=./appimagetool-x86_64
LINUXDEPLOY=./linuxdeploy-x86_64.AppImage
LINUXDEPLOY_PLUGIN_QT=./linuxdeploy-plugin-qt-x86_64.AppImage
APPIMAGETOOL=./appimagetool-x86_64.AppImage
PATCHELF=patchelf
if [ ! -f "$LINUXDEPLOY" ]; then
@@ -78,11 +78,8 @@ if [ ! -f "$LINUXDEPLOY_PLUGIN_QT" ]; then
chmod +x "$LINUXDEPLOY_PLUGIN_QT"
fi
# Using go-appimage
# Backported from https://github.com/stenzek/duckstation/pull/3251
if [ ! -f "$APPIMAGETOOL" ]; then
APPIMAGETOOLURL=$(wget -q https://api.github.com/repos/probonopd/go-appimage/releases -O - | sed 's/[()",{} ]/\n/g' | grep -o 'https.*continuous.*tool.*86_64.*mage$' | head -1)
"$PCSX2DIR/tools/retry.sh" wget -O "$APPIMAGETOOL" "$APPIMAGETOOLURL"
"$PCSX2DIR/tools/retry.sh" wget -O "$APPIMAGETOOL" https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x "$APPIMAGETOOL"
fi
@@ -190,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)
@@ -213,5 +199,4 @@ if [[ "${GIT_VERSION}" == "" ]]; then
fi
rm -f "$NAME.AppImage"
ARCH=x86_64 VERSION="${GIT_VERSION}" "$APPIMAGETOOL" -s "$OUTDIR" && mv ./*.AppImage "$NAME.AppImage"
$APPIMAGETOOL -v "$OUTDIR" "$NAME.AppImage"

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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -6,17 +6,21 @@
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<name>PCSX2</name>
<developer_name>PCSX2</developer_name>
<summary>PlayStation 2 Emulator</summary>
<developer id="net.pcsx2">
<name>PCSX2 Team</name>
</developer>
<summary>PlayStation 2 emulator</summary>
<description>
<p>PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a combination of MIPS CPU Interpreters, Recompilers, and a Virtual Machine which manages hardware states and PS2 system memory. This allows you to play PS2 games on your PC, with many additional features and benefits.</p>
<p>PlayStation 2 and PS2 are registered trademarks of Sony Interactive Entertainment. This application is not affiliated in any way with Sony Interactive Entertainment.</p>
</description>
<url type="homepage">https://pcsx2.net/</url>
<url type="vcs-browser">https://github.com/PCSX2/pcsx2</url>
<url type="bugtracker">https://github.com/PCSX2/pcsx2/issues</url>
<url type="donation">https://github.com/sponsors/PCSX2</url>
<url type="faq">https://pcsx2.net/docs/</url>
<url type="help">https://pcsx2.net/discord</url>
<url type="contribute">https://github.com/PCSX2/pcsx2/blob/master/.github/CONTRIBUTING.md</url>
<url type="translate">https://crowdin.com/project/pcsx2-emulator</url>
<url type="contact">https://mastodon.social/@PCSX2</url>
<screenshots>
@@ -37,6 +41,26 @@
</caption>
</screenshot>
</screenshots>
<categories>
<category>Game</category>
<category>Emulator</category>
</categories>
<branding>
<color type="primary" scheme_preference="light">#3584e4</color>
<color type="primary" scheme_preference="dark">#241f31</color>
</branding>
<supports>
<control>keyboard</control>
<control>pointing</control>
<internet>offline-only</internet>
</supports>
<recommends>
<control>gamepad</control>
<memory>8192</memory>
</recommends>
<requires>
<display_length compare="ge">768</display_length>
</requires>
<content_rating type="oars-1.1"/>
<update_contact>pcsx2_AT_pcsx2.net</update_contact>
<releases>

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
@@ -180,8 +183,8 @@ rm -fr "freetype-$FREETYPE"
tar xf "freetype-$FREETYPE.tar.xz"
cd "freetype-$FREETYPE"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DFT_REQUIRE_ZLIB=ON -DFT_REQUIRE_PNG=ON -DFT_DISABLE_BZIP2=TRUE -DFT_DISABLE_BROTLI=TRUE -DFT_DISABLE_HARFBUZZ=TRUE -B build
cmake --build build --parallel
cmake --install build
make -C build "-j$NPROCS"
make -C build install
cd ..
echo "Building HarfBuzz..."
@@ -189,8 +192,8 @@ rm -fr "harfbuzz-$HARFBUZZ"
tar xf "harfbuzz-$HARFBUZZ.tar.gz"
cd "harfbuzz-$HARFBUZZ"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DHB_BUILD_UTILS=OFF -B build
cmake --build build --parallel
cmake --install build
make -C build "-j$NPROCS"
make -C build install
cd ..
echo "Building FreeType with HarfBuzz..."
@@ -198,8 +201,8 @@ rm -fr "freetype-$FREETYPE"
tar xf "freetype-$FREETYPE.tar.xz"
cd "freetype-$FREETYPE"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DFT_REQUIRE_ZLIB=ON -DFT_REQUIRE_PNG=ON -DFT_DISABLE_BZIP2=TRUE -DFT_DISABLE_BROTLI=TRUE -DFT_REQUIRE_HARFBUZZ=TRUE -B build
cmake --build build --parallel
cmake --install build
make -C build "-j$NPROCS"
make -C build install
cd ..
# MoltenVK already builds universal binaries, nothing special to do here.
@@ -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
make -C build "-j$NPROCS"
make -C build install
cd ..
echo "Cleaning up..."
cd ..
rm -rf deps-build

View File

@@ -10,8 +10,8 @@
"license": "ISC",
"dependencies": {
"@octokit/plugin-retry": "^3.0.9",
"@octokit/plugin-throttling": "^3.5.2",
"@octokit/rest": "^18.12.0",
"@octokit/plugin-throttling": "^9.4.0",
"@octokit/rest": "^21.1.1",
"discord.js": "^13.2.0"
}
},
@@ -61,45 +61,102 @@
}
},
"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==",
"dependencies": {
"@octokit/types": "^6.0.3"
"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/core": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz",
"integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==",
"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": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.0",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
"@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/core/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/core/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/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"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": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
"@octokit/types": "^13.6.2",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint/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/endpoint/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/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"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": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
"@octokit/request": "^9.2.2",
"@octokit/types": "^13.8.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/graphql/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/graphql/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/openapi-types": {
@@ -108,28 +165,75 @@
"integrity": "sha512-dWZfYvCCdjZzDYA3lIAMF72Q0jld8xidqCq5Ryw09eBJXZdcM6he0vWBTvw/b5UnGYqexxOyHWgfrsTlUJL3Gw=="
},
"node_modules/@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==",
"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": "^6.33.0"
"@octokit/types": "^13.7.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-paginate-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/plugin-paginate-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/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==",
"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": ">=3"
"@octokit/core": ">=6"
}
},
"node_modules/@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==",
"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": "^6.33.0",
"deprecation": "^2.3.1"
"@octokit/types": "^13.8.0"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods/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/plugin-rest-endpoint-methods/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/plugin-retry": {
@@ -142,49 +246,107 @@
}
},
"node_modules/@octokit/plugin-throttling": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.7.0.tgz",
"integrity": "sha512-qrKT1Yl/KuwGSC6/oHpLBot3ooC9rq0/ryDYBCpkRtoj+R8T47xTMDT6Tk2CxWopFota/8Pi/2SqArqwC0JPow==",
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.4.0.tgz",
"integrity": "sha512-IOlXxXhZA4Z3m0EEYtrrACkuHiArHLZ3CvqWwOez/pURNqRuwfoFlTPbN5Muf28pzFuztxPyiUiNwz8KctdZaQ==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^6.0.1",
"@octokit/types": "^13.7.0",
"bottleneck": "^2.15.3"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@octokit/core": "^3.5.0"
"@octokit/core": "^6.1.3"
}
},
"node_modules/@octokit/plugin-throttling/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/plugin-throttling/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/request": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz",
"integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==",
"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": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.1",
"universal-user-agent": "^6.0.0"
"@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/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==",
"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": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
"@octokit/types": "^13.6.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request-error/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/request-error/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/request/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/request/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": {
"version": "18.12.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz",
"integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==",
"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": "^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"
"@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"
}
},
"node_modules/@octokit/types": {
@@ -234,9 +396,10 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"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=="
"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/bottleneck": {
"version": "2.19.5",
@@ -270,11 +433,6 @@
"node": ">=0.4.0"
}
},
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"node_modules/discord-api-types": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.23.1.tgz",
@@ -316,6 +474,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"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-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
@@ -324,14 +498,6 @@
"node": ">=8"
}
},
"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==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
@@ -375,14 +541,6 @@
}
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/ow": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
@@ -429,9 +587,10 @@
}
},
"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=="
"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/vali-date": {
"version": "1.0.0",
@@ -455,11 +614,6 @@
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
@@ -517,45 +671,86 @@
}
},
"@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": {
"@octokit/types": "^6.0.3"
}
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
"integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw=="
},
"@octokit/core": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz",
"integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz",
"integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==",
"requires": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.0",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
"@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"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"version": "10.1.3",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz",
"integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==",
"requires": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
"@octokit/types": "^13.6.2",
"universal-user-agent": "^7.0.2"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz",
"integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==",
"requires": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
"@octokit/request": "^9.2.2",
"@octokit/types": "^13.8.0",
"universal-user-agent": "^7.0.0"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@octokit/openapi-types": {
@@ -564,26 +759,55 @@
"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==",
"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==",
"requires": {
"@octokit/types": "^6.33.0"
"@octokit/types": "^13.7.0"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@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==",
"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==",
"requires": {}
},
"@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==",
"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==",
"requires": {
"@octokit/types": "^6.33.0",
"deprecation": "^2.3.1"
"@octokit/types": "^13.8.0"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@octokit/plugin-retry": {
@@ -596,46 +820,88 @@
}
},
"@octokit/plugin-throttling": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.7.0.tgz",
"integrity": "sha512-qrKT1Yl/KuwGSC6/oHpLBot3ooC9rq0/ryDYBCpkRtoj+R8T47xTMDT6Tk2CxWopFota/8Pi/2SqArqwC0JPow==",
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.4.0.tgz",
"integrity": "sha512-IOlXxXhZA4Z3m0EEYtrrACkuHiArHLZ3CvqWwOez/pURNqRuwfoFlTPbN5Muf28pzFuztxPyiUiNwz8KctdZaQ==",
"requires": {
"@octokit/types": "^6.0.1",
"@octokit/types": "^13.7.0",
"bottleneck": "^2.15.3"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@octokit/request": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz",
"integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==",
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz",
"integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.1",
"universal-user-agent": "^6.0.0"
"@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"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@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==",
"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==",
"requires": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
"@octokit/types": "^13.6.2"
},
"dependencies": {
"@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=="
},
"@octokit/types": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
"integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
"requires": {
"@octokit/openapi-types": "^23.0.1"
}
}
}
},
"@octokit/rest": {
"version": "18.12.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz",
"integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==",
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz",
"integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==",
"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"
"@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"
}
},
"@octokit/types": {
@@ -675,9 +941,9 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"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=="
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
},
"bottleneck": {
"version": "2.19.5",
@@ -702,11 +968,6 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"discord-api-types": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.23.1.tgz",
@@ -735,16 +996,16 @@
"is-obj": "^2.0.0"
}
},
"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=="
},
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
},
"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=="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
@@ -771,14 +1032,6 @@
"whatwg-url": "^5.0.0"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"ow": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz",
@@ -813,9 +1066,9 @@
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="
},
"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=="
"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=="
},
"vali-date": {
"version": "1.0.0",
@@ -836,11 +1089,6 @@
"webidl-conversions": "^3.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",

View File

@@ -11,8 +11,8 @@
"license": "ISC",
"dependencies": {
"@octokit/plugin-retry": "^3.0.9",
"@octokit/plugin-throttling": "^3.5.2",
"@octokit/rest": "^18.12.0",
"@octokit/plugin-throttling": "^9.4.0",
"@octokit/rest": "^21.1.1",
"discord.js": "^13.2.0"
}
}

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

@@ -3419,5 +3419,635 @@ https://www.gnu.org/licenses/why-not-lgpl.html.
</pre>
<h3>KDDockWidgets - <a href="https://github.com/KDAB/KDDockWidgets">https://github.com/KDAB/KDDockWidgets</a></h3>
<pre>
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and
other kinds of works.
The licenses for most software and other practical works are designed to take
away your freedom to share and change the works. By contrast, the GNU General
Public License is intended to guarantee your freedom to share and change all
versions of a program--to make sure it remains free software for all its users.
We, the Free Software Foundation, use the GNU General Public License for most
of our software; it applies also to any other work released this way by its
authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for them if you wish), that
you receive source code or can get it if you want it, that you can change
the software or use pieces of it in new free programs, and that you know you
can do these things.
To protect your rights, we need to prevent others from denying you these rights
or asking you to surrender the rights. Therefore, you have certain responsibilities
if you distribute copies of the software, or if you modify it: responsibilities
to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or
for a fee, you must pass on to the recipients the same freedoms that you received.
You must make sure that they, too, receive or can get the source code. And
you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert
copyright on the software, and (2) offer you this License giving you legal
permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that
there is no warranty for this free software. For both users' and authors'
sake, the GPL requires that modified versions be marked as changed, so that
their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified
versions of the software inside them, although the manufacturer can do so.
This is fundamentally incompatible with the aim of protecting users' freedom
to change the software. The systematic pattern of such abuse occurs in the
area of products for individuals to use, which is precisely where it is most
unacceptable. Therefore, we have designed this version of the GPL to prohibit
the practice for those products. If such problems arise substantially in other
domains, we stand ready to extend this provision to those domains in future
versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States
should not allow patents to restrict development and use of software on general-purpose
computers, but in those that do, we wish to avoid the special danger that
patents applied to a free program could make it effectively proprietary. To
prevent this, the GPL assures that patents cannot be used to render the program
non-free.
The precise terms and conditions for copying, distribution and modification
follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of works,
such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this License.
Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals
or organizations.
To "modify" a work means to copy from or adapt all or part of the work in
a fashion requiring copyright permission, other than the making of an exact
copy. The resulting work is called a "modified version" of the earlier work
or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based on the
Program.
To "propagate" a work means to do anything with it that, without permission,
would make you directly or secondarily liable for infringement under applicable
copyright law, except executing it on a computer or modifying a private copy.
Propagation includes copying, distribution (with or without modification),
making available to the public, and in some countries other activities as
well.
To "convey" a work means any kind of propagation that enables other parties
to make or receive copies. Mere interaction with a user through a computer
network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices" to the
extent that it includes a convenient and prominently visible feature that
(1) displays an appropriate copyright notice, and (2) tells the user that
there is no warranty for the work (except to the extent that warranties are
provided), that licensees may convey the work under this License, and how
to view a copy of this License. If the interface presents a list of user commands
or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work for making
modifications to it. "Object code" means any non-source form of a work.
A "Standard Interface" means an interface that either is an official standard
defined by a recognized standards body, or, in the case of interfaces specified
for a particular programming language, one that is widely used among developers
working in that language.
The "System Libraries" of an executable work include anything, other than
the work as a whole, that (a) is included in the normal form of packaging
a Major Component, but which is not part of that Major Component, and (b)
serves only to enable use of the work with that Major Component, or to implement
a Standard Interface for which an implementation is available to the public
in source code form. A "Major Component", in this context, means a major essential
component (kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to produce
the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all the source
code needed to generate, install, and (for an executable work) run the object
code and to modify the work, including scripts to control those activities.
However, it does not include the work's System Libraries, or general-purpose
tools or generally available free programs which are used unmodified in performing
those activities but which are not part of the work. For example, Corresponding
Source includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically linked
subprograms that the work is specifically designed to require, such as by
intimate data communication or control flow between those subprograms and
other parts of the work.
The Corresponding Source need not include anything that users can regenerate
automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright
on the Program, and are irrevocable provided the stated conditions are met.
This License explicitly affirms your unlimited permission to run the unmodified
Program. The output from running a covered work is covered by this License
only if the output, given its content, constitutes a covered work. This License
acknowledges your rights of fair use or other equivalent, as provided by copyright
law.
You may make, run and propagate covered works that you do not convey, without
conditions so long as your license otherwise remains in force. You may convey
covered works to others for the sole purpose of having them make modifications
exclusively for you, or provide you with facilities for running those works,
provided that you comply with the terms of this License in conveying all material
for which you do not control copyright. Those thus making or running the covered
works for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of your copyrighted
material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions
stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure
under any applicable law fulfilling obligations under article 11 of the WIPO
copyright treaty adopted on 20 December 1996, or similar laws prohibiting
or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention
of technological measures to the extent such circumvention is effected by
exercising rights under this License with respect to the covered work, and
you disclaim any intention to limit operation or modification of the work
as a means of enforcing, against the work's users, your or third parties'
legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive
it, in any medium, provided that you conspicuously and appropriately publish
on each copy an appropriate copyright notice; keep intact all notices stating
that this License and any non-permissive terms added in accord with section
7 apply to the code; keep intact all notices of the absence of any warranty;
and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you
may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce
it from the Program, in the form of source code under the terms of section
4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and
giving a relevant date.
b) The work must carry prominent notices stating that it is released under
this License and any conditions added under section 7. This requirement modifies
the requirement in section 4 to "keep intact all notices".
c) You must license the entire work, as a whole, under this License to anyone
who comes into possession of a copy. This License will therefore apply, along
with any applicable section 7 additional terms, to the whole of the work,
and all its parts, regardless of how they are packaged. This License gives
no permission to license the work in any other way, but it does not invalidate
such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate
Legal Notices; however, if the Program has interactive interfaces that do
not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works,
which are not by their nature extensions of the covered work, and which are
not combined with it such as to form a larger program, in or on a volume of
a storage or distribution medium, is called an "aggregate" if the compilation
and its resulting copyright are not used to limit the access or legal rights
of the compilation's users beyond what the individual works permit. Inclusion
of a covered work in an aggregate does not cause this License to apply to
the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections
4 and 5, provided that you also convey the machine-readable Corresponding
Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by the Corresponding Source fixed
on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including
a physical distribution medium), accompanied by a written offer, valid for
at least three years and valid for as long as you offer spare parts or customer
support for that product model, to give anyone who possesses the object code
either (1) a copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical medium customarily
used for software interchange, for a price no more than your reasonable cost
of physically performing this conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written
offer to provide the Corresponding Source. This alternative is allowed only
occasionally and noncommercially, and only if you received the object code
with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis
or for a charge), and offer equivalent access to the Corresponding Source
in the same way through the same place at no further charge. You need not
require recipients to copy the Corresponding Source along with the object
code. If the place to copy the object code is a network server, the Corresponding
Source may be on a different server (operated by you or a third party) that
supports equivalent copying facilities, provided you maintain clear directions
next to the object code saying where to find the Corresponding Source. Regardless
of what server hosts the Corresponding Source, you remain obligated to ensure
that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform
other peers where the object code and Corresponding Source of the work are
being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from
the Corresponding Source as a System Library, need not be included in conveying
the object code work.
A "User Product" is either (1) a "consumer product", which means any tangible
personal property which is normally used for personal, family, or household
purposes, or (2) anything designed or sold for incorporation into a dwelling.
In determining whether a product is a consumer product, doubtful cases shall
be resolved in favor of coverage. For a particular product received by a particular
user, "normally used" refers to a typical or common use of that class of product,
regardless of the status of the particular user or of the way in which the
particular user actually uses, or expects or is expected to use, the product.
A product is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent the
only significant mode of use of the product.
"Installation Information" for a User Product means any methods, procedures,
authorization keys, or other information required to install and execute modified
versions of a covered work in that User Product from a modified version of
its Corresponding Source. The information must suffice to ensure that the
continued functioning of the modified object code is in no case prevented
or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically
for use in, a User Product, and the conveying occurs as part of a transaction
in which the right of possession and use of the User Product is transferred
to the recipient in perpetuity or for a fixed term (regardless of how the
transaction is characterized), the Corresponding Source conveyed under this
section must be accompanied by the Installation Information. But this requirement
does not apply if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has been installed
in ROM).
The requirement to provide Installation Information does not include a requirement
to continue to provide support service, warranty, or updates for a work that
has been modified or installed by the recipient, or for the User Product in
which it has been modified or installed. Access to a network may be denied
when the modification itself materially and adversely affects the operation
of the network or violates the rules and protocols for communication across
the network.
Corresponding Source conveyed, and Installation Information provided, in accord
with this section must be in a format that is publicly documented (and with
an implementation available to the public in source code form), and must require
no special password or key for unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this License
by making exceptions from one or more of its conditions. Additional permissions
that are applicable to the entire Program shall be treated as though they
were included in this License, to the extent that they are valid under applicable
law. If additional permissions apply only to part of the Program, that part
may be used separately under those permissions, but the entire Program remains
governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any
additional permissions from that copy, or from any part of it. (Additional
permissions may be written to require their own removal in certain cases when
you modify the work.) You may place additional permissions on material, added
by you to a covered work, for which you have or can give appropriate copyright
permission.
Notwithstanding any other provision of this License, for material you add
to a covered work, you may (if authorized by the copyright holders of that
material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of
sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author
attributions in that material or in the Appropriate Legal Notices displayed
by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring
that modified versions of such material be marked in reasonable ways as different
from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors
of the material; or
e) Declining to grant rights under trademark law for use of some trade names,
trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by
anyone who conveys the material (or modified versions of it) with contractual
assumptions of liability to the recipient, for any liability that these contractual
assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered "further restrictions"
within the meaning of section 10. If the Program as you received it, or any
part of it, contains a notice stating that it is governed by this License
along with a term that is a further restriction, you may remove that term.
If a license document contains a further restriction but permits relicensing
or conveying under this License, you may add to a covered work material governed
by the terms of that license document, provided that the further restriction
does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place,
in the relevant source files, a statement of the additional terms that apply
to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form
of a separately written license, or stated as exceptions; the above requirements
apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided
under this License. Any attempt otherwise to propagate or modify it is void,
and will automatically terminate your rights under this License (including
any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from
a particular copyright holder is reinstated (a) provisionally, unless and
until the copyright holder explicitly and finally terminates your license,
and (b) permanently, if the copyright holder fails to notify you of the violation
by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently
if the copyright holder notifies you of the violation by some reasonable means,
this is the first time you have received notice of violation of this License
(for any work) from that copyright holder, and you cure the violation prior
to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses
of parties who have received copies or rights from you under this License.
If your rights have been terminated and not permanently reinstated, you do
not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy
of the Program. Ancillary propagation of a covered work occurring solely as
a consequence of using peer-to-peer transmission to receive a copy likewise
does not require acceptance. However, nothing other than this License grants
you permission to propagate or modify any covered work. These actions infringe
copyright if you do not accept this License. Therefore, by modifying or propagating
a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives
a license from the original licensors, to run, modify and propagate that work,
subject to this License. You are not responsible for enforcing compliance
by third parties with this License.
An "entity transaction" is a transaction transferring control of an organization,
or substantially all assets of one, or subdividing an organization, or merging
organizations. If propagation of a covered work results from an entity transaction,
each party to that transaction who receives a copy of the work also receives
whatever licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the Corresponding
Source of the work from the predecessor in interest, if the predecessor has
it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights
granted or affirmed under this License. For example, you may not impose a
license fee, royalty, or other charge for exercise of rights granted under
this License, and you may not initiate litigation (including a cross-claim
or counterclaim in a lawsuit) alleging that any patent claim is infringed
by making, using, selling, offering for sale, or importing the Program or
any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this License
of the Program or a work on which the Program is based. The work thus licensed
is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned or controlled
by the contributor, whether already acquired or hereafter acquired, that would
be infringed by some manner, permitted by this License, of making, using,
or selling its contributor version, but do not include claims that would be
infringed only as a consequence of further modification of the contributor
version. For purposes of this definition, "control" includes the right to
grant patent sublicenses in a manner consistent with the requirements of this
License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent
license under the contributor's essential patent claims, to make, use, sell,
offer for sale, import and otherwise run, modify and propagate the contents
of its contributor version.
In the following three paragraphs, a "patent license" is any express agreement
or commitment, however denominated, not to enforce a patent (such as an express
permission to practice a patent or covenant not to sue for patent infringement).
To "grant" such a patent license to a party means to make such an agreement
or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the
Corresponding Source of the work is not available for anyone to copy, free
of charge and under the terms of this License, through a publicly available
network server or other readily accessible means, then you must either (1)
cause the Corresponding Source to be so available, or (2) arrange to deprive
yourself of the benefit of the patent license for this particular work, or
(3) arrange, in a manner consistent with the requirements of this License,
to extend the patent license to downstream recipients. "Knowingly relying"
means you have actual knowledge that, but for the patent license, your conveying
the covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that country
that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement,
you convey, or propagate by procuring conveyance of, a covered work, and grant
a patent license to some of the parties receiving the covered work authorizing
them to use, propagate, modify or convey a specific copy of the covered work,
then the patent license you grant is automatically extended to all recipients
of the covered work and works based on it.
A patent license is "discriminatory" if it does not include within the scope
of its coverage, prohibits the exercise of, or is conditioned on the non-exercise
of one or more of the rights that are specifically granted under this License.
You may not convey a covered work if you are a party to an arrangement with
a third party that is in the business of distributing software, under which
you make payment to the third party based on the extent of your activity of
conveying the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by you
(or copies made from those copies), or (b) primarily for and in connection
with specific products or compilations that contain the covered work, unless
you entered into that arrangement, or that patent license was granted, prior
to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied
license or other defenses to infringement that may otherwise be available
to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise)
that contradict the conditions of this License, they do not excuse you from
the conditions of this License. If you cannot convey a covered work so as
to satisfy simultaneously your obligations under this License and any other
pertinent obligations, then as a consequence you may not convey it at all.
For example, if you agree to terms that obligate you to collect a royalty
for further conveying from those to whom you convey the Program, the only
way you could satisfy both those terms and this License would be to refrain
entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to
link or combine any covered work with a work licensed under version 3 of the
GNU Affero General Public License into a single combined work, and to convey
the resulting work. The terms of this License will continue to apply to the
part which is the covered work, but the special requirements of the GNU Affero
General Public License, section 13, concerning interaction through a network
will apply to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the
GNU General Public License from time to time. Such new versions will be similar
in spirit to the present version, but may differ in detail to address new
problems or concerns.
Each version is given a distinguishing version number. If the Program specifies
that a certain numbered version of the GNU General Public License "or any
later version" applies to it, you have the option of following the terms and
conditions either of that numbered version or of any later version published
by the Free Software Foundation. If the Program does not specify a version
number of the GNU General Public License, you may choose any version ever
published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of
the GNU General Public License can be used, that proxy's public statement
of acceptance of a version permanently authorizes you to choose that version
for the Program.
Later license versions may give you additional or different permissions. However,
no additional obligations are imposed on any author or copyright holder as
a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM
PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM
AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO
USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot
be given local legal effect according to their terms, reviewing courts shall
apply local law that most closely approximates an absolute waiver of all civil
liability in connection with the Program, unless a warranty or assumption
of liability accompanies a copy of the Program in return for a fee. END OF
TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively state the exclusion
of warranty; and each file should have at least the "copyright" line and a
pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like
this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain
conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands might
be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. For
more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General Public
License instead of this License. But first, please read <https://www.gnu.org/
licenses /why-not-lgpl.html>.
</pre>
</body>
</html>

File diff suppressed because it is too large Load Diff

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,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -111,7 +111,7 @@ layout(binding = 3) uniform sampler2D img_prim_min;
//layout(pixel_center_integer) in vec4 gl_FragCoord;
#endif
vec4 fetch_rt()
vec4 sample_from_rt()
{
#if !NEEDS_RT
return vec4(0.0);
@@ -127,7 +127,7 @@ vec4 fetch_rt()
vec4 sample_c(vec2 uv)
{
#if PS_TEX_IS_FB == 1
return fetch_rt();
return sample_from_rt();
#elif PS_REGION_RECT
return texelFetch(TextureSampler, ivec2(uv), 0);
#else
@@ -312,7 +312,7 @@ int fetch_raw_depth()
float multiplier = exp2(32.0f);
#if PS_TEX_IS_FB == 1
return int(fetch_rt().r * multiplier);
return int(sample_from_rt().r * multiplier);
#else
return int(texelFetch(TextureSampler, ivec2(gl_FragCoord.xy), 0).r * multiplier);
#endif
@@ -321,7 +321,7 @@ int fetch_raw_depth()
vec4 fetch_raw_color()
{
#if PS_TEX_IS_FB == 1
return fetch_rt();
return sample_from_rt();
#else
return texelFetch(TextureSampler, ivec2(gl_FragCoord.xy), 0);
#endif
@@ -697,8 +697,6 @@ vec4 ps_color()
vec4 C = tfx(T, PSin.c);
atst(C);
fog(C, PSin.t_float.z);
return C;
@@ -709,9 +707,9 @@ void ps_fbmask(inout vec4 C)
// FIXME do I need special case for 16 bits
#if PS_FBMASK
#if PS_HDR == 1
vec4 RT = trunc(fetch_rt() * 65535.0f);
vec4 RT = trunc(sample_from_rt() * 65535.0f);
#else
vec4 RT = trunc(fetch_rt() * 255.0f + 0.1f);
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask));
#endif
@@ -799,7 +797,7 @@ float As = As_rgba.a;
#endif
#if SW_BLEND_NEEDS_RT
vec4 RT = fetch_rt();
vec4 RT = sample_from_rt();
#else
// Not used, but we define it to make the selection below simpler.
vec4 RT = vec4(0.0f);
@@ -974,9 +972,9 @@ void ps_main()
#if PS_WRITE_RG == 1
// Pseudo 16 bits access.
float rt_a = fetch_rt().g;
float rt_a = sample_from_rt().g;
#else
float rt_a = fetch_rt().a;
float rt_a = sample_from_rt().a;
#endif
#if (PS_DATE & 3) == 1
@@ -1028,9 +1026,9 @@ void ps_main()
#if SW_AD_TO_HW
#if PS_RTA_CORRECTION
vec4 RT = trunc(fetch_rt() * 128.0f + 0.1f);
vec4 RT = trunc(sample_from_rt() * 128.0f + 0.1f);
#else
vec4 RT = trunc(fetch_rt() * 255.0f + 0.1f);
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
vec4 alpha_blend = vec4(RT.a / 128.0f);

View File

@@ -954,7 +954,7 @@ vec4 ps_color()
T.a = float(denorm_c_before.a & 0x80u);
#else
T.r = float((denorm_c_before.r << 3) & 0xF8u);
T.g = float(((denorm_c_before.r >> 2) & 0x38) | ((denorm_c_before.g << 6) & 0xC0u));
T.g = float(((denorm_c_before.r >> 2) & 0x38u) | ((denorm_c_before.g << 6) & 0xC0u));
T.b = float((denorm_c_before.g << 1) & 0xF8u);
T.a = float(denorm_c_before.g & 0x80u);
#endif

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

@@ -168,6 +168,7 @@ else()
${DBUS_LINK_LIBRARIES}
X11::X11
X11::Xrandr
X11::Xi
)
if(USE_BACKTRACE)
target_compile_definitions(common PRIVATE "HAS_LIBBACKTRACE=1")

View File

@@ -27,6 +27,7 @@
#include <mach/task.h>
#include <mach/vm_map.h>
#include <mutex>
#include <ApplicationServices/ApplicationServices.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
// Darwin (OSX) is a bit different from Linux when requesting properties of
@@ -127,6 +128,69 @@ bool Common::InhibitScreensaver(bool inhibit)
return true;
}
void Common::SetMousePosition(int x, int y)
{
// Little bit ugly but;
// Creating mouse move events and posting them wasn't very reliable.
// Calling CGWarpMouseCursorPosition without CGAssociateMouseAndMouseCursorPosition(false)
// ends up with the cursor feeling "sticky".
CGAssociateMouseAndMouseCursorPosition(false);
CGWarpMouseCursorPosition(CGPointMake(x, y));
CGAssociateMouseAndMouseCursorPosition(true); // The default state
return;
}
CFMachPortRef mouseEventTap = nullptr;
CFRunLoopSourceRef mouseRunLoopSource = nullptr;
static std::function<void(int, int)> fnMouseMoveCb;
CGEventRef mouseMoveCallback(CGEventTapProxy, CGEventType type, CGEventRef event, void* arg)
{
if (type == kCGEventMouseMoved)
{
const CGPoint location = CGEventGetLocation(event);
fnMouseMoveCb(location.x, location.y);
}
return event;
}
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
{
if (!AXIsProcessTrusted())
{
Console.Warning("Process isn't trusted with accessibility permissions. Mouse tracking will not work!");
}
fnMouseMoveCb = cb;
mouseEventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventMouseMoved), mouseMoveCallback, nullptr);
if (!mouseEventTap)
{
Console.Warning("Unable to create mouse moved event tap. Mouse tracking will not work!");
return false;
}
mouseRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mouseEventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), mouseRunLoopSource, kCFRunLoopCommonModes);
return true;
}
void Common::DetachMousePositionCb()
{
if (mouseRunLoopSource)
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mouseRunLoopSource, kCFRunLoopCommonModes);
CFRelease(mouseRunLoopSource);
}
if (mouseEventTap)
{
CFRelease(mouseEventTap);
}
mouseRunLoopSource = nullptr;
mouseEventTap = nullptr;
}
void Threading::Sleep(int ms)
{
usleep(1000 * ms);

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

@@ -6,6 +6,7 @@
#include "common/Pcsx2Defs.h"
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <string>
@@ -198,4 +199,8 @@ namespace Common
/// Abstracts platform-specific code for asynchronously playing a sound.
/// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound.
bool PlaySoundAsync(const char* path);
void SetMousePosition(int x, int y);
bool AttachMousePositionCb(std::function<void(int,int)> cb);
void DetachMousePositionCb();
} // namespace Common

View File

@@ -13,17 +13,20 @@
#include "fmt/format.h"
#include <ctype.h>
#include <time.h>
#include <unistd.h>
#include <optional>
#include <dbus/dbus.h>
#include <spawn.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <dbus/dbus.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <optional>
#include <thread>
// Returns 0 on failure (not supported by the operating system).
u64 GetPhysicalMemory()
@@ -177,6 +180,111 @@ bool Common::InhibitScreensaver(bool inhibit)
return SetScreensaverInhibitDBus(inhibit, "PCSX2", "PCSX2 VM is running.");
}
void Common::SetMousePosition(int x, int y)
{
Display* display = XOpenDisplay(nullptr);
if (!display)
return;
Window root = DefaultRootWindow(display);
XWarpPointer(display, None, root, 0, 0, 0, 0, x, y);
XFlush(display);
XCloseDisplay(display);
}
static std::function<void(int, int)> fnMouseMoveCb;
static std::atomic<bool> trackingMouse = false;
static std::thread mouseThread;
void mouseEventLoop()
{
Threading::SetNameOfCurrentThread("X11 Mouse Thread");
Display* display = XOpenDisplay(nullptr);
if (!display)
{
return;
}
int opcode, eventcode, error;
if (!XQueryExtension(display, "XInputExtension", &opcode, &eventcode, &error))
{
XCloseDisplay(display);
return;
}
const Window root = DefaultRootWindow(display);
XIEventMask evmask;
unsigned char mask[(XI_LASTEVENT + 7) / 8] = {0};
evmask.deviceid = XIAllDevices;
evmask.mask_len = sizeof(mask);
evmask.mask = mask;
XISetMask(mask, XI_RawMotion);
XISelectEvents(display, root, &evmask, 1);
XSync(display, False);
XEvent event;
while (trackingMouse)
{
// XNextEvent is blocking, this is a zombie process risk if no events arrive
// while we are trying to shutdown.
// https://nrk.neocities.org/articles/x11-timeout-with-xsyncalarm might be
// a better solution than using XPending.
if (!XPending(display))
{
Threading::Sleep(1);
Threading::SpinWait();
continue;
}
XNextEvent(display, &event);
if (event.xcookie.type == GenericEvent &&
event.xcookie.extension == opcode &&
XGetEventData(display, &event.xcookie))
{
XIRawEvent* raw_event = reinterpret_cast<XIRawEvent*>(event.xcookie.data);
if (raw_event->evtype == XI_RawMotion)
{
Window w;
int root_x, root_y, win_x, win_y;
unsigned int mask;
XQueryPointer(display, root, &w, &w, &root_x, &root_y, &win_x, &win_y, &mask);
if (fnMouseMoveCb)
fnMouseMoveCb(root_x, root_y);
}
XFreeEventData(display, &event.xcookie);
}
}
XCloseDisplay(display);
}
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
{
fnMouseMoveCb = cb;
if (trackingMouse)
return true;
trackingMouse = true;
mouseThread = std::thread(mouseEventLoop);
mouseThread.detach();
return true;
}
void Common::DetachMousePositionCb()
{
trackingMouse = false;
fnMouseMoveCb = nullptr;
if (mouseThread.joinable())
{
mouseThread.join();
}
}
bool Common::PlaySoundAsync(const char* path)
{
#ifdef __linux__

View File

@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/HostSys.h"
#include "common/RedtapeWindows.h"
@@ -86,6 +87,61 @@ bool Common::InhibitScreensaver(bool inhibit)
return true;
}
void Common::SetMousePosition(int x, int y)
{
SetCursorPos(x, y);
}
/*
static HHOOK mouseHook = nullptr;
static std::function<void(int, int)> fnMouseMoveCb;
LRESULT CALLBACK Mousecb(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0 && wParam == WM_MOUSEMOVE)
{
MSLLHOOKSTRUCT* mouse = (MSLLHOOKSTRUCT*)lParam;
fnMouseMoveCb(mouse->pt.x, mouse->pt.y);
}
return CallNextHookEx(mouseHook, nCode, wParam, lParam);
}
*/
// This (and the above) works, but is not recommended on Windows and is only here for consistency.
// Defer to using raw input instead.
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
{
/*
if (mouseHook)
Common::DetachMousePositionCb();
fnMouseMoveCb = cb;
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, Mousecb, GetModuleHandle(NULL), 0);
if (!mouseHook)
{
Console.Warning("Failed to set mouse hook: %d", GetLastError());
return false;
}
#if defined(PCSX2_DEBUG) || defined(PCSX2_DEVBUILD)
static bool warned = false;
if (!warned)
{
Console.Warning("Mouse hooks are enabled, and this isn't a release build! Using a debugger, or loading symbols, _will_ stall the hook and cause global mouse lag.");
warned = true;
}
#endif
*/
return true;
}
void Common::DetachMousePositionCb()
{
/*
UnhookWindowsHookEx(mouseHook);
mouseHook = nullptr;
*/
}
bool Common::PlaySoundAsync(const char* path)
{
const std::wstring wpath = FileSystem::GetWin32Path(path);

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())
@@ -246,7 +260,7 @@ void Host::BeginPresentFrame()
GSJoinSnapshotThreads();
// queue dumping of this frame
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
std::string dump_path(fmt::format("{}_frame{:05}.png", s_output_prefix, s_dump_frame_number));
GSQueueSnapshot(dump_path);
}
@@ -443,8 +457,17 @@ static void PrintCommandLineHelp(const char* progname)
std::fprintf(stderr, " -help: Displays this information and exits.\n");
std::fprintf(stderr, " -version: Displays version information and exits.\n");
std::fprintf(stderr, " -dumpdir <dir>: Frame dump directory (will be dumped as filename_frameN.png).\n");
std::fprintf(stderr, " -dump [rt|tex|z|f|a|i]: Enabling dumping of render target, texture, z buffer, frame, "
"alphas, and info (context, vertices), respectively, per draw. Generates lots of data.\n");
std::fprintf(stderr, " -dumprange N[,L,B]: Start dumping from draw N (base 0), stops after L draws, and only "
"those draws that are multiples of B (intersection of -dumprange and -dumprangef used)."
"Defaults to 0,-1,1 (all draws). Only used if -dump used.\n");
std::fprintf(stderr, " -dumprangef NF[,LF,BF]: Start dumping from frame NF (base 0), stops after LF frames, "
"and only those frames that are multiples of BF (intersection of -dumprange and -dumprangef used).\n"
"Defaults to 0,-1,1 (all frames). Only used if -dump is used.\n");
std::fprintf(stderr, " -loop <count>: Loops dump playback N times. Defaults to 1. 0 will loop infinitely.\n");
std::fprintf(stderr, " -renderer <renderer>: Sets the graphics renderer. Defaults to Auto.\n");
std::fprintf(stderr, " -swthreads <threads>: Sets the number of threads for the software renderer.\n");
std::fprintf(stderr, " -window: Forces a window to be displayed.\n");
std::fprintf(stderr, " -surfaceless: Disables showing a window.\n");
std::fprintf(stderr, " -logfile <filename>: Writes emu log to filename.\n");
@@ -465,6 +488,7 @@ void GSRunner::InitializeConsole()
bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& params)
{
std::string dumpdir; // Save from argument -dumpdir for creating sub-directories
bool no_more_args = false;
for (int i = 1; i < argc; i++)
{
@@ -485,7 +509,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
}
else if (CHECK_ARG_PARAM("-dumpdir"))
{
s_output_prefix = StringUtil::StripWhitespace(argv[++i]);
dumpdir = s_output_prefix = StringUtil::StripWhitespace(argv[++i]);
if (s_output_prefix.empty())
{
Console.Error("Invalid dump directory specified.");
@@ -500,6 +524,86 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
continue;
}
else if (CHECK_ARG_PARAM("-dump"))
{
std::string str(argv[++i]);
s_settings_interface.SetBoolValue("EmuCore/GS", "dump", true);
if (str.find("rt") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveRT", true);
if (str.find("f") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveFrame", true);
if (str.find("tex") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveTexture", true);
if (str.find("z") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveDepth", true);
if (str.find("a") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveAlpha", true);
if (str.find("i") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveInfo", true);
continue;
}
else if (CHECK_ARG_PARAM("-dumprange"))
{
std::string str(argv[++i]);
std::vector<std::string_view> split = StringUtil::SplitString(str, ',');
int start = 0;
int num = -1;
int by = 1;
if (split.size() > 0)
{
start = StringUtil::FromChars<int>(split[0]).value_or(0);
}
if (split.size() > 1)
{
num = StringUtil::FromChars<int>(split[1]).value_or(-1);
}
if (split.size() > 2)
{
by = std::max(1, StringUtil::FromChars<int>(split[2]).value_or(1));
}
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"))
{
std::string str(argv[++i]);
std::vector<std::string_view> split = StringUtil::SplitString(str, ',');
int start = 0;
int num = -1;
int by = 1;
if (split.size() > 0)
{
start = StringUtil::FromChars<int>(split[0]).value_or(0);
}
if (split.size() > 1)
{
num = StringUtil::FromChars<int>(split[1]).value_or(-1);
}
if (split.size() > 2)
{
by = std::max(1, StringUtil::FromChars<int>(split[2]).value_or(1));
}
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"))
{
s_settings_interface.SetStringValue("EmuCore/GS", "HWDumpDirectory", argv[++i]);
continue;
}
else if (CHECK_ARG_PARAM("-dumpdirsw"))
{
s_settings_interface.SetStringValue("EmuCore/GS", "SWDumpDirectory", argv[++i]);
continue;
}
else if (CHECK_ARG_PARAM("-loop"))
{
s_loop_count = StringUtil::FromChars<s32>(argv[++i]).value_or(0);
@@ -543,6 +647,19 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", static_cast<int>(type));
continue;
}
else if (CHECK_ARG_PARAM("-swthreads"))
{
const int swthreads = StringUtil::FromChars<int>(argv[++i]).value_or(0);
if (swthreads < 0)
{
Console.WriteLn("Invalid number of software threads");
return false;
}
Console.WriteLn(fmt::format("Setting number of software threads to {}", swthreads));
s_settings_interface.SetIntValue("EmuCore/GS", "SWExtraThreads", swthreads);
continue;
}
else if (CHECK_ARG_PARAM("-renderhacks"))
{
std::string str(argv[++i]);
@@ -643,6 +760,18 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
return false;
}
if (s_settings_interface.GetBoolValue("EmuCore/GS", "dump") && !dumpdir.empty())
{
if (s_settings_interface.GetStringValue("EmuCore/GS", "HWDumpDirectory").empty())
s_settings_interface.SetStringValue("EmuCore/GS", "HWDumpDirectory", dumpdir.c_str());
if (s_settings_interface.GetStringValue("EmuCore/GS", "SWDumpDirectory").empty())
s_settings_interface.SetStringValue("EmuCore/GS", "SWDumpDirectory", dumpdir.c_str());
// Disable saving frames with SaveSnapshotToMemory()
// Instead we save more "raw" snapshots when using -dump.
s_output_prefix = "";
}
// set up the frame dump directory
if (!s_output_prefix.empty())
{

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

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

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