Compare commits

...
210 Commits
Author SHA1 Message Date
Stenzekandrefractionpcsx2 bb60058fa0 Achievements: Handle fake memory map 2024-01-30 15:26:40 +00:00
Stenzekandrefractionpcsx2 1ad25605d1 GS: Check for D3D Mapping Layers and disable Vulkan if present 2024-01-30 15:26:21 +00:00
Stenzek 9809265be4 GS/Vulkan: Fix potential race between submit and main thread
Backport of https://github.com/stenzek/duckstation/commit/bcf7f55b93d6c61a66c1aabec5b8568d10a5b26c
2024-01-29 12:40:06 +10:00
Stenzek 63872e6b28 GS/Capture: Stop capture on VM shutdown 2024-01-28 21:23:05 +10:00
Stenzek fb3a9eae9b ImGuiOverlays: Use white font for recording time 2024-01-28 21:23:05 +10:00
Stenzek f53c3050df GS/Capture: Show video timestamp instead of wall time 2024-01-28 21:23:05 +10:00
PCSX2 BotandConnor McLaughlin 67bd0dfcdb Qt: Update Base Translation 2024-01-28 21:12:06 +10:00
Stenzek 12196359f7 Qt: Deprecate per-game WS/NI toggles in favor of Patches 2024-01-28 21:09:53 +10:00
Stenzek 609165e412 USB: Fix buffer copies in EyeToy 2024-01-28 17:04:24 +10:00
lightningterror 41f14a8cf8 GameDB: Add mipmap full with ps2 trilinear to SWAT Global Strike Team.
Improves building textures to match sw renderer.
2024-01-28 02:36:46 +01:00
lightningterror a8bdd3fbfc GameDB: Add gshwfixes to Band Hero.
Fixes character shadow misalignment.
Fixes icons.
2024-01-28 00:27:25 +01:00
refractionpcsx2 993ec82a9e GSDumpReplayer: Fix UI not responded to messages when replaying dumps 2024-01-27 16:38:54 +00:00
Stenzek c5a2844367 USB: Fix possible buffer overflow in webcam 2024-01-27 21:26:04 +10:00
Stenzek e5bb405b47 USB: Don't reset configuration when saving state 2024-01-27 21:26:04 +10:00
Stenzek 8c9a65d094 USB: Audio devices don't need port numbers 2024-01-27 21:26:04 +10:00
Stenzek 047b8593ac USB: Fix incorrect config keys for usb-headset 2024-01-27 21:26:04 +10:00
Stenzek 4fc4eb8a66 USB: Rewrite RingBuffer class
It was overflowing and corrupting the heap...
2024-01-27 21:26:04 +10:00
PCSX2 BotandConnor McLaughlin 1f95a86f0a Qt: Update Base Translation 2024-01-27 13:32:21 +10:00
lightningterrorandConnor McLaughlin 2b90451c4a Qt: Fix more compiler warnings.
-Wsign-compare, -Wunused-variable.
2024-01-27 13:30:45 +10:00
Stenzek 77a6525556 Counters: Move input poll to after throttle 2024-01-27 13:29:55 +10:00
refractionpcsx2 5eacab387d GameDB: Add Merge Targets to Shadow Hearts 2024-01-26 12:17:52 +00:00
refractionpcsx2 ddaf57d5b0 GS/HW: Kill old source using target if rect is outside target surface 2024-01-26 12:17:52 +00:00
Stenzek 1144c46109 ImGuiOverlays: Fix OSD margin 2024-01-26 19:47:50 +10:00
Stenzek 850b839fc3 Qt: Make "Ignore Inversion" a global mapping setting
Instead of being specific to DInput.
2024-01-26 18:14:08 +10:00
PCSX2 BotandConnor McLaughlin 22037e75ba Qt: Update Base Translation 2024-01-26 13:04:42 +10:00
TheLastRarandConnor McLaughlin 303ab163ab DEV9: Remove some casts in ATA Info 2024-01-26 13:03:06 +10:00
TheLastRarandConnor McLaughlin 2833537167 DEV9: Various ATA fixes 2024-01-26 13:03:06 +10:00
TheLastRarandConnor McLaughlin 1aa7b591b6 DEV9: LBA48 Support 2024-01-26 13:02:53 +10:00
KamFretoZandConnor McLaughlin 2f0463d936 Qt: Icon Refresh 2024-01-26 13:02:36 +10:00
TellowKrinkleandConnor McLaughlin 82a1ec0c3a Qt: Fix use of tr outside of Qt class 2024-01-26 12:48:37 +10:00
TellowKrinkleandConnor McLaughlin d65133451d Common: Add helper for creating an NSString from a string_view 2024-01-26 12:48:37 +10:00
TellowKrinkleandConnor McLaughlin 36e81949e0 Qt: Use proper Show in Finder on macOS 2024-01-26 12:48:37 +10:00
TellowKrinkleandConnor McLaughlin 6b62945632 CMake: Set qm files as non-source data in Xcode
Prevents their contents from coming up in searches
2024-01-26 12:48:37 +10:00
Stenzek fa2f578900 GS/HW: Handle more double-half clear edge cases
Spiderman: Web of Shadows clears its depth buffer with 32-bit FRAME
and 24-bit Z. So the upper 8 bits of half the depth buffer are not
cleared, yay. We can't turn this into a 32-bit clear, because then
it'll zero out those bits, which other games need (e.g. Jak 2). We
can't do a 24-bit clear, because something might rely on half those
bits actually getting zeroed. So, instead, we toss the depth buffer,
and let the mem clear path write out FRAME and Z separately, with
their associated masks. Limit it to black to avoid false positives.

Also fixes FBW getting blown out to 20 for a horizontal double-half
clear in Wakeboarding Unleashed.
2024-01-26 12:08:29 +10:00
JordanTheToasterandrefractionpcsx2 e1c9987059 GameDB: Hot Shots Golf Fore Fixes 2024-01-26 01:51:00 +00:00
GovanifYandConnor McLaughlin 325e219bb1 PINE: fix save state regression introduced during the switch to Qt
Ensure thread safety when loading/saving state
2024-01-25 20:05:44 +10:00
Dan McCarthyandConnor McLaughlin be208df11b Debugger: Allow copying address of memory search results
Adds the ability to copy the address directly of a memory search result through the right click context menu.
2024-01-25 16:55:43 +10:00
KamFretoZandConnor McLaughlin 49df804d1b FSUI: Add Recording Indicator and Time 2024-01-25 00:05:34 +10:00
JordanTheToasterandConnor McLaughlin 122fde79bd GameDB: Thrillville fixes 2024-01-24 20:07:12 +10:00
Pierre GRASSERandConnor McLaughlin 2099e68c41 GUI: Memory Card "slot" instead of "port"
"Slot" is the official term, we should use it instead of "port".
2024-01-24 20:06:54 +10:00
Dan McCarthyandConnor McLaughlin 52ccc609cd Debugger: Adds loading breakpoints/saved addresses from settings
Adds `/inis/debuggersettings/` settings folder to contain settings specifically for the debugger. Adds functionality to manually save (to settings) Breakpoints/Saved addresses and automatically load them upon launching the debugger.
2024-01-24 20:06:20 +10:00
Stenzek b402f6a404 Qt: Fix unclickable links in Setup Wizard 2024-01-24 17:49:18 +10:00
KamFretoZandConnor McLaughlin 3576a1824d Qt: Remove the duplicate memcard button 2024-01-24 13:47:20 +10:00
Stenzek c3afaae169 Qt: Remove NTFS compression checkbox on Linux/Mac 2024-01-24 13:46:47 +10:00
Stenzek c5a9ce6b56 MemoryCardFile: Log size/formatted state on load 2024-01-24 13:46:47 +10:00
Stenzek 4f0d944bb9 MemoryCardFile: Don't compress file on opening 2024-01-24 13:46:47 +10:00
Stenzek 5684bcb6f4 Qt: Explicitly set memcard compression regardless of choice
Fixes compressed cards being created when the directory has the
compressed flag set (probably from wx..).
2024-01-24 13:46:47 +10:00
JordanTheToasterandrefractionpcsx2 5087fcea88 GameDB: Various fixes 2024-01-23 18:52:09 +00:00
refractionpcsx2 28f984e87f GS/HW: Don't preload target from other targets after hw clear 2024-01-22 20:38:49 +00:00
PCSX2 Botandlightningterror e0233daeb0 PAD: Update to latest controller database. 2024-01-22 19:30:53 +01:00
refractionpcsx2 49922ebe32 GS/HW: Fix up Tekken 5 CRC hack to not remove post + break other scenes 2024-01-21 14:56:54 +00:00
MrlinkwiiandConnor McLaughlin 8fe0c3441c config: remove unused variables 2024-01-21 21:39:07 +10:00
PCSX2 BotandConnor McLaughlin ba55caa404 Qt: Update Base Translation 2024-01-21 19:28:44 +10:00
Stenzek e50e651078 Qt: Warning fix 2024-01-21 19:04:16 +10:00
Stenzek 39b4fd47c7 GS/HW: Fix incorrect page mask in ClearGSLocalMemory() 2024-01-21 19:04:16 +10:00
Stenzek 56b83e5cfa GS/HW: Fix double-half clears misfiring on CT32+Z24 when clearing depth 2024-01-21 19:04:16 +10:00
Stenzek ed57355948 GS/HW: Prevent creating texture with invalid mipmap levels
Fixes crash during loading in Jak with Metal renderer, and UB with
Vulkan.
2024-01-21 19:04:16 +10:00
Stenzek b4b2f3a0b6 GS/Metal: Don't discard alpha on RGB depth copy 2024-01-21 19:03:16 +10:00
TheTechnician27andrefractionpcsx2 1946882952 GameDB: A few trilinear+mipmap games 2024-01-21 09:00:09 +00:00
refractionpcsx2 dbe4930c17 GS: Correct flush reasons 2024-01-20 18:41:06 +00:00
Stenzek 3b25fa3065 GS/HW: Better avoid target uploads on HW clear 2024-01-21 00:49:35 +10:00
ZiemasandConnor McLaughlin 01849565aa SPU: VolumeSlide: disregard phase when exp + decr 2024-01-21 00:05:50 +10:00
Stenzek e3ca5833d0 GS/HW: Fix false positive on shared bits with double-half clears 2024-01-20 23:03:01 +10:00
Stenzek dfac1f034b Qt: Make Shift+Wheel scroll settings info panel 2024-01-20 22:58:22 +10:00
Stenzek a8d07df81d GunCon2: Fix cursor assertions in devbuilds 2024-01-20 22:58:22 +10:00
refractionpcsx2andlightningterror 609cb44b01 GS/HW: Optimize out some situations of texture loading 2024-01-20 06:05:49 +01:00
JordanTheToasterandrefractionpcsx2 feb4d605d0 GameDB: Various fixes 2: Electric Boogaloo
Revert VP2 changes as they cause issues and fix a shadow issue in Star Wars Force Unleashed.
2024-01-19 11:38:54 +00:00
JordanTheToasterandrefractionpcsx2 276d887917 GameDB: Various fixes 2024-01-19 08:27:43 +00:00
PCSX2 Botandlightningterror 61aef9c96f Qt: Update Base Translation 2024-01-18 16:56:06 +01:00
lightningterror b4005682f2 GS/HW: Fix compiler warnings. 2024-01-18 16:41:05 +01:00
Pierre GRASSERandrefractionpcsx2 7d98461fc6 Qt: Remove non-printable character from some strings
This non-printable character (U+FE0F, VARIATION SELECTOR 16) has been added for seemingly no reason to multiple strings and have polluted Translation Memory on Crowdin for some languages (👀).
Let's get rid of it.
2024-01-18 15:02:20 +00:00
Stenzek 862d03b78e FullscreenUI: Link create memory card to Qt dialog 2024-01-18 21:48:22 +10:00
Stenzek 0ecf46240f Qt: Fix OSD/Big Picture display for missing languages 2024-01-18 21:10:52 +10:00
Tyler WildingandGitHub a2755e212b translations: Syncing Crowdin translations (#10638)
* New translations pcsx2-qt_en.ts (Romanian)
[ci skip]

* New translations pcsx2-qt_en.ts (French)
[ci skip]

* New translations pcsx2-qt_en.ts (Spanish)
[ci skip]

* New translations pcsx2-qt_en.ts (Afrikaans)
[ci skip]

* New translations pcsx2-qt_en.ts (Arabic)
[ci skip]

* New translations pcsx2-qt_en.ts (Catalan)
[ci skip]

* New translations pcsx2-qt_en.ts (Czech)
[ci skip]

* New translations pcsx2-qt_en.ts (Danish)
[ci skip]

* New translations pcsx2-qt_en.ts (German)
[ci skip]

* New translations pcsx2-qt_en.ts (Greek)
[ci skip]

* New translations pcsx2-qt_en.ts (Finnish)
[ci skip]

* New translations pcsx2-qt_en.ts (Hebrew)
[ci skip]

* New translations pcsx2-qt_en.ts (Hungarian)
[ci skip]

* New translations pcsx2-qt_en.ts (Italian)
[ci skip]

* New translations pcsx2-qt_en.ts (Japanese)
[ci skip]

* New translations pcsx2-qt_en.ts (Korean)
[ci skip]

* New translations pcsx2-qt_en.ts (Lithuanian)
[ci skip]

* New translations pcsx2-qt_en.ts (Dutch)
[ci skip]

* New translations pcsx2-qt_en.ts (Norwegian)
[ci skip]

* New translations pcsx2-qt_en.ts (Polish)
[ci skip]

* New translations pcsx2-qt_en.ts (Portuguese)
[ci skip]

* New translations pcsx2-qt_en.ts (Russian)
[ci skip]

* New translations pcsx2-qt_en.ts (Serbian (Cyrillic))
[ci skip]

* New translations pcsx2-qt_en.ts (Swedish)
[ci skip]

* New translations pcsx2-qt_en.ts (Turkish)
[ci skip]

* New translations pcsx2-qt_en.ts (Ukrainian)
[ci skip]

* New translations pcsx2-qt_en.ts (Chinese Simplified)
[ci skip]

* New translations pcsx2-qt_en.ts (Chinese Traditional)
[ci skip]

* New translations pcsx2-qt_en.ts (Vietnamese)
[ci skip]

* New translations pcsx2-qt_en.ts (Portuguese, Brazilian)
[ci skip]

* New translations pcsx2-qt_en.ts (Indonesian)
[ci skip]

* New translations pcsx2-qt_en.ts (Persian)
[ci skip]

* New translations pcsx2-qt_en.ts (Croatian)
[ci skip]

* New translations pcsx2-qt_en.ts (Latvian)
[ci skip]

* New translations pcsx2-qt_en.ts (Hindi)
[ci skip]

* New translations pcsx2-qt_en.ts (Spanish, Latin America)
[ci skip]
2024-01-18 20:21:15 +10:00
forrvalhallaandrefractionpcsx2 2ec5559dd6 GameDB: Add HPO Native to SLAI 2024-01-18 09:28:34 +00:00
RedDevilusandrefractionpcsx2 6f904759ee GameDB: Gun Align to Native + typo fix
Looks better with the bloom and character shadows
2024-01-18 09:27:14 +00:00
RedPanda4552andConnor McLaughlin 507fafd601 Pad: Force multitapped slots to Not Connected if their multitap is missing 2024-01-18 14:05:21 +10:00
refractionpcsx2andConnor McLaughlin 9c463f1338 GS/HW: Add DS and Alpha Test checks 2024-01-18 13:33:34 +10:00
Stenzek 8818cd0285 GS/HW: Further improve no_rt heuristics
Reduces copies by almost 500 in Crash and Burn, few hundred drawcall
reductions in other games.
2024-01-18 13:33:34 +10:00
Stenzek 23b72d08d2 ImGuiOverlays: Fix analog input display 2024-01-17 19:02:33 +10:00
Stenzek 227049b6f2 FileSystem: Handle infinite symlink loops in FindFiles() 2024-01-17 13:57:19 +10:00
Stenzek 97abd3e1f9 ImGuiOverlays: Display inputs as integer, ignoring deadzone 2024-01-17 13:56:54 +10:00
Stenzek 6f34b7ba99 Pad: Add GetEffectiveInput()
Returns the value, incorporating any deadzone.
2024-01-17 13:56:54 +10:00
Alessa BakerandConnor McLaughlin c0d86c3852 Adding Jetbrains Rider to Gitignore. 2024-01-17 12:24:15 +10:00
Stenzek bf743ff467 gitignore: Add portable.ini/fix font paths 2024-01-16 22:13:56 +10:00
Connor McLaughlin 306f994464 CI/Linux: Use wildcard for AppImage libc downgrade 2024-01-16 21:43:25 +10:00
TheLastRarandrefractionpcsx2 f56676942b DEV9: Remove SDMA code 2024-01-16 10:45:14 +00:00
TheLastRarandrefractionpcsx2 bfd0a05289 DEV9: Fix ATA MDMA 2024-01-16 10:45:14 +00:00
JordanTheToasterandrefractionpcsx2 3999c08f00 GameDB: Various fixes 2024-01-16 10:43:38 +00:00
TheTechnician27andGitHub febb8ec1a6 GameDB: Fix water in Just Cause (#10645) 2024-01-16 10:29:31 +00:00
RedPanda4552andConnor McLaughlin cdd38ef7aa Game List: Ignore exclusion paths if they are empty string
Works around a bug of unknown origin which causes empty string to be added to the exclusions list, clearing the games list completely.
2024-01-16 18:38:18 +10:00
Stenzek 54c620a5df CI: Default to non-portable builds 2024-01-16 13:13:02 +10:00
Martino FontanaandConnor McLaughlin 089b7fa73e Build: Remove unused option DISABLE_BUILD_DATE 2024-01-16 13:12:20 +10:00
PCSX2 Botandlightningterror d9d0e84ca1 PAD: Update to latest controller database. 2024-01-15 17:17:01 +01:00
refractionpcsx2 badede2e2d GS/HW: ignore lower 3 bits of 16bit color for AEM check 2024-01-15 14:45:00 +00:00
Stenzek e2ae28741d Qt: Look for a file named DefaultUpdaterChannel.h
Make manual releases create this file, and make it contain

  #define DEFAULT_UPDATER_CHANNEL "stable"

So that the release defaults to the stable update channel.
2024-01-15 15:35:21 +10:00
Stenzek ff34150b15 Build: Simplify Git version extraction
Get rid of SysForwardDefs.h
Use last known git tag to determine version info, if it is not a tagged commit.
2024-01-15 15:35:21 +10:00
Tyler Wilding 4a93ceac58 Update Crowdin configuration file 2024-01-14 14:18:03 -05:00
JordanTheToasterandrefractionpcsx2 5a724bf49e GameDB: Flower sun and rain fixes 2024-01-14 17:40:58 +00:00
Connor McLaughlinandGitHub c23bcf6322 Qt: Remove debug popup after update cleanup 2024-01-14 18:36:01 +10:00
PCSX2 BotandConnor McLaughlin c863149ad9 Qt: Update Base Translation 2024-01-14 18:07:44 +10:00
Connor McLaughlin 5fb6e22bed Qt: Invoke Updater as Administrator for Program Files installs 2024-01-14 18:03:10 +10:00
refractionpcsx2 d634088282 GS/HW: Don't mark 24bit alpha as valid on upgrade 2024-01-14 06:56:57 +00:00
Stenzek c99a935831 CDVD: Fix block dump path generation 2024-01-14 15:23:25 +10:00
Stenzek 165da677ed Qt: Use native path separators for block dump 2024-01-14 15:23:25 +10:00
Stenzek 0bbde2ca52 IPU: Fix regression from #10617 2024-01-14 15:22:43 +10:00
Stenzek 3d13c5d13c CI: Allow user to override deps build path 2024-01-14 14:05:16 +10:00
Stenzek 9d978c67ec GS/DX11: Remove unused field 2024-01-14 12:51:51 +10:00
Stenzek 8766e1fa57 GS: Fix enum type return warnings 2024-01-14 12:51:51 +10:00
Stenzek c0e4883987 GS: Make GSVertex POD
Same code is generated with optimization (256-bit store in AVX2).
2024-01-14 12:51:51 +10:00
Stenzek 57f2cd5f9e GS: Make GSDrawingContext and GSDrawingEnvironment POD 2024-01-14 12:51:51 +10:00
Stenzek 50d8d06a94 GS: Make vector types POD
Generates the same code in Release, but slightly difference code in Debug.
In Debug, with the default copy assignment operator, it turns into two
8-byte moves. With the overloaded operator, it turns into a function call
with a vector move.
2024-01-14 12:51:51 +10:00
Stenzek 2e07328878 GS: Skip vertex append for GS_INVALID PRIM 2024-01-14 12:51:51 +10:00
Stenzek ad8294e521 GS: Remove unused vertex types 2024-01-14 12:51:51 +10:00
Stenzek 14426a7b45 GSCapture: Fix invalid substring comparison 2024-01-14 12:51:51 +10:00
Stenzek 6697e76be1 x86/iR5900: Fix signed/unsigned comparison in timeout loop 2024-01-14 12:51:51 +10:00
Stenzek 73cd876f6d SPU2: Make StereoOut16/32 POD 2024-01-14 12:51:51 +10:00
Stenzek e626754000 IPU: Remove last parameter from IDCT_Add
It was always being used uninitialized.
2024-01-14 12:51:51 +10:00
Stenzek 1fedd31229 HW: Make various DMAC/IPU/VIF register types POD 2024-01-14 12:51:51 +10:00
Stenzek bfe1746ddb Cache: Make CacheTag/CacheData POD 2024-01-14 12:51:51 +10:00
PCSX2 BotandConnor McLaughlin 4800c32db4 Qt: Update Base Translation 2024-01-13 12:27:29 +10:00
KamFretoZandConnor McLaughlin 140fc28b3e FSUI: Allow toggling fullscreen when VM is paused 2024-01-13 12:27:05 +10:00
Stenzek f9833bb8af Qt: Tidy up Tools menu
Make System Console and Verbose contingent on Advanced Settings being enabled.
Make Debug Console contingent on actually running under a debugger.
2024-01-13 12:26:34 +10:00
TheTechnician27andConnor McLaughlin 4b6ddaf09e Update VMManager.cpp to not count paused time toward time played
Hopefully resolves #10497. Between each update to played time, we calculate the sum of the deltas between VM resume and pause in order to subtract that off from the total session (paused and unpaused) session time.

Thus, if someone pauses the emulator, that time spent paused will no longer be reflected in the GameList.
2024-01-12 22:52:34 +10:00
Stenzek d427db4a15 GS/HW: Fix drawing with mismatched target sizes 2024-01-12 19:40:30 +10:00
Stenzek deb39cc20f CMake: Set LINUX in current and parent scope 2024-01-12 19:21:31 +10:00
Stenzek 3a4c25e916 VMManager: Force TimeStretch in Achievements HC Mode 2024-01-12 19:20:38 +10:00
f416eb3d4f Translation\UI: Updates Bitrate "Kbps" to "kpbs"
Co-Authored-By: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
2024-01-12 15:59:15 +10:00
Dan McCarthyandConnor McLaughlin 87cbb1f90c Settings: Adds description to 'All CRCs' on hover for cheat/patch settings
Add an on hover description to explain the purpose of the 'All CRCs' button in the Cheat and Patch Settings window.
2024-01-12 15:59:15 +10:00
Stenzek 68f43121fb MTGS: Explicitly set FP control register on startup
Linux appears? to inherit the value of MXCSR from the creating thread,
and that breaks parts of the software renderer.
2024-01-12 15:23:31 +10:00
Stenzek 51ceab1f3c PINE: Convert from class to namespace
Fewer global objects, indirect includes via headers.
2024-01-12 12:57:28 +10:00
Stenzek 2257992a3f PINE: Tidy up and fix shutdown hang on Linux 2024-01-12 12:57:28 +10:00
TellowKrinkleandConnor McLaughlin 40ead584d7 GS:MTL: Properly handle hdr rendering to cleared textures 2024-01-12 12:51:50 +10:00
PCSX2 BotandConnor McLaughlin 6f2be7bd17 Qt: Update Base Translation 2024-01-12 12:51:24 +10:00
KamFretoZandConnor McLaughlin af9ca4cf7d 3rdparty: Add source for modified PromptFont that we use for OSD 2024-01-12 12:44:43 +10:00
Stenzek ea98203ac5 Qt: Add log window 2024-01-12 12:40:46 +10:00
Stenzek e908bbfae6 Qt: Add accessors for UI thread view of current game 2024-01-12 12:40:46 +10:00
Stenzek fa00069068 Console: Remove WX rubbish and replace 2024-01-12 12:40:46 +10:00
refractionpcsx2 efae58de52 GS/HW: Don't bother trying to draw empty draws 2024-01-11 14:43:04 +00:00
refractionpcsx2 39c7f703cb GameDB: Remove GS fixes from Ty The Tasmanian Tiger 2024-01-11 13:52:53 +00:00
refractionpcsx2 96220e509c GSDumpRunner: Fix dump runner 2024-01-11 13:52:53 +00:00
refractionpcsx2 4de495ad59 GS/HW: Upgrade target to C32 if alpha requested on src is rt 2024-01-11 13:52:53 +00:00
refractionpcsx2 56a950a074 GS/HW: Update cached source alpha if linked to target 2024-01-11 12:46:23 +00:00
forrvalhallaandGitHub 2b47254bdd GameDB: Set HPO Native to Fast and Furious (#10595) 2024-01-11 08:20:56 +00:00
Tyler WildingandGitHub da28e2cc37 CI: fix flatpak cron and add a way to manually kick off a release (#10567)
* CI: fix mistake stopping flatpak cron from running

* CI: add a way to manually kick off releases with a custom tag

* cmake: handle edge-case where a commit can have multiple tags

* CI: set release channel appropriately when it's a stable release
2024-01-10 19:04:42 -05:00
refractionpcsx2 3d64f4a548 GameDB: Merge sprite to remove blur on Sakura Taisen - Atsuki Chishioni 2024-01-10 15:50:33 +00:00
refractionpcsx2 abf03b0c79 GS/HW: Detected striped moves in HW 2024-01-10 15:50:33 +00:00
refractionpcsx2 8381742422 GS: Flush draw when address matches FRAME/Z 2024-01-10 15:43:06 +00:00
Stenzek 724bb09aec GS/HW: Don't rely on old targets for double-half clear 2024-01-10 19:36:49 +10:00
Stenzek ad5de248b4 Qt: Fix incorrect renderer shown in status bar 2024-01-10 19:35:26 +10:00
AKuHAKandrefractionpcsx2 1a1fdf6a5a DEV9: various fixes for ATA Identify
improved 48-bit addressing
UDMA mode now set correctly
MDMA0 mode now set correctly
fixed user addressable sectors limit
2024-01-09 21:39:27 +00:00
Stenzekandrefractionpcsx2 c469b17350 GS/HW: Fix incorrect resizing of Ico move target 2024-01-09 19:50:51 +00:00
refractionpcsx2 01842a3c6b GS/HW: Allow blending on normal shuffles 2024-01-09 13:17:52 +00:00
Berylskidandrefractionpcsx2 4cd385dbff GameDB: Fix broken shadows in Armored Core Last Raven 2024-01-09 11:56:31 +00:00
GovanifYandrefractionpcsx2 72787d103f PINE: fix regressions introduced in #10448 2024-01-08 22:01:24 +00:00
PCSX2 Botandlightningterror 59072272a9 PAD: Update to latest controller database. 2024-01-08 17:06:21 +01:00
Stenzek ef76666e5a Misc: Warning fixes 2024-01-08 23:33:43 +10:00
Stenzek 0a0994b19c Qt: Fix docs file not getting included on Linux/Mac 2024-01-08 23:33:43 +10:00
psykoseandConnor McLaughlin ab68c570a9 Debugger: initialise SavedAddress.address with 0 instead of NULL 2024-01-08 20:44:26 +10:00
JordanTheToasterandrefractionpcsx2 dcdf3b39a3 GameDB: Various fixes 2024-01-08 10:38:53 +00:00
Stenzek 476cb2db34 Qt: Add third-party license statements 2024-01-07 22:20:43 +10:00
Stenzek 34a7e00413 GameDB: Use chop rounding for division in GT4 NTSC-J 2024-01-07 21:42:18 +10:00
Stenzek 413ba09ee0 Config: Replace FpuNegDivHack with eeDivRoundMode 2024-01-07 21:42:18 +10:00
KamFretoZandrefractionpcsx2 33eefefffb Qt: Update Base Translation 2024-01-07 10:44:12 +00:00
KamFretoZandrefractionpcsx2 b3c822e31b Qt: Minor Translation Fixes 2024-01-07 10:44:12 +00:00
Connor McLaughlinandGitHub 14a458a275 Qt: Fix HTTP download error format string 2024-01-07 20:14:01 +10:00
JordanTheToasterandConnor McLaughlin 6c49f9cf52 GameDB: DMC 1 Fixes 2024-01-07 18:40:47 +10:00
Stenzek 803ea4dda5 Qt: Update base translation 2024-01-07 18:35:50 +10:00
Stenzek c9b23e6e4e Qt: Fix opening game properties causing patch reload 2024-01-07 18:35:50 +10:00
Stenzek 509c9318af Qt: Fix missing font message not being translatable 2024-01-07 18:35:50 +10:00
Stenzek 46e1e3d904 Qt: Add runtime downloading of CJK OSD fonts 2024-01-07 17:23:57 +10:00
Stenzek 7a045d837b Qt: Add runtime resource downloading 2024-01-07 17:23:57 +10:00
Stenzek 574ea820f7 Config: Add UserResources to EmuFolders
Allowing some resources, such as fonts/sounds to be overridden by the
user.
2024-01-07 17:23:57 +10:00
Stenzek 7061d48dde SDLInputSource: Replace hint strings with macros 2024-01-07 16:19:29 +10:00
Stenzek 53633b7279 Qt: Fix columns in cheat tree list not auto-sizing 2024-01-07 16:03:37 +10:00
Stenzek 05bb126af9 Patch: Fix ungrouped patches from >1 file not merging 2024-01-07 16:03:37 +10:00
Stenzek 436fba77ac SDLInputSource: Log axes/buttons when controller is opened 2024-01-07 14:07:53 +10:00
Ty LamontagneandConnor McLaughlin 91c0e64159 Misc: Enable some disabled warnings
smite you windows for keeping long 32 bits
2024-01-07 13:19:14 +10:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Tyler Wilding
7207681485 CI: migrate to upload-artifact@v4, download-artifact@v4 and labeler@v5 (#10431)
* Bump actions/upload-artifact from 3 to 4

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump actions/download-artifact from 3 to 4

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* github: group github actions updates going forward

* ci: update to labeler@v5

* ci: properly differentiate between windows build artifacts on PRs

* ci: workaround flatpak-builder temporarily as well as test fix

* ci: simplify things, just explicitly specify the artifact names in the workflows

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tyler Wilding <xtvaser@gmail.com>
2024-01-06 15:44:44 -05:00
KamFretoZandrefractionpcsx2 eef53f1b54 Qt: Minor Translation Fixes 2024-01-06 16:03:19 +00:00
KamFretoZandrefractionpcsx2 150d8d3214 Qt: Add Popn icon to Input OSD 2024-01-06 16:03:19 +00:00
JordanTheToasterandrefractionpcsx2 a14376abb6 GameDB: Various fixes 2024-01-06 15:58:00 +00:00
KamFretoZandrefractionpcsx2 dfb45a8ebc GameDB: Fixes for Bully 2024-01-06 15:56:51 +00:00
Dan McCarthyandConnor McLaughlin 00137c3031 Misc: Uses Qt::UserRole for hardcoded literals & const changes
Adds const to variables that could use them but currently don't and replaces 256 literal uses with Qt::UserRole to be clear what it is for and how this number is used as currently it's a bit confusing.
2024-01-06 13:17:39 +10:00
Dan McCarthyandConnor McLaughlin b9c7dacbd1 Debugger: Double clicking Memory Search result switches to memory view tab
Previously when double clicking a memory search result it would go to the address but not switch to the memory view tab if the user wasn't already on it.
Now it ensures the user moves to the memory view widget, as this is almost always going to be the user's intention.
2024-01-06 13:17:39 +10:00
Dan McCarthyandConnor McLaughlin 3a03b579d2 Debugger: Add Saved Addresses tab widget for bookmarking mem addresses
Adds a tab widget to the debugger that allows saving/bookmarking memory addresses and giving them labels/descriptions for convenience. Includes the ability to jump back to memory addresses from the Saved Addresses tab, and adding Saved Addresses from memory search search results context menu and the memory view context menu.

Also supports importing and exporting the saved addresses as CSV format.
2024-01-06 13:17:39 +10:00
lightningterror e2bbe5cd8b SIO: Fix Wunused-variable compiler warnings. 2024-01-05 14:30:40 +01:00
PCSX2 Botandlightningterror c7347a3a16 Qt: Update Base Translation 2024-01-05 14:30:18 +01:00
Stenzek 8749b3c782 Patch: Fix last duplicate patch group not being ignored 2024-01-05 22:32:11 +10:00
RedPanda4552andConnor McLaughlin 10389dc3f1 Pad: Add Pop'n Music controller type 2024-01-05 16:44:36 +10:00
Stenzek 5e191a6cd8 GameDB: Add fullblend/autoflush/tex-in-RT for Catwoman 2024-01-05 13:55:18 +10:00
Stenzek a247625095 GS/HW: Handle offset RTs when offset texture is detected
Catwoman draws to the target at 2E60 via 2D00, one page back
horizontally, and one vertically.

The Destroy All Humans fix also works here, but it only applied to the
texture, not the RT. So do a very conservative form of RT-in-RT to
handle Catwoman.

Unfortunately nothing else benefits from it.
2024-01-05 13:55:18 +10:00
refractionpcsx2 0fb234f3e4 Linux: Stop OpenGL overriding Vulkan if both VK and OpenGL are enabled 2024-01-05 00:03:09 +00:00
Dan McCarthyandrefractionpcsx2 e3e35d3bc6 Misc: Add patches folder to gitignore
Adds Patches folder to git ignore so that changing branches/stashing/unstashing does not impact patches files in repository folder.
This matches how the cheats folder is handled.
2024-01-04 23:29:19 +00:00
Dan McCarthyandrefractionpcsx2 6b12ca5b39 CheatsSettings/PatchSettings: Add toggle for showing All CRCs
This used to be off by default, but was changed to show found patches/cheats for all CRCs for a game serial. However, this also clutters and shows cheats for CRCs that may not be applicable without a way to hide them.

This puts this setting behind a toggle to choose if this should be enabled/disabled so the user has choice. Still defaults to enabled to keep current behavior.
2024-01-04 23:29:19 +00:00
PCSX2 Botandlightningterror 44e2837efb Qt: Update Base Translation 2024-01-04 23:50:13 +01:00
Ty Lamontagneandrefractionpcsx2 27dcd1bba2 Core: Remove unused variable
Caused from my previous commit. oops
2024-01-04 21:31:47 +00:00
Ty Lamontagneandrefractionpcsx2 ebf8cba6a4 Core: Remove syscon log and merge with EE logs 2024-01-04 21:10:37 +00:00
Christian Kennyandrefractionpcsx2 e06cf23a03 Readme: Update and Remove Redundant Info
This PR updates the readme to be more concise. Much of the old information has been moved over to the new documentation site and no longer needs to be duplicated.
2024-01-04 20:31:09 +00:00
Dan McCarthyandrefractionpcsx2 dd55887f4b Debugger: Add support for Not Equals Array filter searches
Adds the ability to do Not Equals filter searches for arrays and strings. Refactors array search function for easily adding new search types.

Adds error message that informs the user that Not Equals can not be used for initial searches and can only be used for filter searches.
2024-01-04 20:24:38 +00:00
Dan McCarthyandrefractionpcsx2 39b7b29af8 Debugger: Combine Start & End addresses to 1 line & add comparison label
- Combines the start and end address inputs for memory search into a single line to not take up unnecessary space and allow room for other UI elements.

- Adds 'Comparison' label to memory search UI to describe the comparison input dropdown.
2024-01-04 20:24:38 +00:00
Dan McCarthyandrefractionpcsx2 6f0aef3344 Settings: Clearing settings disables Cheats/Patches
The 'Clear Settings' button will now disable all cheats and patches from the configuration. This will allow quickly resetting to having no game altering pnach patches/cheats.
2024-01-04 20:23:18 +00:00
Mrlinkwiiandrefractionpcsx2 85c3a413fa GameDB: remove Knights Of The Temple 2 CRC 2024-01-04 20:19:52 +00:00
Mrlinkwiiandrefractionpcsx2 b6c365b89d HW/CRC: remove Knights Of The Temple 2 CRC hacks 2024-01-04 20:19:52 +00:00
Stenzek 9eedf3901a GS: Fix autoflush init after renderer switch 2024-01-04 15:27:59 +10:00
Mrlinkwiiandrefractionpcsx2 2a6ecaeb09 GameDB: fixes for Mary-Kate & Ashley - Sweet Sixteen - Licensed to Drive 2024-01-02 22:03:10 +00:00
JordanTheToasterandrefractionpcsx2 027db2480a GameDB: Rock Band Track Pack Volume 2 Fixes
Fixes for complete lack of visuals in menus and game.
2024-01-02 10:02:25 +00:00
KamFretoZandrefractionpcsx2 f4cabca62d Qt: More Translation Fixes 2024-01-02 07:48:23 +00:00
ElTioRataandrefractionpcsx2 0b3edc5fd4 GameDB: 007 - Agent Under Fire
Recommends 'Full' blending setting to fix lighting issues
2024-01-02 07:40:39 +00:00
PCSX2 Botandlightningterror b05152da0f PAD: Update to latest controller database. 2024-01-01 17:22:34 +01:00
304 changed files with 195908 additions and 124908 deletions
+4
View File
@@ -6,3 +6,7 @@ updates:
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
groups:
ci-deps:
patterns:
- "*"
+147 -87
View File
@@ -2,127 +2,187 @@
# General Labels
'Build | Project System':
- '.github/*'
- '.github/**/*'
- '*.sln'
- '**/*.sln'
- '*.vcxproj*'
- '**/*.vcxproj*'
- 'cmake/*'
- 'cmake/**/*'
- 'CMakeLists.txt'
- '**/CMakeLists.txt'
- 'build.sh'
- changed-files:
- any-glob-to-any-file:
- '.github/*'
- '.github/**/*'
- '*.sln'
- '**/*.sln'
- '*.vcxproj*'
- '**/*.vcxproj*'
- 'cmake/*'
- 'cmake/**/*'
- 'CMakeLists.txt'
- '**/CMakeLists.txt'
- 'build.sh'
'Dependencies':
- '3rdparty/*'
- '3rdparty/**/*'
- '**/3rdpartyDeps.props'
- '.gitmodules'
- changed-files:
- any-glob-to-any-file:
- '3rdparty/*'
- '3rdparty/**/*'
- '**/3rdpartyDeps.props'
- '.gitmodules'
'Documentation':
- '*.md'
- '**/*.md'
- '*.pdf'
- '**/*.pdf'
- changed-files:
- any-glob-to-any-file:
- '*.md'
- '**/*.md'
- '*.pdf'
- '**/*.pdf'
'GUI/Qt':
- 'pcsx2-qt/*'
- 'pcsx2-qt/**/*'
- '3rdparty/Qt/*'
- '3rdparty/Qt/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2-qt/*'
- 'pcsx2-qt/**/*'
- '3rdparty/Qt/*'
- '3rdparty/Qt/**/*'
'GameDB':
- '**/GameIndex.*'
- changed-files:
- any-glob-to-any-file:
- '**/GameIndex.*'
'Installer | Package':
- 'build.sh'
- changed-files:
- any-glob-to-any-file:
- 'build.sh'
'Translations':
- 'pcsx2-qt/Translations/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2-qt/Translations/*'
# Tools / Features
'Debugger':
- 'pcsx2/DebugTools/*'
- 'pcsx2/DebugTools/**/*'
- 'pcsx2-qt/Debugger/*'
- 'pcsx2-qt/Debugger/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/DebugTools/*'
- 'pcsx2/DebugTools/**/*'
- 'pcsx2-qt/Debugger/*'
- 'pcsx2-qt/Debugger/**/*'
'IPC':
- 'pcsx2/IPC*'
- 'pcsx2/**/IPC*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/IPC*'
- 'pcsx2/**/IPC*'
'TAS Functionality':
- 'pcsx2/Recording/*'
- 'pcsx2/Recording/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Recording/*'
- 'pcsx2/Recording/**/*'
'RetroAchievements':
- 'pcsx2/Frontend/Achievements.*'
- 'pcsx2/Achievements.*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Frontend/Achievements.*'
- 'pcsx2/Achievements.*'
# Emulation Components
'Counters':
- 'pcsx2/Counters.*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Counters.*'
'Vector Units':
- 'pcsx2/VU*'
- 'pcsx2/**/VU*'
- 'pcsx2/*VU*'
- 'pcsx2/**/*VU*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/VU*'
- 'pcsx2/**/VU*'
- 'pcsx2/*VU*'
- 'pcsx2/**/*VU*'
'VIF':
- 'pcsx2/Vif*'
- 'pcsx2/**/Vif*'
- 'pcsx2/VIF*'
- 'pcsx2/**/VIF*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Vif*'
- 'pcsx2/**/Vif*'
- 'pcsx2/VIF*'
- 'pcsx2/**/VIF*'
# GS Related Labels
'GS':
- 'pcsx2/GS/*'
- 'pcsx2/GS/**/*'
- 'bin/resources/shaders/*'
- 'bin/resources/shaders/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/*'
- 'pcsx2/GS/**/*'
- 'bin/resources/shaders/*'
- 'bin/resources/shaders/**/*'
'GS: Direct3D 11':
- 'pcsx2/GS/Renderers/DX11/*'
- 'pcsx2/GS/Renderers/DX11/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/DX11/*'
- 'pcsx2/GS/Renderers/DX11/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
'GS: Direct3D 12':
- 'pcsx2/GS/Renderers/DX12/*'
- 'pcsx2/GS/Renderers/DX12/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/DX12/*'
- 'pcsx2/GS/Renderers/DX12/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
'GS: Hardware':
- 'pcsx2/GS/Renderers/HW/*'
- 'pcsx2/GS/Renderers/HW/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/HW/*'
- 'pcsx2/GS/Renderers/HW/**/*'
'GS: OpenGL':
- 'pcsx2/GS/Renderers/OpenGL/*'
- 'pcsx2/GS/Renderers/OpenGL/**/*'
- 'bin/resources/shaders/opengl/*'
- 'bin/resources/shaders/opengl/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/OpenGL/*'
- 'pcsx2/GS/Renderers/OpenGL/**/*'
- 'bin/resources/shaders/opengl/*'
- 'bin/resources/shaders/opengl/**/*'
'GS: Vulkan':
- 'pcsx2/GS/Renderers/Vulkan/*'
- 'pcsx2/GS/Renderers/Vulkan/**/*'
- 'bin/resources/shaders/vulkan/*'
- 'bin/resources/shaders/vulkan/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/Vulkan/*'
- 'pcsx2/GS/Renderers/Vulkan/**/*'
- 'bin/resources/shaders/vulkan/*'
- 'bin/resources/shaders/vulkan/**/*'
'GS: Metal':
- 'pcsx2/GS/Renderers/Metal/*'
- 'pcsx2/GS/Renderers/Metal/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/Metal/*'
- 'pcsx2/GS/Renderers/Metal/**/*'
'GS: Texture Cache':
- 'pcsx2/GS/Renderers/*TextureCache*.*'
- 'pcsx2/GS/Renderers/**/*TextureCache*.*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/*TextureCache*.*'
- 'pcsx2/GS/Renderers/**/*TextureCache*.*'
'GS: Software':
- 'pcsx2/GS/Renderers/SW/*'
- 'pcsx2/GS/Renderers/SW/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/SW/*'
- 'pcsx2/GS/Renderers/SW/**/*'
# Other Core Components
'CDVD':
- 'pcsx2/CDVD/*'
- 'pcsx2/CDVD/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/CDVD/*'
- 'pcsx2/CDVD/**/*'
'DEV9':
- 'pcsx2/DEV9/*'
- 'pcsx2/DEV9/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/DEV9/*'
- 'pcsx2/DEV9/**/*'
'IPU':
- 'pcsx2/IPU/*'
- 'pcsx2/IPU/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/IPU/*'
- 'pcsx2/IPU/**/*'
'Memory Card':
- 'pcsx2/SIO/Memcard/*'
- 'pcsx2/SIO/Memcard/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/SIO/Memcard/*'
- 'pcsx2/SIO/Memcard/**/*'
'PAD':
- 'pcsx2/SIO/Pad/*'
- 'pcsx2/SIO/Pad/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/SIO/Pad/*'
- 'pcsx2/SIO/Pad/**/*'
'SPU2':
- 'pcsx2/SPU2/*'
- 'pcsx2/SPU2/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/SPU2/*'
- 'pcsx2/SPU2/**/*'
'USB':
- 'pcsx2/USB/*'
- 'pcsx2/USB/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/USB/*'
- 'pcsx2/USB/**/*'
@@ -33,6 +33,7 @@ jobs:
uses: ./.github/workflows/linux_build_flatpak.yml
with:
jobName: "Qt"
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
publish: true
+31 -4
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -36,6 +39,10 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_linux:
@@ -56,13 +63,18 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Prepare Artifact Metadata
id: artifact-metadata
shell: bash
env:
OS: linux
BUILD_SYSTEM: flatpak
ARCH: ${{ inputs.platform }}
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
@@ -90,9 +102,11 @@ jobs:
flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6.3
# TODO - based on v6.3, switch back to the main action once change is accepted (or they release a breaking change that uses upload-artifact@v4)
uses: xTVaser/flatpak-github-actions/flatpak-builder@64267d4cf01f7955a40128039d0abab17bb48aac
with:
bundle: ${{ steps.artifact-metadata.outputs.artifact-name }}.flatpak
upload-artifact: false
manifest-path: .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json
arch: x86_64
build-bundle: true
@@ -131,3 +145,16 @@ jobs:
repository: stable
token: ${{ secrets.FLATHUB_TOKEN }}
# NOTE - this is done after on purpose so the flatpak file is wherever it needs to be for the previous pushes
- name: Prepare artifacts folder
# NOTE - 'flatpak-builder' dumps the artifact out into the current directory
run: |
mkdir -p "$GITHUB_WORKSPACE"/ci-artifacts/
mv "./${{ steps.artifact-metadata.outputs.artifact-name }}.flatpak" "$GITHUB_WORKSPACE"/ci-artifacts/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts
+2
View File
@@ -15,6 +15,7 @@ jobs:
uses: ./.github/workflows/linux_build_qt.yml
with:
jobName: "AppImage Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-appimage-sse4"
compiler: clang
cmakeflags: ""
buildAppImage: true
@@ -25,6 +26,7 @@ jobs:
uses: ./.github/workflows/linux_build_flatpak.yml
with:
jobName: "Flatpak Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak-sse4"
compiler: clang
cmakeflags: ""
publish: false
+18 -5
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -36,6 +39,10 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_linux:
@@ -57,7 +64,7 @@ jobs:
run: |
sudo rm -f /etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list
sudo apt-get update
sudo apt-get install -y --allow-downgrades libc6=2.35-0ubuntu3.5 libc6-dev=2.35-0ubuntu3.5 libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04
sudo apt-get install -y --allow-downgrades 'libc6=2.35-0ubuntu*' 'libc6-dev=2.35-0ubuntu*' libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04
- name: Checkout Repository
uses: actions/checkout@v4
@@ -69,12 +76,18 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Prepare Artifact Metadata
id: artifact-metadata
shell: bash
env:
OS: linux
ARCH: ${{ inputs.platform }}
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
@@ -107,7 +120,7 @@ jobs:
- name: Build Dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: .github/workflows/scripts/linux/build-dependencies-qt.sh
run: .github/workflows/scripts/linux/build-dependencies-qt.sh "$HOME/deps"
- name: Download patches
run: |
@@ -142,7 +155,7 @@ jobs:
- name: Upload artifact
if: inputs.buildAppImage == true
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts
+16 -2
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -18,6 +21,10 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_macos:
@@ -44,6 +51,13 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Use Xcode 14.3.1
run: sudo xcode-select -s /Applications/Xcode_14.3.1.app
@@ -51,7 +65,7 @@ jobs:
id: artifact-metadata
shell: bash
env:
OS: macos
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
@@ -149,7 +163,7 @@ jobs:
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS.tar.xz
- name: Upload Artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: "*.tar.xz"
+1
View File
@@ -15,4 +15,5 @@ jobs:
uses: ./.github/workflows/macos_build.yml
with:
jobName: "MacOS Build"
artifactPrefixName: "PCSX2-macos-Qt"
secrets: inherit
+35 -16
View File
@@ -10,16 +10,19 @@ on:
push:
branches:
- master
# TODO - future work
# workflow_dispatch:
# inputs:
# isStable:
# description: 'Should it be a stable release?'
# required: true
# default: 'false'
# versionTag:
# description: 'The version to tag with'
# required: true
workflow_dispatch:
inputs:
is_prelease:
description: 'Should be a pre-release?'
required: true
default: 'true'
type: choice
options:
- 'true'
- 'false'
tag_value:
description: 'Create a new release from latest master with the given tag, if this is left blank it will bump the patch version. You dont need to include the "v" prefix'
required: false
permissions:
contents: write
@@ -42,6 +45,8 @@ jobs:
github_token: ${{ github.token }}
tag_prefix: v
default_bump: patch
# if set, it will overwrite the bump settings
custom_tag: ${{ github.event.inputs.tag_value == '' && null || github.event.inputs.tag_value }}
# TODO - we could do this and remove the node.js script, but auto-generated notes only work
# with PRs -- not commits (determine how much we care).
@@ -62,9 +67,18 @@ jobs:
node index.js
mv ./release-notes.md ${GITHUB_WORKSPACE}/release-notes.md
- name: Create a GitHub Release
- name: Create a GitHub Release (Manual)
uses: softprops/action-gh-release@v1
if: steps.tag_version.outputs.new_tag
if: steps.tag_version.outputs.new_tag && github.event_name == 'workflow_dispatch'
with:
body_path: ./release-notes.md
draft: true
prerelease: ${{ github.event_name != 'workflow_dispatch' || inputs.is_prelease == 'true' }}
tag_name: ${{ steps.tag_version.outputs.new_tag }}
- name: Create a GitHub Release (Push)
uses: softprops/action-gh-release@v1
if: steps.tag_version.outputs.new_tag && github.event_name != 'workflow_dispatch'
with:
body_path: ./release-notes.md
draft: true
@@ -81,10 +95,12 @@ jobs:
uses: ./.github/workflows/linux_build_qt.yml
with:
jobName: "AppImage Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-appimage"
compiler: clang
cmakeflags: ""
buildAppImage: true
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
build_linux_flatpak:
@@ -95,11 +111,13 @@ jobs:
uses: ./.github/workflows/linux_build_flatpak.yml
with:
jobName: "Flatpak Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
branch: "stable"
publish: false
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
# Windows
@@ -111,10 +129,12 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "Windows Build"
artifactPrefixName: "PCSX2-windows-Qt-x64"
configuration: CMake
buildSystem: cmake
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
# MacOS
@@ -126,7 +146,9 @@ jobs:
uses: ./.github/workflows/macos_build.yml
with:
jobName: "MacOS Build"
artifactPrefixName: "PCSX2-macos-Qt"
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
# Upload the Artifacts
@@ -146,7 +168,7 @@ jobs:
- name: Prepare Artifact Folder
run: mkdir ./ci-artifacts/
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
name: Download all Artifacts
with:
path: ./ci-artifacts/
@@ -159,9 +181,6 @@ jobs:
working-directory: ./ci-artifacts/
run: for d in *windows*/; do 7z a "${d}asset.7z" ./$d/*; done
# Artifact Naming:
# MacOS: PCSX2-<tag>-macOS-[additional hyphen seperated tags]
# Windows|Linux: PCSX2-<tag>-<windows|linux>-<32bit|64bit>--[additional hyphen seperated tags]
- name: Name and Upload the Release Assets
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -1,39 +1,26 @@
#!/bin/bash
# Artifact Naming Scheme:
# PCSX2-<OS>-Qt-[ARCH]-[SIMD]-[pr\[PR_NUM\]]-[title|sha\[SHA|PR_TITLE\]
# PCSX2-<OS>-Qt-[BUILD_SYSTEM]-[ARCH]-[SIMD]-[pr\[PR_NUM\]]-[title|sha\[SHA|PR_TITLE\]
# -- limited to 200 chars
# Outputs:
# - artifact-name
# Example - PCSX2-linux-Qt-x64-flatpak-sse4-sha[e880a2749]
# Inputs as env-vars
# OS
# BUILD_SYSTEM
# ARCH
# SIMD
# PREFIX
# EVENT_NAME
# PR_TITLE
# PR_NUM
# PR_SHA
NAME=""
if [ "${OS}" == "macos" ]; then
# MacOS has combined binaries for x64 and ARM64.
NAME="PCSX2-${OS}-Qt"
elif [[ ("${OS}" == "windows" && "$BUILD_SYSTEM" != "cmake") ]]; then
NAME="PCSX2-${OS}-Qt-${ARCH}-${SIMD}"
else
NAME="PCSX2-${OS}-Qt-${ARCH}"
if [[ -z "${PREFIX}" ]]; then
echo "PREFIX is not set, can't name artifact without it!"
exit 1
fi
# Add cmake if used to differentate it from msbuild builds
# Else the two artifacts will have the same name and the files will be merged
if [[ ! -z "${BUILD_SYSTEM}" ]]; then
if [[ "${BUILD_SYSTEM}" == "cmake" ]] || [[ "${BUILD_SYSTEM}" == "flatpak" ]]; then
NAME="${NAME}-${BUILD_SYSTEM}"
fi
fi
NAME="${PREFIX}"
# Add PR / Commit Metadata
if [ "$EVENT_NAME" == "pull_request" ]; then
@@ -2,7 +2,12 @@
set -e
INSTALLDIR="$HOME/deps"
if [ "$#" -ne 1 ]; then
echo "Syntax: $0 <output directory>"
exit 1
fi
INSTALLDIR="$1"
NPROCS="$(getconf _NPROCESSORS_ONLN)"
SDL=SDL2-2.28.5
QT=6.6.1
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
if: github.repository == 'PCSX2/pcsx2'
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4.3.0
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -27,6 +27,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "MSVC SSE4"
artifactPrefixName: "PCSX2-windows-Qt-x64-sse4-msvc"
configuration: Release
simd: "SSE4"
secrets: inherit
@@ -38,6 +39,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "MSVC AVX2"
artifactPrefixName: "PCSX2-windows-Qt-x64-avx2-msvc"
configuration: Release AVX2
secrets: inherit
@@ -47,6 +49,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "CMake MSVC"
artifactPrefixName: "PCSX2-windows-Qt-x64-cmake-msvc"
configuration: CMake
buildSystem: cmake
secrets: inherit
@@ -58,6 +61,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "Clang SSE4"
artifactPrefixName: "PCSX2-windows-Qt-x64-sse4-clang"
configuration: Release Clang
simd: "SSE4"
secrets: inherit
@@ -69,6 +73,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "Clang AVX2"
artifactPrefixName: "PCSX2-windows-Qt-x64-avx2-clang"
configuration: Release Clang AVX2
secrets: inherit
@@ -78,6 +83,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "CMake Clang"
artifactPrefixName: "PCSX2-windows-Qt-x64-cmake-clang"
configuration: CMake
buildSystem: cmake
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DPCSX2_EXE_NAME=pcsx2-qt-clang
+17 -6
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -37,6 +40,10 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_windows_qt:
@@ -58,14 +65,18 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Prepare Artifact Metadata
id: artifact-metadata
shell: bash
env:
OS: windows
BUILD_SYSTEM: ${{ inputs.buildSystem }}
ARCH: ${{ inputs.platform }}
SIMD: ${{ inputs.simd }}
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
@@ -122,7 +133,7 @@ jobs:
cmake --build build --config Release --target unittests
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: |
@@ -136,7 +147,7 @@ jobs:
!./bin/**/*.lib
- name: Upload artifact - with symbols
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}-symbols
path: ./bin/**/*.pdb
+17
View File
@@ -56,6 +56,9 @@ oprofile_data/
/UpgradeLog*.htm
/.vscode*
# Jetbrains Rider
/.idea*
# KDevelop 4 Workspace Configuration Files
*.kdev4
/.kdev4*
@@ -71,11 +74,13 @@ oprofile_data/
/bin/bios
/bin/cache
/bin/cheats
/bin/patches
/bin/covers
/bin/dumps
/bin/gamesettings
/bin/help
/bin/inis
/bin/inis/debuggersettings
/bin/logs
/bin/memcards
/bin/plugins
@@ -84,6 +89,18 @@ oprofile_data/
/bin/textures
/bin/translations
/bin/inputprofiles
/bin/videos
/bin/portable.ini
# Manually added by user.
/bin/resources/patches.zip
# Resources that are runtime downloaded.
/bin/resources/fonts/NotoSansJP-Regular.ttf
/bin/resources/fonts/NotoSansKR-Regular.ttf
/bin/resources/fonts/NotoSansSC-Regular.ttf
/bin/resources/fonts/NotoSansTC-Regular.ttf
/deps
/ipch
+1
View File
@@ -706,6 +706,7 @@
#define ICON_FA_RANDOM "\xef\x81\xb4" // U+f074
#define ICON_FA_RECEIPT "\xef\x95\x83" // U+f543
#define ICON_FA_RECORD_VINYL "\xef\xa3\x99" // U+f8d9
#define ICON_FA_RECORDING "\xef\xa3\x9a" // U+f8da
#define ICON_FA_RECYCLE "\xef\x86\xb8" // U+f1b8
#define ICON_FA_REDO "\xef\x80\x9e" // U+f01e
#define ICON_FA_REDO_ALT "\xef\x8b\xb9" // U+f2f9
+11 -1
View File
@@ -329,4 +329,14 @@
#define ICON_PF_SHOPPING_CART "\xF0\x9F\x9B\x92"
#define ICON_PF_ACHIEVEMENTS_PROGRESSION "\xE2\x8E\xB2"
#define ICON_PF_ACHIEVEMENTS_MISSABLE "\xE2\x8E\xB3"
#define ICON_PF_ACHIEVEMENTS_WIN "\xE2\x8E\xB4"
#define ICON_PF_ACHIEVEMENTS_WIN "\xE2\x8E\xB4"
#define ICON_PF_POPN_CONTROLLER "\xE2\x8A\xBF"
#define ICON_PF_POPN_BLUE_LEFT "\xE2\x8B\x80"
#define ICON_PF_POPN_BLUE_RIGHT "\xE2\x8B\x81"
#define ICON_PF_POPN_GREEN_LEFT "\xE2\x8B\x82"
#define ICON_PF_POPN_GREEN_RIGHT "\xE2\x8B\x83"
#define ICON_PF_POPN_YELLOW_LEFT "\xE2\x8B\x84"
#define ICON_PF_POPN_YELLOW_RIGHT "\xE2\x8B\x85"
#define ICON_PF_POPN_WHITE_LEFT "\xE2\x8B\x86"
#define ICON_PF_POPN_WHITE_RIGHT "\xE2\x8B\x87"
#define ICON_PF_POPN_RED "\xE2\x8B\x88"
+91
View File
@@ -0,0 +1,91 @@
This Font Software is licensed under the SIL Open Font License,
Version 1.1. This license is copied below, and is also available
with a FAQ at <http://scripts.sil.org/OFL>
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+33619
View File
File diff suppressed because it is too large Load Diff
+6 -40
View File
@@ -10,51 +10,17 @@ PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to
## Project Details
The PCSX2 project has been running for more than twenty years. Past versions could only run a few public domain game demos, but newer versions can run most games at full speed, including popular titles such as Final Fantasy X and Devil May Cry 3. Visit the [PCSX2 compatibility list](https://pcsx2.net/compat/) to check the latest compatibility status of games (with more than 2500 titles tested), or ask for help in the [official forums](https://forums.pcsx2.net/).
PCSX2 has been in development for more than 20 years. Past versions could only run a few public domain game demos, but newer versions can run most games at full speed, including popular titles such as Final Fantasy X and Devil May Cry 3. Visit the [PCSX2 compatibility list](https://pcsx2.net/compat/) to check the latest compatibility status of games (with more than 2500 titles tested).
The latest officially released stable version is version 1.6.0.
Installers and binaries for both stable and development builds are available from [our website](https://pcsx2.net/downloads/).
Installers and binaries for both stable and nightly builds are available from [our website](https://pcsx2.net/downloads/).
## System Requirements
### Minimum
PCSX2 supports Windows, Linux, and Mac platforms. Our [setup documentation page](https://pcsx2.net/docs/usage/setup/requirements) contains additional details on software and hardware requirements.
| Operating System | CPU | GPU | RAM |
| ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| - Windows 10 Version 1809 or later (64-bit) <br/> - Ubuntu 22.04/Debian or newer, Arch Linux, or other distro (64-bit) <br/> - macOS 11.0 | - Supports SSE4.1 <br/> - [PassMark Thread Performance](https://www.cpubenchmark.net/CPU_mega_page.html) rating near or greater than 1500<br/> - Two physical cores, with hyperthreading | - Direct3D 11 (Feature Level 11.0) support <br/> - OpenGL 3.3 support <br/> - Vulkan 1.1 support <br/> - Metal support <br/> - [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 3000 (Geforce GTX 750, Radeon RX 560, Intel Arc A380) <br/> - 2 GB Video Memory | 4 GB |
### Recommended
| Operating System | CPU | GPU | RAM |
| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| - Windows 10 22H2 (64-bit) <br/> - Ubuntu 23.04/Debian or newer, Arch Linux, or other distro (64-bit) <br/> - macOS 11.0 | - Supports AVX2 <br/> - [PassMark Single Thread Performance](https://www.cpubenchmark.net/CPU_mega_page.html) rating near or greater than 2000<br/> - Four physical cores, with or without hyperthreading | - Direct3D12 support <br/> - OpenGL 4.6 support <br/> - Vulkan 1.3 support <br/> - Metal support <br/> - [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 6000 (GeForce GTX 1650, Radeon RX 570) <br/> - 4 GB Video Memory | 8 GB |
### Heavy
| Operating System | CPU | GPU | RAM |
| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| - Windows 10 22H2 (64-bit) <br/> - Ubuntu 23.04/Debian or newer, Arch Linux, or other distro (64-bit) <br/> - macOS 11.0 | - Supports AVX2 <br/> - [PassMark Single Thread Performance](https://www.cpubenchmark.net/CPU_mega_page.html) rating near or greater than 2600<br/> - Four physical cores, with or without hyperthreading | - Direct3D12 support <br/> - OpenGL 4.6 support <br/> - Vulkan 1.3 support <br/> - Metal support <br/> - [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 6000 (GeForce GTX 1650, Radeon RX 570) <br/> - 4 GB Video Memory | 8 GB |
_Note1: Recommended Single Thread Performance is based on moderately complex games. Games that pushed the PS2 hardware to its limits will struggle on CPUs at this level. Some release titles and 2D games which underutilized the PS2 hardware may run on CPUs rated as low as 1200. A quick reference for CPU **intensive games**: [Wiki](https://wiki.pcsx2.net/Category:CPU_intensive_games), [Forum](https://forums.pcsx2.net/Thread-LIST-The-Most-CPU-Intensive-Games) and CPU **light** games: [Forum](https://forums.pcsx2.net/Thread-LIST-Games-that-don-t-need-a-strong-CPU-to-emulate)_
_Note2: Recommended GPU is based on 3x Internal, ~1080p resolution requirements. Higher resolutions will require stronger cards; 6x Internal, ~4K resolution will require a [PassMark G3D Mark](https://www.videocardbenchmark.net/high_end_gpus.html) rating around 12000 (GeForce RTX 2060 Radeon RX 6600 Intel Arc A750). Just like CPU requirements, this is also highly game dependent. A quick reference for GPU **intensive games**: [Wiki](https://wiki.pcsx2.net/Category:GPU_intensive_games)_
## Technical Notes
- You need the [Visual C++ 2019 x64 Redistributables](https://support.microsoft.com/en-us/help/2977003/) to run PCSX2 on Windows.
- Windows XP and Direct3D9 support was dropped after stable release 1.4.0.
- Windows 7, Windows 8.0, and Windows 8.1 support was dropped after stable release 1.6.0.
- 32-bit and wxWidgets support was dropped after stable release 1.6.0, with the wxWidgets code being removed completely on 25th December 2022.
- Make sure to update your operating system and drivers to ensure you have the best experience possible. Having a newer GPU is also recommended so you have the latest supported drivers.
- Because of copyright issues, and the complexity of trying to work around it, you need a BIOS dump extracted from a legitimately-owned PS2 console to use the emulator. For more information about the BIOS and how to get it from your console, visit [this page](pcsx2/Docs/PCSX2_FAQ.md#question-13-where-do-i-get-a-ps2-bios).
- PCSX2 uses two CPU cores for emulation by default. A third core can be used via the MTVU speed hack, which is compatible with most games. This can be a significant speedup on CPUs with 3+ cores, but it may be a slowdown on GS-limited games (or on CPUs with fewer than 2 cores). Software renderers will then additionally use however many rendering threads it is set to and will need higher core counts to run efficiently.
- Requirements benchmarks are based on a statistic from the Passmark CPU bench marking software. When we say "STR", we are referring to Passmark's "Single Thread Rating" statistic. You can look up your CPU on [Passmark's website for CPUs](https://cpubenchmark.net) to see how it compares to PCSX2's requirements.
- Vulkan requires an up-to-date GPU driver; old drivers may cause graphical problems.
Please note that a BIOS dump from a legitimately-owned PS2 console is required to use the emulator. For more information, visit [this page](https://pcsx2.net/docs/usage/setup/gather/#how-to-dump-your-ps2-bios).
## Contributing / Building
Want to contribute to the development of PCSX2?
PCSX2 supports translation into other languages using [Crowdin](https://crowdin.com/project/pcsx2-emulator).
See the [Contributing Guidelines](https://github.com/PCSX2/pcsx2/blob/master/.github/CONTRIBUTING.md) or visit the [GitHub Wiki](https://github.com/PCSX2/pcsx2/wiki)
Want more? [Check out the PCSX2 website](https://pcsx2.net/).
See the [Contributing Guidelines](https://github.com/PCSX2/pcsx2/blob/master/.github/CONTRIBUTING.md) or visit the [GitHub Wiki](https://github.com/PCSX2/pcsx2/wiki) for more info on how to contribute.
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
RunWizard=0
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
+20 -7
View File
@@ -314,6 +314,7 @@
03000000242e00000b20000000000000,Hyperkin Admiral N64 Controller,+rightx:b11,+righty:b13,-rightx:b8,-righty:b12,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,platform:Windows,
03000000242e0000ff0b000000000000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Windows,
03000000790000004e95000000000000,Hyperkin N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Windows,
03000000242e00006a48000000000000,Hyperkin RetroN Sq,a:b3,b:b7,back:b5,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b0,rightshoulder:b1,start:b4,x:b2,y:b6,platform:Windows,
03000000242e00006a38000000000000,Hyperkin Trooper 2,a:b0,b:b1,back:b4,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b3,start:b5,platform:Windows,
03000000d81d00000e00000000000000,iBuffalo AC02 Arcade Joystick,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,rightx:a2,righty:a5,start:b8,x:b4,y:b5,platform:Windows,
03000000d81d00000f00000000000000,iBuffalo BSGP1204 Series,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:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
@@ -352,7 +353,7 @@
030000006d040000d2ca000000000000,Logitech Cordless Precision,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,
030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,
030000006d04000016c2000000000000,Logitech Dual Action,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:Windows,
030000006d0400001dc2000000000000,Logitech F310,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000006d0400001dc2000000000000,Logitech F310,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,
030000006d04000018c2000000000000,Logitech F510,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:Windows,
030000006d0400001ec2000000000000,Logitech F510,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000006d04000019c2000000000000,Logitech F710,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:Windows,
@@ -452,7 +453,7 @@
03000000550900001072000000000000,NVIDIA Shield,a:b9,b:b8,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b3,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b2,righttrigger:a4,rightx:a2,righty:a5,start:b0,x:b7,y:b6,platform:Windows,
030000005509000000b4000000000000,NVIDIA Virtual,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:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000120c00000288000000000000,Nyko Air Flo Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
030000004b120000014d000000000000,Nyko Airflo,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,rightshoulder:b5,rightstick:a2,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows,
030000004b120000014d000000000000,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:Windows,
03000000d62000001d57000000000000,Nyko Airflo 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,
03000000791d00000900000000000000,Nyko Playpad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000782300000a10000000000000,Onlive Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows,
@@ -461,7 +462,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,
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:b13,rightx:a3,righty:a4,x:b1,y:b2,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,
030000006f0e00008501000000000000,PDP Fightpad Pro 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,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
@@ -534,6 +535,7 @@
030000004c050000a00b000000000000,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,x:b0,y:b3,platform:Windows,
030000004c050000c405000000000000,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,
030000004c050000cc09000000000000,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,x:b0,y:b3,platform:Windows,
030000004c0500005f0e000000000000,PS5 Access 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
030000004c050000e60c000000000000,PS5 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
030000004c050000f20d000000000000,PS5 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
03000000830500005020000000000000,PSX,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Windows,
@@ -558,8 +560,8 @@
030000009b2800003200000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,
030000009b2800006000000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,
030000009b2800001800000000000000,Raphnet Jaguar Adapter,a:b2,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b10,start:b3,x:b11,y:b12,platform:Windows,
030000009b2800006300000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,
030000009b2800006100000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,
030000009b2800006300000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,
030000009b2800000200000000000000,Raphnet NES Adapter,a:b7,b:b6,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b4,platform:Windows,
030000009b2800004400000000000000,Raphnet PS1 and PS2 Adapter,a:b1,b:b2,back:b5,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b9,rightx:a3,righty:a4,start:b4,x:b0,y:b3,platform:Windows,
030000009b2800004300000000000000,Raphnet Saturn,a:b0,b:b1,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
@@ -611,6 +613,7 @@
03000000050b00001a1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
03000000050b00001c1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
030000004f04000001d0000000000000,Rumble Force,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
030000000d0f0000ad00000000000000,RX Gamepad,a:b0,b:b4,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,rightshoulder:b6,start:b9,x:b2,y:b1,platform:Windows,
030000008916000000fe000000000000,Sabertooth,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,
03000000c6240000045d000000000000,Sabertooth,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,
03000000a30600001af5000000000000,Saitek Cyborg,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:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
@@ -677,6 +680,7 @@
03000000457500002211000000000000,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:Windows,
030000004f0400000ab1000000000000,T16000M,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b10,x:b2,y:b3,platform:Windows,
030000000d0f00007b00000000000000,TAC GEAR,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:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000e40a00000307000000000000,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:Windows,
03000000e40a00000207000000000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b9,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Windows,
03000000d814000001a0000000000000,TE Kitty,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:Windows,
03000000fa1900000706000000000000,Team 5,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,
@@ -964,6 +968,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000004c050000c405000000000000,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,x:b0,y:b3,platform:Mac OS X,
030000004c050000c405000000010000,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,x:b0,y:b3,platform:Mac OS X,
030000004c050000cc09000000010000,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,x:b0,y:b3,platform:Mac OS X,
0300004b4c0500005f0e000000010000,PS5 Access 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
030000004c050000e60c000000010000,PS5 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
030000004c050000f20d000000010000,PS5 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
050000004c050000e60c000000010000,PS5 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:Mac OS X,
@@ -1016,6 +1021,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
030000000d0f0000f600000000010000,Switch Hori Pad,a:b0,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:b2,y:b3,platform:Mac OS X,
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,
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,
@@ -1167,14 +1174,15 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c21100000791000011010000,Be1 GC101 Controller 1.03,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:Linux,
03000000c31100000791000011010000,Be1 GC101 Controller 1.03,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,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:Linux,
030000005e0400008e02000003030000,Be1 GC101 Xbox 360,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,
03000000bc2000004d50000011010000,BEITONG A1T2 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000bc2000000055000001000000,BETOP AX1 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000bc2000004d50000011010000,Beitong A1T2 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000bc2000000055000001000000,Betop AX1 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000bc2000006412000011010000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b30,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:Linux,
030000006b1400000209000011010000,Bigben,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,
03000000120c0000200e000011010000,Brook Mars 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,x:b0,y:b3,platform:Linux,
03000000120c0000210e000011010000,Brook Mars 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:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000120c0000f70e000011010000,Brook Universal Fighting Board,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,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
03000000e82000006058000001010000,Cideko AK08b,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:Linux,
03000000af1e00002400000010010000,Clockwork Pi DevTerm,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b9,x:b3,y:b0,platform:Linux,
030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux,
03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,
03000000a306000022f6000011010000,Cyborg V3 Rumble,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:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
@@ -1258,6 +1266,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000242e00008816000001010000,Hyperkin X91,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,
03000000f00300008d03000011010000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,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:Linux,
03000000830500006020000010010000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
030000008f0e00001330000001010000,iCode Retro Adapter,b:b3,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b1,start:b7,x:b2,y:b0,platform:Linux,
050000006964726f69643a636f6e0000,idroidcon 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,
03000000b50700001503000010010000,Impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
03000000d80400008200000003000000,IMS PCU0,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,platform:Linux,
@@ -1391,6 +1400,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,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:Linux,
05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,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:Linux,
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:Linux,
03000000451300000830000010010000,NYKO CORE,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:a5,start:b9,x:b0,y:b3,platform:Linux,
19000000010000000100000001010000,ODROID Go 2,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux,
19000000010000000200000011000000,ODROID Go 2,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux,
@@ -1409,7 +1419,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006f0e0000c802000012010000,PDP Kingdom Hearts 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,
030000006f0e00002801000011010000,PDP PS3 Rock Candy 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,
030000006f0e00000901000011010000,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:Linux,
030000006f0e00002f01000011010000,PDP Wired PS3 Controller,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,
030000006f0e00002f01000011010000,PDP Wired 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:Linux,
03000000ad1b000004f9000000010000,PDP Xbox 360 Versus Fighting,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,
030000006f0e0000a802000023020000,PDP Xbox One Controller,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:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
030000006f0e0000a702000023020000,PDP Xbox One Raven Black,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,
@@ -1466,6 +1476,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000004c050000cc09000000010000,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,x:b0,y:b3,platform:Linux,
050000004c050000cc09000000810000,PS4 Controller,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:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
050000004c050000cc09000001800000,PS4 Controller,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:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
0300004b4c0500005f0e000011010000,PS5 Access 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
030000004c050000e60c000011010000,PS5 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
030000004c050000e60c000011810000,PS5 Controller,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:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
030000004c050000f20d000011010000,PS5 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,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
@@ -1568,6 +1579,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000457500000401000011010000,SZMY Power DS4 Wired 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,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000457500002211000010010000,SZMY Power Gamepad,a:b2,b:b1,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:b3,y:b0,platform:Linux,
030000008f0e00001431000010010000,SZMY Power 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,
03000000e40a00000307000011010000,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:Linux,
03000000e40a00000207000011010000,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:Linux,
03000000ba2200000701000001010000,Technology Innovation PS2 Adapter,a:b0,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:a5,righty:a2,start:b9,x:b3,y:b2,platform:Linux,
03000000790000001c18000011010000,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000591c00002400000010010000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 98 KiB

+77 -43
View File
@@ -631,7 +631,7 @@ float4 sample_color(float2 st, float uv_w)
}
else if(PS_AEM_FMT == FMT_16)
{
c[i].a = c[i].a >= 0.5 ? TA.y : !PS_AEM || any(c[i].rgb) ? TA.x : 0;
c[i].a = c[i].a >= 0.5 ? TA.y : !PS_AEM || any(int3(c[i].rgb * 255.0f) & 0xF8) ? TA.x : 0;
}
}
@@ -742,6 +742,25 @@ float4 ps_color(PS_INPUT input)
float4 T = sample_color(st, input.t.w);
#endif
if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC)
{
uint4 denorm_c_before = uint4(T);
if (PS_READ_BA)
{
T.r = float((denorm_c_before.b << 3) & 0xF8);
T.g = float(((denorm_c_before.b >> 2) & 0x38) | ((denorm_c_before.a << 6) & 0xC0));
T.b = float((denorm_c_before.a << 1) & 0xF8);
T.a = float(denorm_c_before.a & 0x80);
}
else
{
T.r = float((denorm_c_before.r << 3) & 0xF8);
T.g = float(((denorm_c_before.r >> 2) & 0x38) | ((denorm_c_before.g << 6) & 0xC0));
T.b = float((denorm_c_before.g << 1) & 0xF8);
T.a = float(denorm_c_before.g & 0x80);
}
}
float4 C = tfx(T, input.c);
atst(C);
@@ -925,48 +944,6 @@ PS_OUTPUT ps_main(PS_INPUT input)
discard;
}
if (PS_SHUFFLE)
{
uint4 denorm_c = uint4(C);
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
// Special case for 32bit input and 16bit output, shuffle used by The Godfather
if (PS_SHUFFLE_SAME)
{
if (PS_READ_BA)
C = (float4)(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u)));
else
C.ga = C.rg;
}
// Copy of a 16bit source in to this target
else if (PS_READ16_SRC)
{
C.rb = (float2)float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5));
if (denorm_c.a & 0x80u)
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u));
else
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u));
}
// Write RB part. Mask will take care of the correct destination
else if (PS_READ_BA)
{
C.rb = C.bb;
if (denorm_c.a & 0x80u)
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
}
else
{
C.rb = C.rr;
if (denorm_c.g & 0x80u)
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
}
}
// Must be done before alpha correction
// AA (Fixed one) will output a coverage of 1.0 as alpha
@@ -1023,6 +1000,63 @@ PS_OUTPUT ps_main(PS_INPUT input)
ps_blend(C, alpha_blend, input.p.xy);
if (PS_SHUFFLE)
{
if (!PS_SHUFFLE_SAME && !PS_READ16_SRC)
{
uint4 denorm_c_after = uint4(C);
if (PS_READ_BA)
{
C.b = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
C.a = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
}
else
{
C.r = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
C.g = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
}
}
uint4 denorm_c = uint4(C);
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
// Special case for 32bit input and 16bit output, shuffle used by The Godfather
if (PS_SHUFFLE_SAME)
{
if (PS_READ_BA)
C = (float4)(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u)));
else
C.ga = C.rg;
}
// Copy of a 16bit source in to this target
else if (PS_READ16_SRC)
{
C.rb = (float2)float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5));
if (denorm_c.a & 0x80u)
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u));
else
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u));
}
// Write RB part. Mask will take care of the correct destination
else if (PS_READ_BA)
{
C.rb = C.bb;
if (denorm_c.a & 0x80u)
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
}
else
{
C.rb = C.rr;
if (denorm_c.g & 0x80u)
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
}
}
ps_dither(C.rgb, input.p.xy);
// Color clamp/wrap needs to be done after sw blending and dithering
+65 -38
View File
@@ -580,7 +580,7 @@ vec4 sample_color(vec2 st)
c[i].a = ( (PS_AEM == 0) || any(bvec3(c[i].rgb)) ) ? TA.x : 0.0f;
//c[i].a = ( (PS_AEM == 0) || (sum > 0.0f) ) ? TA.x : 0.0f;
#elif (PS_AEM_FMT == FMT_16)
c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || any(bvec3(c[i].rgb)) ) ? TA.x : 0.0f;
c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || any(bvec3(ivec3(c[i].rgb * 255.0f) & ivec3(0xF8))) ) ? TA.x : 0.0f;
//c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || (sum > 0.0f) ) ? TA.x : 0.0f;
#endif
}
@@ -687,6 +687,21 @@ vec4 ps_color()
vec4 T = sample_color(st);
#endif
#if PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC
uvec4 denorm_c_before = uvec4(T);
#if PS_READ_BA
T.r = float((denorm_c_before.b << 3) & 0xF8);
T.g = float(((denorm_c_before.b >> 2) & 0x38) | ((denorm_c_before.a << 6) & 0xC0));
T.b = float((denorm_c_before.a << 1) & 0xF8);
T.a = float(denorm_c_before.a & 0x80);
#else
T.r = float((denorm_c_before.r << 3) & 0xF8);
T.g = float(((denorm_c_before.r >> 2) & 0x38) | ((denorm_c_before.g << 6) & 0xC0));
T.b = float((denorm_c_before.g << 1) & 0xF8);
T.a = float(denorm_c_before.g & 0x80);
#endif
#endif
vec4 C = tfx(T, PSin.c);
atst(C);
@@ -937,7 +952,56 @@ void ps_main()
vec4 C = ps_color();
// Must be done before alpha correction
// AA (Fixed one) will output a coverage of 1.0 as alpha
#if PS_FIXED_ONE_A
C.a = 128.0f;
#endif
#if SW_AD_TO_HW
vec4 RT = trunc(fetch_rt() * 255.0f + 0.1f);
vec4 alpha_blend = vec4(RT.a / 128.0f);
#else
vec4 alpha_blend = vec4(C.a / 128.0f);
#endif
// Correct the ALPHA value based on the output format
#if (PS_DST_FMT == FMT_16)
float A_one = 128.0f; // alpha output will be 0x80
C.a = (PS_FBA != 0) ? A_one : step(128.0f, C.a) * A_one;
#elif (PS_DST_FMT == FMT_32) && (PS_FBA != 0)
if(C.a < 128.0f) C.a += 128.0f;
#endif
// Get first primitive that will write a failling alpha value
#if PS_DATE == 1
// DATM == 0
// Pixel with alpha equal to 1 will failed (128-255)
SV_Target0 = (C.a > 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF);
return;
#elif PS_DATE == 2
// DATM == 1
// Pixel with alpha equal to 0 will failed (0-127)
SV_Target0 = (C.a < 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF);
return;
#endif
ps_blend(C, alpha_blend);
#if PS_SHUFFLE
#if !PS_SHUFFLE_SAME && !PS_READ16_SRC
uvec4 denorm_c_after = uvec4(C);
#if PS_READ_BA
C.b = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
C.a = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
#else
C.r = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
C.g = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
#endif
#endif
uvec4 denorm_c = uvec4(C);
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
@@ -991,43 +1055,6 @@ void ps_main()
#endif // PS_SHUFFLE_SAME
#endif // PS_SHUFFLE
// Must be done before alpha correction
// AA (Fixed one) will output a coverage of 1.0 as alpha
#if PS_FIXED_ONE_A
C.a = 128.0f;
#endif
#if SW_AD_TO_HW
vec4 RT = trunc(fetch_rt() * 255.0f + 0.1f);
vec4 alpha_blend = vec4(RT.a / 128.0f);
#else
vec4 alpha_blend = vec4(C.a / 128.0f);
#endif
// Correct the ALPHA value based on the output format
#if (PS_DST_FMT == FMT_16)
float A_one = 128.0f; // alpha output will be 0x80
C.a = (PS_FBA != 0) ? A_one : step(128.0f, C.a) * A_one;
#elif (PS_DST_FMT == FMT_32) && (PS_FBA != 0)
if(C.a < 128.0f) C.a += 128.0f;
#endif
// Get first primitive that will write a failling alpha value
#if PS_DATE == 1
// DATM == 0
// Pixel with alpha equal to 1 will failed (128-255)
SV_Target0 = (C.a > 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF);
return;
#elif PS_DATE == 2
// DATM == 1
// Pixel with alpha equal to 0 will failed (0-127)
SV_Target0 = (C.a < 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF);
return;
#endif
ps_blend(C, alpha_blend);
ps_dither(C.rgb);
// Color clamp/wrap needs to be done after sw blending and dithering
+61 -36
View File
@@ -817,7 +817,7 @@ vec4 sample_color(vec2 st)
#if (PS_AEM_FMT == FMT_24)
c[i].a = (PS_AEM == 0 || any(bvec3(c[i].rgb))) ? TA.x : 0.0f;
#elif (PS_AEM_FMT == FMT_16)
c[i].a = (c[i].a >= 0.5) ? TA.y : ((PS_AEM == 0 || any(bvec3(c[i].rgb))) ? TA.x : 0.0f);
c[i].a = (c[i].a >= 0.5) ? TA.y : ((PS_AEM == 0 || any(bvec3(ivec3(c[i].rgb * 255.0f) & ivec3(0xF8)))) ? TA.x : 0.0f);
#endif
}
@@ -933,6 +933,21 @@ vec4 ps_color()
vec4 T = sample_color(st);
#endif
#if PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC
uvec4 denorm_c_before = uvec4(T);
#if PS_READ_BA
T.r = float((denorm_c_before.b << 3) & 0xF8);
T.g = float(((denorm_c_before.b >> 2) & 0x38) | ((denorm_c_before.a << 6) & 0xC0));
T.b = float((denorm_c_before.a << 1) & 0xF8);
T.a = float(denorm_c_before.a & 0x80);
#else
T.r = float((denorm_c_before.r << 3) & 0xF8);
T.g = float(((denorm_c_before.r >> 2) & 0x38) | ((denorm_c_before.g << 6) & 0xC0));
T.b = float((denorm_c_before.g << 1) & 0xF8);
T.a = float(denorm_c_before.g & 0x80);
#endif
#endif
vec4 C = tfx(T, vsIn.c);
atst(C);
@@ -1184,40 +1199,6 @@ void main()
vec4 C = ps_color();
#if PS_SHUFFLE
uvec4 denorm_c = uvec4(C);
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
// Special case for 32bit input and 16bit output, shuffle used by The Godfather
#if PS_SHUFFLE_SAME
#if (PS_READ_BA)
C = vec4(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u)));
#else
C.ga = C.rg;
#endif
// Copy of a 16bit source in to this target
#elif PS_READ16_SRC
C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)));
if ((denorm_c.a & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)));
// Write RB part. Mask will take care of the correct destination
#elif PS_READ_BA
C.rb = C.bb;
if ((denorm_c.a & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
#else
C.rb = C.rr;
if ((denorm_c.g & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
#endif // PS_SHUFFLE_SAME
#endif // PS_SHUFFLE
// Must be done before alpha correction
// AA (Fixed one) will output a coverage of 1.0 as alpha
@@ -1254,9 +1235,53 @@ void main()
o_col0 = (C.a < 127.5f) ? vec4(gl_PrimitiveID) : vec4(0x7FFFFFFF);
#else
ps_blend(C, alpha_blend);
#if PS_SHUFFLE
#if !PS_SHUFFLE_SAME && !PS_READ16_SRC
uvec4 denorm_c_after = uvec4(C);
#if PS_READ_BA
C.b = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
C.a = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
#else
C.r = float(((denorm_c_after.r >> 3) & 0x1F) | ((denorm_c_after.g << 2) & 0xE0));
C.g = float(((denorm_c_after.g >> 6) & 0x3) | ((denorm_c_after.b >> 1) & 0x7C) | (denorm_c_after.a & 0x80));
#endif
#endif
uvec4 denorm_c = uvec4(C);
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
// Special case for 32bit input and 16bit output, shuffle used by The Godfather
#if PS_SHUFFLE_SAME
#if (PS_READ_BA)
C = vec4(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u)));
#else
C.ga = C.rg;
#endif
// Copy of a 16bit source in to this target
#elif PS_READ16_SRC
C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)));
if ((denorm_c.a & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)));
// Write RB part. Mask will take care of the correct destination
#elif PS_READ_BA
C.rb = C.bb;
if ((denorm_c.a & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
#else
C.rb = C.rr;
if ((denorm_c.g & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
#endif // PS_SHUFFLE_SAME
#endif // PS_SHUFFLE
ps_dither(C.rgb);
// Color clamp/wrap needs to be done after sw blending and dithering
+1 -17
View File
@@ -4,7 +4,6 @@ set(PCSX2_DEFS "")
#-------------------------------------------------------------------------------
# Misc option
#-------------------------------------------------------------------------------
option(DISABLE_BUILD_DATE "Disable including the binary compile date")
option(ENABLE_TESTS "Enables building the unit tests" ON)
option(LTO_PCSX2_CORE "Enable LTO/IPO/LTCG on the subset of pcsx2 that benefits most from it but not anything else")
option(USE_VTUNE "Plug VTUNE to profile GS JIT.")
@@ -195,22 +194,12 @@ endif()
# -Wno-attributes: "always_inline function might not be inlinable" <= real spam (thousand of warnings!!!)
# -Wno-missing-field-initializers: standard allow to init only the begin of struct/array in static init. Just a silly warning.
# Note: future GCC (aka GCC 5.1.1) has less false positive so warning could maybe put back
# -Wno-unused-function: warn for function not used in release build
# -Wno-unused-value: lots of warning for this kind of statements "0 && ...". There are used to disable some parts of code in release/dev build.
# -Wno-format*: Yeah, these need to be taken care of, but...
# -Wno-stringop-truncation: Who comes up with these compiler warnings, anyways?
# -Wno-stringop-overflow: Probably the same people as this one...
# -Wno-maybe-uninitialized: Lots of gcc warnings like "test.GSVector8i::<anonymous>.GSVector8i::<unnamed union>::m may be used uninitialized" if this is removed.
if (MSVC)
set(DEFAULT_WARNINGS)
else()
set(DEFAULT_WARNINGS -Wall -Wextra -Wno-attributes -Wno-unused-function -Wno-unused-parameter -Wno-missing-field-initializers -Wno-format -Wno-format-security -Wno-unused-value)
endif()
if (USE_GCC)
list(APPEND DEFAULT_WARNINGS -Wno-stringop-truncation -Wno-stringop-overflow -Wno-maybe-uninitialized )
set(DEFAULT_WARNINGS -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -Wno-missing-field-initializers)
endif()
if (USE_PGO_GENERATE OR USE_PGO_OPTIMIZE)
@@ -241,11 +230,6 @@ endif()
set(PCSX2_WARNINGS ${DEFAULT_WARNINGS})
if(DISABLE_BUILD_DATE)
message(STATUS "Disabling the inclusion of the binary compile date.")
list(APPEND PCSX2_DEFS DISABLE_BUILD_DATE)
endif()
#-------------------------------------------------------------------------------
# MacOS-specific things
#-------------------------------------------------------------------------------
+66 -50
View File
@@ -1,7 +1,12 @@
function(detect_operating_system)
message(STATUS "CMake Version: ${CMAKE_VERSION}")
message(STATUS "CMake System Name: ${CMAKE_SYSTEM_NAME}")
# LINUX wasn't added until CMake 3.25.
if (CMAKE_VERSION VERSION_LESS 3.25.0 AND CMAKE_SYSTEM_NAME MATCHES "Linux")
# Have to make it visible in this scope as well for below.
set(LINUX TRUE PARENT_SCOPE)
set(LINUX TRUE)
endif()
if(WIN32)
@@ -39,84 +44,92 @@ function(detect_compiler)
endfunction()
function(get_git_version_info)
set(PCSX2_WC_TIME 0)
set(PCSX2_GIT_REV "")
set(PCSX2_GIT_TAG "")
set(PCSX2_GIT_HASH "")
if (GIT_FOUND AND EXISTS ${PROJECT_SOURCE_DIR}/.git)
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} show -s --format=%ci HEAD
OUTPUT_VARIABLE PCSX2_WC_TIME
OUTPUT_STRIP_TRAILING_WHITESPACE)
# Output: "YYYY-MM-DD HH:MM:SS +HHMM" (last part is time zone, offset from UTC)
string(REGEX REPLACE "[%:\\-]" "" PCSX2_WC_TIME "${PCSX2_WC_TIME}")
string(REGEX REPLACE "([0-9]+) ([0-9]+).*" "\\1\\2" PCSX2_WC_TIME "${PCSX2_WC_TIME}")
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tags
OUTPUT_VARIABLE PCSX2_GIT_REV
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} tag --points-at HEAD
OUTPUT_VARIABLE PCSX2_GIT_TAG
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} tag --points-at HEAD --sort=version:refname
OUTPUT_VARIABLE PCSX2_GIT_TAG_LIST
RESULT_VARIABLE TAG_RESULT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
# CAUTION: There is a race here, this solves the problem of a commit being tagged multiple times (take the last tag)
# however, if simultaneous builds are pushing tags to the same commit you might get inconsistent results (it's a race)
#
# The easy solution is, don't do that, but just something to be aware of.
if(PCSX2_GIT_TAG_LIST AND TAG_RESULT EQUAL 0)
string(REPLACE "\n" ";" PCSX2_GIT_TAG_LIST "${PCSX2_GIT_TAG_LIST}")
if (PCSX2_GIT_TAG_LIST)
list(GET PCSX2_GIT_TAG_LIST -1 PCSX2_GIT_TAG)
message("Using tag: ${PCSX2_GIT_TAG}")
endif()
endif()
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
OUTPUT_VARIABLE PCSX2_GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} log -1 --format=%cd --date=local
OUTPUT_VARIABLE PCSX2_GIT_DATE
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
endif()
if ("${PCSX2_GIT_TAG}" MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" PCSX2_VERSION_LONG "${PCSX2_GIT_TAG}")
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" PCSX2_VERSION_SHORT "${PCSX2_GIT_TAG}")
elseif(PCSX2_GIT_REV)
set(PCSX2_VERSION_LONG "${PCSX2_GIT_REV}")
string(REGEX MATCH "[0-9]+\\.[0-9]+(\\.[0-9]+)?(-[a-z][a-z0-9]+)?" PCSX2_VERSION_SHORT "${PCSX2_VERSION_LONG}")
else()
set(PCSX2_VERSION_LONG "Unknown (git unavailable)")
set(PCSX2_VERSION_SHORT "Unknown")
endif()
if ("${PCSX2_WC_TIME}" STREQUAL "")
set(PCSX2_WC_TIME 0)
if (NOT PCSX2_GIT_REV)
EXECUTE_PROCESS(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
OUTPUT_VARIABLE PCSX2_GIT_REV
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
if (NOT PCSX2_GIT_REV)
set(PCSX2_GIT_REV "Unknown")
endif()
endif()
set(PCSX2_WC_TIME "${PCSX2_WC_TIME}" PARENT_SCOPE)
set(PCSX2_GIT_REV "${PCSX2_GIT_REV}" PARENT_SCOPE)
set(PCSX2_GIT_TAG "${PCSX2_GIT_TAG}" PARENT_SCOPE)
set(PCSX2_GIT_HASH "${PCSX2_GIT_HASH}" PARENT_SCOPE)
set(PCSX2_VERSION_LONG "${PCSX2_VERSION_LONG}" PARENT_SCOPE)
set(PCSX2_VERSION_SHORT "${PCSX2_VERSION_SHORT}" PARENT_SCOPE)
set(PCSX2_GIT_DATE "${PCSX2_GIT_DATE}" PARENT_SCOPE)
endfunction()
function(write_svnrev_h)
if(PCSX2_GIT_TAG)
if ("${PCSX2_GIT_TAG}" MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
file(WRITE ${CMAKE_BINARY_DIR}/common/include/svnrev.h
"#define SVN_REV ${PCSX2_WC_TIME}ll\n"
"#define GIT_TAG \"${PCSX2_GIT_TAG}\"\n"
"#define GIT_TAGGED_COMMIT 1\n"
"#define GIT_TAG_HI ${CMAKE_MATCH_1}\n"
"#define GIT_TAG_MID ${CMAKE_MATCH_2}\n"
"#define GIT_TAG_LO ${CMAKE_MATCH_3}\n"
"#define GIT_REV \"\"\n"
"#define GIT_HASH \"${PCSX2_GIT_HASH}\"\n"
)
else()
file(WRITE ${CMAKE_BINARY_DIR}/common/include/svnrev.h
"#define SVN_REV ${PCSX2_WC_TIME}ll\n"
"#define GIT_TAG \"${PCSX2_GIT_TAG}\"\n"
"#define GIT_TAGGED_COMMIT 1\n"
"#define GIT_REV \"\"\n"
"#define GIT_HASH \"${PCSX2_GIT_HASH}\"\n"
)
endif()
else()
file(WRITE ${CMAKE_BINARY_DIR}/common/include/svnrev.h
"#define SVN_REV ${PCSX2_WC_TIME}ll\n"
if ("${PCSX2_GIT_TAG}" MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)$")
file(WRITE ${CMAKE_BINARY_DIR}/common/include/svnrev.h
"#define GIT_TAG \"${PCSX2_GIT_TAG}\"\n"
"#define GIT_TAGGED_COMMIT 1\n"
"#define GIT_TAG_HI ${CMAKE_MATCH_1}\n"
"#define GIT_TAG_MID ${CMAKE_MATCH_2}\n"
"#define GIT_TAG_LO ${CMAKE_MATCH_3}\n"
"#define GIT_REV \"${PCSX2_GIT_TAG}\"\n"
"#define GIT_HASH \"${PCSX2_GIT_HASH}\"\n"
"#define GIT_DATE \"${PCSX2_GIT_DATE}\"\n"
)
elseif ("${PCSX2_GIT_REV}" MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)")
file(WRITE ${CMAKE_BINARY_DIR}/common/include/svnrev.h
"#define GIT_TAG \"${PCSX2_GIT_TAG}\"\n"
"#define GIT_TAGGED_COMMIT 0\n"
"#define GIT_TAG_HI ${CMAKE_MATCH_1}\n"
"#define GIT_TAG_MID ${CMAKE_MATCH_2}\n"
"#define GIT_TAG_LO ${CMAKE_MATCH_3}\n"
"#define GIT_REV \"${PCSX2_GIT_REV}\"\n"
"#define GIT_HASH \"${PCSX2_GIT_HASH}\"\n"
"#define GIT_DATE \"${PCSX2_GIT_DATE}\"\n"
)
else()
file(WRITE ${CMAKE_BINARY_DIR}/common/include/svnrev.h
"#define GIT_TAG \"${PCSX2_GIT_TAG}\"\n"
"#define GIT_TAGGED_COMMIT 0\n"
"#define GIT_TAG_HI 0\n"
"#define GIT_TAG_MID 0\n"
"#define GIT_TAG_LO 0\n"
"#define GIT_REV \"${PCSX2_GIT_REV}\"\n"
"#define GIT_HASH \"${PCSX2_GIT_HASH}\"\n"
"#define GIT_DATE \"${PCSX2_GIT_DATE}\"\n"
)
endif()
endfunction()
@@ -200,6 +213,9 @@ function(fixup_file_properties target)
if("${source}" MATCHES "\\.(inl|h)$")
set_source_files_properties("${source}" PROPERTIES XCODE_EXPLICIT_FILE_TYPE sourcecode.cpp.h)
endif()
if("${source}" MATCHES "\\.(qm)$")
set_source_files_properties("${source}" PROPERTIES XCODE_EXPLICIT_FILE_TYPE compiled)
endif()
# CMake makefile and ninja generators will attempt to share one PCH for both cpp and mm files
# That's not actually OK
if("${source}" MATCHES "\\.mm$")
+2
View File
@@ -23,6 +23,8 @@ namespace CocoaTools
std::optional<std::string> MoveToTrash(std::string_view file);
/// Launch the given application once this one quits
bool DelayedLaunch(std::string_view file);
/// Open a Finder window to the given URL
bool ShowInFinder(std::string_view file);
}
#endif // __APPLE__
+15 -2
View File
@@ -17,6 +17,11 @@
// MARK: - Metal Layers
static NSString*_Nonnull NSStringFromStringView(std::string_view sv)
{
return [[NSString alloc] initWithBytes:sv.data() length:sv.size() encoding:NSUTF8StringEncoding];
}
bool CocoaTools::CreateMetalLayer(WindowInfo* wi)
{
if (![NSThread isMainThread])
@@ -156,7 +161,7 @@ std::optional<std::string> CocoaTools::GetNonTranslocatedBundlePath()
std::optional<std::string> CocoaTools::MoveToTrash(std::string_view file)
{
NSURL* url = [NSURL fileURLWithPath:[[NSString alloc] initWithBytes:file.data() length:file.size() encoding:NSUTF8StringEncoding]];
NSURL* url = [NSURL fileURLWithPath:NSStringFromStringView(file)];
NSURL* new_url;
if (![[NSFileManager defaultManager] trashItemAtURL:url resultingItemURL:&new_url error:nil])
return std::nullopt;
@@ -170,9 +175,17 @@ bool CocoaTools::DelayedLaunch(std::string_view file)
[task setExecutableURL:[NSURL fileURLWithPath:@"/bin/sh"]];
[task setEnvironment:@{
@"WAITPID": [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]],
@"LAUNCHAPP": [[NSString alloc] initWithBytes:file.data() length:file.size() encoding:NSUTF8StringEncoding],
@"LAUNCHAPP": NSStringFromStringView(file),
}];
[task setArguments:@[@"-c", @"while /bin/ps -p $WAITPID > /dev/null; do /bin/sleep 0.1; done; exec /usr/bin/open \"$LAUNCHAPP\";"]];
return [task launchAndReturnError:nil];
}
}
// MARK: - Directory Services
bool CocoaTools::ShowInFinder(std::string_view file)
{
return [[NSWorkspace sharedWorkspace] selectFile:NSStringFromStringView(file)
inFileViewerRootedAtPath:nil];
}
+455 -470
View File
@@ -1,524 +1,509 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "common/Threading.h"
#include "common/TraceLog.h"
#include "common/Console.h"
#include "common/Assertions.h"
#include "common/RedtapeWindows.h" // OutputDebugString
#include "common/FileSystem.h"
#include "common/SmallString.h"
#include "common/Timer.h"
using namespace Threading;
#include "fmt/format.h"
// thread-local console indentation setting.
static thread_local int conlog_Indent(0);
#include <mutex>
#include <vector>
// thread-local console color storage.
static thread_local ConsoleColors conlog_Color(DefaultConsoleColor);
#ifdef __POSIX__
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#else
#include <unistd.h>
static FILE* stdout_fp = stdout;
static bool checkSupportsColor()
{
if (!isatty(fileno(stdout_fp)))
return false;
char* term = getenv("TERM");
if (!term || (0 == strcmp(term, "dumb")))
return false;
return true; // Probably supports color
}
static bool supports_color = checkSupportsColor();
void Console_SetStdout(FILE* fp)
{
stdout_fp = fp;
}
#endif
// This function re-assigns the console log writer(s) to the specified target. It makes sure
// to flush any contents from the buffered console log (which typically accumulates due to
// log suspension during log file/window re-init operations) into the new log.
//
// Important! Only Assert and Null console loggers are allowed during C++ startup init (when
// the program or DLL first loads). Other log targets rely on the static buffer and a
// threaded mutex lock, which are only valid after C++ initialization has finished.
void Console_SetActiveHandler(const IConsoleWriter& writer, FILE* flushfp)
using namespace std::string_view_literals;
// Dummy objects, need to get rid of them...
ConsoleLogWriter<LOGLEVEL_INFO> Console;
ConsoleLogWriter<LOGLEVEL_DEV> DevCon;
#ifdef _DEBUG
ConsoleLogWriter<LOGLEVEL_DEBUG> DbgConWriter;
#else
NullLogWriter DbgConWriter;
#endif
#define TIMESTAMP_FORMAT_STRING "[{:10.4f}] "
#define TIMESTAMP_PRINTF_STRING "[%10.4f] "
namespace Log
{
pxAssertMsg(
(writer.WriteRaw != NULL) && (writer.DoWriteLn != NULL) &&
(writer.Newline != NULL) && (writer.SetTitle != NULL) &&
(writer.DoSetColor != NULL),
"Invalid IConsoleWriter object! All function pointer interfaces must be implemented.");
static void WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_view message);
static void WriteToDebug(LOGLEVEL level, ConsoleColors color, std::string_view message);
static void WriteToFile(LOGLEVEL level, ConsoleColors color, std::string_view message);
Console = writer;
DevConWriter = writer;
static void UpdateMaxLevel();
#ifdef PCSX2_DEBUG
DbgCon = writer;
static void ExecuteCallbacks(LOGLEVEL level, ConsoleColors color, std::string_view message);
static Common::Timer::Value s_start_timestamp = Common::Timer::GetCurrentValue();
static LOGLEVEL s_max_level = LOGLEVEL_NONE;
static LOGLEVEL s_console_level = LOGLEVEL_NONE;
static LOGLEVEL s_debug_level = LOGLEVEL_NONE;
static LOGLEVEL s_file_level = LOGLEVEL_NONE;
static LOGLEVEL s_host_level = LOGLEVEL_NONE;
static bool s_log_timestamps = true;
static FileSystem::ManagedCFilePtr s_file_handle;
static std::string s_file_path;
static std::mutex s_file_mutex;
static HostCallbackType s_host_callback;
#ifdef _WIN32
static HANDLE s_hConsoleStdIn = NULL;
static HANDLE s_hConsoleStdOut = NULL;
static HANDLE s_hConsoleStdErr = NULL;
#endif
} // namespace Log
float Log::GetCurrentMessageTime()
{
return static_cast<float>(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_start_timestamp));
}
__ri void Log::WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_view message)
{
static constexpr std::string_view s_ansi_color_codes[ConsoleColors_Count] = {
"\033[0m"sv, // default
"\033[30m\033[1m"sv, // black
"\033[32m"sv, // green
"\033[31m"sv, // red
"\033[34m"sv, // blue
"\033[35m"sv, // magenta
"\033[35m"sv, // orange (FIXME)
"\033[37m"sv, // gray
"\033[36m"sv, // cyan
"\033[33m"sv, // yellow
"\033[37m"sv, // white
"\033[30m\033[1m"sv, // strong black
"\033[31m\033[1m"sv, // strong red
"\033[32m\033[1m"sv, // strong green
"\033[34m\033[1m"sv, // strong blue
"\033[35m\033[1m"sv, // strong magenta
"\033[35m\033[1m"sv, // strong orange (FIXME)
"\033[37m\033[1m"sv, // strong gray
"\033[36m\033[1m"sv, // strong cyan
"\033[33m\033[1m"sv, // strong yellow
"\033[37m\033[1m"sv, // strong white
};
static constexpr size_t BUFFER_SIZE = 512;
SmallStackString<BUFFER_SIZE> buffer;
buffer.reserve(32 + message.length());
buffer.append(s_ansi_color_codes[color]);
if (s_log_timestamps)
buffer.append_fmt(TIMESTAMP_FORMAT_STRING, Log::GetCurrentMessageTime());
buffer.append(message);
buffer.append('\n');
#ifdef _WIN32
const HANDLE hOutput = (level <= LOGLEVEL_WARNING) ? s_hConsoleStdErr : s_hConsoleStdOut;
// Convert to UTF-16 first so Unicode characters display correctly. NT is going to do it anyway...
wchar_t wbuf[BUFFER_SIZE];
wchar_t* wmessage_buf = wbuf;
int wmessage_buflen = static_cast<int>(std::size(wbuf) - 1);
if (buffer.length() >= std::size(wbuf))
{
wmessage_buflen = static_cast<int>(buffer.length());
wmessage_buf = static_cast<wchar_t*>(std::malloc(static_cast<size_t>(wmessage_buflen) * sizeof(wchar_t)));
if (!wmessage_buf)
return;
}
const int wmessage_size = MultiByteToWideChar(CP_UTF8, 0, buffer.data(), static_cast<int>(buffer.length()), wmessage_buf, wmessage_buflen);
if (wmessage_size <= 0)
goto cleanup;
DWORD chars_written;
WriteConsoleW(hOutput, wmessage_buf, static_cast<DWORD>(wmessage_size), &chars_written, nullptr);
cleanup:
if (wmessage_buf != wbuf)
std::free(wmessage_buf);
#else
const int fd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO;
write(fd, buffer.data(), buffer.length());
#endif
}
// Writes text to the Visual Studio Output window (Microsoft Windows only).
// On all other platforms this pipes to Stdout instead.
static void MSW_OutputDebugString(const char* text)
bool Log::IsConsoleOutputEnabled()
{
return (s_console_level > LOGLEVEL_NONE);
}
void Log::SetConsoleOutputLevel(LOGLEVEL level)
{
if (s_console_level == level)
return;
const bool was_enabled = (s_console_level > LOGLEVEL_NONE);
const bool now_enabled = (level > LOGLEVEL_NONE);
s_console_level = level;
UpdateMaxLevel();
if (was_enabled == now_enabled)
return;
// Worst that happens here is we write to a bad handle..
#if defined(_WIN32)
static constexpr auto enable_virtual_terminal_processing = [](HANDLE hConsole) {
DWORD old_mode;
if (!GetConsoleMode(hConsole, &old_mode))
return;
// already enabled?
if (old_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
return;
SetConsoleMode(hConsole, old_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
};
// On windows, no console is allocated by default on a windows based application
static bool console_was_allocated = false;
static HANDLE old_stdin = NULL;
static HANDLE old_stdout = NULL;
static HANDLE old_stderr = NULL;
if (now_enabled)
{
old_stdin = GetStdHandle(STD_INPUT_HANDLE);
old_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
old_stderr = GetStdHandle(STD_ERROR_HANDLE);
if (!old_stdout)
{
// Attach to the parent console if we're running from a command window
if (!AttachConsole(ATTACH_PARENT_PROCESS) && !AllocConsole())
return;
s_hConsoleStdIn = GetStdHandle(STD_INPUT_HANDLE);
s_hConsoleStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
s_hConsoleStdErr = GetStdHandle(STD_ERROR_HANDLE);
enable_virtual_terminal_processing(s_hConsoleStdOut);
enable_virtual_terminal_processing(s_hConsoleStdErr);
std::FILE* fp;
freopen_s(&fp, "CONIN$", "r", stdin);
freopen_s(&fp, "CONOUT$", "w", stdout);
freopen_s(&fp, "CONOUT$", "w", stderr);
console_was_allocated = true;
}
else
{
s_hConsoleStdIn = old_stdin;
s_hConsoleStdOut = old_stdout;
s_hConsoleStdErr = old_stderr;
}
}
else
{
if (console_was_allocated)
{
console_was_allocated = false;
std::FILE* fp;
freopen_s(&fp, "NUL:", "w", stderr);
freopen_s(&fp, "NUL:", "w", stdout);
freopen_s(&fp, "NUL:", "w", stdin);
SetStdHandle(STD_ERROR_HANDLE, old_stderr);
SetStdHandle(STD_OUTPUT_HANDLE, old_stdout);
SetStdHandle(STD_INPUT_HANDLE, old_stdin);
s_hConsoleStdIn = NULL;
s_hConsoleStdOut = NULL;
s_hConsoleStdErr = NULL;
FreeConsole();
}
}
#endif
}
__ri void Log::WriteToDebug(LOGLEVEL level, ConsoleColors color, std::string_view message)
{
#ifdef _WIN32
static bool hasDebugger = IsDebuggerPresent();
if (hasDebugger)
OutputDebugStringA(text);
static constexpr size_t BUFFER_SIZE = 512;
// Convert to UTF-16 first so Unicode characters display correctly. NT is going to do it anyway...
wchar_t wbuf[BUFFER_SIZE];
wchar_t* wmessage_buf = wbuf;
int wmessage_buflen = static_cast<int>(std::size(wbuf) - 1);
if (message.length() >= std::size(wbuf))
{
wmessage_buflen = static_cast<int>(message.length());
wmessage_buf = static_cast<wchar_t*>(std::malloc((static_cast<size_t>(wmessage_buflen) + 2) * sizeof(wchar_t)));
if (!wmessage_buf)
return;
}
int wmessage_size = 0;
if (!message.empty()) [[likely]]
{
wmessage_size = MultiByteToWideChar(CP_UTF8, 0, message.data(), static_cast<int>(message.length()), wmessage_buf, wmessage_buflen);
if (wmessage_size <= 0)
goto cleanup;
}
wmessage_buf[wmessage_size++] = L'\n';
wmessage_buf[wmessage_size++] = 0;
OutputDebugStringW(wmessage_buf);
cleanup:
if (wmessage_buf != wbuf)
std::free(wmessage_buf);
#endif
}
bool Log::IsDebugOutputAvailable()
{
#ifdef _WIN32
return IsDebuggerPresent();
#else
fputs(text, stdout_fp);
fflush(stdout_fp);
return false;
#endif
}
// --------------------------------------------------------------------------------------
// ConsoleNull
// --------------------------------------------------------------------------------------
static void ConsoleNull_SetTitle(const char* title) {}
static void ConsoleNull_DoSetColor(ConsoleColors color) {}
static void ConsoleNull_Newline() {}
static void ConsoleNull_DoWrite(const char* fmt) {}
static void ConsoleNull_DoWriteLn(const char* fmt) {}
const IConsoleWriter ConsoleWriter_Null =
{
ConsoleNull_DoWrite,
ConsoleNull_DoWriteLn,
ConsoleNull_DoSetColor,
ConsoleNull_DoWrite,
ConsoleNull_Newline,
ConsoleNull_SetTitle,
0, // instance-level indentation (should always be 0)
};
// --------------------------------------------------------------------------------------
// Console_Stdout
// --------------------------------------------------------------------------------------
#if defined(__POSIX__)
static __fi const char* GetLinuxConsoleColor(ConsoleColors color)
bool Log::IsDebugOutputEnabled()
{
switch (color)
{
case Color_Black:
case Color_StrongBlack:
return "\033[30m\033[1m";
case Color_Red:
return "\033[31m";
case Color_StrongRed:
return "\033[31m\033[1m";
case Color_Green:
return "\033[32m";
case Color_StrongGreen:
return "\033[32m\033[1m";
case Color_Yellow:
return "\033[33m";
case Color_StrongYellow:
return "\033[33m\033[1m";
case Color_Blue:
return "\033[34m";
case Color_StrongBlue:
return "\033[34m\033[1m";
// No orange, so use magenta.
case Color_Orange:
case Color_Magenta:
return "\033[35m";
case Color_StrongOrange:
case Color_StrongMagenta:
return "\033[35m\033[1m";
case Color_Cyan:
return "\033[36m";
case Color_StrongCyan:
return "\033[36m\033[1m";
// Use 'white' instead of grey.
case Color_Gray:
case Color_White:
return "\033[37m";
case Color_StrongGray:
case Color_StrongWhite:
return "\033[37m\033[1m";
// On some other value being passed, clear any formatting.
case Color_Default:
default:
return "\033[0m";
}
}
#endif
// One possible default write action at startup and shutdown is to use the stdout.
static void ConsoleStdout_DoWrite(const char* fmt)
{
MSW_OutputDebugString(fmt);
return (s_console_level > LOGLEVEL_NONE);
}
// Default write action at startup and shutdown is to use the stdout.
static void ConsoleStdout_DoWriteLn(const char* fmt)
void Log::SetDebugOutputLevel(LOGLEVEL level)
{
MSW_OutputDebugString(fmt);
MSW_OutputDebugString("\n");
s_debug_level = level;
UpdateMaxLevel();
}
static void ConsoleStdout_Newline()
__ri void Log::WriteToFile(LOGLEVEL level, ConsoleColors color, std::string_view message)
{
MSW_OutputDebugString("\n");
}
static void ConsoleStdout_DoSetColor(ConsoleColors color)
{
#if defined(__POSIX__)
if (!supports_color)
std::unique_lock lock(s_file_mutex);
if (!s_file_handle) [[unlikely]]
return;
fprintf(stdout_fp, "\033[0m%s", GetLinuxConsoleColor(color));
fflush(stdout_fp);
#endif
}
static void ConsoleStdout_SetTitle(const char* title)
{
#if defined(__POSIX__)
if (supports_color)
fputs("\033]0;", stdout_fp);
fputs(title, stdout_fp);
if (supports_color)
fputs("\007", stdout_fp);
#endif
}
const IConsoleWriter ConsoleWriter_Stdout =
if (!message.empty()) [[likely]]
{
ConsoleStdout_DoWrite, // Writes without newlines go to buffer to avoid error log spam.
ConsoleStdout_DoWriteLn,
ConsoleStdout_DoSetColor,
ConsoleNull_DoWrite, // writes from re-piped stdout are ignored here, lest we create infinite loop hell >_<
ConsoleStdout_Newline,
ConsoleStdout_SetTitle,
0, // instance-level indentation (should always be 0)
};
// --------------------------------------------------------------------------------------
// ConsoleAssert
// --------------------------------------------------------------------------------------
static void ConsoleAssert_DoWrite(const char* fmt)
{
pxFailRel("Console class has not been initialized");
}
static void ConsoleAssert_DoWriteLn(const char* fmt)
{
pxFailRel("Console class has not been initialized");
}
const IConsoleWriter ConsoleWriter_Assert =
{
ConsoleAssert_DoWrite,
ConsoleAssert_DoWriteLn,
ConsoleNull_DoSetColor,
ConsoleNull_DoWrite,
ConsoleNull_Newline,
ConsoleNull_SetTitle,
0, // instance-level indentation (should always be 0)
};
// =====================================================================================================
// IConsoleWriter (implementations)
// =====================================================================================================
// (all non-virtual members that do common work and then pass the result through DoWrite
// or DoWriteLn)
// Parameters:
// glob_indent - this parameter is used to specify a global indentation setting. It is used by
// WriteLn function, but defaults to 0 for Warning and Error calls. Local indentation always
// applies to all writes.
std::string IConsoleWriter::_addIndentation(const std::string& src, int glob_indent = 0) const
{
const int indent = glob_indent + _imm_indentation;
std::string indentStr;
for (int i = 0; i < indent; i++)
indentStr += '\t';
std::string result;
result.reserve(src.length() + 16 * indent);
result.append(indentStr);
result.append(src);
std::string::size_type pos = result.find('\n');
while (pos != std::string::npos)
{
result.insert(pos + 1, indentStr);
pos = result.find('\n', pos + 1);
}
return result;
}
// Sets the indentation to be applied to all WriteLn's. The indentation is added to the
// primary write, and to any newlines specified within the write. Note that this applies
// to calls to WriteLn *only* -- calls to Write bypass the indentation parser.
const IConsoleWriter& IConsoleWriter::SetIndent(int tabcount) const
{
conlog_Indent += tabcount;
pxAssert(conlog_Indent >= 0);
return *this;
}
IConsoleWriter IConsoleWriter::Indent(int tabcount) const
{
IConsoleWriter retval = *this;
retval._imm_indentation = tabcount;
return retval;
}
// Changes the active console color.
// This color will be unset by calls to colored text methods
// such as ErrorMsg and Notice.
const IConsoleWriter& IConsoleWriter::SetColor(ConsoleColors color) const
{
// Ignore current color requests since, well, the current color is already set. ;)
if (color == Color_Current)
return *this;
pxAssertMsg((color > Color_Current) && (color < ConsoleColors_Count), "Invalid ConsoleColor specified.");
if (conlog_Color != color)
DoSetColor(conlog_Color = color);
return *this;
}
ConsoleColors IConsoleWriter::GetColor() const
{
return conlog_Color;
}
// Restores the console color to default (usually black, or low-intensity white if the console uses a black background)
const IConsoleWriter& IConsoleWriter::ClearColor() const
{
if (conlog_Color != DefaultConsoleColor)
DoSetColor(conlog_Color = DefaultConsoleColor);
return *this;
}
// --------------------------------------------------------------------------------------
// ASCII/UTF8 (char*)
// --------------------------------------------------------------------------------------
bool IConsoleWriter::FormatV(const char* fmt, va_list args) const
{
// TODO: Make this less rubbish
if ((_imm_indentation + conlog_Indent) > 0)
{
DoWriteLn(_addIndentation(StringUtil::StdStringFromFormatV(fmt, args), conlog_Indent).c_str());
if (s_log_timestamps)
{
std::fprintf(s_file_handle.get(), TIMESTAMP_PRINTF_STRING "%.*s\n", GetCurrentMessageTime(),
static_cast<int>(message.size()), message.data());
}
else
{
std::fprintf(s_file_handle.get(), "%.*s\n", static_cast<int>(message.size()), message.data());
}
}
else
{
DoWriteLn(StringUtil::StdStringFromFormatV(fmt, args).c_str());
if (s_log_timestamps)
{
std::fprintf(s_file_handle.get(), TIMESTAMP_PRINTF_STRING "\n", GetCurrentMessageTime());
}
else
{
std::fputc('\n', s_file_handle.get());
}
}
return false;
std::fflush(s_file_handle.get());
}
bool IConsoleWriter::WriteLn(const char* fmt, ...) const
bool Log::IsFileOutputEnabled()
{
va_list args;
va_start(args, fmt);
FormatV(fmt, args);
va_end(args);
return false;
return (s_file_level > LOGLEVEL_NONE);
}
bool IConsoleWriter::WriteLn(ConsoleColors color, const char* fmt, ...) const
bool Log::SetFileOutputLevel(LOGLEVEL level, std::string path)
{
va_list args;
va_start(args, fmt);
ConsoleColorScope cs(color);
FormatV(fmt, args);
va_end(args);
std::unique_lock lock(s_file_mutex);
return false;
}
bool IConsoleWriter::Error(const char* fmt, ...) const
{
va_list args;
va_start(args, fmt);
ConsoleColorScope cs(Color_StrongRed);
FormatV(fmt, args);
va_end(args);
return false;
}
bool IConsoleWriter::Warning(const char* fmt, ...) const
{
va_list args;
va_start(args, fmt);
ConsoleColorScope cs(Color_StrongOrange);
FormatV(fmt, args);
va_end(args);
return false;
}
bool IConsoleWriter::WriteLn(ConsoleColors color, const std::string& str) const
{
ConsoleColorScope cs(color);
return WriteLn(str);
}
bool IConsoleWriter::WriteLn(const std::string& str) const
{
// TODO: Make this less rubbish
if ((_imm_indentation + conlog_Indent) > 0)
const bool was_enabled = (s_file_level > LOGLEVEL_NONE);
const bool new_enabled = (level > LOGLEVEL_NONE && !path.empty());
if (was_enabled != new_enabled || (new_enabled && path == s_file_path))
{
DoWriteLn(_addIndentation(str, conlog_Indent).c_str());
if (new_enabled)
{
if (!s_file_handle || s_file_path != path)
{
s_file_handle.reset();
s_file_handle = FileSystem::OpenManagedCFile(path.c_str(), "wb");
if (s_file_handle)
{
s_file_path = std::move(path);
}
else
{
s_file_path = {};
if (IsConsoleOutputEnabled())
WriteToConsole(LOGLEVEL_ERROR, Color_StrongRed, TinyString::from_fmt("Failed to open log file '{}'", path));
}
}
}
else
{
s_file_handle.reset();
s_file_path = {};
}
}
s_file_level = s_file_handle ? level : LOGLEVEL_NONE;
return IsFileOutputEnabled();
}
std::FILE* Log::GetFileLogHandle()
{
std::unique_lock lock(s_file_mutex);
return s_file_handle.get();
}
bool Log::IsHostOutputEnabled()
{
return (s_host_level > LOGLEVEL_NONE);
}
void Log::SetHostOutputLevel(LOGLEVEL level, HostCallbackType callback)
{
s_host_callback = callback;
s_host_level = callback ? level : LOGLEVEL_NONE;
UpdateMaxLevel();
}
bool Log::AreTimestampsEnabled()
{
return s_log_timestamps;
}
void Log::SetTimestampsEnabled(bool enabled)
{
s_log_timestamps = enabled;
}
LOGLEVEL Log::GetMaxLevel()
{
return s_max_level;
}
__ri void Log::UpdateMaxLevel()
{
s_max_level = std::max(s_console_level, std::max(s_debug_level, std::max(s_file_level, s_host_level)));
}
void Log::ExecuteCallbacks(LOGLEVEL level, ConsoleColors color, std::string_view message)
{
// TODO: Cache the message time.
// Split newlines into separate messages.
std::string_view::size_type start_pos = 0;
if (std::string_view::size_type end_pos = message.find('\n'); end_pos != std::string::npos) [[unlikely]]
{
for (;;)
{
std::string_view message_line;
if (start_pos != end_pos)
message_line = message.substr(start_pos, (end_pos == std::string_view::npos) ? end_pos : end_pos - start_pos);
ExecuteCallbacks(level, color, message_line);
if (end_pos == std::string_view::npos)
return;
start_pos = end_pos + 1;
end_pos = message.find('\n', start_pos);
}
return;
}
pxAssert(level > LOGLEVEL_NONE);
if (level <= s_console_level)
WriteToConsole(level, color, message);
if (level <= s_debug_level)
WriteToDebug(level, color, message);
if (level <= s_file_level)
WriteToFile(level, color, message);
if (level <= s_host_level)
{
// double check in case of race here
const HostCallbackType callback = s_host_callback;
if (callback)
s_host_callback(level, color, message);
}
}
void Log::Write(LOGLEVEL level, ConsoleColors color, std::string_view message)
{
if (level > s_max_level)
return;
ExecuteCallbacks(level, color, message);
}
void Log::Writef(LOGLEVEL level, ConsoleColors color, const char* format, ...)
{
std::va_list ap;
va_start(ap, format);
Writev(level, color, format, ap);
va_end(ap);
}
void Log::Writev(LOGLEVEL level, ConsoleColors color, const char* format, va_list ap)
{
if (level > s_max_level)
return;
std::va_list ap_copy;
va_copy(ap_copy, ap);
#ifdef _WIN32
const u32 required_size = static_cast<u32>(_vscprintf(format, ap_copy));
#else
const u32 required_size = std::vsnprintf(nullptr, 0, format, ap_copy);
#endif
va_end(ap_copy);
if (required_size < 512)
{
char buffer[512];
const int len = std::vsnprintf(buffer, std::size(buffer), format, ap);
if (len > 0)
ExecuteCallbacks(level, color, std::string_view(buffer, static_cast<size_t>(len)));
}
else
{
DoWriteLn(str.c_str());
}
return false;
}
bool IConsoleWriter::Error(const std::string& str) const
{
return WriteLn(Color_StrongRed, str);
}
bool IConsoleWriter::Warning(const std::string& str) const
{
return WriteLn(Color_StrongOrange, str);
}
// --------------------------------------------------------------------------------------
// ConsoleColorScope / ConsoleIndentScope
// --------------------------------------------------------------------------------------
ConsoleColorScope::ConsoleColorScope(ConsoleColors newcolor)
{
m_IsScoped = false;
m_newcolor = newcolor;
EnterScope();
}
ConsoleColorScope::~ConsoleColorScope()
{
LeaveScope();
}
void ConsoleColorScope::EnterScope()
{
if (!m_IsScoped)
{
m_old_color = Console.GetColor();
Console.SetColor(m_newcolor);
m_IsScoped = true;
char* buffer = new char[required_size + 1];
const int len = std::vsnprintf(buffer, required_size + 1, format, ap);
if (len > 0)
ExecuteCallbacks(level, color, std::string_view(buffer, static_cast<size_t>(len)));
delete[] buffer;
}
}
void ConsoleColorScope::LeaveScope()
void Log::WriteFmtArgs(LOGLEVEL level, ConsoleColors color, fmt::string_view fmt, fmt::format_args args)
{
m_IsScoped = m_IsScoped && (Console.SetColor(m_old_color), false);
}
if (level > s_max_level)
return;
ConsoleIndentScope::ConsoleIndentScope(int tabs)
{
m_IsScoped = false;
m_amount = tabs;
EnterScope();
}
fmt::memory_buffer buffer;
fmt::vformat_to(std::back_inserter(buffer), fmt, args);
ConsoleIndentScope::~ConsoleIndentScope()
{
LeaveScope();
}
void ConsoleIndentScope::EnterScope()
{
m_IsScoped = m_IsScoped || (Console.SetIndent(m_amount), true);
}
void ConsoleIndentScope::LeaveScope()
{
m_IsScoped = m_IsScoped && (Console.SetIndent(-m_amount), false);
}
ConsoleAttrScope::ConsoleAttrScope(ConsoleColors newcolor, int indent)
{
m_old_color = Console.GetColor();
Console.SetIndent(m_tabsize = indent);
Console.SetColor(newcolor);
}
ConsoleAttrScope::~ConsoleAttrScope()
{
Console.SetColor(m_old_color);
Console.SetIndent(-m_tabsize);
}
// --------------------------------------------------------------------------------------
// Default Writer for C++ init / startup:
// --------------------------------------------------------------------------------------
// Currently all build types default to Stdout, which is very functional on Linux but not
// always so useful on Windows (which itself lacks a proper stdout console without using
// platform specific code). Under windows Stdout will attempt to write to the IDE Debug
// console, if one is available (such as running pcsx2 via MSVC). If not available, then
// the log message will pretty much be lost into the ether.
//
#define _DefaultWriter_ ConsoleWriter_Stdout
IConsoleWriter Console = _DefaultWriter_;
IConsoleWriter DevConWriter = _DefaultWriter_;
bool DevConWriterEnabled = false;
#ifdef PCSX2_DEBUG
IConsoleWriter DbgConWriter = _DefaultWriter_;
#endif
NullConsoleWriter NullCon = {};
// --------------------------------------------------------------------------------------
// ConsoleLogSource (implementations)
// --------------------------------------------------------------------------------------
// Writes to the console using the specified color. This overrides the default color setting
// for this log.
bool ConsoleLogSource::WriteV(ConsoleColors color, const char* fmt, va_list list) const
{
ConsoleColorScope cs(color);
Console.WriteLn(StringUtil::StdStringFromFormatV(fmt, list));
return false;
}
// Writes to the console using the source's default color. Note that the source's default
// color will always be used, thus ConsoleColorScope() will not be effectual unless the
// console's default color is Color_Default.
bool ConsoleLogSource::WriteV(const char* fmt, va_list list) const
{
WriteV(DefaultColor, fmt, list);
return false;
ExecuteCallbacks(level, color, std::string_view(buffer.data(), buffer.size()));
}
+129 -171
View File
@@ -1,16 +1,19 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include "Pcsx2Defs.h"
#include "fmt/core.h"
#include <cstdarg>
#include <string>
// TODO: This whole thing needs to get ripped out.
enum ConsoleColors
{
Color_Current = -1,
Color_Default = 0,
Color_Black,
@@ -42,191 +45,146 @@ enum ConsoleColors
ConsoleColors_Count
};
static const ConsoleColors DefaultConsoleColor = Color_Default;
// ----------------------------------------------------------------------------------------
// IConsoleWriter -- For printing messages to the console.
// ----------------------------------------------------------------------------------------
// General ConsoleWrite Threading Guideline:
// PCSX2 is a threaded environment and multiple threads can write to the console asynchronously.
// Individual calls to ConsoleWriter APIs will be written in atomic fashion, however "partial"
// logs may end up interrupted by logs on other threads. This is usually not a problem for
// WriteLn, but may be undesirable for typical uses of Write. In cases where you want to
// print multi-line blocks of uninterrupted logs, compound the entire log into a single large
// string and issue that to WriteLn.
//
struct IConsoleWriter
enum LOGLEVEL
{
// A direct console write, without tabbing or newlines. Useful to devs who want to do quick
// logging of various junk; but should *not* be used in production code due.
void(* WriteRaw)(const char* fmt);
LOGLEVEL_NONE, // Silences all log traffic
LOGLEVEL_ERROR,
LOGLEVEL_WARNING,
LOGLEVEL_INFO,
LOGLEVEL_DEV,
LOGLEVEL_DEBUG,
LOGLEVEL_TRACE,
// WriteLn implementation for internal use only. Bypasses tabbing, prefixing, and other
// formatting.
void(* DoWriteLn)(const char* fmt);
// SetColor implementation for internal use only.
void(* DoSetColor)(ConsoleColors color);
// Special implementation of DoWrite that's pretty much for MSVC use only.
// All implementations should map to DoWrite, except Stdio which should map to Null.
// (This avoids circular/recursive stdio output)
void(* DoWriteFromStdout)(const char* fmt);
void(* Newline)();
void(* SetTitle)(const char* title);
// internal value for indentation of individual lines. Use the Indent() member to invoke.
int _imm_indentation;
// For internal use only.
std::string _addIndentation(const std::string& src, int glob_indent) const;
// ----------------------------------------------------------------------------
// Public members; call these to print stuff to console!
//
// All functions always return false. Return value is provided only so that we can easily
// disable logs at compile time using the "0&&action" macro trick.
ConsoleColors GetColor() const;
const IConsoleWriter& SetColor(ConsoleColors color) const;
const IConsoleWriter& ClearColor() const;
const IConsoleWriter& SetIndent(int tabcount = 1) const;
IConsoleWriter Indent(int tabcount = 1) const;
bool FormatV(const char* fmt, va_list args) const;
bool WriteLn(ConsoleColors color, const char* fmt, ...) const;
bool WriteLn(const char* fmt, ...) const;
bool Error(const char* fmt, ...) const;
bool Warning(const char* fmt, ...) const;
bool WriteLn(ConsoleColors color, const std::string& str) const;
bool WriteLn(const std::string& str) const;
bool Error(const std::string& str) const;
bool Warning(const std::string& str) const;
LOGLEVEL_COUNT,
};
// --------------------------------------------------------------------------------------
// NullConsoleWriter
// --------------------------------------------------------------------------------------
// Used by Release builds for Debug and Devel writes (DbgCon / DevCon). Inlines to NOPs. :)
//
struct NullConsoleWriter
// TODO: Move this elsewhere, add channels.
namespace Log
{
void WriteRaw(const char* fmt) {}
void DoWriteLn(const char* fmt) {}
void DoSetColor(ConsoleColors color) {}
void DoWriteFromStdout(const char* fmt) {}
void Newline() {}
void SetTitle(const char* title) {}
// log message callback type
using HostCallbackType = void (*)(LOGLEVEL level, ConsoleColors color, std::string_view message);
// returns the time in seconds since the start of the process
float GetCurrentMessageTime();
ConsoleColors GetColor() const { return Color_Current; }
const NullConsoleWriter& SetColor(ConsoleColors color) const { return *this; }
const NullConsoleWriter& ClearColor() const { return *this; }
const NullConsoleWriter& SetIndent(int tabcount = 1) const { return *this; }
// adds a standard console output
bool IsConsoleOutputEnabled();
void SetConsoleOutputLevel(LOGLEVEL level);
NullConsoleWriter Indent(int tabcount = 1) const { return NullConsoleWriter(); }
// adds a debug console output
bool IsDebugOutputAvailable();
bool IsDebugOutputEnabled();
void SetDebugOutputLevel(LOGLEVEL level);
bool FormatV(const char* fmt, va_list args) const { return false; }
bool WriteLn(ConsoleColors color, const char* fmt, ...) const { return false; }
bool WriteLn(const char* fmt, ...) const { return false; }
bool Error(const char* fmt, ...) const { return false; }
bool Warning(const char* fmt, ...) const { return false; }
// adds a file output
bool IsFileOutputEnabled();
bool SetFileOutputLevel(LOGLEVEL level, std::string path);
// returns the log file, this is really dangerous to use if it changes...
std::FILE* GetFileLogHandle();
// adds host output
bool IsHostOutputEnabled();
void SetHostOutputLevel(LOGLEVEL level, HostCallbackType callback);
// sets logging timestamps
bool AreTimestampsEnabled();
void SetTimestampsEnabled(bool enabled);
// Returns the current global filtering level.
LOGLEVEL GetMaxLevel();
// writes a message to the log
void Write(LOGLEVEL level, ConsoleColors color, std::string_view message);
void Writef(LOGLEVEL level, ConsoleColors color, const char* format, ...);
void Writev(LOGLEVEL level, ConsoleColors color, const char* format, va_list ap);
void WriteFmtArgs(LOGLEVEL level, ConsoleColors color, fmt::string_view fmt, fmt::format_args args);
template <typename... T>
__fi static void Write(LOGLEVEL level, ConsoleColors color, fmt::format_string<T...> fmt, T&&... args)
{
// Avoid arg packing if filtered.
if (level <= GetMaxLevel())
return WriteFmtArgs(level, color, fmt, fmt::make_format_args(args...));
}
} // namespace Log
// Adapter classes to handle old code.
template <LOGLEVEL level>
struct ConsoleLogWriter
{
__fi static void Error(std::string_view str) { Log::Write(level, Color_StrongRed, str); }
__fi static void Warning(std::string_view str) { Log::Write(level, Color_StrongOrange, str); }
__fi static void WriteLn(std::string_view str) { Log::Write(level, Color_Default, str); }
__fi static void WriteLn(ConsoleColors color, std::string_view str) { Log::Write(level, color, str); }
__fi static void WriteLn() { Log::Write(level, Color_Default, std::string_view()); }
__fi static void FormatV(const char* format, va_list ap) { Log::Writev(level, Color_Default, format, ap); }
__fi static void FormatV(ConsoleColors color, const char* format, va_list ap) { Log::Writev(level, color, format, ap); }
#define MAKE_PRINTF_CONSOLE_WRITER(color) \
do \
{ \
std::va_list ap; \
va_start(ap, format); \
Log::Writev(level, color, format, ap); \
va_end(ap); \
} while (0)
// clang-format off
__fi static void Error(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_StrongRed); }
__fi static void Warning(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_StrongOrange); }
__fi static void WriteLn(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_Default); }
__fi static void WriteLn(ConsoleColors color, const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(color); }
// clang-format on
#undef MAKE_PRINTF_CONSOLE_WRITER
#define MAKE_FMT_CONSOLE_WRITER(color) do \
{ \
if (level <= Log::GetMaxLevel()) \
Log::WriteFmtArgs(level, color, fmt, fmt::make_format_args(args...)); \
} \
while (0)
// clang-format off
template<typename... T> __fi static void ErrorFmt(fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_StrongRed); }
template<typename... T> __fi static void WarningFmt(fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_StrongOrange); }
template<typename... T> __fi static void WriteLnFmt(fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_Default); }
template<typename... T> __fi static void WriteLnFmt(ConsoleColors color, fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(color); }
// clang-format on
#undef MAKE_FMT_CONSOLE_WRITER
};
// --------------------------------------------------------------------------------------
// ConsoleIndentScope
// --------------------------------------------------------------------------------------
// Provides a scoped indentation of the IConsoleWriter interface for the current thread.
// Any console writes performed from this scope will be indented by the specified number
// of tab characters.
//
// Scoped Object Notes: Like most scoped objects, this is intended to be used as a stack
// or temporary object only. Using it in a situation where the object's lifespan out-lives
// a scope will almost certainly result in unintended /undefined side effects.
//
class ConsoleIndentScope
struct NullLogWriter
{
DeclareNoncopyableObject(ConsoleIndentScope);
// clang-format off
__fi static bool Error(std::string_view str) { return false; }
__fi static bool Warning(std::string_view str) { return false; }
__fi static bool WriteLn(std::string_view str) { return false; }
__fi static bool WriteLn(ConsoleColors color, std::string_view str) { return false; }
__fi static bool WriteLn() { return false; }
protected:
int m_amount;
bool m_IsScoped;
__fi static bool Error(const char* format, ...) { return false; }
__fi static bool Warning(const char* format, ...) { return false; }
__fi static bool WriteLn(const char* format, ...) { return false; }
__fi static bool WriteLn(ConsoleColors color, const char* format, ...) { return false; }
public:
// Constructor: The specified number of tabs will be appended to the current indentation
// setting. The tabs will be unrolled when the object leaves scope or is destroyed.
ConsoleIndentScope(int tabs = 1);
virtual ~ConsoleIndentScope();
void EnterScope();
void LeaveScope();
template<typename... T> __fi static bool ErrorFmt(fmt::format_string<T...> fmt, T&&... args) { return false; }
template<typename... T> __fi static bool WarningFmt(fmt::format_string<T...> fmt, T&&... args) { return false; }
template<typename... T> __fi static bool WriteLnFmt(fmt::format_string<T...> fmt, T&&... args) { return false; }
template<typename... T> __fi static bool WriteLnFmt(ConsoleColors color, fmt::format_string<T...> fmt, T&&... args) { return false; }
// clang-format on
};
// --------------------------------------------------------------------------------------
// ConsoleColorScope
// --------------------------------------------------------------------------------------
class ConsoleColorScope
{
DeclareNoncopyableObject(ConsoleColorScope);
extern ConsoleLogWriter<LOGLEVEL_INFO> Console;
extern ConsoleLogWriter<LOGLEVEL_DEV> DevCon;
protected:
ConsoleColors m_newcolor;
ConsoleColors m_old_color;
bool m_IsScoped;
public:
ConsoleColorScope(ConsoleColors newcolor);
virtual ~ConsoleColorScope();
void EnterScope();
void LeaveScope();
};
// --------------------------------------------------------------------------------------
// ConsoleAttrScope
// --------------------------------------------------------------------------------------
// Applies both color and tab attributes in a single object constructor.
//
class ConsoleAttrScope
{
DeclareNoncopyableObject(ConsoleAttrScope);
protected:
ConsoleColors m_old_color;
int m_tabsize;
public:
ConsoleAttrScope(ConsoleColors newcolor, int indent = 0);
virtual ~ConsoleAttrScope();
};
extern IConsoleWriter Console;
#if defined(__POSIX__)
extern void Console_SetStdout(FILE* fp);
#endif
extern void Console_SetActiveHandler(const IConsoleWriter& writer, FILE* flushfp = NULL);
extern const IConsoleWriter ConsoleWriter_Null;
extern const IConsoleWriter ConsoleWriter_Stdout;
extern const IConsoleWriter ConsoleWriter_Assert;
extern NullConsoleWriter NullCon;
extern IConsoleWriter DevConWriter;
extern bool DevConWriterEnabled;
#ifdef PCSX2_DEVBUILD
#define DevCon DevConWriter
#else
#define DevCon DevConWriterEnabled&& DevConWriter
#endif
#ifdef PCSX2_DEBUG
extern IConsoleWriter DbgConWriter;
#ifdef _DEBUG
extern ConsoleLogWriter<LOGLEVEL_DEBUG> DbgConWriter;
#define DbgCon DbgConWriter
#else
#define DbgCon 0 && NullCon
extern NullLogWriter DbgConWriter;
#define DbgCon 0 && DbgConWriter
#endif
+92 -56
View File
@@ -1076,19 +1076,19 @@ 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)
u32 flags, FileSystem::FindResultsArray* results, std::vector<std::string>& visited)
{
std::string tempStr;
std::string search_dir;
if (path)
{
if (parent_path)
tempStr = StringUtil::StdStringFromFormat("%s\\%s\\%s\\*", origin_path, parent_path, path);
search_dir = fmt::format("{}\\{}\\{}\\*", origin_path, parent_path, path);
else
tempStr = StringUtil::StdStringFromFormat("%s\\%s\\*", origin_path, path);
search_dir = fmt::format("{}\\{}\\*", origin_path, path);
}
else
{
tempStr = StringUtil::StdStringFromFormat("%s\\*", origin_path);
search_dir = fmt::format("{}\\*", origin_path);
}
// holder for utf-8 conversion
@@ -1096,7 +1096,7 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
std::string utf8_filename;
utf8_filename.reserve((sizeof(wfd.cFileName) / sizeof(wfd.cFileName[0])) * 2);
HANDLE hFind = FindFirstFileW(StringUtil::UTF8StringToWideString(tempStr).c_str(), &wfd);
const HANDLE hFind = FindFirstFileW(StringUtil::UTF8StringToWideString(search_dir).c_str(), &wfd);
if (hFind == INVALID_HANDLE_VALUE)
return 0;
@@ -1104,7 +1104,7 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
bool hasWildCards = false;
bool wildCardMatchAll = false;
u32 nFiles = 0;
if (std::strpbrk(pattern, "*?") != nullptr)
if (std::strpbrk(pattern, "*?"))
{
hasWildCards = true;
wildCardMatchAll = !(std::strcmp(pattern, "*"));
@@ -1132,15 +1132,29 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
{
if (flags & FILESYSTEM_FIND_RECURSIVE)
{
// recurse into this directory
if (parent_path != nullptr)
{
const std::string recurseDir = StringUtil::StdStringFromFormat("%s\\%s", parent_path, path);
nFiles += RecursiveFindFiles(origin_path, recurseDir.c_str(), utf8_filename.c_str(), pattern, flags, results);
}
// check that we're not following an infinite symbolic link loop
std::string real_recurse_dir;
if (parent_path)
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}\\{}\\{}", origin_path, parent_path, path, utf8_filename));
else if (path)
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}\\{}", origin_path, path, utf8_filename));
else
real_recurse_dir = Path::RealPath(fmt::format("{}\\{}", origin_path, utf8_filename));
if (real_recurse_dir.empty() || std::find(visited.begin(), visited.end(), real_recurse_dir) == visited.end())
{
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results);
if (!real_recurse_dir.empty())
visited.push_back(std::move(real_recurse_dir));
// recurse into this directory
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);
}
else
{
nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results, visited);
}
}
}
@@ -1171,23 +1185,21 @@ static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path,
}
// add file to list
// TODO string formatter, clean this mess..
if (!(flags & FILESYSTEM_FIND_RELATIVE_PATHS))
{
if (parent_path != nullptr)
outData.FileName =
StringUtil::StdStringFromFormat("%s\\%s\\%s\\%s", origin_path, parent_path, path, utf8_filename.c_str());
else if (path != nullptr)
outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", origin_path, path, utf8_filename.c_str());
if (parent_path)
outData.FileName = fmt::format("{}\\{}\\{}\\{}", origin_path, parent_path, path, utf8_filename);
else if (path)
outData.FileName = fmt::format("{}\\{}\\{}", origin_path, path, utf8_filename);
else
outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", origin_path, utf8_filename.c_str());
outData.FileName = fmt::format("{}\\{}", origin_path, utf8_filename);
}
else
{
if (parent_path != nullptr)
outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", parent_path, path, utf8_filename.c_str());
else if (path != nullptr)
outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", path, utf8_filename.c_str());
if (parent_path)
outData.FileName = fmt::format("{}\\{}\\{}", parent_path, path, utf8_filename);
else if (path)
outData.FileName = fmt::format("{}\\{}", path, utf8_filename);
else
outData.FileName = utf8_filename;
}
@@ -1214,10 +1226,18 @@ bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, Fin
if (!(flags & FILESYSTEM_FIND_KEEP_ARRAY))
results->clear();
// enter the recursive function
return (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results) > 0);
}
// add self if recursive, we don't want to visit it twice
std::vector<std::string> visited;
if (flags & FILESYSTEM_FIND_RECURSIVE)
{
std::string real_path = Path::RealPath(path);
if (!real_path.empty())
visited.push_back(std::move(real_path));
}
// enter the recursive function
return (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) > 0);
}
static void TranslateStat64(struct stat* st, const struct _stat64& st64)
{
@@ -1620,23 +1640,23 @@ bool FileSystem::SetPathCompression(const char* path, bool enable)
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)
u32 Flags, FileSystem::FindResultsArray* pResults, std::vector<std::string>& visited)
{
std::string tempStr;
if (Path)
{
if (ParentPath)
tempStr = StringUtil::StdStringFromFormat("%s/%s/%s", OriginPath, ParentPath, Path);
tempStr = fmt::format("{}/{}/{}", OriginPath, ParentPath, Path);
else
tempStr = StringUtil::StdStringFromFormat("%s/%s", OriginPath, Path);
tempStr = fmt::format("{}/{}", OriginPath, Path);
}
else
{
tempStr = StringUtil::StdStringFromFormat("%s", OriginPath);
tempStr = fmt::format("{}", OriginPath);
}
DIR* pDir = opendir(tempStr.c_str());
if (pDir == nullptr)
if (!pDir)
return 0;
// small speed optimization for '*' case
@@ -1663,12 +1683,12 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
}
std::string full_path;
if (ParentPath != nullptr)
full_path = StringUtil::StdStringFromFormat("%s/%s/%s/%s", OriginPath, ParentPath, Path, pDirEnt->d_name);
else if (Path != nullptr)
full_path = StringUtil::StdStringFromFormat("%s/%s/%s", OriginPath, Path, pDirEnt->d_name);
if (ParentPath)
full_path = fmt::format("{}/{}/{}/{}", OriginPath, ParentPath, Path, pDirEnt->d_name);
else if (Path)
full_path = fmt::format("{}/{}/{}", OriginPath, Path, pDirEnt->d_name);
else
full_path = StringUtil::StdStringFromFormat("%s/%s", OriginPath, pDirEnt->d_name);
full_path = fmt::format("{}/{}", OriginPath, pDirEnt->d_name);
FILESYSTEM_FIND_DATA outData;
outData.Attributes = 0;
@@ -1681,15 +1701,23 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
{
if (Flags & FILESYSTEM_FIND_RECURSIVE)
{
// recurse into this directory
if (ParentPath != nullptr)
// check that we're not following an infinite symbolic link loop
if (std::string real_recurse_dir = Path::RealPath(full_path);
real_recurse_dir.empty() || std::find(visited.begin(), visited.end(), real_recurse_dir) == visited.end())
{
std::string recursiveDir = StringUtil::StdStringFromFormat("%s/%s", ParentPath, Path);
nFiles += RecursiveFindFiles(OriginPath, recursiveDir.c_str(), pDirEnt->d_name, Pattern, Flags, pResults);
}
else
{
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults);
if (!real_recurse_dir.empty())
visited.push_back(std::move(real_recurse_dir));
// recurse into this directory
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);
}
else
{
nFiles += RecursiveFindFiles(OriginPath, Path, pDirEnt->d_name, Pattern, Flags, pResults, visited);
}
}
}
@@ -1721,17 +1749,16 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
}
// add file to list
// TODO string formatter, clean this mess..
if (!(Flags & FILESYSTEM_FIND_RELATIVE_PATHS))
{
outData.FileName = std::move(full_path);
}
else
{
if (ParentPath != nullptr)
outData.FileName = StringUtil::StdStringFromFormat("%s/%s/%s", ParentPath, Path, pDirEnt->d_name);
else if (Path != nullptr)
outData.FileName = StringUtil::StdStringFromFormat("%s/%s", Path, pDirEnt->d_name);
if (ParentPath)
outData.FileName = fmt::format("{}/{}/{}", ParentPath, Path, pDirEnt->d_name);
else if (Path)
outData.FileName = fmt::format("{}/{}", Path, pDirEnt->d_name);
else
outData.FileName = pDirEnt->d_name;
}
@@ -1744,18 +1771,27 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co
return nFiles;
}
bool FileSystem::FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults)
bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results)
{
// has a path
if (Path[0] == '\0')
if (path[0] == '\0')
return false;
// clear result array
if (!(Flags & FILESYSTEM_FIND_KEEP_ARRAY))
pResults->clear();
if (!(flags & FILESYSTEM_FIND_KEEP_ARRAY))
results->clear();
// add self if recursive, we don't want to visit it twice
std::vector<std::string> visited;
if (flags & FILESYSTEM_FIND_RECURSIVE)
{
std::string real_path = Path::RealPath(path);
if (!real_path.empty())
visited.push_back(std::move(real_path));
}
// enter the recursive function
return (RecursiveFindFiles(Path, nullptr, nullptr, Pattern, Flags, pResults) > 0);
return (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results, visited) > 0);
}
bool FileSystem::StatFile(const char* path, struct stat* st)
+10
View File
@@ -55,6 +55,11 @@ using r128 = __m128i;
return _mm_set_epi64x(0, val);
}
[[maybe_unused]] __fi static r128 r128_from_u32_dup(u32 val)
{
return _mm_set1_epi32(val);
}
[[maybe_unused]] __fi static r128 r128_from_u32x4(u32 lo0, u32 lo1, u32 hi0, u32 hi1)
{
return _mm_setr_epi32(lo0, lo1, hi0, hi1);
@@ -150,6 +155,11 @@ using r128 = uint32x4_t;
return vreinterpretq_u32_u64(vcombine_u64(vcreate_u64(val), vcreate_u64(0)));
}
[[maybe_unused]] __fi static r128 r128_from_u32_dup(u32 val)
{
return vdupq_n_u32(val);
}
[[maybe_unused]] __fi static r128 r128_from_u32x4(u32 lo0, u32 lo1, u32 hi0, u32 hi1)
{
const u32 values[4] = {lo0, lo1, hi0, hi1};
+3 -4
View File
@@ -59,10 +59,7 @@ public:
};
};
inline color_rgba()
{
static_assert(sizeof(*this) == 4, "sizeof(*this) != 4");
}
inline color_rgba() = default;
inline color_rgba(int y)
{
@@ -190,6 +187,8 @@ public:
static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); }
};
static_assert(sizeof(color_rgba) == 4);
bool unpack_bc7(const void *pBlock, color_rgba *pPixels);
} // namespace bc7decomp
+10 -2
View File
@@ -183,7 +183,15 @@ public:
return false;
}
bool WriteV(const char* fmt, va_list list) const;
bool WriteV(const char* fmt, va_list list) const
{
Console.FormatV(fmt, list);
return false;
}
bool WriteV(ConsoleColors color, const char* fmt, va_list list) const;
bool WriteV(ConsoleColors color, const char* fmt, va_list list) const
{
Console.FormatV(color, fmt, list);
return false;
}
};
+3
View File
@@ -6,6 +6,9 @@
#include "common/Threading.h"
#include "common/Assertions.h"
#include "common/RedtapeWindows.h"
#include <memory>
#include <mmsystem.h>
#include <process.h>
#include <timeapi.h>
+39 -45
View File
@@ -18,12 +18,15 @@ IF EXIST "%ProgramFiles%\Git\bin\git.exe" SET "GITPATH=%ProgramFiles%\Git\bin"
IF EXIST "%ProgramW6432%\Git\bin\git.exe" SET "GITPATH=%ProgramW6432%\Git\bin"
IF DEFINED GITPATH SET "PATH=%PATH%;%GITPATH%"
FOR /F "tokens=1-2" %%i IN ('"git show -s --format=%%%ci HEAD 2> NUL"') do (
set REV3=%%i%%j
)
FOR /F %%i IN ('"git describe 2> NUL"') do (
set GIT_REV=%%i
git describe --tags > NUL 2>NUL
if !ERRORLEVEL! EQU 0 (
FOR /F %%i IN ('"git describe --tags 2> NUL"') do (
set GIT_REV=%%i
)
) else (
FOR /F %%i IN ('"git rev-parse --short HEAD 2> NUL"') do (
set GIT_REV=%%i
)
)
FOR /F "tokens=* USEBACKQ" %%i IN (`git tag --points-at HEAD`) DO (
@@ -34,11 +37,11 @@ FOR /F "tokens=* USEBACKQ" %%i IN (`git rev-parse HEAD`) DO (
set GIT_HASH=%%i
)
set REV2=%REV3: =%
set REV1=%REV2:-=%
set REV=%REV1::=%
FOR /F "tokens=* USEBACKQ" %%i IN (`git log -1 "--format=%%cd" "--date=local"`) DO (
set GIT_DATE=%%i
)
SET SIGNATURELINE=// H[%GIT_HASH%] T[%GIT_TAG%]
SET SIGNATURELINE=// R[%GIT_REV%] H[%GIT_HASH%] T[%GIT_TAG%]
SET /P EXISTINGLINE=<"%CD%\svnrev.h"
IF "%EXISTINGLINE%"=="%SIGNATURELINE%" (
@@ -48,45 +51,36 @@ IF "%EXISTINGLINE%"=="%SIGNATURELINE%" (
ECHO Updating "%CD%\svnrev.h"...
echo %SIGNATURELINE%> "%CD%\svnrev.h"
git show -s > NUL 2>&1
if %ERRORLEVEL% NEQ 0 (
echo Automatic version detection unavailable.
echo If you want to have the version string print correctly,
echo make sure your Git.exe is in the default installation directory,
echo or in your PATH.
echo You can safely ignore this message - a dummy string will be printed.
echo #define GIT_HASH "%GIT_HASH%" >> "%CD%\svnrev.h"
echo #define GIT_TAG "%GIT_TAG%" >> "%CD%\svnrev.h"
echo #define GIT_DATE "%GIT_DATE%" >> "%CD%\svnrev.h"
echo #define SVN_REV_UNKNOWN >> "%CD%\svnrev.h"
echo #define SVN_REV 0ll >> "%CD%\svnrev.h"
echo #define GIT_REV "" >> "%CD%\svnrev.h"
echo #define GIT_HASH "" >> "%CD%\svnrev.h"
echo #define GIT_TAG "" >> "%CD%\svnrev.h"
echo #define GIT_TAGGED_COMMIT 0 >> "%CD%\svnrev.h"
echo %GIT_TAG%|FINDSTR /R "^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$" > NUL
if !ERRORLEVEL! EQU 0 (
echo #define GIT_REV "%GIT_TAG%" >> "%CD%\svnrev.h"
echo #define GIT_TAGGED_COMMIT 1 >> "%CD%\svnrev.h"
FOR /F "tokens=1,2,3 delims=v." %%a in ("%GIT_TAG%") DO (
echo #define GIT_TAG_HI %%a >> "%CD%\svnrev.h"
echo #define GIT_TAG_MID %%b >> "%CD%\svnrev.h"
echo #define GIT_TAG_LO %%c >> "%CD%\svnrev.h"
)
) else (
:: Support New Tagged Release Model
if [%GIT_TAG%] NEQ [] (
echo Detected that the current commit is tagged, using that!
echo #define SVN_REV %REV%ll >> "%CD%\svnrev.h"
echo #define GIT_REV "" >> "%CD%\svnrev.h"
echo #define GIT_HASH "%GIT_HASH%" >> "%CD%\svnrev.h"
echo #define GIT_TAG "%GIT_TAG%" >> "%CD%\svnrev.h"
echo %GIT_TAG%|FINDSTR /R "^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$" > NUL
if !ERRORLEVEL! EQU 0 (
FOR /F "tokens=1,2,3 delims=v." %%a in ("%GIT_TAG%") DO (
echo #define GIT_TAG_HI %%a >> "%CD%\svnrev.h"
echo #define GIT_TAG_MID %%b >> "%CD%\svnrev.h"
echo #define GIT_TAG_LO %%c >> "%CD%\svnrev.h"
)
:: Local branches
echo #define GIT_REV "%GIT_REV%" >> "%CD%\svnrev.h"
echo #define GIT_TAGGED_COMMIT 0 >> "%CD%\svnrev.h"
echo %GIT_REV%|FINDSTR /R "^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]-*" > NUL
if !ERRORLEVEL! EQU 0 (
FOR /F "tokens=1,2,3 delims=v." %%a in ("%GIT_REV%") DO (
echo #define GIT_TAG_HI %%a >> "%CD%\svnrev.h"
echo #define GIT_TAG_MID %%b >> "%CD%\svnrev.h"
FOR /F "tokens=1 delims=-" %%d in ("%%c%") DO (
echo #define GIT_TAG_LO %%d >> "%CD%\svnrev.h"
)
)
echo #define GIT_TAGGED_COMMIT 1 >> "%CD%\svnrev.h"
) else (
echo #define SVN_REV %REV%ll >> "%CD%\svnrev.h"
echo #define GIT_REV "%GIT_REV%" >> "%CD%\svnrev.h"
echo #define GIT_HASH "%GIT_HASH%" >> "%CD%\svnrev.h"
echo #define GIT_TAG "" >> "%CD%\svnrev.h"
echo #define GIT_TAGGED_COMMIT 0 >> "%CD%\svnrev.h"
echo #define GIT_TAG_HI 0 >> "%CD%\svnrev.h"
echo #define GIT_TAG_MID 0 >> "%CD%\svnrev.h"
echo #define GIT_TAG_LO 0 >> "%CD%\svnrev.h"
)
)
+3 -3
View File
@@ -1,5 +1,5 @@
pull_request_title: "translations: Syncing Crowdin translations"
commit_message: "[ci skip]"
pull_request_title: 'translations: Syncing Crowdin translations'
commit_message: '[ci skip]'
files:
- source: pcsx2-qt/Translations/pcsx2-qt_en.ts
translation: '/pcsx2-qt/Translations/pcsx2-qt_%locale%.ts'
translation: /pcsx2-qt/Translations/pcsx2-qt_%locale%.ts
+11 -5
View File
@@ -33,9 +33,9 @@
#include "pcsx2/GameList.h"
#include "pcsx2/Host.h"
#include "pcsx2/INISettingsInterface.h"
#include "pcsx2/ImGui/FullscreenUI.h"
#include "pcsx2/ImGui/ImGuiManager.h"
#include "pcsx2/Input/InputManager.h"
#include "pcsx2/LogSink.h"
#include "pcsx2/MTGS.h"
#include "pcsx2/SIO/Pad/Pad.h"
#include "pcsx2/PerformanceMetrics.h"
@@ -95,6 +95,8 @@ bool GSRunner::InitializeConfig()
if (!VMManager::PerformEarlyHardwareChecks(&error))
return false;
ImGuiManager::SetFontPathAndRange(Path::Combine(EmuFolders::Resources, "fonts" FS_OSPATH_SEPARATOR_STR "Roboto-Regular.ttf"), {});
// don't provide an ini path, or bother loading. we'll store everything in memory.
MemorySettingsInterface& si = s_settings_interface;
Host::Internal::SetBaseSettingsLayer(&si);
@@ -390,6 +392,11 @@ void Host::OnCoverDownloaderOpenRequested()
// noop
}
void Host::OnCreateMemoryCardOpenRequested()
{
// noop
}
std::optional<u32> InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str)
{
return std::nullopt;
@@ -440,7 +447,7 @@ void GSRunner::InitializeConsole()
const char* var = std::getenv("PCSX2_NOCONSOLE");
s_no_console = (var && StringUtil::FromChars<bool>(var).value_or(false));
if (!s_no_console)
LogSink::InitializeEarlyConsole();
Log::SetConsoleOutputLevel(LOGLEVEL_DEBUG);
}
bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& params)
@@ -566,7 +573,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
{
// disable timestamps, since we want to be able to diff the logs
Console.WriteLn("Logging to %s...", logfile);
LogSink::SetFileLogPath(logfile);
VMManager::Internal::SetFileLogPath(logfile);
s_settings_interface.SetBoolValue("Logging", "EnableFileLogging", true);
s_settings_interface.SetBoolValue("Logging", "EnableTimestamps", false);
}
@@ -696,12 +703,11 @@ int main(int argc, char* argv[])
VMManager::Internal::CPUThreadShutdown();
GSRunner::DestroyPlatformWindow();
LogSink::CloseFileLog();
return EXIT_SUCCESS;
}
void Host::VSyncOnCPUThread()
void Host::PumpMessagesOnCPUThread()
{
// update GS thread copy of frame number
MTGS::RunOnGSThread([frame_number = GSDumpReplayer::GetFrameNumber()]() { s_dump_frame_number = frame_number; });
+81 -5
View File
@@ -1,13 +1,37 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "pcsx2/SysForwardDefs.h"
#include "pcsx2/SupportURLs.h"
#include "AboutDialog.h"
#include "QtHost.h"
#include "QtUtils.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/SmallString.h"
#include <QtCore/QFile>
#include <QtCore/QString>
#include <QtGui/QDesktopServices>
#include <QtWidgets/QDialog>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTextBrowser>
static QString GetDocFileUrl(std::string_view name)
{
#ifdef _WIN32
// Windows uses the docs directory in bin.
const std::string path = Path::Combine(EmuFolders::AppRoot,
TinyString::from_fmt("docs" FS_OSPATH_SEPARATOR_STR "{}", name));
#else
// Linux/Mac has this in the Resources directory.
const std::string path = Path::Combine(EmuFolders::Resources,
TinyString::from_fmt("docs" FS_OSPATH_SEPARATOR_STR "{}", name));
#endif
return QUrl::fromLocalFile(QString::fromStdString(path)).toString();
}
AboutDialog::AboutDialog(QWidget* parent)
: QDialog(parent)
@@ -21,9 +45,8 @@ AboutDialog::AboutDialog(QWidget* parent)
m_ui.scmversion->setText(QtHost::GetAppNameAndVersion());
m_ui.links->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_ui.links->setOpenExternalLinks(true);
m_ui.links->setText(QStringLiteral(
R"(<a href="%1">%2</a> | <a href="%3">%4</a> | <a href="%5">%6</a> | <a href="%7">%8</a>)")
R"(<a href="%1">%2</a> | <a href="%3">%4</a> | <a href="%5">%6</a> | <a href="%7">%8</a> | <a href="%9">%10</a>)")
.arg(getWebsiteUrl())
.arg(tr("Website"))
.arg(getSupportForumsUrl())
@@ -31,8 +54,11 @@ AboutDialog::AboutDialog(QWidget* parent)
.arg(getGitHubRepositoryUrl())
.arg(tr("GitHub Repository"))
.arg(getLicenseUrl())
.arg(tr("License")));
.arg(tr("License"))
.arg(getThirdPartyLicensesUrl())
.arg(tr("Third-Party Licenses")));
connect(m_ui.links, &QLabel::linkActivated, this, &AboutDialog::linksLinkActivated);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
}
@@ -55,10 +81,60 @@ QString AboutDialog::getGitHubRepositoryUrl()
QString AboutDialog::getLicenseUrl()
{
return QString::fromUtf8(PCSX2_LICENSE_URL);
return GetDocFileUrl("GPL.html");
}
QString AboutDialog::getThirdPartyLicensesUrl()
{
return GetDocFileUrl("ThirdPartyLicenses.html");
}
QString AboutDialog::getDiscordServerUrl()
{
return QString::fromUtf8(PCSX2_DISCORD_URL);
}
void AboutDialog::linksLinkActivated(const QString& link)
{
const QUrl url(link);
if (!url.isValid())
return;
if (!url.isLocalFile())
{
QDesktopServices::openUrl(url);
return;
}
showHTMLDialog(this, tr("View Document"), url.toLocalFile());
}
void AboutDialog::showHTMLDialog(QWidget* parent, const QString& title, const QString& path)
{
QDialog dialog(parent);
dialog.setMinimumSize(700, 400);
dialog.setWindowTitle(title);
dialog.setWindowIcon(QtHost::GetAppIcon());
QVBoxLayout* layout = new QVBoxLayout(&dialog);
QTextBrowser* tb = new QTextBrowser(&dialog);
tb->setAcceptRichText(true);
tb->setReadOnly(true);
tb->setOpenExternalLinks(true);
QFile file(path);
file.open(QIODevice::ReadOnly);
if (const QByteArray data = file.readAll(); !data.isEmpty())
tb->setText(QString::fromUtf8(data));
else
tb->setText(tr("File not found: %1").arg(path));
layout->addWidget(tb, 1);
QDialogButtonBox* bb = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
connect(bb->button(QDialogButtonBox::Close), &QPushButton::clicked, &dialog, &QDialog::done);
layout->addWidget(bb, 0);
dialog.exec();
}
+6
View File
@@ -18,8 +18,14 @@ public:
static QString getSupportForumsUrl();
static QString getGitHubRepositoryUrl();
static QString getLicenseUrl();
static QString getThirdPartyLicensesUrl();
static QString getDiscordServerUrl();
static void showHTMLDialog(QWidget* parent, const QString& title, const QString& url);
private Q_SLOTS:
void linksLinkActivated(const QString& link);
private:
Ui::AboutDialog m_ui;
};
+74 -49
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "AutoUpdaterDialog.h"
@@ -8,7 +8,6 @@
#include "QtUtils.h"
#include "pcsx2/Host.h"
#include "pcsx2/SysForwardDefs.h"
#include "svnrev.h"
#include "updater/UpdaterExtractor.h"
@@ -16,8 +15,10 @@
#include "common/Assertions.h"
#include "common/CocoaTools.h"
#include "common/Console.h"
#include "common/Error.h"
#include "common/FileSystem.h"
#include "common/HTTPDownloader.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "cpuinfo.h"
@@ -38,12 +39,17 @@
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressDialog>
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include <shellapi.h>
#endif
// Interval at which HTTP requests are polled.
static constexpr u32 HTTP_POLL_INTERVAL = 10;
// Logic to detect whether we can use the auto updater.
// We use tagged commit, because this gets set on nightly builds.
#if (defined(_WIN32) || defined(__linux__) || defined(__APPLE__)) && defined(GIT_TAG_LO)
#if (defined(_WIN32) || defined(__linux__) || defined(__APPLE__)) && GIT_TAGGED_COMMIT
#define AUTO_UPDATER_SUPPORTED 1
@@ -73,8 +79,13 @@ static constexpr u32 HTTP_POLL_INTERVAL = 10;
// Available release channels.
static const char* UPDATE_TAGS[] = {"stable", "nightly"};
// Bit annoying, because PCSX2_isReleaseVersion is a bool, but whatever.
#define THIS_RELEASE_TAG (PCSX2_isReleaseVersion ? "stable" : "nightly")
// TODO: Make manual releases create this file, and make it contain `#define DEFAULT_UPDATER_CHANNEL "stable"`.
#if __has_include("DefaultUpdaterChannel.h")
#include "DefaultUpdaterChannel.h"
#endif
#ifndef DEFAULT_UPDATER_CHANNEL
#define DEFAULT_UPDATER_CHANNEL "nightly"
#endif
#endif
@@ -129,7 +140,7 @@ QStringList AutoUpdaterDialog::getTagList()
std::string AutoUpdaterDialog::getDefaultTag()
{
#ifdef AUTO_UPDATER_SUPPORTED
return THIS_RELEASE_TAG;
return DEFAULT_UPDATER_CHANNEL;
#else
return {};
#endif
@@ -142,15 +153,13 @@ QString AutoUpdaterDialog::getCurrentVersion()
QString AutoUpdaterDialog::getCurrentVersionDate()
{
// 20220403235450ll
const QDateTime current_build_date(QDateTime::fromString(QStringLiteral("%1").arg(SVN_REV), "yyyyMMddhhmmss"));
return current_build_date.toString();
return QStringLiteral(GIT_DATE);
}
QString AutoUpdaterDialog::getCurrentUpdateTag() const
{
#ifdef AUTO_UPDATER_SUPPORTED
return QString::fromStdString(Host::GetBaseStringSettingValue("AutoUpdater", "UpdateTag", THIS_RELEASE_TAG));
return QString::fromStdString(Host::GetBaseStringSettingValue("AutoUpdater", "UpdateTag", DEFAULT_UPDATER_CHANNEL));
#else
return QString();
#endif
@@ -566,70 +575,74 @@ void AutoUpdaterDialog::remindMeLaterClicked()
#if defined(_WIN32)
bool AutoUpdaterDialog::doesUpdaterNeedElevation(const std::string& application_dir) const
{
// Try to create a dummy text file in the PCSX2 updater directory. If it fails, we probably won't have write permission.
const std::string dummy_path = Path::Combine(application_dir, "update.txt");
auto fp = FileSystem::OpenManagedCFile(dummy_path.c_str(), "wb");
if (!fp)
return true;
fp.reset();
FileSystem::DeleteFilePath(dummy_path.c_str());
return false;
}
bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& data, QProgressDialog&)
{
const QString update_directory = QCoreApplication::applicationDirPath();
const QString update_zip_path = QStringLiteral("%1" FS_OSPATH_SEPARATOR_STR "%2").arg(update_directory).arg(UPDATER_ARCHIVE_NAME);
const QString updater_path = QStringLiteral("%1" FS_OSPATH_SEPARATOR_STR "%2").arg(update_directory).arg(UPDATER_EXECUTABLE);
const std::string& application_dir = EmuFolders::AppRoot;
const std::string update_zip_path = Path::Combine(EmuFolders::DataRoot, UPDATER_ARCHIVE_NAME);
const std::string updater_path = Path::Combine(EmuFolders::DataRoot, UPDATER_EXECUTABLE);
Q_ASSERT(!update_zip_path.isEmpty() && !updater_path.isEmpty() && !update_directory.isEmpty());
if ((QFile::exists(update_zip_path) && !QFile::remove(update_zip_path)) ||
(QFile::exists(updater_path) && !QFile::remove(updater_path)))
if ((FileSystem::FileExists(update_zip_path.c_str()) && !FileSystem::DeleteFilePath(update_zip_path.c_str())))
{
reportError("Removing existing update zip/updater failed");
reportError("Removing existing update zip failed");
return false;
}
if (!FileSystem::WriteBinaryFile(update_zip_path.c_str(), data.data(), data.size()))
{
QFile update_zip_file(update_zip_path);
if (!update_zip_file.open(QIODevice::WriteOnly) ||
update_zip_file.write(reinterpret_cast<const char*>(data.data()), static_cast<qint64>(data.size())) != static_cast<qint64>(data.size()))
{
reportError("Writing update zip to '%s' failed", update_zip_path.toUtf8().constData());
return false;
}
update_zip_file.close();
reportError("Writing update zip to '%s' failed", update_zip_path.c_str());
return false;
}
std::string updater_extract_error;
if (!ExtractUpdater(update_zip_path.toUtf8().constData(), updater_path.toUtf8().constData(), &updater_extract_error))
if (!ExtractUpdater(update_zip_path.c_str(), updater_path.c_str(), &updater_extract_error))
{
reportError("Extracting updater failed: %s", updater_extract_error.c_str());
return false;
}
if (!doUpdate(update_zip_path, updater_path, update_directory))
{
reportError("Launching updater failed");
return false;
}
return true;
return doUpdate(application_dir, update_zip_path, updater_path);
}
bool AutoUpdaterDialog::doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path)
bool AutoUpdaterDialog::doUpdate(const std::string& application_dir, const std::string& zip_path, const std::string& updater_path)
{
const QString program_path = QCoreApplication::applicationFilePath();
if (program_path.isEmpty())
const std::string program_path = QDir::toNativeSeparators(QCoreApplication::applicationFilePath()).toStdString();
if (program_path.empty())
{
reportError("Failed to get current application path");
return false;
}
QStringList arguments;
arguments << QString::number(QCoreApplication::applicationPid());
arguments << destination_path;
arguments << zip_path;
arguments << program_path;
const std::wstring wupdater_path = StringUtil::UTF8StringToWideString(updater_path);
const std::wstring wapplication_dir = StringUtil::UTF8StringToWideString(application_dir);
const std::wstring arguments = StringUtil::UTF8StringToWideString(fmt::format("{} \"{}\" \"{}\" \"{}\"",
QCoreApplication::applicationPid(), application_dir, zip_path, program_path));
// this will leak, but not sure how else to handle it...
QProcess* updater_process = new QProcess();
updater_process->setProgram(updater_path);
updater_process->setArguments(arguments);
updater_process->start(QIODevice::NotOpen);
if (!updater_process->waitForStarted())
const bool needs_elevation = doesUpdaterNeedElevation(application_dir);
SHELLEXECUTEINFOW sei = {};
sei.cbSize = sizeof(sei);
sei.lpVerb = needs_elevation ? L"runas" : nullptr; // needed to trigger elevation
sei.lpFile = wupdater_path.c_str();
sei.lpParameters = arguments.c_str();
sei.lpDirectory = wapplication_dir.c_str();
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteExW(&sei))
{
reportError("Failed to start updater");
reportError("Failed to start %s: %s", needs_elevation ? "elevated updater" : "updater",
Error::CreateWin32(GetLastError()).GetDescription().c_str());
return false;
}
@@ -638,7 +651,19 @@ bool AutoUpdaterDialog::doUpdate(const QString& zip_path, const QString& updater
void AutoUpdaterDialog::cleanupAfterUpdate()
{
// Nothing to do on Windows for now, the updater stub cleans everything up.
// If we weren't portable, then updater executable gets left in the application directory.
if (EmuFolders::AppRoot == EmuFolders::DataRoot)
return;
const std::string updater_path = Path::Combine(EmuFolders::DataRoot, UPDATER_EXECUTABLE);
if (!FileSystem::FileExists(updater_path.c_str()))
return;
if (!FileSystem::DeleteFilePath(updater_path.c_str()))
{
QMessageBox::critical(nullptr, tr("Updater Error"), tr("Failed to remove updater exe after update."));
return;
}
}
#elif defined(__linux__)
+3 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
@@ -60,7 +60,8 @@ private:
bool processUpdate(const std::vector<u8>& data, QProgressDialog& progress);
#if defined(_WIN32)
bool doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path);
bool doesUpdaterNeedElevation(const std::string& application_dir) const;
bool doUpdate(const std::string& application_dir, const std::string& zip_path, const std::string& updater_path);
#endif
Ui::AutoUpdaterDialog m_ui;
+9
View File
@@ -21,6 +21,8 @@ target_sources(pcsx2-qt PRIVATE
DisplayWidget.cpp
DisplayWidget.h
EarlyHardwareCheck.cpp
LogWindow.cpp
LogWindow.h
MainWindow.cpp
MainWindow.h
MainWindow.ui
@@ -64,6 +66,7 @@ target_sources(pcsx2-qt PRIVATE
Settings/ControllerBindingWidget.ui
Settings/ControllerBindingWidget_DualShock2.ui
Settings/ControllerBindingWidget_Guitar.ui
Settings/ControllerBindingWidget_Popn.ui
Settings/ControllerBindingWidgets.cpp
Settings/ControllerBindingWidgets.h
Settings/ControllerLEDSettingsDialog.ui
@@ -72,6 +75,7 @@ target_sources(pcsx2-qt PRIVATE
Settings/ControllerGlobalSettingsWidget.ui
Settings/ControllerMacroEditWidget.ui
Settings/ControllerMacroWidget.ui
Settings/ControllerMappingSettingsDialog.ui
Settings/ControllerMouseSettingsDialog.ui
Settings/ControllerSettingsWindow.cpp
Settings/ControllerSettingsWindow.h
@@ -146,6 +150,8 @@ target_sources(pcsx2-qt PRIVATE
Debugger/CpuWidget.cpp
Debugger/CpuWidget.h
Debugger/CpuWidget.ui
Debugger/DebuggerSettingsManager.cpp
Debugger/DebuggerSettingsManager.h
Debugger/DebuggerWindow.cpp
Debugger/DebuggerWindow.h
Debugger/DebuggerWindow.ui
@@ -167,6 +173,8 @@ target_sources(pcsx2-qt PRIVATE
Debugger/Models/ThreadModel.h
Debugger/Models/StackModel.cpp
Debugger/Models/StackModel.h
Debugger/Models/SavedAddressesModel.cpp
Debugger/Models/SavedAddressesModel.h
Tools/InputRecording/NewInputRecordingDlg.cpp
Tools/InputRecording/NewInputRecordingDlg.h
Tools/InputRecording/NewInputRecordingDlg.ui
@@ -242,4 +250,5 @@ if(WIN32)
)
endif()
fixup_file_properties(pcsx2-qt)
setup_main_executable(pcsx2-qt)
+286 -153
View File
@@ -7,6 +7,8 @@
#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"
@@ -39,13 +41,24 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& 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](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, m_ui.memoryviewWidget, &MemoryViewWidget::gotoAddress);
connect(m_ui.disassemblyWidget, &DisassemblyWidget::gotoInMemory, m_ui.memoryviewWidget, &MemoryViewWidget::gotoAddress);
@@ -99,7 +112,11 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.btnSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked);
connect(m_ui.btnFilterSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked);
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) { m_ui.memoryviewWidget->gotoAddress(item->text().toUInt(nullptr, 16)); });
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item)
{
m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory);
m_ui.memoryviewWidget->gotoAddress(item->text().toUInt(nullptr, 16));
});
connect(m_ui.listSearchResults->verticalScrollBar(), &QScrollBar::valueChanged, this, &CpuWidget::onSearchResultsListScroll);
connect(m_ui.listSearchResults, &QListView::customContextMenuRequested, this, &CpuWidget::onListSearchResultsContextMenu);
connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, [this](int i) {
@@ -127,6 +144,21 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
m_resultsLoadTimer.setInterval(100);
m_resultsLoadTimer.setSingleShot(true);
connect(&m_resultsLoadTimer, &QTimer::timeout, this, &CpuWidget::loadSearchResults);
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());
});
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
}
CpuWidget::~CpuWidget() = default;
@@ -282,46 +314,63 @@ void CpuWidget::onBPListDoubleClicked(const QModelIndex& index)
void CpuWidget::onBPListContextMenu(QPoint pos)
{
if (!m_cpu.isAlive())
return;
QMenu* contextMenu = new QMenu(tr("Breakpoint List Context Menu"), m_ui.breakpointList);
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())
if (m_cpu.isAlive())
{
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* 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* copyAction = new QAction(tr("Copy"), m_ui.breakpointList);
connect(copyAction, &QAction::triggered, this, &CpuWidget::contextBPListCopy);
contextMenu->addAction(copyAction);
}
QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList);
connect(editAction, &QAction::triggered, this, &CpuWidget::contextBPListEdit);
contextMenu->addAction(editAction);
QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList);
connect(deleteAction, &QAction::triggered, this, &CpuWidget::contextBPListDelete);
contextMenu->addAction(deleteAction);
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();
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_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);
}
QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList);
connect(actionImport, &QAction::triggered, this, &CpuWidget::contextBPListPasteCSV);
contextMenu->addAction(actionImport);
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));
}
@@ -388,7 +437,7 @@ void CpuWidget::contextBPListPasteCSV()
// 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"("([^"]|\\.)*")");
QRegularExpression eachQuotePair(R"("([^"]|\\.)*")");
QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line);
while (it.hasNext())
{
@@ -396,107 +445,136 @@ void CpuWidget::contextBPListPasteCSV()
QString matchedValue = match.captured(0);
fields << matchedValue.mid(1, matchedValue.length() - 2);
}
if (fields.size() != BreakpointModel::BreakpointColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger CSV Import: Invalid number of columns, skipping");
continue;
}
bool ok;
int type = fields[BreakpointModel::BreakpointColumns::TYPE].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse type '%s', skipping", fields[BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
continue;
}
// This is how we differentiate between breakpoints and memchecks
if (type == MEMCHECK_INVALID)
{
BreakPoint bp;
// Address
bp.addr = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
continue;
}
// Condition
if (!fields[BreakpointModel::BreakpointColumns::CONDITION].isEmpty())
{
PostfixExpression expr;
bp.hasCond = true;
bp.cond.debug = &m_cpu;
if (!m_cpu.initExpression(fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), expr))
{
Console.WriteLn("Debugger CSV Import: Failed to parse cond '%s', skipping", fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData());
continue;
}
bp.cond.expression = expr;
strncpy(&bp.cond.expressionString[0], fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), sizeof(bp.cond.expressionString));
}
// Enabled
bp.enabled = fields[BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse enable flag '%s', skipping", fields[BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
continue;
}
m_bpModel.insertBreakpointRows(0, 1, {bp});
}
else
{
MemCheck mc;
// Mode
if (type >= MEMCHECK_INVALID)
{
Console.WriteLn("Debugger CSV Import: Failed to parse cond type '%s', skipping", fields [BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
continue;
}
mc.cond = static_cast<MemCheckCondition>(type);
// Address
mc.start = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
continue;
}
// Size
mc.end = fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUInt(&ok) + mc.start;
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse length '%s', skipping", fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUtf8().constData());
continue;
}
// Result
int result = fields [BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse result flag '%s', skipping", fields [BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
continue;
}
mc.result = static_cast<MemCheckResult>(result);
m_bpModel.insertBreakpointRows(0, 1, {mc});
}
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->gotoAddress(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::contextSearchResultGoToDisassembly()
{
const QItemSelectionModel* selModel = m_ui.listSearchResults->selectionModel();
if (!selModel->hasSelection())
return;
m_ui.disassemblyWidget->gotoAddress(m_ui.listSearchResults->selectedItems().first()->data(256).toUInt());
m_ui.disassemblyWidget->gotoAddress(m_ui.listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt());
}
void CpuWidget::contextRemoveSearchResult()
@@ -506,14 +584,25 @@ void CpuWidget::contextRemoveSearchResult()
return;
const int selectedResultIndex = m_ui.listSearchResults->row(m_ui.listSearchResults->selectedItems().first());
auto* rowToRemove = m_ui.listSearchResults->takeItem(selectedResultIndex);
if (m_searchResults.size() > static_cast<size_t>(selectedResultIndex) && m_searchResults.at(selectedResultIndex) == rowToRemove->data(256).toUInt())
const auto* rowToRemove = m_ui.listSearchResults->takeItem(selectedResultIndex);
if (m_searchResults.size() > static_cast<size_t>(selectedResultIndex) && m_searchResults.at(selectedResultIndex) == rowToRemove->data(Qt::UserRole).toUInt())
{
m_searchResults.erase(m_searchResults.begin() + selectedResultIndex);
}
delete rowToRemove;
}
void CpuWidget::contextCopySearchResultAddress()
{
if (!m_ui.listSearchResults->selectionModel()->hasSelection())
return;
const u32 selectedResultIndex = m_ui.listSearchResults->row(m_ui.listSearchResults->selectedItems().first());
const u32 rowAddress = m_ui.listSearchResults->item(selectedResultIndex)->data(Qt::UserRole).toUInt();
const QString addressString = FilledQStringFromValue(rowAddress, 16);
QApplication::clipboard()->setText(addressString);
}
void CpuWidget::updateFunctionList(bool whenEmpty)
{
if (!m_cpu.isAlive())
@@ -548,7 +637,7 @@ void CpuWidget::updateFunctionList(bool whenEmpty)
item->setText(QString("%0 %1").arg(FilledQStringFromValue(symbol.address, 16)).arg(symbolName));
item->setData(256, symbol.address);
item->setData(Qt::UserRole, symbol.address);
m_ui.listFunctions->addItem(item);
}
@@ -576,7 +665,7 @@ void CpuWidget::updateFunctionList(bool whenEmpty)
symbolName = demangledName;
}
QTreeWidgetItem* functionItem = new QTreeWidgetItem(moduleItem, QStringList(QString("%0 %1").arg(FilledQStringFromValue(sym.address, 16)).arg(symbolName)));
functionItem->setData(0, 256, sym.address);
functionItem->setData(0, Qt::UserRole, sym.address);
functions.append(functionItem);
}
moduleItem->addChildren(functions);
@@ -653,7 +742,7 @@ void CpuWidget::onFuncListContextMenu(QPoint pos)
else
m_funclistContextMenu->clear();
if (m_ui.listFunctions->selectedItems().count() && m_ui.listFunctions->selectedItems().first()->data(256).isValid())
if (m_ui.listFunctions->selectedItems().count() && m_ui.listFunctions->selectedItems().first()->data(Qt::UserRole).isValid())
{
QAction* copyName = new QAction(tr("Copy Function Name"), m_ui.listFunctions);
connect(copyName, &QAction::triggered, [this] {
@@ -661,14 +750,14 @@ void CpuWidget::onFuncListContextMenu(QPoint pos)
// Resolve the function name by fetching the symbolmap and filtering the address
const QListWidgetItem* selectedItem = m_ui.listFunctions->selectedItems().first();
const QString functionName = QString(m_cpu.GetSymbolMap().GetLabelName(selectedItem->data(256).toUInt()).c_str());
const QString functionName = QString(m_cpu.GetSymbolMap().GetLabelName(selectedItem->data(Qt::UserRole).toUInt()).c_str());
QApplication::clipboard()->setText(functionName);
});
m_funclistContextMenu->addAction(copyName);
QAction* copyAddress = new QAction(tr("Copy Function Address"), m_ui.listFunctions);
connect(copyAddress, &QAction::triggered, [this] {
const QString addressString = FilledQStringFromValue(m_ui.listFunctions->selectedItems().first()->data(256).toUInt(), 16);
const QString addressString = FilledQStringFromValue(m_ui.listFunctions->selectedItems().first()->data(Qt::UserRole).toUInt(), 16);
QApplication::clipboard()->setText(addressString);
});
@@ -678,14 +767,14 @@ void CpuWidget::onFuncListContextMenu(QPoint pos)
QAction* gotoDisasm = new QAction(tr("Go to in Disassembly"), m_ui.listFunctions);
connect(gotoDisasm, &QAction::triggered, [this] {
m_ui.disassemblyWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(256).toUInt());
m_ui.disassemblyWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(Qt::UserRole).toUInt());
});
m_funclistContextMenu->addAction(gotoDisasm);
QAction* gotoMemory = new QAction(tr("Go to in Memory View"), m_ui.listFunctions);
connect(gotoMemory, &QAction::triggered, [this] {
m_ui.memoryviewWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(256).toUInt());
m_ui.memoryviewWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(Qt::UserRole).toUInt());
});
m_funclistContextMenu->addAction(gotoMemory);
@@ -725,7 +814,7 @@ void CpuWidget::onFuncListContextMenu(QPoint pos)
void CpuWidget::onFuncListDoubleClick(QListWidgetItem* item)
{
m_ui.disassemblyWidget->gotoAddress(item->data(256).toUInt());
m_ui.disassemblyWidget->gotoAddress(item->data(Qt::UserRole).toUInt());
}
void CpuWidget::onModuleTreeContextMenu(QPoint pos)
@@ -735,17 +824,17 @@ void CpuWidget::onModuleTreeContextMenu(QPoint pos)
else
m_moduleTreeContextMenu->clear();
if (m_ui.treeModules->selectedItems().count() && m_ui.treeModules->selectedItems().first()->data(0, 256).isValid())
if (m_ui.treeModules->selectedItems().count() && m_ui.treeModules->selectedItems().first()->data(0, Qt::UserRole).isValid())
{
QAction* copyName = new QAction(tr("Copy Function Name"), m_ui.treeModules);
connect(copyName, &QAction::triggered, [this] {
QApplication::clipboard()->setText(m_cpu.GetSymbolMap().GetLabelName(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt()).c_str());
QApplication::clipboard()->setText(m_cpu.GetSymbolMap().GetLabelName(m_ui.treeModules->selectedItems().first()->data(0, Qt::UserRole).toUInt()).c_str());
});
m_moduleTreeContextMenu->addAction(copyName);
QAction* copyAddress = new QAction(tr("Copy Function Address"), m_ui.treeModules);
connect(copyAddress, &QAction::triggered, [this] {
const QString addressString = FilledQStringFromValue(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt(), 16);
const QString addressString = FilledQStringFromValue(m_ui.treeModules->selectedItems().first()->data(0, Qt::UserRole).toUInt(), 16);
QApplication::clipboard()->setText(addressString);
});
m_moduleTreeContextMenu->addAction(copyAddress);
@@ -754,13 +843,13 @@ void CpuWidget::onModuleTreeContextMenu(QPoint pos)
QAction* gotoDisasm = new QAction(tr("Go to in Disassembly"), m_ui.treeModules);
connect(gotoDisasm, &QAction::triggered, [this] {
m_ui.disassemblyWidget->gotoAddress(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt());
m_ui.disassemblyWidget->gotoAddress(m_ui.treeModules->selectedItems().first()->data(0, Qt::UserRole).toUInt());
});
m_moduleTreeContextMenu->addAction(gotoDisasm);
QAction* gotoMemory = new QAction(tr("Go to in Memory View"), m_ui.treeModules);
connect(gotoMemory, &QAction::triggered, [this] {
m_ui.memoryviewWidget->gotoAddress(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt());
m_ui.memoryviewWidget->gotoAddress(m_ui.treeModules->selectedItems().first()->data(0, Qt::UserRole).toUInt());
});
m_moduleTreeContextMenu->addAction(gotoMemory);
}
@@ -798,9 +887,9 @@ void CpuWidget::onModuleTreeContextMenu(QPoint pos)
void CpuWidget::onModuleTreeDoubleClick(QTreeWidgetItem* item)
{
if (item->data(0, 256).isValid())
if (item->data(0, Qt::UserRole).isValid())
{
m_ui.disassemblyWidget->gotoAddress(item->data(0, 256).toUInt());
m_ui.disassemblyWidget->gotoAddress(item->data(0, Qt::UserRole).toUInt());
}
}
void CpuWidget::updateStackFrames()
@@ -812,13 +901,24 @@ void CpuWidget::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;
if (selModel->hasSelection())
{
QAction* copyAddressAction = new QAction(tr("Copy Address"), m_ui.listSearchResults);
connect(copyAddressAction, &QAction::triggered, this, &CpuWidget::contextCopySearchResultAddress);
contextMenu->addAction(copyAddressAction);
QAction* goToDisassemblyAction = new QAction(tr("Go to in Disassembly"), m_ui.listSearchResults);
connect(goToDisassemblyAction, &QAction::triggered, this, &CpuWidget::contextSearchResultGoToDisassembly);
contextMenu->addAction(goToDisassemblyAction);
QAction* addToSavedAddressesAction = new QAction(tr("Add to Saved Memory Addresses"), m_ui.listSearchResults);
connect(addToSavedAddressesAction, &QAction::triggered, this, [this, listSearchResults]() {
addAddressToSavedAddressesList(listSearchResults->selectedItems().first()->data(Qt::UserRole).toUInt());
});
contextMenu->addAction(addToSavedAddressesAction);
QAction* removeResultAction = new QAction(tr("Remove Result"), m_ui.listSearchResults);
connect(removeResultAction, &QAction::triggered, this, &CpuWidget::contextRemoveSearchResult);
contextMenu->addAction(removeResultAction);
@@ -1002,19 +1102,37 @@ std::vector<u32> searchWorker(DebugInterface* cpu, std::vector<u32> searchAddres
return hitAddresses;
}
static bool compareByteArrayAtAddress(DebugInterface* cpu, u32 addr, QByteArray value)
static bool compareByteArrayAtAddress(DebugInterface* cpu, SearchComparison searchComparison, u32 addr, QByteArray value)
{
const bool isNotOperator = searchComparison == SearchComparison::NotEquals;
for (qsizetype i = 0; i < value.length(); i++)
{
if (static_cast<char>(cpu->read8(addr + i)) != value[i])
const char nextByte = cpu->read8(addr + i);
switch (searchComparison)
{
return false;
case SearchComparison::Equals:
{
if (nextByte != value[i])
return false;
break;
}
case SearchComparison::NotEquals:
{
if (nextByte != value[i])
return true;
break;
}
default:
{
Console.Error("Debugger: Unknown search comparison when doing memory search");
return false;
}
}
}
return true;
return !isNotOperator;
}
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, std::vector<u32> searchAddresses, u32 start, u32 end, QByteArray value)
static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, SearchComparison searchComparison, std::vector<u32> searchAddresses, u32 start, u32 end, QByteArray value)
{
std::vector<u32> hitAddresses;
const bool isSearchingRange = searchAddresses.size() <= 0;
@@ -1022,7 +1140,7 @@ static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, std::vector<u
{
for (u32 addr = start; addr < end; addr += 1)
{
if (compareByteArrayAtAddress(cpu, addr, value))
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value))
{
hitAddresses.emplace_back(addr);
addr += value.length() - 1;
@@ -1033,7 +1151,7 @@ static std::vector<u32> searchWorkerByteArray(DebugInterface* cpu, std::vector<u
{
for (u32 addr : searchAddresses)
{
if (compareByteArrayAtAddress(cpu, addr, value))
if (compareByteArrayAtAddress(cpu, searchComparison, addr, value))
{
hitAddresses.emplace_back(addr);
}
@@ -1060,9 +1178,9 @@ std::vector<u32> startWorker(DebugInterface* cpu, const SearchType type, const S
case SearchType::DoubleType:
return searchWorker<double>(cpu, searchAddresses, searchComparison, start, end, value.toDouble());
case SearchType::StringType:
return searchWorkerByteArray(cpu, searchAddresses, start, end, value.toUtf8());
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, value.toUtf8());
case SearchType::ArrayType:
return searchWorkerByteArray(cpu, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8()));
return searchWorkerByteArray(cpu, searchComparison, searchAddresses, start, end, QByteArray::fromHex(value.toUtf8()));
default:
Console.Error("Debugger: Unknown type when doing memory search!");
break;
@@ -1107,7 +1225,13 @@ void CpuWidget::onSearchButtonClicked()
unsigned long long value;
const bool isVariableSize = searchType == SearchType::ArrayType || searchType == SearchType::StringType;
if (isVariableSize && searchComparison != SearchComparison::Equals)
if (isVariableSize && !isFilterSearch && searchComparison == SearchComparison::NotEquals)
{
QMessageBox::critical(this, tr("Debugger"), tr("Search types Array and String can use the Not Equals search comparison type with new searches."));
return;
}
if (isVariableSize && searchComparison != SearchComparison::Equals && searchComparison != SearchComparison::NotEquals)
{
QMessageBox::critical(this, tr("Debugger"), tr("Search types Array and String can only be used with Equals search comparisons."));
return;
@@ -1214,8 +1338,17 @@ void CpuWidget::loadSearchResults()
{
u32 address = m_searchResults.at(numLoaded + i);
QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16));
item->setData(256, address);
item->setData(Qt::UserRole, address);
m_ui.listSearchResults->addItem(item);
}
}
void CpuWidget::saveBreakpointsToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(&m_bpModel);
}
void CpuWidget::saveSavedAddressesToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(&m_savedAddressesModel);
}
+11
View File
@@ -13,6 +13,7 @@
#include "Models/BreakpointModel.h"
#include "Models/ThreadModel.h"
#include "Models/StackModel.h"
#include "Models/SavedAddressesModel.h"
#include "QtHost.h"
#include <QtWidgets/QWidget>
@@ -74,6 +75,11 @@ public slots:
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);
@@ -110,8 +116,12 @@ public slots:
void loadSearchResults();
void contextSearchResultGoToDisassembly();
void contextRemoveSearchResult();
void contextCopySearchResultAddress();
void onListSearchResultsContextMenu(QPoint pos);
void saveBreakpointsToDebuggerSettings();
void saveSavedAddressesToDebuggerSettings();
private:
std::vector<QTableWidget*> m_registerTableViews;
std::vector<u32> m_searchResults;
@@ -128,6 +138,7 @@ private:
ThreadModel m_threadModel;
QSortFilterProxyModel m_threadProxyModel;
StackModel m_stackModel;
SavedAddressesModel m_savedAddressesModel;
QTimer m_resultsLoadTimer;
bool m_demangleFunctions = true;
+71 -28
View File
@@ -183,20 +183,6 @@
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="6" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>End</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="3">
<widget class="QLineEdit" name="txtSearchStart">
<property name="text">
<string notr="true">0x00</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@@ -207,20 +193,6 @@
<item row="0" column="1">
<widget class="QLineEdit" name="txtSearchValue"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="3">
<widget class="QLineEdit" name="txtSearchEnd">
<property name="text">
<string notr="true">0x2000000</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
@@ -340,6 +312,45 @@
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="comparisonLabel">
<property name="text">
<string>Comparison</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" alignment="Qt::AlignLeft">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="0" column="1" alignment="Qt::AlignLeft">
<widget class="QLineEdit" name="txtSearchStart">
<property name="text">
<string notr="true">0x00</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>End</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="txtSearchEnd">
<property name="text">
<string notr="true">0x2000000</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@@ -582,6 +593,38 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_savedaddresses">
<attribute name="title">
<string>Saved Addresses</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<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>
</item>
</layout>
@@ -0,0 +1,164 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "DebuggerSettingsManager.h"
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QFile>
#include "common/Console.h"
#include "fmt/core.h"
#include "VMManager.h"
#include "Models/BreakpointModel.h"
std::mutex DebuggerSettingsManager::writeLock;
const QString DebuggerSettingsManager::settingsFileVersion = "0.00";
QJsonObject DebuggerSettingsManager::loadGameSettingsJSON() {
std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly))
{
Console.WriteLnFmt("Debugger Settings Manager: No Debugger Settings file found for game at: '{}'", path);
return QJsonObject();
}
QByteArray fileContent = file.readAll();
file.close();
const QJsonDocument jsonDoc(QJsonDocument::fromJson(fileContent));
if (jsonDoc.isNull() || !jsonDoc.isObject())
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to load contents of settings file for file at: '{}'", path);
return QJsonObject();
}
return jsonDoc.object();
}
void DebuggerSettingsManager::writeJSONToPath(std::string path, QJsonDocument jsonDocument)
{
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::WriteOnly))
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to write Debugger Settings file to path: '{}'", path);
return;
}
file.write(jsonDocument.toJson(QJsonDocument::Indented));
file.close();
}
void DebuggerSettingsManager::loadGameSettings(BreakpointModel* bpModel)
{
const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
if (path.empty())
return;
const QJsonValue breakpointsValue = loadGameSettingsJSON().value("Breakpoints");
const QString valueToLoad = breakpointsValue.toString();
if (breakpointsValue.isUndefined() || !breakpointsValue.isArray())
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to read Breakpoints array from settings file: '{}'", path);
return;
}
const QJsonArray breakpointsArray = breakpointsValue.toArray();
for (u32 row = 0; row < breakpointsArray.size(); row++)
{
const QJsonValue rowValue = breakpointsArray.at(row);
if (rowValue.isUndefined() || !rowValue.isObject())
{
Console.WriteLn("Debugger Settings Manager: Failed to load invalid Breakpoint object.");
continue;
}
const QJsonObject rowObject = rowValue.toObject();
QStringList fields;
u32 col = 0;
for (auto iter = rowObject.begin(); iter != rowObject.end(); iter++, col++)
{
QString headerColKey = bpModel->headerData(col, Qt::Horizontal, Qt::UserRole).toString();
fields << rowObject.value(headerColKey).toString();
}
bpModel->loadBreakpointFromFieldList(fields);
}
}
void DebuggerSettingsManager::loadGameSettings(SavedAddressesModel* savedAddressesModel)
{
const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
if (path.empty())
return;
const QJsonValue savedAddressesValue = loadGameSettingsJSON().value("SavedAddresses");
QString valueToLoad = savedAddressesValue.toString();
if (savedAddressesValue.isUndefined() || !savedAddressesValue.isArray())
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to read Saved Addresses array from settings file: '{}'", path);
return;
}
const QJsonArray breakpointsArray = savedAddressesValue.toArray();
for (u32 row = 0; row < breakpointsArray.size(); row++)
{
const QJsonValue rowValue = breakpointsArray.at(row);
if (rowValue.isUndefined() || !rowValue.isObject())
{
Console.WriteLn("Debugger Settings Manager: Failed to load invalid Breakpoint object.");
continue;
}
const QJsonObject rowObject = rowValue.toObject();
QStringList fields;
u32 col = 0;
for (auto iter = rowObject.begin(); iter != rowObject.end(); iter++, col++)
{
QString headerColKey = savedAddressesModel->headerData(col, Qt::Horizontal, Qt::UserRole).toString();
fields << rowObject.value(headerColKey).toString();
}
savedAddressesModel->loadSavedAddressFromFieldList(fields);
}
}
void DebuggerSettingsManager::saveGameSettings(BreakpointModel* bpModel)
{
saveGameSettings(bpModel, "Breakpoints", BreakpointModel::ExportRole);
}
void DebuggerSettingsManager::saveGameSettings(SavedAddressesModel* savedAddressesModel)
{
saveGameSettings(savedAddressesModel, "SavedAddresses", Qt::DisplayRole);
}
void DebuggerSettingsManager::saveGameSettings(QAbstractTableModel* abstractTableModel, QString settingsKey, u32 role)
{
const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
if (path.empty())
return;
const std::lock_guard<std::mutex> lock(writeLock);
QJsonObject loadedSettings = loadGameSettingsJSON();
QJsonArray rowsArray;
QStringList keys;
for (int col = 0; col < abstractTableModel->columnCount(); ++col)
{
keys << abstractTableModel->headerData(col, Qt::Horizontal, Qt::UserRole).toString();
}
for (int row = 0; row < abstractTableModel->rowCount(); row++)
{
QJsonObject rowObject;
for (int col = 0; col < abstractTableModel->columnCount(); col++)
{
const QModelIndex index = abstractTableModel->index(row, col);
const QString data = abstractTableModel->data(index, role).toString();
rowObject.insert(keys[col], QJsonValue::fromVariant(data));
}
rowsArray.append(rowObject);
}
loadedSettings.insert(settingsKey, rowsArray);
loadedSettings.insert("Version", settingsFileVersion);
QJsonDocument doc(loadedSettings);
writeJSONToPath(path, doc);
}
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <QtWidgets/QDialog>
#include <mutex>
#include "Models/BreakpointModel.h"
#include "Models/SavedAddressesModel.h"
class DebuggerSettingsManager final
{
public:
DebuggerSettingsManager(QWidget* parent = nullptr);
~DebuggerSettingsManager();
static void loadGameSettings(BreakpointModel* bpModel);
static void loadGameSettings(SavedAddressesModel* savedAddressesModel);
static void saveGameSettings(BreakpointModel* bpModel);
static void saveGameSettings(SavedAddressesModel* savedAddressesModel);
static void saveGameSettings(QAbstractTableModel* abstractTableModel, QString settingsKey, u32 role);
private:
static std::mutex writeLock;
static void writeJSONToPath(std::string path, QJsonDocument jsonDocument);
static QJsonObject loadGameSettingsJSON();
const static QString settingsFileVersion;
};
+5 -1
View File
@@ -391,7 +391,7 @@ void MemoryViewWidget::customMenuRequested(QPoint pos)
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { QApplication::clipboard()->setText(QString::number(m_table.selectedAddress, 16).toUpper()); });
action = new QAction(tr("Go to in disassembly"));
action = new QAction(tr("Go to in Disassembly"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { emit gotoInDisasm(m_table.selectedAddress); });
@@ -424,6 +424,10 @@ void MemoryViewWidget::customMenuRequested(QPoint pos)
m_contextMenu->addSeparator();
action = new QAction((tr("Add to Saved Memory Addresses")));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { emit addToSavedAddresses(m_table.selectedAddress); });
action = new QAction(tr("Copy Byte"));
m_contextMenu->addAction(action);
connect(action, &QAction::triggered, this, [this]() { contextCopyByte(); });
+1
View File
@@ -104,6 +104,7 @@ public slots:
signals:
void gotoInDisasm(u32 address, bool should_set_focus = true);
void addToSavedAddresses(u32 address);
void VMUpdate();
private:
@@ -6,6 +6,7 @@
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/DisassemblyManager.h"
#include "common/Console.h"
#include "QtHost.h"
#include "QtUtils.h"
@@ -226,6 +227,28 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, i
return QVariant();
}
}
if (role == Qt::UserRole && orientation == Qt::Horizontal)
{
switch (section)
{
case BreakpointColumns::TYPE:
return "TYPE";
case BreakpointColumns::OFFSET:
return "OFFSET";
case BreakpointColumns::SIZE_LABEL:
return "SIZE / LABEL";
case BreakpointColumns::OPCODE:
return "INSTRUCTION";
case BreakpointColumns::CONDITION:
return "CONDITION";
case BreakpointColumns::HITS:
return "HITS";
case BreakpointColumns::ENABLED:
return "X";
default:
return QVariant();
}
}
return QVariant();
}
@@ -402,3 +425,106 @@ void BreakpointModel::refreshData()
endResetModel();
}
void BreakpointModel::loadBreakpointFromFieldList(QStringList fields)
{
bool ok;
if (fields.size() != BreakpointModel::BreakpointColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger Breakpoint Model: Invalid number of columns, skipping");
return;
}
const int type = fields[BreakpointModel::BreakpointColumns::TYPE].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse type '%s', skipping", fields[BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
return;
}
// This is how we differentiate between breakpoints and memchecks
if (type == MEMCHECK_INVALID)
{
BreakPoint bp;
// Address
bp.addr = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
return;
}
// Condition
if (!fields[BreakpointModel::BreakpointColumns::CONDITION].isEmpty())
{
PostfixExpression expr;
bp.hasCond = true;
bp.cond.debug = &m_cpu;
if (!m_cpu.initExpression(fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), expr))
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse cond '%s', skipping", fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData());
return;
}
bp.cond.expression = expr;
strncpy(&bp.cond.expressionString[0], fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), sizeof(bp.cond.expressionString));
}
// Enabled
bp.enabled = fields[BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse enable flag '%s', skipping", fields[BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
return;
}
insertBreakpointRows(0, 1, {bp});
}
else
{
MemCheck mc;
// Mode
if (type >= MEMCHECK_INVALID)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse cond type '%s', skipping", fields[BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
return;
}
mc.cond = static_cast<MemCheckCondition>(type);
// Address
QString test = fields[BreakpointModel::BreakpointColumns::OFFSET];
mc.start = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
return;
}
// Size
mc.end = fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUInt(&ok) + mc.start;
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse length '%s', skipping", fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUtf8().constData());
return;
}
// Result
const int result = fields[BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse result flag '%s', skipping", fields[BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
return;
}
mc.result = static_cast<MemCheckResult>(result);
insertBreakpointRows(0, 1, {mc});
}
}
void BreakpointModel::clear()
{
beginResetModel();
m_breakpoints.clear();
endResetModel();
}
@@ -55,10 +55,12 @@ public:
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
bool removeRows(int row, int count, const QModelIndex& index = QModelIndex()) override;
bool insertBreakpointRows(int row, int count, std::vector<BreakpointMemcheck> breakpoints, const QModelIndex& index = QModelIndex());
void loadBreakpointFromFieldList(QStringList breakpointFields);
BreakpointMemcheck at(int row) const { return m_breakpoints.at(row); };
void refreshData();
void clear();
private:
DebugInterface& m_cpu;
@@ -0,0 +1,201 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "PrecompiledHeader.h"
#include "SavedAddressesModel.h"
#include "common/Console.h"
SavedAddressesModel::SavedAddressesModel(DebugInterface& cpu, QObject* parent)
: QAbstractTableModel(parent)
, m_cpu(cpu)
{
}
QVariant SavedAddressesModel::data(const QModelIndex& index, int role) const
{
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();
case HeaderColumns::LABEL:
return savedAddress.label;
case HeaderColumns::DESCRIPTION:
return savedAddress.description;
}
}
if (role == Qt::UserRole)
{
SavedAddress savedAddress = m_savedAddresses.at(index.row());
switch (index.column())
{
case HeaderColumns::ADDRESS:
return savedAddress.address;
case HeaderColumns::LABEL:
return savedAddress.label;
case HeaderColumns::DESCRIPTION:
return savedAddress.description;
}
}
return QVariant();
}
bool SavedAddressesModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
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;
else
return false;
}
if (index.column() == HeaderColumns::DESCRIPTION)
addressToEdit.description = value.toString();
if (index.column() == HeaderColumns::LABEL)
addressToEdit.label = value.toString();
m_savedAddresses.at(index.row()) = addressToEdit;
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;
}
if (index.column() == HeaderColumns::DESCRIPTION)
addressToEdit.description = value.toString();
if (index.column() == HeaderColumns::LABEL)
addressToEdit.label = value.toString();
m_savedAddresses.at(index.row()) = addressToEdit;
emit dataChanged(index, index, QList<int>(role));
return true;
}
return false;
}
QVariant SavedAddressesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal)
return QVariant();
if (role == Qt::DisplayRole)
{
switch (section)
{
case SavedAddressesModel::ADDRESS:
return tr("MEMORY ADDRESS");
case SavedAddressesModel::LABEL:
return tr("LABEL");
case SavedAddressesModel::DESCRIPTION:
return tr("DESCRIPTION");
default:
return QVariant();
}
}
if (role == Qt::UserRole)
{
switch (section)
{
case SavedAddressesModel::ADDRESS:
return "MEMORY ADDRESS";
case SavedAddressesModel::LABEL:
return "LABEL";
case SavedAddressesModel::DESCRIPTION:
return "DESCRIPTION";
default:
return QVariant();
}
}
return QVariant();
}
Qt::ItemFlags SavedAddressesModel::flags(const QModelIndex& index) const
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
void SavedAddressesModel::addRow()
{
const SavedAddress defaultNewAddress = {0, "Name", "Description"};
addRow(defaultNewAddress);
}
void SavedAddressesModel::addRow(SavedAddress addresstoSave)
{
const int newRowIndex = m_savedAddresses.size();
beginInsertRows(QModelIndex(), newRowIndex, newRowIndex);
m_savedAddresses.push_back(addresstoSave);
endInsertRows();
}
bool SavedAddressesModel::removeRows(int row, int count, const QModelIndex& parent)
{
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();
return true;
}
int SavedAddressesModel::rowCount(const QModelIndex&) const
{
return m_savedAddresses.size();
}
int SavedAddressesModel::columnCount(const QModelIndex&) const
{
return HeaderColumns::COLUMN_COUNT;
}
void SavedAddressesModel::loadSavedAddressFromFieldList(QStringList fields)
{
if (fields.size() != SavedAddressesModel::HeaderColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger Saved Addresses Model: Invalid number of columns, skipping");
return;
}
bool ok;
const u32 address = fields[SavedAddressesModel::HeaderColumns::ADDRESS].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger Saved Addresses Model: Failed to parse address '%s', skipping", fields[SavedAddressesModel::HeaderColumns::ADDRESS].toUtf8().constData());
return;
}
const QString label = fields[SavedAddressesModel::HeaderColumns::LABEL];
const QString description = fields[SavedAddressesModel::HeaderColumns::DESCRIPTION];
const SavedAddressesModel::SavedAddress importedAddress = {address, label, description};
addRow(importedAddress);
}
void SavedAddressesModel::clear()
{
beginResetModel();
m_savedAddresses.clear();
endResetModel();
}
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <QtCore/QAbstractTableModel>
#include <QtWidgets/QHeaderView>
#include "DebugTools/DebugInterface.h"
class SavedAddressesModel : public QAbstractTableModel
{
Q_OBJECT
public:
struct SavedAddress
{
u32 address;
QString label;
QString description;
};
enum HeaderColumns: int
{
ADDRESS = 0,
LABEL,
DESCRIPTION,
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);
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;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
void addRow();
void addRow(SavedAddress addresstoSave);
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
void loadSavedAddressFromFieldList(QStringList fields);
void clear();
private:
DebugInterface& m_cpu;
std::vector<SavedAddress> m_savedAddresses;
};
+1 -1
View File
@@ -272,7 +272,7 @@ void RegisterWidget::customMenuRequested(QPoint pos)
m_contextMenu->addAction(action = new QAction(tr("Go to in Disassembly"), this));
connect(action, &QAction::triggered, this, &RegisterWidget::contextGotoDisasm);
m_contextMenu->addAction(action = new QAction(tr("Go to in Memory"), this));
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));
+4 -2
View File
@@ -6,6 +6,7 @@
#include "QtHost.h"
#include "QtUtils.h"
#include "pcsx2/ImGui/FullscreenUI.h"
#include "pcsx2/ImGui/ImGuiManager.h"
#include "common/Assertions.h"
@@ -323,8 +324,9 @@ bool DisplayWidget::event(QEvent* event)
// don't toggle fullscreen when we're bound.. that wouldn't end well.
if (event->type() == QEvent::MouseButtonDblClick &&
static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton &&
QtHost::IsVMValid() && !QtHost::IsVMPaused() &&
!InputManager::HasAnyBindingsForKey(InputManager::MakePointerButtonKey(0, 0)) &&
QtHost::IsVMValid() && !FullscreenUI::HasActiveWindow() &&
((!QtHost::IsVMPaused() && !InputManager::HasAnyBindingsForKey(InputManager::MakePointerButtonKey(0, 0))) ||
(QtHost::IsVMPaused() && !ImGuiManager::WantsMouseInput())) &&
Host::GetBoolSettingValue("UI", "DoubleClickTogglesFullscreen", true))
{
g_emu_thread->toggleFullscreen();
+399
View File
@@ -0,0 +1,399 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "LogWindow.h"
#include "MainWindow.h"
#include "QtHost.h"
#include "SettingWidgetBinder.h"
#include <QtCore/QLatin1StringView>
#include <QtCore/QUtf8StringView>
#include <QtGui/QIcon>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QScrollBar>
#include <mutex>
// Need a lock so the other threads don't try to write to a deleting window.
LogWindow* g_log_window;
static std::mutex s_log_mutex;
static LOGLEVEL GetWindowLogLevel()
{
#ifdef _DEBUG
return LOGLEVEL_DEBUG;
#else
return (IsDevBuild || Host::GetBaseBoolSettingValue("Logging", "EnableVerbose", false)) ? LOGLEVEL_DEV : LOGLEVEL_INFO;
#endif
}
LogWindow::LogWindow(bool attach_to_main)
: QMainWindow()
, m_attached_to_main_window(attach_to_main)
{
restoreSize();
createUi();
Log::SetHostOutputLevel(GetWindowLogLevel(), &LogWindow::logCallback);
}
LogWindow::~LogWindow()
{
Log::SetHostOutputLevel(LOGLEVEL_NONE, nullptr);
}
void LogWindow::updateSettings()
{
std::unique_lock lock(s_log_mutex);
const bool new_enabled = Host::GetBaseBoolSettingValue("Logging", "EnableLogWindow", false) && !QtHost::InNoGUIMode();
const bool attach_to_main = Host::GetBaseBoolSettingValue("Logging", "AttachLogWindowToMainWindow", true);
const bool curr_enabled = Log::IsHostOutputEnabled();
if (new_enabled == curr_enabled)
{
if (g_log_window && g_log_window->m_attached_to_main_window != attach_to_main)
{
g_log_window->m_attached_to_main_window = attach_to_main;
if (attach_to_main)
g_log_window->reattachToMainWindow();
}
// Update level.
if (new_enabled)
Log::SetHostOutputLevel(GetWindowLogLevel(), &LogWindow::logCallback);
return;
}
if (new_enabled)
{
g_log_window = new LogWindow(attach_to_main);
if (attach_to_main && g_main_window && g_main_window->isVisible())
g_log_window->reattachToMainWindow();
g_log_window->show();
}
else if (g_log_window)
{
g_log_window->m_destroying = true;
g_log_window->close();
g_log_window->deleteLater();
g_log_window = nullptr;
}
}
void LogWindow::destroy()
{
std::unique_lock lock(s_log_mutex);
if (!g_log_window)
return;
g_log_window->m_destroying = true;
g_log_window->close();
g_log_window->deleteLater();
g_log_window = nullptr;
}
void LogWindow::reattachToMainWindow()
{
// Skip when maximized.
if (g_main_window->windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen))
return;
resize(width(), g_main_window->height());
const QPoint new_pos = g_main_window->pos() + QPoint(g_main_window->width() + 10, 0);
if (pos() != new_pos)
move(new_pos);
}
void LogWindow::updateWindowTitle()
{
QString title;
const QString& serial = QtHost::GetCurrentGameSerial();
if (QtHost::IsVMValid() && !serial.isEmpty())
{
const QFileInfo fi(QtHost::GetCurrentGamePath());
title = tr("Log Window - %1 [%2]").arg(serial).arg(fi.fileName());
}
else
{
title = tr("Log Window");
}
setWindowTitle(title);
}
void LogWindow::createUi()
{
setWindowIcon(QtHost::GetAppIcon());
updateWindowTitle();
QAction* action;
QMenuBar* menu = new QMenuBar(this);
setMenuBar(menu);
QMenu* log_menu = menu->addMenu("&Log");
action = log_menu->addAction(tr("&Clear"));
connect(action, &QAction::triggered, this, &LogWindow::onClearTriggered);
action = log_menu->addAction(tr("&Save..."));
connect(action, &QAction::triggered, this, &LogWindow::onSaveTriggered);
log_menu->addSeparator();
action = log_menu->addAction(tr("Cl&ose"));
connect(action, &QAction::triggered, this, &LogWindow::close);
QMenu* settings_menu = menu->addMenu(tr("&Settings"));
#if 0
// TODO: These are duplicated with the main window...
action = settings_menu->addAction(tr("Log To &System Console"));
action->setCheckable(true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "EnableSystemConsole", false);
action = settings_menu->addAction(tr("Log To &Debug Console"));
action->setCheckable(true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "EnableDebugConsole", false);
action = settings_menu->addAction(tr("Log To &File"));
action->setCheckable(true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "EnableFileLogging", false);
settings_menu->addSeparator();
#endif
action = settings_menu->addAction(tr("Attach To &Main Window"));
action->setCheckable(true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "AttachLogWindowToMainWindow", true);
action = settings_menu->addAction(tr("Show &Timestamps"));
action->setCheckable(true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "EnableTimestamps", true);
settings_menu->addSeparator();
// TODO: Log Level
m_text = new QPlainTextEdit(this);
m_text->setReadOnly(true);
m_text->setUndoRedoEnabled(false);
m_text->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse);
m_text->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_text->setWordWrapMode(QTextOption::WrapAnywhere);
#if defined(_WIN32)
QFont font("Consolas");
font.setPointSize(10);
#elif defined(__APPLE__)
QFont font("Monaco");
font.setPointSize(11);
#else
QFont font("Monospace");
font.setStyleHint(QFont::TypeWriter);
#endif
m_text->setFont(font);
setCentralWidget(m_text);
}
void LogWindow::onClearTriggered()
{
m_text->clear();
}
void LogWindow::onSaveTriggered()
{
const QString path = QFileDialog::getSaveFileName(this, tr("Select Log File"), QString(), tr("Log Files (*.txt)"));
if (path.isEmpty())
return;
QFile file(path);
if (!file.open(QFile::WriteOnly | QFile::Text))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to open file for writing."));
return;
}
file.write(m_text->toPlainText().toUtf8());
file.close();
appendMessage(LOGLEVEL_INFO, Color_Default, tr("Log was written to %1.\n").arg(path));
}
void LogWindow::logCallback(LOGLEVEL level, ConsoleColors color, std::string_view message)
{
std::unique_lock lock(s_log_mutex);
if (!g_log_window)
return;
// I don't like the memory allocations here either...
QString qmessage;
qmessage.reserve(message.length() + 1);
qmessage.append(QUtf8StringView(message.data(), message.length()));
qmessage.append(QChar('\n'));
if (g_emu_thread->isOnUIThread())
{
g_log_window->appendMessage(static_cast<u32>(level), static_cast<u32>(color), qmessage);
}
else
{
QMetaObject::invokeMethod(g_log_window, "appendMessage", Qt::QueuedConnection,
Q_ARG(quint32, static_cast<u32>(level)), Q_ARG(quint32, static_cast<u32>(color)),
Q_ARG(const QString&, qmessage));
}
}
void LogWindow::closeEvent(QCloseEvent* event)
{
Log::SetHostOutputLevel(LOGLEVEL_NONE, nullptr);
// Save size when actually closing, disable ourselves if the user closed us.
if (m_destroying)
{
saveSize();
}
else
{
Host::SetBaseBoolSettingValue("Logging", "EnableLogWindow", false);
Host::CommitBaseSettingChanges();
}
QMainWindow::closeEvent(event);
}
void LogWindow::appendMessage(quint32 level, quint32 color, const QString& message)
{
QTextCursor temp_cursor = m_text->textCursor();
QScrollBar* scrollbar = m_text->verticalScrollBar();
const bool cursor_at_end = temp_cursor.atEnd();
const bool scroll_at_end = scrollbar->sliderPosition() == scrollbar->maximum();
temp_cursor.movePosition(QTextCursor::End);
{
static constexpr const QColor qcolors[2][ConsoleColors_Count] = {
// Light theme
{
QColor(0, 0, 0), // Color_Default
QColor(0, 0, 0), // Color_Black
QColor(128, 0, 0), // Color_Red
QColor(0, 128, 0), // Color_Green
QColor(0, 0, 128), // Color_Blue
QColor(160, 0, 160), // Color_Magenta
QColor(160, 120, 0), // Color_Orange
QColor(108, 108, 108), // Color_Gray
QColor(128, 180, 180), // Color_Cyan
QColor(180, 180, 128), // Color_Yellow
QColor(160, 160, 160), // Color_White
QColor(0, 0, 0), // Color_StrongBlack
QColor(128, 0, 0), // Color_StrongRed
QColor(0, 128, 0), // Color_StrongGreen
QColor(0, 0, 128), // Color_StrongBlue
QColor(160, 0, 160), // Color_StrongMagenta
QColor(160, 120, 0), // Color_StrongOrange
QColor(108, 108, 108), // Color_StrongGray
QColor(128, 180, 180), // Color_StrongCyan
QColor(180, 180, 128), // Color_StrongYellow
QColor(160, 160, 160), // Color_StrongWhite
},
// Dark theme
{
QColor(208, 208, 208), // Color_Default
QColor(255, 255, 255), // Color_Black
QColor(180, 0, 0), // Color_Red
QColor(0, 160, 0), // Color_Green
QColor(32, 32, 204), // Color_Blue
QColor(160, 0, 160), // Color_Magenta
QColor(160, 120, 0), // Color_Orange
QColor(128, 128, 128), // Color_Gray
QColor(128, 180, 180), // Color_Cyan
QColor(180, 180, 128), // Color_Yellow
QColor(160, 160, 160), // Color_White
QColor(255, 255, 255), // Color_StrongBlack
QColor(180, 0, 0), // Color_StrongRed
QColor(0, 160, 0), // Color_StrongGreen
QColor(32, 32, 204), // Color_StrongBlue
QColor(160, 0, 160), // Color_StrongMagenta
QColor(160, 120, 0), // Color_StrongOrange
QColor(128, 128, 128), // Color_StrongGray
QColor(128, 180, 180), // Color_StrongCyan
QColor(180, 180, 128), // Color_StrongYellow
QColor(160, 160, 160), // Color_StrongWhite
},
};
static constexpr const QColor timestamp_color = QColor(0xcc, 0xcc, 0xcc);
QTextCharFormat format = temp_cursor.charFormat();
if (Log::AreTimestampsEnabled())
{
const float message_time = Log::GetCurrentMessageTime();
const QString qtimestamp = QStringLiteral("[%1] ").arg(message_time, 10, 'f', 4);
format.setForeground(QBrush(timestamp_color));
temp_cursor.setCharFormat(format);
temp_cursor.insertText(qtimestamp);
}
const bool dark = static_cast<u32>(QtHost::IsDarkApplicationTheme());
// message has \n already
format.setForeground(QBrush(qcolors[static_cast<u32>(dark)][color]));
temp_cursor.setCharFormat(format);
temp_cursor.insertText(message);
}
if (cursor_at_end)
{
if (scroll_at_end)
{
m_text->setTextCursor(temp_cursor);
scrollbar->setSliderPosition(scrollbar->maximum());
}
else
{
// Can't let changing the cursor affect the scroll bar...
const int pos = scrollbar->sliderPosition();
m_text->setTextCursor(temp_cursor);
scrollbar->setSliderPosition(pos);
}
}
}
void LogWindow::saveSize()
{
const int current_width = Host::GetBaseIntSettingValue("UI", "LogWindowWidth", DEFAULT_WIDTH);
const int current_height = Host::GetBaseIntSettingValue("UI", "LogWindowHeight", DEFAULT_HEIGHT);
const QSize wsize = size();
bool changed = false;
if (current_width != wsize.width())
{
Host::SetBaseIntSettingValue("UI", "LogWindowWidth", wsize.width());
changed = true;
}
if (current_height != wsize.height())
{
Host::SetBaseIntSettingValue("UI", "LogWindowHeight", wsize.height());
changed = true;
}
if (changed)
Host::CommitBaseSettingChanges();
}
void LogWindow::restoreSize()
{
const int width = Host::GetBaseIntSettingValue("UI", "LogWindowWidth", DEFAULT_WIDTH);
const int height = Host::GetBaseIntSettingValue("UI", "LogWindowHeight", DEFAULT_HEIGHT);
resize(width, height);
}
+54
View File
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include "common/Console.h"
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QPlainTextEdit>
class LogWindow : public QMainWindow
{
Q_OBJECT
public:
LogWindow(bool attach_to_main);
~LogWindow();
static void updateSettings();
static void destroy();
__fi bool isAttachedToMainWindow() const { return m_attached_to_main_window; }
void reattachToMainWindow();
void updateWindowTitle();
private:
void createUi();
static void logCallback(LOGLEVEL level, ConsoleColors color, std::string_view message);
protected:
void closeEvent(QCloseEvent* event);
private Q_SLOTS:
void onClearTriggered();
void onSaveTriggered();
void appendMessage(quint32 level, quint32 color, const QString& message);
private:
static constexpr int DEFAULT_WIDTH = 750;
static constexpr int DEFAULT_HEIGHT = 400;
void saveSize();
void restoreSize();
QPlainTextEdit* m_text;
QMenu* m_level_menu;
bool m_attached_to_main_window = true;
bool m_destroying = false;
};
extern LogWindow* g_log_window;
+144 -42
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "AboutDialog.h"
@@ -7,6 +7,7 @@
#include "DisplayWidget.h"
#include "GameList/GameListRefreshThread.h"
#include "GameList/GameListWidget.h"
#include "LogWindow.h"
#include "MainWindow.h"
#include "QtHost.h"
#include "QtUtils.h"
@@ -15,6 +16,7 @@
#include "Settings/ControllerSettingsWindow.h"
#include "Settings/GameListSettingsWidget.h"
#include "Settings/InterfaceSettingsWidget.h"
#include "Settings/MemoryCardCreateDialog.h"
#include "Tools/InputRecording/InputRecordingViewer.h"
#include "Tools/InputRecording/NewInputRecordingDlg.h"
#include "svnrev.h"
@@ -27,7 +29,6 @@
#include "pcsx2/GSDumpReplayer.h"
#include "pcsx2/GameList.h"
#include "pcsx2/Host.h"
#include "pcsx2/LogSink.h"
#include "pcsx2/MTGS.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/Recording/InputRecording.h"
@@ -91,6 +92,12 @@ static bool s_use_central_widget = false;
// UI thread VM validity.
static bool s_vm_valid = false;
static bool s_vm_paused = false;
static QString s_current_title;
static QString s_current_elf_override;
static QString s_current_disc_path;
static QString s_current_disc_serial;
static quint32 s_current_disc_crc;
static quint32 s_current_running_crc;
MainWindow::MainWindow()
{
@@ -172,11 +179,8 @@ QWidget* MainWindow::getContentParent()
void MainWindow::setupAdditionalUi()
{
const bool show_advanced_settings = QtHost::ShouldShowAdvancedSettings();
makeIconsMasks(menuBar());
m_ui.menuDebug->menuAction()->setVisible(show_advanced_settings);
updateAdvancedSettingsVisibility();
const bool toolbar_visible = Host::GetBaseBoolSettingValue("UI", "ShowToolbar", false);
m_ui.actionViewToolbar->setChecked(toolbar_visible);
@@ -250,7 +254,7 @@ void MainWindow::setupAdditionalUi()
#ifdef ENABLE_RAINTEGRATION
if (Achievements::IsUsingRAIntegration())
{
QMenu* raMenu = new QMenu(QStringLiteral("RAIntegration"), m_ui.menu_Tools);
QMenu* raMenu = new QMenu(QStringLiteral("RAIntegration"), m_ui.menuTools);
connect(raMenu, &QMenu::aboutToShow, this, [this, raMenu]() {
raMenu->clear();
@@ -274,7 +278,7 @@ void MainWindow::setupAdditionalUi()
[id = id]() { Host::RunOnCPUThread([id]() { Achievements::RAIntegration::ActivateMenuItem(id); }, false); });
}
});
m_ui.menu_Tools->insertMenu(m_ui.menuInput_Recording->menuAction(), raMenu);
m_ui.menuTools->insertMenu(m_ui.menuInputRecording->menuAction(), raMenu);
}
#endif
}
@@ -361,6 +365,18 @@ void MainWindow::connectSignals()
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionViewStatusBarVerbose, "UI", "VerboseStatusBar", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableSystemConsole, "Logging", "EnableSystemConsole", false);
if (Log::IsDebugOutputAvailable())
{
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableDebugConsole, "Logging", "EnableDebugConsole", false);
}
else
{
m_ui.menuTools->removeAction(m_ui.actionEnableDebugConsole);
m_ui.actionEnableDebugConsole->deleteLater();
m_ui.actionEnableDebugConsole = nullptr;
}
#ifndef PCSX2_DEVBUILD
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableVerboseLogging, "Logging", "EnableVerbose", false);
#else
@@ -370,6 +386,7 @@ void MainWindow::connectSignals()
#endif
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableEEConsoleLogging, "Logging", "EnableEEConsole", true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableIOPConsoleLogging, "Logging", "EnableIOPConsole", true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableLogWindow, "Logging", "EnableLogWindow", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableFileLogging, "Logging", "EnableFileLogging", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableLogTimestamps, "Logging", "EnableTimestamps", true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableCDVDVerboseReads, "EmuCore", "CdvdVerboseReads", false);
@@ -423,6 +440,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
connect(thread, &EmuThread::onAchievementsLoginSucceeded, this, &MainWindow::onAchievementsLoginSucceeded);
connect(thread, &EmuThread::onAchievementsHardcoreModeChanged, this, &MainWindow::onAchievementsHardcoreModeChanged);
connect(thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(thread, &EmuThread::onCreateMemoryCardOpenRequested, this, &MainWindow::onCreateMemoryCardOpenRequested);
connect(m_ui.actionReset, &QAction::triggered, this, &MainWindow::requestReset);
connect(m_ui.actionPause, &QAction::toggled, thread, &EmuThread::setVMPaused);
@@ -503,6 +521,11 @@ void MainWindow::recreate()
new_main_window->show();
deleteLater();
// Recreate log window as well. Then make sure we're still on top.
LogWindow::updateSettings();
new_main_window->raise();
new_main_window->activateWindow();
// Reload the sources we just closed.
g_emu_thread->reloadInputSources();
@@ -574,6 +597,8 @@ void MainWindow::destroySubWindows()
}
SettingsWindow::closeGamePropertiesDialogs();
LogWindow::destroy();
}
void MainWindow::onScreenshotActionTriggered()
@@ -591,13 +616,13 @@ void MainWindow::onBlockDumpActionToggled(bool checked)
if (!checked)
return;
std::string old_directory(Host::GetBaseStringSettingValue("EmuCore", "BlockDumpSaveDirectory", ""));
std::string old_directory = Host::GetBaseStringSettingValue("EmuCore", "BlockDumpSaveDirectory", "");
if (old_directory.empty())
old_directory = FileSystem::GetWorkingDirectory();
// prompt for a location to save
const QString new_dir(
QFileDialog::getExistingDirectory(this, tr("Select location to save block dump:"), QString::fromStdString(old_directory)));
const QString new_dir(QDir::toNativeSeparators(
QFileDialog::getExistingDirectory(this, tr("Select location to save block dump:"), QString::fromStdString(old_directory))));
if (new_dir.isEmpty())
{
// disable it again
@@ -647,13 +672,25 @@ void MainWindow::onShowAdvancedSettingsToggled(bool checked)
Host::SetBaseBoolSettingValue("UI", "ShowAdvancedSettings", checked);
Host::CommitBaseSettingChanges();
m_ui.menuDebug->menuAction()->setVisible(checked);
updateAdvancedSettingsVisibility();
// just recreate the entire settings window, it's easier.
if (m_settings_window)
recreateSettings();
}
void MainWindow::updateAdvancedSettingsVisibility()
{
const bool enabled = QtHost::ShouldShowAdvancedSettings();
m_ui.menuDebug->menuAction()->setVisible(enabled);
m_ui.actionEnableSystemConsole->setVisible(enabled);
if (m_ui.actionEnableDebugConsole)
m_ui.actionEnableDebugConsole->setVisible(enabled);
m_ui.actionEnableVerboseLogging->setVisible(enabled);
}
void MainWindow::onToolsVideoCaptureToggled(bool checked)
{
if (!s_vm_valid)
@@ -899,9 +936,9 @@ void MainWindow::updateWindowTitle()
{
QString suffix(QtHost::GetAppConfigSuffix());
QString main_title(QtHost::GetAppNameAndVersion() + suffix);
QString display_title(m_current_title + suffix);
QString display_title(s_current_title + suffix);
if (!s_vm_valid || m_current_title.isEmpty())
if (!s_vm_valid || s_current_title.isEmpty())
display_title = main_title;
else if (isRenderingToMain())
main_title = display_title;
@@ -915,6 +952,9 @@ void MainWindow::updateWindowTitle()
if (container->windowTitle() != display_title)
container->setWindowTitle(display_title);
}
if (g_log_window)
g_log_window->updateWindowTitle();
}
void MainWindow::updateWindowState(bool force_visible)
@@ -1109,7 +1149,7 @@ bool MainWindow::requestShutdown(bool allow_confirm, bool allow_save_to_state, b
return true;
// If we don't have a crc, we can't save state.
allow_save_to_state &= (m_current_disc_crc != 0);
allow_save_to_state &= (s_current_disc_crc != 0);
bool save_state = allow_save_to_state && default_save_to_state;
VMLock lock(pauseAndLockVM());
@@ -1187,6 +1227,8 @@ void MainWindow::checkForSettingChanges()
updateDisplayWidgetCursor();
updateWindowState();
LogWindow::updateSettings();
}
std::optional<WindowInfo> MainWindow::getWindowInfo()
@@ -1276,11 +1318,10 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
});
}
//: Refers to the directory where a game is contained.
action = menu.addAction(tr("Open Containing Directory..."));
action = menu.addAction(QtUtils::GetShowInFileExplorerMessage());
connect(action, &QAction::triggered, [this, entry]() {
const QFileInfo fi(QString::fromStdString(entry->path));
QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath()));
QtUtils::ShowInFileExplorer(this, fi);
});
action = menu.addAction(tr("Set Cover Image..."));
@@ -1408,13 +1449,13 @@ void MainWindow::onChangeDiscMenuAboutToHide()
void MainWindow::onLoadStateMenuAboutToShow()
{
m_ui.menuLoadState->clear();
populateLoadStateMenu(m_ui.menuLoadState, m_current_disc_path, m_current_disc_serial, m_current_disc_crc);
populateLoadStateMenu(m_ui.menuLoadState, s_current_disc_path, s_current_disc_serial, s_current_disc_crc);
}
void MainWindow::onSaveStateMenuAboutToShow()
{
m_ui.menuSaveState->clear();
populateSaveStateMenu(m_ui.menuSaveState, m_current_disc_serial, m_current_disc_crc);
populateSaveStateMenu(m_ui.menuSaveState, s_current_disc_serial, s_current_disc_crc);
}
void MainWindow::onStartFullscreenUITriggered()
@@ -1476,36 +1517,36 @@ void MainWindow::onViewGamePropertiesActionTriggered()
return;
// prefer to use a game list entry, if we have one, that way the summary is populated
if (!m_current_disc_path.isEmpty() || !m_current_elf_override.isEmpty())
if (!s_current_disc_path.isEmpty() || !s_current_elf_override.isEmpty())
{
auto lock = GameList::GetLock();
const QString& path = (m_current_elf_override.isEmpty() ? m_current_disc_path : m_current_elf_override);
const QString& path = (s_current_elf_override.isEmpty() ? s_current_disc_path : s_current_elf_override);
const GameList::Entry* entry = GameList::GetEntryForPath(path.toUtf8().constData());
if (entry)
{
SettingsWindow::openGamePropertiesDialog(
entry, entry->title, m_current_elf_override.isEmpty() ? entry->serial : std::string(), entry->crc);
entry, entry->title, s_current_elf_override.isEmpty() ? entry->serial : std::string(), entry->crc);
return;
}
}
// open properties for the current running file (isn't in the game list)
if (m_current_disc_crc == 0)
if (s_current_disc_crc == 0)
{
QMessageBox::critical(this, tr("Game Properties"), tr("Game properties is unavailable for the current game."));
return;
}
// can't use serial for ELFs, because they might have a disc set
if (m_current_elf_override.isEmpty())
if (s_current_elf_override.isEmpty())
{
SettingsWindow::openGamePropertiesDialog(
nullptr, m_current_title.toStdString(), m_current_disc_serial.toStdString(), m_current_disc_crc);
nullptr, s_current_title.toStdString(), s_current_disc_serial.toStdString(), s_current_disc_crc);
}
else
{
SettingsWindow::openGamePropertiesDialog(
nullptr, m_current_title.toStdString(), std::string(), m_current_disc_crc);
nullptr, s_current_title.toStdString(), std::string(), s_current_disc_crc);
}
}
@@ -1600,17 +1641,17 @@ void MainWindow::onToolsCoverDownloaderTriggered()
{
// This can be invoked via big picture, so exit fullscreen.
VMLock lock(pauseAndLockVM());
CoverDownloadDialog dlg(this);
CoverDownloadDialog dlg(lock.getDialogParent());
connect(&dlg, &CoverDownloadDialog::coverRefreshRequested, m_game_list_widget, &GameListWidget::refreshGridCovers);
dlg.exec();
}
void MainWindow::onToolsEditCheatsPatchesTriggered(bool cheats)
{
if (m_current_disc_serial.isEmpty() || m_current_running_crc == 0)
if (s_current_disc_serial.isEmpty() || s_current_running_crc == 0)
return;
const std::string path = Patch::GetPnachFilename(m_current_disc_serial.toStdString(), m_current_running_crc, cheats);
const std::string path = Patch::GetPnachFilename(s_current_disc_serial.toStdString(), s_current_running_crc, cheats);
if (!FileSystem::FileExists(path.c_str()))
{
if (QMessageBox::question(this, tr("Confirm File Creation"),
@@ -1631,6 +1672,14 @@ void MainWindow::onToolsEditCheatsPatchesTriggered(bool cheats)
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(path)));
}
void MainWindow::onCreateMemoryCardOpenRequested()
{
// This can be invoked via big picture, so exit fullscreen.
VMLock lock(pauseAndLockVM());
MemoryCardCreateDialog dlg(lock.getDialogParent());
dlg.exec();
}
void MainWindow::updateTheme()
{
QtHost::UpdateApplicationTheme();
@@ -1644,7 +1693,10 @@ void MainWindow::reloadThemeSpecificImages()
void MainWindow::updateLanguage()
{
QtHost::InstallTranslator();
// Remove the settings window, so it doesn't mess with any popups that happen (e.g. font download).
destroySubWindows();
QtHost::InstallTranslator(this);
recreate();
}
@@ -1874,12 +1926,12 @@ void MainWindow::onVMStopped()
void MainWindow::onGameChanged(const QString& title, const QString& elf_override, const QString& disc_path,
const QString& serial, quint32 disc_crc, quint32 crc)
{
m_current_title = title;
m_current_elf_override = elf_override;
m_current_disc_path = disc_path;
m_current_disc_serial = serial;
m_current_disc_crc = disc_crc;
m_current_running_crc = crc;
s_current_title = title;
s_current_elf_override = elf_override;
s_current_disc_path = disc_path;
s_current_disc_serial = serial;
s_current_disc_crc = disc_crc;
s_current_running_crc = crc;
updateWindowTitle();
updateGameDependentActions();
}
@@ -2033,6 +2085,22 @@ void MainWindow::dropEvent(QDropEvent* event)
}
}
void MainWindow::moveEvent(QMoveEvent* event)
{
QMainWindow::moveEvent(event);
if (g_log_window && g_log_window->isAttachedToMainWindow())
g_log_window->reattachToMainWindow();
}
void MainWindow::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
if (g_log_window && g_log_window->isAttachedToMainWindow())
g_log_window->reattachToMainWindow();
}
void MainWindow::registerForDeviceNotifications()
{
#ifdef _WIN32
@@ -2643,8 +2711,8 @@ void MainWindow::loadSaveStateFile(const QString& filename, const QString& state
{
if (s_vm_valid)
{
if (!filename.isEmpty() && m_current_disc_path != filename)
g_emu_thread->changeDisc(CDVD_SourceType::Iso, m_current_disc_path);
if (!filename.isEmpty() && s_current_disc_path != filename)
g_emu_thread->changeDisc(CDVD_SourceType::Iso, s_current_disc_path);
g_emu_thread->loadState(state_filename);
}
else
@@ -2769,13 +2837,13 @@ void MainWindow::populateSaveStateMenu(QMenu* menu, const QString& serial, quint
void MainWindow::updateGameDependentActions()
{
const bool valid_serial_and_crc = (s_vm_valid && !m_current_disc_serial.isEmpty() && m_current_disc_crc != 0);
const bool valid_serial_and_crc = (s_vm_valid && !s_current_disc_serial.isEmpty() && s_current_disc_crc != 0);
m_ui.menuLoadState->setEnabled(valid_serial_and_crc);
m_ui.actionToolbarLoadState->setEnabled(valid_serial_and_crc);
m_ui.menuSaveState->setEnabled(valid_serial_and_crc);
m_ui.actionToolbarSaveState->setEnabled(valid_serial_and_crc);
const bool can_use_pnach = (s_vm_valid && !m_current_disc_serial.isEmpty() && m_current_running_crc != 0);
const bool can_use_pnach = (s_vm_valid && !s_current_disc_serial.isEmpty() && s_current_running_crc != 0);
m_ui.actionEditCheats->setEnabled(can_use_pnach);
m_ui.actionEditPatches->setEnabled(can_use_pnach);
m_ui.actionReloadPatches->setEnabled(s_vm_valid);
@@ -2838,7 +2906,7 @@ void MainWindow::doDiscChange(CDVD_SourceType source, const QString& path)
if (reset_system)
{
// Clearing ELF override will reset the system.
if (!m_current_elf_override.isEmpty())
if (!s_current_elf_override.isEmpty())
g_emu_thread->setELFOverride(QString());
else
g_emu_thread->resetVM();
@@ -2885,6 +2953,12 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
// Now we'll either have a borderless window, or a regular window (if we were exclusive fullscreen).
QWidget* dialog_parent = getDisplayContainer();
// Ensure main window is visible.
if (!g_main_window->isVisible())
g_main_window->show();
g_main_window->raise();
g_main_window->activateWindow();
return VMLock(dialog_parent, was_paused, was_fullscreen);
}
@@ -2922,6 +2996,19 @@ MainWindow::VMLock::~VMLock()
g_emu_thread->setVMPaused(false);
}
MainWindow::VMLock& MainWindow::VMLock::operator=(VMLock&& lock)
{
m_dialog_parent = lock.m_dialog_parent;
m_was_paused = lock.m_was_paused;
m_was_fullscreen = lock.m_was_fullscreen;
lock.m_dialog_parent = nullptr;
lock.m_was_paused = true;
lock.m_was_fullscreen = false;
return *this;
}
void MainWindow::VMLock::cancelResume()
{
m_was_paused = true;
@@ -2938,3 +3025,18 @@ bool QtHost::IsVMPaused()
{
return s_vm_paused;
}
const QString& QtHost::GetCurrentGameTitle()
{
return s_current_title;
}
const QString& QtHost::GetCurrentGameSerial()
{
return s_current_disc_serial;
}
const QString& QtHost::GetCurrentGamePath()
{
return s_current_disc_path;
}
+7 -10
View File
@@ -54,6 +54,9 @@ public:
VMLock(const VMLock&) = delete;
~VMLock();
VMLock& operator=(VMLock&& lock);
VMLock& operator=(const VMLock&) = delete;
/// Returns the parent widget, which can be used for any popup dialogs.
__fi QWidget* getDialogParent() const { return m_dialog_parent; }
@@ -155,6 +158,7 @@ private Q_SLOTS:
void onToolsOpenDataDirectoryTriggered();
void onToolsCoverDownloaderTriggered();
void onToolsEditCheatsPatchesTriggered(bool cheats);
void onCreateMemoryCardOpenRequested();
void updateTheme();
void reloadThemeSpecificImages();
void updateLanguage();
@@ -194,15 +198,14 @@ protected:
void changeEvent(QEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
void moveEvent(QMoveEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
#ifdef _WIN32
bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;
#endif
private:
static void setStyleFromSettings();
static void setIconThemeFromStyle();
void setupAdditionalUi();
void connectSignals();
void createRendererSwitchMenu();
@@ -220,6 +223,7 @@ private:
void updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen);
void updateGameDependentActions();
void updateStatusBarWidgetVisibility();
void updateAdvancedSettingsVisibility();
void updateWindowTitle();
void updateWindowState(bool force_visible = false);
void setProgressBar(int current, int total);
@@ -290,13 +294,6 @@ private:
QMenu* m_settings_toolbar_menu = nullptr;
QString m_current_title;
QString m_current_elf_override;
QString m_current_disc_path;
QString m_current_disc_serial;
quint32 m_current_disc_crc;
quint32 m_current_running_crc;
bool m_display_created = false;
bool m_relative_mouse_mode = false;
bool m_hide_mouse_cursor = false;
+32 -6
View File
@@ -146,7 +146,7 @@
<addaction name="actionEnableIOPConsoleLogging"/>
<addaction name="actionEnableCDVDVerboseReads"/>
</widget>
<widget class="QMenu" name="menu_View">
<widget class="QMenu" name="menuView">
<property name="title">
<string>&amp;View</string>
</property>
@@ -176,11 +176,11 @@
<addaction name="actionGridViewZoomOut"/>
<addaction name="actionGridViewRefreshCovers"/>
</widget>
<widget class="QMenu" name="menu_Tools">
<widget class="QMenu" name="menuTools">
<property name="title">
<string>&amp;Tools</string>
</property>
<widget class="QMenu" name="menuInput_Recording">
<widget class="QMenu" name="menuInputRecording">
<property name="title">
<string>Input Recording</string>
</property>
@@ -206,10 +206,12 @@
<addaction name="actionReloadPatches"/>
<addaction name="separator"/>
<addaction name="actionToggleSoftwareRendering"/>
<addaction name="menuInput_Recording"/>
<addaction name="menuInputRecording"/>
<addaction name="actionToolsVideoCapture"/>
<addaction name="separator"/>
<addaction name="actionEnableSystemConsole"/>
<addaction name="actionEnableDebugConsole"/>
<addaction name="actionEnableLogWindow"/>
<addaction name="actionEnableFileLogging"/>
<addaction name="actionEnableVerboseLogging"/>
<addaction name="separator"/>
@@ -219,8 +221,8 @@
</widget>
<addaction name="menuSystem"/>
<addaction name="menuSettings"/>
<addaction name="menu_View"/>
<addaction name="menu_Tools"/>
<addaction name="menuView"/>
<addaction name="menuTools"/>
<addaction name="menuDebug"/>
<addaction name="menuHelp"/>
</widget>
@@ -901,6 +903,22 @@
<string>Enable System Console</string>
</property>
</action>
<action name="actionEnableDebugConsole">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable Debug Console</string>
</property>
</action>
<action name="actionEnableLogWindow">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable Log Window</string>
</property>
</action>
<action name="actionEnableVerboseLogging">
<property name="checkable">
<bool>true</bool>
@@ -1071,11 +1089,19 @@
<property name="text">
<string>Edit Cheats...</string>
</property>
<property name="icon">
<iconset theme="pencil-line">
<normaloff>.</normaloff>.</iconset>
</property>
</action>
<action name="actionEditPatches">
<property name="text">
<string>Edit Patches...</string>
</property>
<property name="icon">
<iconset theme="pencil-line">
<normaloff>.</normaloff>.</iconset>
</property>
</action>
</widget>
<resources>
+144 -41
View File
@@ -4,8 +4,10 @@
#include "AutoUpdaterDialog.h"
#include "DisplayWidget.h"
#include "GameList/GameListWidget.h"
#include "LogWindow.h"
#include "MainWindow.h"
#include "QtHost.h"
#include "QtProgressCallback.h"
#include "QtUtils.h"
#include "SetupWizardDialog.h"
#include "svnrev.h"
@@ -25,16 +27,15 @@
#include "pcsx2/ImGui/ImGuiManager.h"
#include "pcsx2/ImGui/ImGuiOverlays.h"
#include "pcsx2/Input/InputManager.h"
#include "pcsx2/LogSink.h"
#include "pcsx2/MTGS.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/SysForwardDefs.h"
#include "pcsx2/VMManager.h"
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/CrashHandler.h"
#include "common/FileSystem.h"
#include "common/HTTPDownloader.h"
#include "common/Path.h"
#include "common/SettingsWrapper.h"
#include "common/StringUtil.h"
@@ -52,6 +53,8 @@
#include <csignal>
static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
static constexpr const char* RUNTIME_RESOURCES_URL =
"https://github.com/PCSX2/pcsx2-windows-dependencies/releases/download/runtime-resources/";
EmuThread* g_emu_thread = nullptr;
@@ -60,6 +63,7 @@ EmuThread* g_emu_thread = nullptr;
//////////////////////////////////////////////////////////////////////////
namespace QtHost
{
static void InitializeEarlyConsole();
static void PrintCommandLineVersion();
static void PrintCommandLineHelp(const std::string_view& progname);
static std::shared_ptr<VMBootParameters>& AutoBoot(std::shared_ptr<VMBootParameters>& autoboot);
@@ -69,6 +73,7 @@ namespace QtHost
static void HookSignals();
static void RegisterTypes();
static bool RunSetupWizard();
static std::optional<bool> DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector<u8>* data);
} // namespace QtHost
//////////////////////////////////////////////////////////////////////////
@@ -82,6 +87,7 @@ static bool s_start_fullscreen_ui = false;
static bool s_start_fullscreen_ui_fullscreen = false;
static bool s_test_config_and_exit = false;
static bool s_run_setup_wizard = false;
static bool s_cleanup_after_update = false;
static bool s_boot_and_debug = false;
//////////////////////////////////////////////////////////////////////////
@@ -96,11 +102,6 @@ EmuThread::EmuThread(QThread* ui_thread)
EmuThread::~EmuThread() = default;
bool EmuThread::isOnEmuThread() const
{
return QThread::currentThread() == this;
}
void EmuThread::start()
{
pxAssertRel(!g_emu_thread, "Emu thread does not exist");
@@ -496,7 +497,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
MTGS::WaitGS();
// If we're using exclusive fullscreen, the refresh rate may have changed.
UpdateVSyncRate(true);
VMManager::UpdateTargetSpeed();
}
void EmuThread::setSurfaceless(bool surfaceless)
@@ -990,7 +991,7 @@ void EmuThread::updatePerformanceMetrics(bool force)
QMetaObject::invokeMethod(g_main_window->getStatusVerboseWidget(), "setText", Qt::QueuedConnection, Q_ARG(const QString&, gs_stat));
}
const GSRendererType renderer = GSConfig.Renderer;
const GSRendererType renderer = GSGetCurrentRenderer(); // Reading from GS thread, therefore racey, but it's just visual.
const float speed = std::round(PerformanceMetrics::GetSpeed());
const float gfps = std::round(PerformanceMetrics::GetInternalFPS());
const float vfps = std::round(PerformanceMetrics::GetFPS());
@@ -1116,7 +1117,12 @@ void Host::OnCoverDownloaderOpenRequested()
emit g_emu_thread->onCoverDownloaderOpenRequested();
}
void Host::VSyncOnCPUThread()
void Host::OnCreateMemoryCardOpenRequested()
{
emit g_emu_thread->onCreateMemoryCardOpenRequested();
}
void Host::PumpMessagesOnCPUThread()
{
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
}
@@ -1235,9 +1241,10 @@ bool QtHost::InitializeConfig()
s_run_setup_wizard =
s_run_setup_wizard || s_base_settings_interface->GetBoolValue("UI", "SetupWizardIncomplete", false);
LogSink::SetBlockSystemConsole(QtHost::InNoGUIMode());
// TODO: -nogui console block?
VMManager::Internal::LoadStartupSettings();
InstallTranslator();
InstallTranslator(nullptr);
return true;
}
@@ -1334,24 +1341,7 @@ bool Host::RequestResetSettings(bool folders, bool core, bool controllers, bool
QString QtHost::GetAppNameAndVersion()
{
QString ret;
if constexpr (!PCSX2_isReleaseVersion && GIT_TAGGED_COMMIT)
{
ret = QStringLiteral("PCSX2 Nightly - " GIT_TAG);
}
else if constexpr (PCSX2_isReleaseVersion)
{
#define APPNAME_STRINGIZE(x) #x
ret = QStringLiteral(
"PCSX2 " APPNAME_STRINGIZE(PCSX2_VersionHi) "." APPNAME_STRINGIZE(PCSX2_VersionMid) "." APPNAME_STRINGIZE(PCSX2_VersionLo));
#undef APPNAME_STRINGIZE
}
else
{
return QStringLiteral("PCSX2 " GIT_REV);
}
return ret;
return QStringLiteral("PCSX2 " GIT_REV);
}
QString QtHost::GetAppConfigSuffix()
@@ -1365,11 +1355,99 @@ QString QtHost::GetAppConfigSuffix()
#endif
}
QIcon QtHost::GetAppIcon()
{
return QIcon(QStringLiteral(":/icons/AppIcon64.png"));
}
QString QtHost::GetResourcesBasePath()
{
return QString::fromStdString(EmuFolders::Resources);
}
std::string QtHost::GetRuntimeDownloadedResourceURL(std::string_view name)
{
return fmt::format("{}/{}", RUNTIME_RESOURCES_URL, HTTPDownloader::URLEncode(name));
}
std::optional<bool> QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector<u8>* data)
{
static constexpr u32 HTTP_POLL_INTERVAL = 10;
std::unique_ptr<HTTPDownloader> http = HTTPDownloader::Create(Host::GetHTTPUserAgent());
if (!http)
{
QMessageBox::critical(parent, qApp->translate("EmuThread", "Error"), qApp->translate("EmuThread", "Failed to create HTTPDownloader."));
return false;
}
std::optional<bool> download_result;
const std::string::size_type url_file_part_pos = url.rfind('/');
QtModalProgressCallback progress(parent);
progress.GetDialog().setLabelText(
qApp->translate("EmuThread", "Downloading %1...").arg(QtUtils::StringViewToQString(
std::string_view(url).substr((url_file_part_pos != std::string::npos) ? (url_file_part_pos + 1) : 0))));
progress.GetDialog().setWindowTitle(title);
progress.GetDialog().setWindowIcon(GetAppIcon());
progress.SetCancellable(true);
http->CreateRequest(
std::move(url), [parent, data, &download_result](s32 status_code, const std::string&, std::vector<u8> hdata) {
if (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED)
return;
if (status_code != HTTPDownloader::HTTP_STATUS_OK)
{
QMessageBox::critical(parent, qApp->translate("EmuThread", "Error"),
qApp->translate("EmuThread", "Download failed with HTTP status code %1.").arg(status_code));
download_result = false;
return;
}
if (hdata.empty())
{
QMessageBox::critical(parent, qApp->translate("EmuThread", "Error"),
qApp->translate("EmuThread", "Download failed: Data is empty.").arg(status_code));
download_result = false;
return;
}
*data = std::move(hdata);
download_result = true;
},
&progress);
// Block until completion.
while (http->HasAnyRequests())
{
QApplication::processEvents(QEventLoop::AllEvents, HTTP_POLL_INTERVAL);
http->PollRequests();
}
return download_result;
}
bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url, const std::string& path)
{
std::vector<u8> data;
if (!DownloadFile(parent, title, std::move(url), &data).value_or(false) || data.empty())
return false;
// Directory may not exist. Create it.
const std::string directory(Path::GetDirectory(path));
if ((!directory.empty() && !FileSystem::DirectoryExists(directory.c_str()) &&
!FileSystem::CreateDirectoryPath(directory.c_str(), true)) ||
!FileSystem::WriteBinaryFile(path.c_str(), data.data(), data.size()))
{
QMessageBox::critical(parent, qApp->translate("EmuThread", "Error"),
qApp->translate("EmuThread", "Failed to write '%1'.").arg(QString::fromStdString(path)));
return false;
}
return true;
}
void Host::ReportErrorAsync(const std::string_view& title, const std::string_view& message)
{
if (!title.empty() && !message.empty())
@@ -1482,12 +1560,27 @@ static void SignalHandler(int signal)
#endif
}
#ifdef _WIN32
static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType)
{
if (dwCtrlType != CTRL_C_EVENT)
return FALSE;
SignalHandler(SIGTERM);
return TRUE;
}
#endif
void QtHost::HookSignals()
{
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);
#ifdef __linux__
#if defined(_WIN32)
SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE);
#elif defined(__linux__)
// Ignore SIGCHLD by default on Linux, since we kick off aplay asynchronously.
struct sigaction sa_chld = {};
sigemptyset(&sa_chld.sa_mask);
@@ -1496,9 +1589,14 @@ void QtHost::HookSignals()
#endif
}
void QtHost::InitializeEarlyConsole()
{
Log::SetConsoleOutputLevel(LOGLEVEL_DEBUG);
}
void QtHost::PrintCommandLineVersion()
{
LogSink::InitializeEarlyConsole();
InitializeEarlyConsole();
std::fprintf(stderr, "%s\n", (GetAppNameAndVersion() + GetAppConfigSuffix()).toUtf8().constData());
std::fprintf(stderr, "https://pcsx2.net/\n");
std::fprintf(stderr, "\n");
@@ -1622,7 +1720,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
}
else if (CHECK_ARG_PARAM(QStringLiteral("-logfile")))
{
LogSink::SetFileLogPath((++it)->toStdString());
VMManager::Internal::SetFileLogPath((++it)->toStdString());
continue;
}
else if (CHECK_ARG(QStringLiteral("-bios")))
@@ -1643,7 +1741,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
}
else if (CHECK_ARG(QStringLiteral("-earlyconsolelog")))
{
LogSink::InitializeEarlyConsole();
InitializeEarlyConsole();
continue;
}
else if (CHECK_ARG(QStringLiteral("-bigpicture")))
@@ -1668,9 +1766,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
}
else if (CHECK_ARG(QStringLiteral("-updatecleanup")))
{
if (AutoUpdaterDialog::isSupported())
AutoUpdaterDialog::cleanupAfterUpdate();
s_cleanup_after_update = AutoUpdaterDialog::isSupported();
continue;
}
#ifdef ENABLE_RAINTEGRATION
@@ -1705,7 +1801,6 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
// or disc, we don't want to actually start.
if (autoboot && !autoboot->source_type.has_value() && autoboot->filename.empty() && autoboot->elf_override.empty())
{
LogSink::InitializeEarlyConsole();
Console.Warning("Skipping autoboot due to no boot parameters.");
autoboot.reset();
}
@@ -1802,9 +1897,16 @@ int main(int argc, char* argv[])
if (s_test_config_and_exit)
return EXIT_SUCCESS;
// Remove any previous-version remanants.
if (s_cleanup_after_update)
AutoUpdaterDialog::cleanupAfterUpdate();
// Set theme before creating any windows.
QtHost::UpdateApplicationTheme();
// Start logging early.
LogWindow::updateSettings();
// Start up the CPU thread.
QtHost::HookSignals();
EmuThread::start();
@@ -1829,7 +1931,11 @@ int main(int argc, char* argv[])
// Don't bother showing the window in no-gui mode.
if (!s_nogui_mode)
{
g_main_window->show();
g_main_window->raise();
g_main_window->activateWindow();
}
// Initialize big picture mode if requested.
if (s_start_fullscreen_ui)
@@ -1863,8 +1969,5 @@ shutdown_and_exit:
if (s_base_settings_interface->IsDirty())
s_base_settings_interface->Save();
// Ensure emulog is flushed.
LogSink::CloseFileLog();
return result;
}
+27 -4
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
@@ -7,6 +7,8 @@
#include <memory>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include "pcsx2/Host.h"
#include "pcsx2/Input/InputManager.h"
@@ -22,6 +24,8 @@
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <QtGui/QIcon>
class SettingsInterface;
class DisplayWidget;
@@ -59,7 +63,8 @@ public:
__fi bool isSurfaceless() const { return m_is_surfaceless; }
__fi bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
bool isOnEmuThread() const;
__fi bool isOnEmuThread() const { return (QThread::currentThread() == this); }
__fi bool isOnUIThread() const { return (QThread::currentThread() == m_ui_thread); }
bool shouldRenderToMain() const;
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
@@ -163,8 +168,9 @@ Q_SIGNALS:
/// Called when hardcore mode is enabled or disabled.
void onAchievementsHardcoreModeChanged(bool enabled);
/// Called when cover download is requested.
/// Big Picture UI requests.
void onCoverDownloaderOpenRequested();
void onCreateMemoryCardOpenRequested();
/// Called when video capture starts/stops.
void onCaptureStarted(const QString& filename);
@@ -233,6 +239,9 @@ namespace QtHost
/// Sets application theme according to settings.
void UpdateApplicationTheme();
/// Returns true if the application theme is using dark colours.
bool IsDarkApplicationTheme();
/// Sets the icon theme, based on the current style (light/dark).
void SetIconThemeFromStyle();
@@ -255,7 +264,7 @@ namespace QtHost
std::vector<std::pair<QString, QString>> GetAvailableLanguageList();
/// Call when the language changes.
void InstallTranslator();
void InstallTranslator(QWidget* dialog_parent);
/// Returns the application name and version, optionally including debug/devel config indicator.
QString GetAppNameAndVersion();
@@ -263,13 +272,27 @@ namespace QtHost
/// Returns the debug/devel config indicator.
QString GetAppConfigSuffix();
/// Returns the main application icon.
QIcon GetAppIcon();
/// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
QString GetResourcesBasePath();
/// Returns the URL to a runtime-downloaded resource.
std::string GetRuntimeDownloadedResourceURL(std::string_view name);
/// Downloads the specified URL to the provided path.
bool DownloadFile(QWidget* parent, const QString& title, std::string url, const std::string& path);
/// VM state, safe to access on UI thread.
bool IsVMValid();
bool IsVMPaused();
/// Accessors for game information.
const QString& GetCurrentGameTitle();
const QString& GetCurrentGameSerial();
const QString& GetCurrentGamePath();
/// Compare strings in the locale of the current UI language
int LocaleSensitiveCompare(QStringView lhs, QStringView rhs);
} // namespace QtHost
+41 -2
View File
@@ -4,6 +4,7 @@
#include "QtUtils.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
#include <QtCore/QMetaObject>
#include <QtGui/QAction>
#include <QtGui/QGuiApplication>
@@ -26,10 +27,12 @@
#include <array>
#include <map>
#include "common/CocoaTools.h"
#include "common/Console.h"
#if defined(_WIN32)
#include "common/RedtapeWindows.h"
#include <Shlobj.h>
#elif !defined(APPLE)
#include <qpa/qplatformnativeinterface.h>
#endif
@@ -129,12 +132,48 @@ namespace QtUtils
ResizeColumnsForView(view, widths);
}
void ShowInFileExplorer(QWidget* parent, const QFileInfo& file)
{
#if defined(_WIN32)
std::wstring wstr = QDir::toNativeSeparators(file.absoluteFilePath()).toStdWString();
bool ok = false;
if (PIDLIST_ABSOLUTE pidl = ILCreateFromPath(wstr.c_str()))
{
ok = SUCCEEDED(SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0));
ILFree(pidl);
}
#elif defined(__APPLE__)
bool ok = CocoaTools::ShowInFinder(file.absoluteFilePath().toStdString());
#else
bool ok = QDesktopServices::openUrl(QUrl::fromLocalFile(file.absolutePath()));
#endif
if (!ok)
{
QMessageBox::critical(parent, QCoreApplication::translate("FileOperations", "Failed to show file"),
QCoreApplication::translate("FileOperations", "Failed to show file in file explorer.\n\nThe file was: %1").arg(file.absoluteFilePath()));
}
}
QString GetShowInFileExplorerMessage()
{
#if defined(_WIN32)
//: Windows action to show a file in Windows Explorer
return QCoreApplication::translate("FileOperations", "Show in Folder");
#elif defined(__APPLE__)
//: macOS action to show a file in Finder
return QCoreApplication::translate("FileOperations", "Show in Finder");
#else
//: Opens the system file manager to the directory containing a selected file
return QCoreApplication::translate("FileOperations", "Open Containing Directory");
#endif
}
void OpenURL(QWidget* parent, const QUrl& qurl)
{
if (!QDesktopServices::openUrl(qurl))
{
QMessageBox::critical(parent, QObject::tr("Failed to open URL"),
QObject::tr("Failed to open URL.\n\nThe URL was: %1").arg(qurl.toString()));
QMessageBox::critical(parent, QCoreApplication::translate("FileOperations", "Failed to open URL"),
QCoreApplication::translate("FileOperations", "Failed to open URL.\n\nThe URL was: %1").arg(qurl.toString()));
}
}
+7
View File
@@ -18,6 +18,7 @@ class ByteStream;
class QAction;
class QComboBox;
class QFileInfo;
class QFrame;
class QKeyEvent;
class QTableView;
@@ -49,6 +50,12 @@ namespace QtUtils
/// NOTE: Defined in QtKeyCodes.cpp, not QtUtils.cpp.
u32 KeyEventToCode(const QKeyEvent* ev);
/// Shows a file, or the containing folder if unsupported, with the system file explorer
void ShowInFileExplorer(QWidget* parent, const QFileInfo& file);
/// Get the context menu name for the action performed by ShowInFileExplorer
QString GetShowInFileExplorerMessage();
/// Opens a URL with the default handler.
void OpenURL(QWidget* parent, const QUrl& qurl);
+5 -3
View File
@@ -29,9 +29,10 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vu1Recompiler, "EmuCore/CPU/Recompiler", "EnableVU1", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vuFlagHack, "EmuCore/Speedhacks", "vuFlagHack", true);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.eeRoundingMode, "EmuCore/CPU", "FPU.Roundmode", 3);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.vu0RoundingMode, "EmuCore/CPU", "VU0.Roundmode", 3);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.vu1RoundingMode, "EmuCore/CPU", "VU1.Roundmode", 3);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.eeRoundingMode, "EmuCore/CPU", "FPU.Roundmode", static_cast<int>(FPRoundMode::ChopZero));
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.eeDivRoundingMode, "EmuCore/CPU", "FPUDiv.Roundmode", static_cast<int>(FPRoundMode::Nearest));
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.vu0RoundingMode, "EmuCore/CPU", "VU0.Roundmode", static_cast<int>(FPRoundMode::ChopZero));
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.vu1RoundingMode, "EmuCore/CPU", "VU1.Roundmode", static_cast<int>(FPRoundMode::ChopZero));
if (m_dialog->isPerGameSettings())
{
m_ui.eeClampMode->insertItem(0, tr("Use Global Setting [%1]").arg(m_ui.eeClampMode->itemText(getGlobalClampingModeIndex(-1))));
@@ -58,6 +59,7 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsWindow* dialog, QWidget*
dialog->registerWidgetHelp(m_ui.eeRoundingMode, tr("Rounding Mode"), tr("Chop/Zero (Default)"), tr("Changes how PCSX2 handles rounding while emulating the Emotion Engine's Floating Point Unit (EE FPU). "
"Because the various FPUs in the PS2 are non-compliant with international standards, some games may need different modes to do math correctly. The default value handles the vast majority of games; <b>modifying this setting when a game is not having a visible problem can cause instability.</b>"));
dialog->registerWidgetHelp(m_ui.eeDivRoundingMode, tr("Division Rounding Mode"), tr("Nearest (Default)"), tr("Determines how the results of floating-point division is rounded. Some games need specific settings; <b>modifying this setting when a game is not having a visible problem can cause instability.</b>"));
dialog->registerWidgetHelp(m_ui.eeClampMode, tr("Clamping Mode"), tr("Normal (Default)"), tr("Changes how PCSX2 handles keeping floats in a standard x86 range. "
"The default value handles the vast majority of games; <b>modifying this setting when a game is not having a visible problem can cause instability.</b>"));
+33 -2
View File
@@ -95,13 +95,44 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string extracomment="Rounding refers here to the mathematical term.">Division Rounding Mode:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="eeDivRoundingMode">
<item>
<property name="text">
<string>Nearest (Default)</string>
</property>
</item>
<item>
<property name="text">
<string>Negative</string>
</property>
</item>
<item>
<property name="text">
<string>Positive</string>
</property>
</item>
<item>
<property name="text">
<string>Chop/Zero</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string extracomment="Clamping: Forcing out of bounds things in bounds by changing them to the closest possible value. In this case, this refers to clamping large PS2 floating point values (which map to infinity or NaN in PCs' IEEE754 floats) to non-infinite ones.">Clamping Mode:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QComboBox" name="eeClampMode">
<item>
<property name="text">
@@ -125,7 +156,7 @@
</item>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QCheckBox" name="eeWaitLoopDetection">
@@ -0,0 +1,531 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ControllerBindingWidget_Popn</class>
<widget class="QWidget" name="ControllerBindingWidget_Popn">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1232</width>
<height>644</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>1100</width>
<height>500</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_35">
<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 row="0" column="2" rowspan="3">
<layout class="QVBoxLayout" name="verticalLayout"/>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer">
<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="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>266</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources/resources.qrc">:/images/Popn.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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 row="0" column="0" rowspan="3">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</item>
<item row="0" column="1">
<layout class="QGridLayout" name="gridLayout_27">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_21">
<property name="title">
<string extracomment="Leave this button name as-is or uppercase it entirely.">Select</string>
</property>
<layout class="QGridLayout" name="gridLayout_21">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Select">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_22">
<property name="title">
<string>Yellow (Left)</string>
</property>
<layout class="QGridLayout" name="gridLayout_22">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="YellowL">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="3">
<widget class="QGroupBox" name="groupBox_24">
<property name="title">
<string>Yellow (Right)</string>
</property>
<layout class="QGridLayout" name="gridLayout_24">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="YellowR">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="2">
<widget class="QGroupBox" name="groupBox_26">
<property name="title">
<string>Blue (Right)</string>
</property>
<layout class="QGridLayout" name="gridLayout_26">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="BlueR">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="groupBox_25">
<property name="title">
<string>Blue (Left)</string>
</property>
<layout class="QGridLayout" name="gridLayout_25">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="BlueL">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_23">
<property name="title">
<string extracomment="Leave this button name as-is or uppercase it entirely.">Start</string>
</property>
<layout class="QGridLayout" name="gridLayout_23">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Start">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<layout class="QGridLayout" name="gridLayout_32">
<item row="0" column="2">
<widget class="QGroupBox" name="groupBox_34">
<property name="title">
<string>Red</string>
</property>
<layout class="QGridLayout" name="gridLayout_34">
<item row="0" column="0">
<widget class="InputBindingWidget" name="Red">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="5">
<spacer name="verticalSpacer">
<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 row="0" column="3">
<widget class="QGroupBox" name="groupBox_28">
<property name="title">
<string>Green (Right)</string>
</property>
<layout class="QGridLayout" name="gridLayout_29">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="GreenR">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_27">
<property name="title">
<string>White (Left)</string>
</property>
<layout class="QGridLayout" name="gridLayout_28">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="WhiteL">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_29">
<property name="title">
<string>Green (Left)</string>
</property>
<layout class="QGridLayout" name="gridLayout_30">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="GreenL">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="4">
<widget class="QGroupBox" name="groupBox_30">
<property name="title">
<string>White (Right)</string>
</property>
<layout class="QGridLayout" name="gridLayout_31">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="WhiteR">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>InputBindingWidget</class>
<extends>QPushButton</extends>
<header>Settings/InputBindingWidget.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>
@@ -108,6 +108,10 @@ void ControllerBindingWidget::onTypeChanged()
{
m_bindings_widget = ControllerBindingWidget_Guitar::createInstance(this);
}
else if (cinfo->type == Pad::ControllerType::Popn)
{
m_bindings_widget = ControllerBindingWidget_Popn::createInstance(this);
}
else
{
m_bindings_widget = new ControllerBindingWidget_Base(this);
@@ -897,6 +901,27 @@ ControllerBindingWidget_Base* ControllerBindingWidget_Guitar::createInstance(Con
return new ControllerBindingWidget_Guitar(parent);
}
ControllerBindingWidget_Popn::ControllerBindingWidget_Popn(ControllerBindingWidget* parent)
: ControllerBindingWidget_Base(parent)
{
m_ui.setupUi(this);
initBindingWidgets();
}
ControllerBindingWidget_Popn::~ControllerBindingWidget_Popn()
{
}
QIcon ControllerBindingWidget_Popn::getIcon() const
{
return QIcon::fromTheme("Popn-line");
}
ControllerBindingWidget_Base* ControllerBindingWidget_Popn::createInstance(ControllerBindingWidget* parent)
{
return new ControllerBindingWidget_Popn(parent);
}
//////////////////////////////////////////////////////////////////////////
USBDeviceWidget::USBDeviceWidget(QWidget* parent, ControllerSettingsWindow* dialog, u32 port)
@@ -12,6 +12,7 @@
#include "ui_ControllerBindingWidget.h"
#include "ui_ControllerBindingWidget_DualShock2.h"
#include "ui_ControllerBindingWidget_Guitar.h"
#include "ui_ControllerBindingWidget_Popn.h"
#include "ui_ControllerMacroWidget.h"
#include "ui_ControllerMacroEditWidget.h"
#include "ui_USBDeviceWidget.h"
@@ -199,6 +200,22 @@ private:
Ui::ControllerBindingWidget_Guitar m_ui;
};
class ControllerBindingWidget_Popn final : public ControllerBindingWidget_Base
{
Q_OBJECT
public:
ControllerBindingWidget_Popn(ControllerBindingWidget* parent);
~ControllerBindingWidget_Popn();
QIcon getIcon() const override;
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
private:
Ui::ControllerBindingWidget_Popn m_ui;
};
//////////////////////////////////////////////////////////////////////////
class USBDeviceWidget final : public QWidget
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "Settings/ControllerGlobalSettingsWidget.h"
@@ -40,7 +40,6 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
#ifdef _WIN32
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDInputSource, "InputSources", "DInput", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.ignoreDInputInversion, "InputSources", "IgnoreDInputInversion", false);
#else
m_ui.mainLayout->removeWidget(m_ui.xinputGroup);
m_ui.xinputGroup->deleteLater();
@@ -176,3 +175,19 @@ ControllerMouseSettingsDialog::ControllerMouseSettingsDialog(QWidget* parent, Co
}
ControllerMouseSettingsDialog::~ControllerMouseSettingsDialog() = default;
ControllerMappingSettingsDialog::ControllerMappingSettingsDialog(ControllerSettingsWindow* parent)
: QDialog(parent)
{
m_ui.setupUi(this);
SettingsInterface* sif = parent->getProfileSettingsInterface();
m_ui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("settings-3-line")).pixmap(32, 32));
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.ignoreInversion, "InputSources", "IgnoreInversion", false);
connect(m_ui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &QDialog::accept);
}
ControllerMappingSettingsDialog::~ControllerMappingSettingsDialog() = default;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
@@ -12,6 +12,7 @@
#include "ui_ControllerGlobalSettingsWidget.h"
#include "ui_ControllerLEDSettingsDialog.h"
#include "ui_ControllerMappingSettingsDialog.h"
#include "ui_ControllerMouseSettingsDialog.h"
class ControllerSettingsWindow;
@@ -66,3 +67,15 @@ public:
private:
Ui::ControllerMouseSettingsDialog m_ui;
};
class ControllerMappingSettingsDialog : public QDialog
{
Q_OBJECT
public:
ControllerMappingSettingsDialog(ControllerSettingsWindow* parent);
~ControllerMappingSettingsDialog();
private:
Ui::ControllerMappingSettingsDialog m_ui;
};
@@ -82,16 +82,6 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="ignoreDInputInversion">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Some third party controllers incorrectly flag their analog sticks as inverted on the positive component, but not negative.&lt;/p&gt;&lt;p&gt;As a result, the analog stick will be &amp;quot;stuck on&amp;quot; even while resting at neutral position. &lt;/p&gt;&lt;p&gt;Enabling this setting will tell PCSX2 to ignore inversion flags when creating mappings, allowing such controllers to function normally.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Ignore Inversion</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_9">
<property name="text">
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ControllerMappingSettingsDialog</class>
<widget class="QDialog" name="ControllerMappingSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>654</width>
<height>275</height>
</rect>
</property>
<property name="windowTitle">
<string>Controller Mapping Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="icon">
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Controller Mapping Settings&lt;/span&gt;&lt;br/&gt;These settings fine-tune the behavior when mapping physical controllers to the emulated controllers.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="ignoreInversion">
<property name="text">
<string>Ignore Inversion</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Some third party controllers incorrectly flag their analog sticks as inverted on the positive component, but not negative.&lt;/p&gt;&lt;p&gt;As a result, the analog stick will be &amp;quot;stuck on&amp;quot; even while resting at neutral position. &lt;/p&gt;&lt;p&gt;Enabling this setting will tell PCSX2 to ignore inversion flags when creating mappings, allowing such controllers to function normally.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>28</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>
@@ -39,6 +39,7 @@ ControllerSettingsWindow::ControllerSettingsWindow()
connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked);
connect(m_ui.loadProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onLoadProfileClicked);
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
connect(m_ui.mappingSettings, &QPushButton::clicked, this, &ControllerSettingsWindow::onMappingSettingsClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this, &ControllerSettingsWindow::onInputDevicesEnumerated);
@@ -186,6 +187,12 @@ void ControllerSettingsWindow::onDeleteProfileClicked()
switchProfile({});
}
void ControllerSettingsWindow::onMappingSettingsClicked()
{
ControllerMappingSettingsDialog dialog(this);
dialog.exec();
}
void ControllerSettingsWindow::onRestoreDefaultsClicked()
{
if (QMessageBox::question(this, tr("Restore Defaults"),
@@ -76,6 +76,7 @@ private Q_SLOTS:
void onNewProfileClicked();
void onLoadProfileClicked();
void onDeleteProfileClicked();
void onMappingSettingsClicked();
void onRestoreDefaultsClicked();
void onInputDevicesEnumerated(const QList<QPair<QString, QString>>& devices);
@@ -123,6 +123,16 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mappingSettings">
<property name="text">
<string>Mapping Settings</string>
</property>
<property name="icon">
<iconset theme="settings-3-line"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreDefaults">
<property name="text">
+20
View File
@@ -253,6 +253,8 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsWindow* dialog, QWidget* parent)
else
m_ui.hddFile->setText(QString::fromUtf8(m_dialog->getStringValue("DEV9/Hdd", "HddFile", "DEV9hdd.raw").value().c_str()));
connect(m_ui.hddLBA48, QOverload<int>::of(&QCheckBox::stateChanged), this, &DEV9SettingsWidget::onHddLBA48Changed);
UpdateHddSizeUIValues();
connect(m_ui.hddFile, &QLineEdit::textChanged, this, &DEV9SettingsWidget::onHddFileTextChange);
@@ -720,6 +722,19 @@ void DEV9SettingsWidget::onHddSizeAccessorSpin()
m_ui.hddSizeSlider->setValue(m_ui.hddSizeSpinBox->value());
}
void DEV9SettingsWidget::onHddLBA48Changed(int state)
{
m_ui.hddSizeSlider->setMaximum(state ? 2000 : 120);
m_ui.hddSizeSpinBox->setMaximum(state ? 2000 : 120);
m_ui.hddSizeMaxLabel->setText(state ? tr("2000") : tr("120"));
// Bump up min size to have ticks align with 100GiB sizes
m_ui.hddSizeSlider->setMinimum(state ? 100 : 40);
m_ui.hddSizeSpinBox->setMinimum(state ? 100 : 40);
m_ui.hddSizeMinLabel->setText(state ? tr("100") : tr("40"));
m_ui.hddSizeSlider->setTickInterval(state ? 100 : 5);
}
void DEV9SettingsWidget::onHddCreateClicked()
{
//Do the thing
@@ -800,6 +815,11 @@ void DEV9SettingsWidget::UpdateHddSizeUIValues()
if (size < 0)
return;
if (size > static_cast<s64>(120) * 1024 * 1024 * 1024)
m_ui.hddLBA48->setChecked(true);
else
m_ui.hddLBA48->setChecked(false);
const int sizeGB = size / 1024 / 1024 / 1024;
QSignalBlocker sb1(m_ui.hddSizeSpinBox);
QSignalBlocker sb2(m_ui.hddSizeSlider);
+1
View File
@@ -39,6 +39,7 @@ private Q_SLOTS:
void onHddFileEdit();
void onHddSizeSlide(int i);
void onHddSizeAccessorSpin();
void onHddLBA48Changed(int state);
void onHddCreateClicked();
public:
+14
View File
@@ -365,6 +365,20 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="hddLBA48Label">
<property name="text">
<string>48-bit LBA:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="hddLBA48">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+21 -2
View File
@@ -17,12 +17,12 @@ GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* dialog, QWidget
: m_dialog(dialog)
{
m_ui.setupUi(this);
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {300, 100, -1});
reloadList();
SettingsInterface* sif = m_dialog->getSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCheats, "EmuCore", "EnableCheats", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.allCRCsCheckbox, "EmuCore", "ShowCheatsForAllCRCs", false);
updateListEnabled();
connect(m_ui.enableCheats, &QCheckBox::stateChanged, this, &GameCheatSettingsWidget::updateListEnabled);
@@ -31,6 +31,10 @@ GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* dialog, QWidget
connect(m_ui.reloadCheats, &QPushButton::clicked, this, &GameCheatSettingsWidget::onReloadClicked);
connect(m_ui.enableAll, &QPushButton::clicked, this, [this]() { setStateForAll(true); });
connect(m_ui.disableAll, &QPushButton::clicked, this, [this]() { setStateForAll(false); });
connect(m_ui.allCRCsCheckbox, &QCheckBox::stateChanged, this, &GameCheatSettingsWidget::onReloadClicked);
dialog->registerWidgetHelp(m_ui.allCRCsCheckbox, tr("Show Cheats For All CRCs"), tr("Checked"),
tr("Toggles scanning patch files for all CRCs of the game. With this enabled available patches for the game serial with different CRCs will also be loaded."));
}
GameCheatSettingsWidget::~GameCheatSettingsWidget() = default;
@@ -78,6 +82,20 @@ void GameCheatSettingsWidget::updateListEnabled()
m_ui.enableAll->setEnabled(cheats_enabled);
m_ui.disableAll->setEnabled(cheats_enabled);
m_ui.reloadCheats->setEnabled(cheats_enabled);
m_ui.allCRCsCheckbox->setEnabled(cheats_enabled);
}
void GameCheatSettingsWidget::disableAllCheats()
{
SettingsInterface* si = m_dialog->getSettingsInterface();
si->ClearSection(Patch::CHEATS_CONFIG_SECTION);
si->Save();
}
void GameCheatSettingsWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {320, 100, -1});
}
void GameCheatSettingsWidget::setCheatEnabled(std::string name, bool enabled, bool save_and_reload_settings)
@@ -138,7 +156,8 @@ void GameCheatSettingsWidget::setStateRecursively(QTreeWidgetItem* parent, bool
void GameCheatSettingsWidget::reloadList()
{
u32 num_unlabelled_codes = 0;
m_patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), true, &num_unlabelled_codes);
bool showAllCRCS = m_ui.allCRCsCheckbox->isChecked();
m_patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), true, showAllCRCS, & num_unlabelled_codes);
m_enabled_patches =
m_dialog->getSettingsInterface()->GetStringList(Patch::CHEATS_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY);
+6 -1
View File
@@ -30,11 +30,17 @@ public:
GameCheatSettingsWidget(SettingsWindow* dialog, QWidget* parent);
~GameCheatSettingsWidget();
void disableAllCheats();
protected:
void resizeEvent(QResizeEvent* event) override;
private Q_SLOTS:
void onCheatListItemDoubleClicked(QTreeWidgetItem* item, int column);
void onCheatListItemChanged(QTreeWidgetItem* item, int column);
void onReloadClicked();
void updateListEnabled();
void reloadList();
private:
QTreeWidgetItem* getTreeWidgetParent(const std::string_view& parent);
@@ -42,7 +48,6 @@ private:
void setCheatEnabled(std::string name, bool enabled, bool save_and_reload_settings);
void setStateForAll(bool enabled);
void setStateRecursively(QTreeWidgetItem* parent, bool enabled);
void reloadList();
Ui::GameCheatSettingsWidget m_ui;
SettingsWindow* m_dialog;
@@ -84,6 +84,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="allCRCsCheckbox">
<property name="text">
<string>All CRCs</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@@ -18,7 +18,6 @@ GameFixSettingsWidget::GameFixSettingsWidget(SettingsWindow* dialog, QWidget* pa
m_ui.setupUi(this);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.FpuMulHack, "EmuCore/Gamefixes", "FpuMulHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.FpuNegDivHack, "EmuCore/Gamefixes", "FpuNegDivHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.GoemonTlbHack, "EmuCore/Gamefixes", "GoemonTlbHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.SoftwareRendererFMVHack, "EmuCore/Gamefixes", "SoftwareRendererFMVHack", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.SkipMPEGHack, "EmuCore/Gamefixes", "SkipMPEGHack", false);
@@ -38,7 +37,6 @@ GameFixSettingsWidget::GameFixSettingsWidget(SettingsWindow* dialog, QWidget* pa
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.BlitInternalFPSHack, "EmuCore/Gamefixes", "BlitInternalFPSHack", false);
dialog->registerWidgetHelp(m_ui.FpuMulHack, tr("FPU Multiply Hack"), tr("Unchecked"), tr("For Tales of Destiny."));
dialog->registerWidgetHelp(m_ui.FpuNegDivHack, tr("FPU Negative Divide Hack"), tr("Unchecked"), tr("For Gundam Games."));
dialog->registerWidgetHelp(m_ui.GoemonTlbHack, tr("Preload TLB Hack"), tr("Unchecked"), tr("To avoid TLB miss on Goemon."));
dialog->registerWidgetHelp(m_ui.SoftwareRendererFMVHack, tr("Use Software Renderer For FMVs"), tr("Unchecked"), tr("Needed for some games with complex FMV rendering."));
dialog->registerWidgetHelp(m_ui.SkipMPEGHack, tr("Skip MPEG Hack"), tr("Unchecked"), tr("Skips videos/FMVs in games to avoid game hanging/freezes."));
@@ -56,13 +56,6 @@
<string>Game Fixes (NOT recommended to change globally)</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="FpuNegDivHack">
<property name="text">
<string extracomment="FPU = Floating Point Unit. A part of the PS2's CPU. Do not translate.\nNegative Divide: mathematical term.\nGundam: a multimedia franchise name. Leave as-is or use an official translation.">FPU Negative Divide Hack</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="FpuMulHack">
<property name="text">
+20 -3
View File
@@ -5,6 +5,7 @@
#include "QtHost.h"
#include "QtUtils.h"
#include "Settings/GamePatchSettingsWidget.h"
#include "SettingWidgetBinder.h"
#include "Settings/SettingsWindow.h"
#include "pcsx2/GameList.h"
@@ -56,7 +57,14 @@ GamePatchSettingsWidget::GamePatchSettingsWidget(SettingsWindow* dialog, QWidget
setUnlabeledPatchesWarningVisibility(false);
SettingsInterface* sif = m_dialog->getSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.allCRCsCheckbox, "EmuCore", "ShowPatchesForAllCRCs", false);
connect(m_ui.reload, &QPushButton::clicked, this, &GamePatchSettingsWidget::onReloadClicked);
connect(m_ui.allCRCsCheckbox, &QCheckBox::stateChanged, this, &GamePatchSettingsWidget::reloadList);
dialog->registerWidgetHelp(m_ui.allCRCsCheckbox, tr("Show Patches For All CRCs"), tr("Checked"),
tr("Toggles scanning patch files for all CRCs of the game. With this enabled available patches for the game serial with different CRCs will also be loaded."));
reloadList();
}
@@ -71,11 +79,19 @@ void GamePatchSettingsWidget::onReloadClicked()
g_emu_thread->reloadPatches();
}
void GamePatchSettingsWidget::disableAllPatches()
{
SettingsInterface* si = m_dialog->getSettingsInterface();
si->ClearSection(Patch::PATCHES_CONFIG_SECTION);
si->Save();
}
void GamePatchSettingsWidget::reloadList()
{
// Patches shouldn't have any unlabelled patch groups, because they're new.
u32 number_of_unlabeled_patches = 0;
std::vector<Patch::PatchInfo> patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), false, &number_of_unlabeled_patches);
bool showAllCRCS = m_ui.allCRCsCheckbox->isChecked();
std::vector<Patch::PatchInfo> patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), false, showAllCRCS, &number_of_unlabeled_patches);
std::vector<std::string> enabled_list =
m_dialog->getSettingsInterface()->GetStringList(Patch::PATCHES_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY);
@@ -121,6 +137,7 @@ void GamePatchSettingsWidget::reloadList()
m_ui.scrollArea->setWidget(container);
}
void GamePatchSettingsWidget::setUnlabeledPatchesWarningVisibility(bool visible) {
void GamePatchSettingsWidget::setUnlabeledPatchesWarningVisibility(bool visible)
{
m_ui.unlabeledPatchWarning->setVisible(visible);
}
}
@@ -41,6 +41,7 @@ class GamePatchSettingsWidget : public QWidget
public:
GamePatchSettingsWidget(SettingsWindow* dialog, QWidget* parent);
void disableAllPatches();
~GamePatchSettingsWidget();
private Q_SLOTS:
@@ -52,6 +52,19 @@
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="allCRCsCheckbox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>All CRCs</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
+29 -9
View File
@@ -300,6 +300,26 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
}
#endif
// Get rid of widescreen/no-interlace checkboxes from per-game settings, unless the user previously had them set.
if (m_dialog->isPerGameSettings())
{
if ((m_dialog->containsSettingValue("EmuCore", "EnableWideScreenPatches") || m_dialog->containsSettingValue("EmuCore", "EnableNoInterlacingPatches")) &&
QMessageBox::question(QtUtils::GetRootWidget(this), tr("Remove Unsupported Settings"),
tr("You currently have the <strong>Enable Widescreen Patches</strong> or <strong>Enable No-Interlacing Patches</strong> options enabled for this game.<br><br>"
"We no longer support these options, instead <strong>you should select the \"Patches\" section, and explicitly enable the patches you want.</strong><br><br>"
"Do you want to remove these options from your game configuration now?"),
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
{
m_dialog->removeSettingValue("EmuCore", "EnableWideScreenPatches");
m_dialog->removeSettingValue("EmuCore", "EnableNoInterlacingPatches");
}
m_ui.gridLayout->removeWidget(m_ui.widescreenPatches);
m_ui.gridLayout->removeWidget(m_ui.noInterlacingPatches);
safe_delete(m_ui.widescreenPatches);
safe_delete(m_ui.noInterlacingPatches);
}
// Hide advanced options by default.
if (!QtHost::ShouldShowAdvancedSettings())
{
@@ -413,7 +433,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
tr("Changes the aspect ratio used to display the console's output to the screen. The default is Auto Standard (4:3/3:2 "
"Progressive) which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era."));
dialog->registerWidgetHelp(m_ui.interlacing, tr("Deinterlacing"), tr("Automatic (Default)"), tr("Determines the deinterlacing method to be used on the interlaced screen of the emulated console. Automatic should be able to correctly deinterlace most games, but if you see visibly shaky graphics, try choosing one of the available option."));
dialog->registerWidgetHelp(m_ui.interlacing, tr("Deinterlacing"), tr("Automatic (Default)"), tr("Determines the deinterlacing method to be used on the interlaced screen of the emulated console. Automatic should be able to correctly deinterlace most games, but if you see visibly shaky graphics, try one of the available options."));
dialog->registerWidgetHelp(m_ui.screenshotSize, tr("Screenshot Size"), tr("Screen Resolution"),
tr("Determines the resolution at which screenshots will be saved. Internal resolutions preserve more detail at the cost of "
@@ -523,7 +543,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
tr("Tries to detect when a game is drawing its own color palette and then renders it in software, instead of on the GPU."));
dialog->registerWidgetHelp(m_ui.gpuTargetCLUTMode, tr("GPU Target CLUT"), tr("Disabled"),
tr("Tries to detect when a game is drawing its own color palette and then renders it on the GPU with special handling"));
tr("Try to detect when a game is drawing its own color palette and then renders it on the GPU with special handling."));
dialog->registerWidgetHelp(m_ui.skipDrawStart, tr("Skipdraw Range Start"), tr("0"),
tr("Completely skips drawing surfaces from the surface in the left box up to the surface specified in the box on the right."));
@@ -694,7 +714,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
"<b>If unsure, leave it on default.<b>"));
dialog->registerWidgetHelp(m_ui.videoCaptureBitrate, tr("Video Bitrate"), tr("5000 Kbps"), tr("Determines the amount of bitrates to be used."
dialog->registerWidgetHelp(m_ui.videoCaptureBitrate, tr("Video Bitrate"), tr("6000 kbps"), tr("Sets the video bitrate to be used. "
"Larger bitrate generally yields better video quality at the cost of larger resulting file size."));
@@ -706,22 +726,22 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
dialog->registerWidgetHelp(m_ui.enableVideoCaptureArguments, tr("Enable Extra Video Arguments"), tr("Unchecked"), tr("Allows you to pass arguments to the selected video codec."));
dialog->registerWidgetHelp(m_ui.videoCaptureArguments, tr("Extra Video Arguments"), tr("Leave It Blank"),
tr("Parameters passed to selected video codec.<br> "
"<b>You must use '=' to separate key from value and ':' to separate two pairs from each other.<b><br> "
tr("Parameters passed to the selected video codec.<br>"
"<b>You must use '=' to separate key from value and ':' to separate two pairs from each other.</b><br>"
"For example: \"crf = 21 : preset = veryfast\""));
dialog->registerWidgetHelp(m_ui.audioCaptureCodec, tr("Audio Codec"), tr("Default"), tr("Selects which Audio Codec to be used for Video Capture. "
"<b>If unsure, leave it on default.<b>"));
dialog->registerWidgetHelp(m_ui.audioCaptureBitrate, tr("Audio Bitrate"), tr("160 Kbps"), tr("Determines the amount of audio bitrate to be used."));
dialog->registerWidgetHelp(m_ui.audioCaptureBitrate, tr("Audio Bitrate"), tr("160 kbps"), tr("Sets the audio bitrate to be used."));
dialog->registerWidgetHelp(m_ui.enableAudioCaptureArguments, tr("Enable Extra Audio Arguments"), tr("Unchecked"), tr("Allows you to pass arguments to the selected audio codec."));
dialog->registerWidgetHelp(m_ui.audioCaptureArguments, tr("Extra Audio Arguments"), tr("Leave It Blank"),
tr("Parameters passed to selected audio codec.<br> "
"<b>You must use '=' to separate key from value and ':' to separate two pairs from each other.<b><br> "
"<>For example: \"compression_level = 4 : joint_stereo = 1\""));
tr("Parameters passed to the selected audio codec.<br>"
"<b>You must use '=' to separate key from value and ':' to separate two pairs from each other.</b><br>"
"For example: \"compression_level = 4 : joint_stereo = 1\""));
}
// Advanced tab
+1 -1
View File
@@ -324,7 +324,7 @@
</layout>
</item>
<item row="8" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_5">
<layout class="QGridLayout" name="displayGridLayout">
<item row="3" column="0">
<widget class="QCheckBox" name="PCRTCOffsets">
<property name="text">
@@ -259,7 +259,7 @@ void MemoryCardConvertDialog::ConvertCard()
while (m_srcCardInfo.type == MemoryCardType::File ? FileSystem::DirectoryExists(Path::Combine(EmuFolders::MemoryCards, destName.toStdString()).c_str()) : FileSystem::FileExists(Path::Combine(EmuFolders::MemoryCards, destName.toStdString()).c_str()))
{
destName = baseName;
destName.append(StringUtil::StdStringFromFormat("_%02d.ps2", ++num).c_str());
destName.append(StringUtil::StdStringFromFormat("_%02zd.ps2", ++num).c_str());
}
m_destCardName = destName;
+11 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "common/FileSystem.h"
@@ -36,7 +36,13 @@ MemoryCardCreateDialog::MemoryCardCreateDialog(QWidget* parent /* = nullptr */)
connect(m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &MemoryCardCreateDialog::restoreDefaults);
#ifndef _WIN32
m_ui.ntfsCompression->setEnabled(false);
m_ui.ntfsCompressionLayout->removeWidget(m_ui.ntfsCompression);
safe_delete(m_ui.ntfsCompression);
m_ui.ntfsCompressionLayout->removeWidget(m_ui.ntfsCompressionLabel);
safe_delete(m_ui.ntfsCompressionLabel);
m_ui.mainLayout->removeItem(m_ui.ntfsCompressionLayout);
safe_delete(m_ui.ntfsCompressionLayout);
resize(600, 480);
#endif
updateState();
@@ -117,10 +123,10 @@ void MemoryCardCreateDialog::createCard()
}
#ifdef _WIN32
if (m_ui.ntfsCompression->isChecked() && m_type == MemoryCardType::File)
if (m_type == MemoryCardType::File)
{
const std::string fullPath(Path::Combine(EmuFolders::MemoryCards, name_str));
FileSystem::SetPathCompression(fullPath.c_str(), true);
const std::string fullPath = Path::Combine(EmuFolders::MemoryCards, name_str);
FileSystem::SetPathCompression(fullPath.c_str(), m_ui.ntfsCompression->isChecked());
}
#endif
+9 -9
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>593</width>
<height>545</height>
<width>600</width>
<height>535</height>
</rect>
</property>
<property name="windowTitle">
@@ -16,13 +16,13 @@
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<layout class="QVBoxLayout" name="mainLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<property name="bottomMargin">
<property name="spacing">
<number>10</number>
</property>
<property name="spacing">
<property name="bottomMargin">
<number>10</number>
</property>
<item>
@@ -39,8 +39,8 @@
<height>48</height>
</size>
</property>
<property name="pixmap">
<pixmap resource="resources/resources.qrc">:/icons/black/svg/memcard-line.svg</pixmap>
<property name="pixmap">
<pixmap resource="../resources/resources.qrc">:/icons/black/svg/memcard-line.svg</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
@@ -227,7 +227,7 @@
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_8">
<layout class="QVBoxLayout" name="ntfsCompressionLayout">
<item>
<widget class="QCheckBox" name="ntfsCompression">
<property name="text">
@@ -239,7 +239,7 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<widget class="QLabel" name="ntfsCompressionLabel">
<property name="text">
<string>NTFS compression is built-in, fast, and completely reliable. Typically compresses Memory Cards (highly recommended).</string>
</property>
+7 -19
View File
@@ -54,7 +54,6 @@ MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsWindow* dialog, QWidg
connect(m_ui.refreshCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::refresh);
connect(m_ui.createCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::createCard);
connect(m_ui.duplicateCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::duplicateCard);
connect(m_ui.renameCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::renameCard);
connect(m_ui.convertCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::convertCard);
connect(m_ui.deleteCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::deleteCard);
@@ -89,22 +88,22 @@ void MemoryCardSettingsWidget::setupAdditionalUi()
createSlotWidgets(&m_slots[i], i);
// button to swap Memory Cards
QToolButton* swap_button = new QToolButton(m_ui.portGroupBox);
QToolButton* swap_button = new QToolButton(m_ui.slotGroupBox);
swap_button->setIcon(QIcon::fromTheme("arrow-left-right-line"));
swap_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
swap_button->setToolTip(tr("Swap Memory Cards"));
connect(swap_button, &QToolButton::clicked, this, &MemoryCardSettingsWidget::swapCards);
static_cast<QGridLayout*>(m_ui.portGroupBox->layout())->addWidget(swap_button, 0, 1);
static_cast<QGridLayout*>(m_ui.slotGroupBox->layout())->addWidget(swap_button, 0, 1);
}
void MemoryCardSettingsWidget::createSlotWidgets(SlotGroup* port, u32 slot)
{
const bool perGame = m_dialog->isPerGameSettings();
port->root = new QWidget(m_ui.portGroupBox);
port->root = new QWidget(m_ui.slotGroupBox);
SettingsInterface* sif = m_dialog->getSettingsInterface();
port->enable = new QCheckBox(tr("Port %1").arg(slot + 1), port->root);
port->enable = new QCheckBox(tr("Slot %1").arg(slot + 1), port->root);
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, port->enable, CONFIG_SECTION, StringUtil::StdStringFromFormat("Slot%u_Enable", slot + 1), true);
connect(port->enable, &QCheckBox::stateChanged, this, &MemoryCardSettingsWidget::refresh);
@@ -129,7 +128,7 @@ void MemoryCardSettingsWidget::createSlotWidgets(SlotGroup* port, u32 slot)
vert_layout->addWidget(port->enable, 0);
vert_layout->addLayout(bottom_layout, 1);
static_cast<QGridLayout*>(m_ui.portGroupBox->layout())->addWidget(port->root, 0, (slot != 0) ? 2 : 0);
static_cast<QGridLayout*>(m_ui.slotGroupBox->layout())->addWidget(port->root, 0, (slot != 0) ? 2 : 0);
}
void MemoryCardSettingsWidget::autoSizeUI()
@@ -193,20 +192,10 @@ void MemoryCardSettingsWidget::updateCardActions()
bool isPS1 = (cardInfo.has_value() ? cardInfo.value().file_type == MemoryCardFileType::PS1 : false);
m_ui.convertCard->setEnabled(hasSelection && !isPS1);
m_ui.duplicateCard->setEnabled(hasSelection);
m_ui.renameCard->setEnabled(hasSelection);
m_ui.deleteCard->setEnabled(hasSelection);
}
void MemoryCardSettingsWidget::duplicateCard()
{
const QString selectedCard(getSelectedCard());
if (selectedCard.isEmpty())
return;
QMessageBox::critical(this, tr("Error"), tr("Not yet implemented."));
}
void MemoryCardSettingsWidget::deleteCard()
{
const QString selectedCard(getSelectedCard());
@@ -289,12 +278,11 @@ void MemoryCardSettingsWidget::listContextMenuRequested(const QPoint& pos)
{
for (u32 slot = 0; slot < MAX_SLOTS; slot++)
{
connect(menu.addAction(tr("Use for Port %1").arg(slot + 1)), &QAction::triggered, this,
connect(menu.addAction(tr("Use for Slot %1").arg(slot + 1)), &QAction::triggered, this,
[this, &selectedCard, slot]() { tryInsertCard(slot, selectedCard); });
}
menu.addSeparator();
connect(menu.addAction(tr("Duplicate")), &QAction::triggered, this, &MemoryCardSettingsWidget::duplicateCard);
connect(menu.addAction(tr("Rename")), &QAction::triggered, this, &MemoryCardSettingsWidget::renameCard);
connect(menu.addAction(tr("Convert")), &QAction::triggered, this, &MemoryCardSettingsWidget::convertCard);
connect(menu.addAction(tr("Delete")), &QAction::triggered, this, &MemoryCardSettingsWidget::deleteCard);
@@ -336,7 +324,7 @@ void MemoryCardSettingsWidget::swapCards()
if (!card1Name.has_value() || card1Name->empty() || !card2Name.has_value() || card2Name->empty())
{
QMessageBox::critical(
QtUtils::GetRootWidget(this), tr("Error"), tr("Both ports must have a card selected to swap."));
QtUtils::GetRootWidget(this), tr("Error"), tr("Both slots must have a card selected to swap."));
return;
}

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