Compare commits

...
33 Commits
Author SHA1 Message Date
Stenzek 460a2dbbd3 Qt: Add custom binding widget for GunCon2 2023-07-26 13:20:44 +10:00
Stenzek 228f4f1010 USB-GunCon2: Support binding to controller 2023-07-26 13:20:44 +10:00
Stenzek 2703b91e41 USB: Add ConfigKeyExists() 2023-07-26 13:20:44 +10:00
Stenzek 24171787f8 ImGuiManager: Add software cursor support
Can be used to render a crosshair for GunCon.
2023-07-26 13:20:44 +10:00
PCSX2 BotandConnor McLaughlin 49d3338d4a Qt: Update Base Translation 2023-07-26 13:19:37 +10:00
Stenzek a9d693e1c3 GS/HW: Improve readback-avoiding CLUT heuristics
Use target overlap instead of exact match.
2023-07-26 13:18:04 +10:00
Stenzek 4ef69248d0 GSRunner: Add readbacks to stats 2023-07-26 13:18:04 +10:00
Stenzek 247b3ed740 GS/HW: HLE the Burnout games bloom effect 2023-07-26 13:18:04 +10:00
Stenzekandrefractionpcsx2 1edca6235c Sio: Migrate to StateWrapper
Also fixes the crash due to serializing pointers.

[SAVEVERSION+] and actually bump the save version.
2023-07-26 01:22:11 +01:00
Connor McLaughlinandGitHub ab5c03b1d9 Pad: Fixes and cleanup (#9623)
* Pad: Get rid of redundant object lookup

* Pad: Serialize 'stage' state

[SAVEVERSION+]

* Pad: Reduce Freeze duplication and add markers

Catch any possible unhandled device type change.

[SAVEVERSION+]

* Pad: Localize controller type names

* Pad: Handle mismatch between state and current config

* Misc: Drop .at() from vector/array access

We don't use exceptions.

* Pad: Remove redundant GetUnifiedSlot()

Sio has routines for converting these.

* Sio2: Remove redundant optional in Pad()

* Pad: Constify and finalize controller classes

* Pad: Move PadManager to Pad namespace

No point having a class when there's only a single instance.

* Pad: Move PadConfig to Pad namespace

* Pad: Move PadMacros to Pad namespace

* Pad: Re-localize controller bindings/settings

* Pad: Make controller info local

Don't want to create duplicates.

* Pad: Use span for ControllerInfo settings/bindings

* Pad: Fix auto-toggled macro buttons

* Pad: Fix pressure for macros

* Pad: Merge PadConfig/PadManager/PadMacros

Faster compile time.

* Pad: Fix incorrect condition in Sio0::SetTxData()

* Pad: Add deadzone for macro triggers
2023-07-26 01:10:02 +10:00
TheLastRarandrefractionpcsx2 d4cb35469d DEV9: Perform a null check on ifa_addr when searching adapters 2023-07-25 13:23:01 +01:00
Stenzek fd1b1d0c24 GS/HW: Improve Basic blending
- Don't assume no overlap = free. Reduces a number of barriers.
 - Prefer SW blend for Ad blends, except when it's constant. It's
   impossible to emulate accurately in HW, because 128 is 1, not 255.
 - Prefer SW blend for AFIX > 128 on no-overlap sprites.
2023-07-25 13:04:45 +10:00
Stenzek bf96ceeacc GS/HW: Assume primitive does not overlap if it is a single quad
Enables one-barrier software blending for fullscreen quads.
We can also use tex-is-fb safely in these scenarios too.

Fixes Persona 4 menu background, Hard Hitter Tennis shadows at
Basic blending.
2023-07-25 13:04:45 +10:00
RedPanda4552andrefractionpcsx2 2c08b385e5 Pad: Revert back to old Constant 1 behavior
Fixes pad detection in NBA 2K7, possibly others.
2023-07-25 00:27:53 +01:00
JordanTheToasterandrefractionpcsx2 051d13ec7a GameDB: Various fixes
Adds missing fixes and game entry's for guitar hero and re fixes LOTR Third Age.
2023-07-24 23:18:30 +01:00
refractionpcsx2 a2a9643123 GameDB: Move Haunting Ground over to HPO Special + Round Sprite Half 2023-07-24 21:44:24 +01:00
refractionpcsx2 1286bfe75b UI/UX: Added popup when updating if savestate version changes 2023-07-24 19:42:36 +01:00
RedPanda4552andrefractionpcsx2 1f74f82796 [SAVEVERSION+] Reimplement PAD system
Force pushed because git hates me

[SAVEVERSION+] Bump savestate version

CI: Update locations of pad/memcard sources

Discard leftover old PAD code

Fix additional merge oddities

Add translations for OSD messages

Copyright headers

Version bump

Fix a whole boatload of concurrency problems from file moves and other miscellaneous update problems

Partial redo of PS1 pad support

Fix incorrect mode due to analog behavior at startup

Mostly reimplement SIO0 memory card logic

Still needs pocketstation

Use new runtime wrapped translate function

Dead code

Fix multiple port/slot/presence issues for PS1

Save State version bump

Clean up some duplicate/unused headers

More header consistency

Remove old stray files

Fix incorrect return

Fix uninitialized array

Add missing overrides

Switch to init/close model used by other subsystems

Remove old input recording references

Rename SIO globals

Rename SIO2 FIFO globals

Remove commented SIO0 code for illegal write

Add guitar icon
2023-07-24 16:41:17 +01:00
Stenzek 876b576679 CI: Pin flat-manager-client version 2023-07-24 21:26:41 +10:00
Stenzek c927e4785b GS/HW: Get rid of 2 frame forced preload 2023-07-24 18:31:07 +10:00
refractionpcsx2 97672828e8 GameDB: Add patch for Shadow Hearts NTSC-U bad clip arrangement 2023-07-24 09:01:44 +01:00
refractionpcsx2 b96978494b GS/HW: Fix Double Half Clear bug + better detect double buffer changes 2023-07-23 19:40:14 +01:00
Stenzekandrefractionpcsx2 2f105cbe15 VMManager: Display title as serial or ELF 2023-07-23 19:09:41 +01:00
Stenzekandrefractionpcsx2 2539a07b7d Qt: Add dump verification to game properties 2023-07-23 19:09:31 +01:00
Stenzekandrefractionpcsx2 73f903f402 GameDatabase: Add hash database parse/lookup 2023-07-23 19:09:31 +01:00
Stenzekandrefractionpcsx2 94c48a4c2d CDVD: Add IsoHasher 2023-07-23 19:09:31 +01:00
Stenzekandrefractionpcsx2 eb8d938c94 Resources: Add redump database 2023-07-23 19:09:31 +01:00
CRASHARKIandrefractionpcsx2 92b8316182 GameDB: Add nativePaletteDraw and halfPixelOffset to Crash Tag Team Racing 2023-07-23 19:09:15 +01:00
Stenzekandrefractionpcsx2 610cb2626b GS/HW: Use GetUnwrappedEndBlockAddress() where appropriate 2023-07-23 19:09:01 +01:00
Stenzekandrefractionpcsx2 2033f813da GS/HW: Fix incorrect usage of bit_ceil() 2023-07-23 19:09:01 +01:00
pgertandrefractionpcsx2 a6fbd0fe69 GameDB: correct an entry 2023-07-23 19:00:55 +01:00
Stenzek abadb7f80f GSRunner: Fix Unicode command line on Win32 2023-07-24 00:55:54 +10:00
JordanTheToasterandrefractionpcsx2 1c6be3f400 GameDB: Various fixes
Fixes for Link being a doughnut and missing a line and for crusty field textures in all NCAA Football games TW PGA golf games and Madden games.
2023-07-23 13:28:40 +01:00
111 changed files with 75789 additions and 4350 deletions
+4 -4
View File
@@ -115,11 +115,11 @@
- 'pcsx2/IPU/*'
- 'pcsx2/IPU/**/*'
'Memory Card':
- 'pcsx2/gui/MemoryCard*'
- 'pcsx2/gui/**/MemoryCard*'
- 'pcsx2/SIO/Memcard/*'
- 'pcsx2/SIO/Memcard/**/*'
'PAD':
- 'pcsx2/PAD/*'
- 'pcsx2/PAD/**/*'
- 'pcsx2/SIO/Pad/*'
- 'pcsx2/SIO/Pad/**/*'
'SPU2':
- 'pcsx2/SPU2/*'
- 'pcsx2/SPU2/**/*'
@@ -49,7 +49,7 @@ retry_command sudo flatpak -y install "${FLATPAK_PACKAGES[@]}"
echo "Downloading flat-manager-client"
mkdir -p "$FLAT_MANAGER_CLIENT_DIR"
pushd "$FLAT_MANAGER_CLIENT_DIR"
aria2c -Z "https://raw.githubusercontent.com/flatpak/flat-manager/master/flat-manager-client"
aria2c -Z "https://raw.githubusercontent.com/flatpak/flat-manager/9401efbdc0d6bd489507d8401c567ba219d735d5/flat-manager-client"
chmod +x flat-manager-client
echo "$FLAT_MANAGER_CLIENT_DIR" >> $GITHUB_PATH
popd
+288 -13
View File
@@ -9583,9 +9583,9 @@ SLAJ-25049:
region: "NTSC-Unk"
SLAJ-25051:
name: "Lord of the Rings, The - The Third Age"
region: "NTSC-Unk"
region: "NTSC-Ch"
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting in cutscenes.
halfPixelOffset: 2 # Fixes ghosting.
SLAJ-25052:
name: "Urbz, The - Sims in the City"
region: "NTSC-Unk"
@@ -10245,6 +10245,11 @@ SLED-54401:
mergeSprite: 1 # Fixes misaligned bloom.
halfPixelOffset: 2 # Fixes misaligned bloom.
autoFlush: 2 # Fixes sun occlusion and brightness.
SLED-54509:
name: "Guitar Hero II [Demo]"
region: "PAL-M5"
roundModes:
vu1RoundMode: 0 # Massages the Z on the score meter for hardware mode, software doesn't really need this.
SLES-50003:
name: "Swap Magic DVD Disc v2.0"
region: "PAL-Unk"
@@ -10305,6 +10310,9 @@ SLES-50021:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-50022:
name: "NBA Live 2001"
region: "PAL-E"
@@ -10573,9 +10581,15 @@ SLES-50115:
SLES-50118:
name: "Tiger Woods PGA Tour 2001"
region: "PAL-E"
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-50119:
name: "Tiger Woods PGA Tour 2001"
region: "PAL-A"
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-50120:
name: "Rumble Racing"
region: "PAL-M3"
@@ -11268,6 +11282,9 @@ SLES-50422:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-50423:
name: "F1 2001"
region: "PAL-M4"
@@ -11606,6 +11623,7 @@ SLES-50579:
patch=0,EE,00171190,word,48438800
patch=0,EE,00171194,word,4BE521AC
patch=0,EE,00171198,word,30848000
patch=0,EE,0017119c,word,4BE72B3C
SLES-50580:
name: "Kessen II"
region: "PAL-G"
@@ -11887,11 +11905,17 @@ SLES-50728:
region: "PAL-M3"
gameFixes:
- VUSyncHack # Fixes the inverted legs.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-50729:
name: "Tiger Woods USA Tour 2002"
region: "PAL-A"
gameFixes:
- VUSyncHack # Fixes the inverted legs.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-50730:
name: "VIP"
region: "PAL-M5"
@@ -12928,6 +12952,9 @@ SLES-51154:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-51156:
name: "Silent Hill 2 - Director's Cut"
region: "PAL-M5"
@@ -13316,6 +13343,9 @@ SLES-51275:
SLES-51282:
name: "Tiger Woods PGA Tour 2003"
region: "PAL-E"
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-51283:
name: "WWE SmackDown! Shut Your Mouth"
region: "PAL-E"
@@ -14305,6 +14335,9 @@ SLES-51797:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-51798:
name: "NHL 2004"
region: "PAL-M5"
@@ -14596,6 +14629,9 @@ SLES-51887:
region: "PAL-E"
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-51888:
name: "RTL Ski Jumping 2004"
region: "PAL-E-G"
@@ -15889,6 +15925,9 @@ SLES-52509:
region: "PAL-M4"
clampModes:
vuClampMode: 2 # Fix black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-52510:
name: "Neo Contra"
region: "PAL-M3"
@@ -16104,6 +16143,9 @@ SLES-52581:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-52584:
name: "Burnout 3 - Takedown"
region: "PAL-M4"
@@ -16666,25 +16708,27 @@ SLES-52801:
name: "Lord of the Rings, The - The Third Age"
region: "PAL-M5"
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting in cutscenes.
halfPixelOffset: 2 # Fixes ghosting.
SLES-52802:
name: "Seigneur des anneaux, Le - Le Tiers Âge"
region: "PAL-F"
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting in cutscenes.
halfPixelOffset: 2 # Fixes ghosting.
SLES-52803:
name: "Herr der Ringe, Der - Das dritte Zeitalter"
region: "PAL-G"
gsHWFixes:
halfPixelOffset: 2 # Fixes ghosting.
SLES-52804:
name: "Signore degli Anelli, Il - La Terza Era"
region: "PAL-I"
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting in cutscenes.
halfPixelOffset: 2 # Fixes ghosting.
SLES-52805:
name: "Señor de Los Anillos, El - La Tercera Edad"
region: "PAL-S"
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting in cutscenes.
halfPixelOffset: 2 # Fixes ghosting.
SLES-52807:
name: "Lemony Snicket's A Series of Unfortunate Events"
region: "PAL-E"
@@ -16888,7 +16932,8 @@ SLES-52877:
region: "PAL-M5"
compat: 5
gsHWFixes:
halfPixelOffset: 1 # Fixes blurriness.
halfPixelOffset: 2 # Reduces blurriness. Normal Vertex works better, but causes some lights to disappear.
roundSprite: 1 # Further reduces blurriness.
beforeDraw: "OI_HauntingGround" # Fix bloom.
SLES-52882:
name: "Stolen"
@@ -18331,6 +18376,8 @@ SLES-53439:
- IbitHack # Fixes constant recompilation problems.
gsHWFixes:
PCRTCOverscan: 1 # Fixes offscreen image.
nativePaletteDraw: 1 # Fixes the look of screen effects.
halfPixelOffset: 2 # Corrects the alignment of screen effects.
SLES-53441:
name: "Heroes of the Pacific"
region: "PAL-M5"
@@ -18547,6 +18594,9 @@ SLES-53510:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-53518:
name: "Castle Shikigami II"
region: "PAL-E"
@@ -18725,6 +18775,9 @@ SLES-53541:
region: "PAL-M4"
clampModes:
vuClampMode: 3 # Fix black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-53542:
name: "Shadow the Hedgehog"
region: "PAL-M5"
@@ -20684,6 +20737,9 @@ SLES-54248:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-54250:
name: "NBA Live '07"
region: "PAL-F"
@@ -20698,6 +20754,9 @@ SLES-54253:
region: "PAL-E"
clampModes:
vuClampMode: 2 # Fix black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-54254:
name: "Evolution GT"
region: "PAL-PL"
@@ -22156,6 +22215,9 @@ SLES-54812:
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-54813:
name: "NASCAR 08"
region: "PAL-A-E"
@@ -22287,6 +22349,9 @@ SLES-54867:
region: "PAL-E-F"
clampModes:
vuClampMode: 2 # Fix black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-54870:
name: "FIFA '08"
region: "PAL-E"
@@ -22590,6 +22655,8 @@ SLES-54962:
region: "PAL-E"
roundModes:
vu1RoundMode: 0 # Crashes without.
gsHWFixes:
textureInsideRT: 1 # Crowd textures.
SLES-54963:
name: "Tony Hawk's Proving Ground"
region: "PAL-E"
@@ -22621,6 +22688,8 @@ SLES-54974:
region: "PAL-M4"
roundModes:
vu1RoundMode: 0 # Crashes without.
gsHWFixes:
textureInsideRT: 1 # Crowd textures.
SLES-54975:
name: "George Of The Jungle"
region: "PAL-E"
@@ -23260,6 +23329,11 @@ SLES-55216:
SLES-55218:
name: "Tiger Woods PGA Tour 09"
region: "PAL-E"
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-55219:
name: "Hasbro Family Game Night"
region: "PAL-M4"
@@ -23312,6 +23386,11 @@ SLES-55237:
SLES-55238:
name: "Madden NFL 09"
region: "PAL-E"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-55239:
name: "Hasbro Family Game Night"
region: "PAL-I"
@@ -23956,6 +24035,11 @@ SLES-55526:
SLES-55528:
name: "Tiger Woods PGA Tour 10"
region: "PAL-A"
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLES-55529:
name: "Germany's Next Topmodel"
region: "PAL-G"
@@ -24102,6 +24186,7 @@ SLES-55605:
- SoftwareRendererFMVHack # Fixes vertical lines in FMVs.
gsHWFixes:
halfPixelOffset: 1 # Corrects post processing position.
cpuCLUTRender: 1 # Reduces the bloomy blur of characters.
clampModes:
vu1ClampMode: 3 # Fixes SPS on characters.
SLES-55609:
@@ -24928,6 +25013,22 @@ SLKA-25093:
halfPixelOffset: 2 # Fixes ghosting.
roundSprite: 2 # Fixes various lines / reduces bars on right edge.
disablePartialInvalidation: 1 # Fixes textureless graphics ingame.
SLKA-25095:
name: "Madden NFL 2004"
region: "NTSC-K"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLKA-25097:
name: "Tiger Woods PGA Tour 2004"
region: "NTSC-K"
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLKA-25098:
name: "Lord of the Rings, The - The Return of the King"
region: "NTSC-K"
@@ -25357,7 +25458,7 @@ SLKA-25237:
name: "Lord of the Rings, The - The Third Age"
region: "NTSC-K"
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting in cutscenes.
halfPixelOffset: 2 # Fixes ghosting.
SLKA-25241:
name: "Need for Speed - Underground 2"
region: "NTSC-K"
@@ -25845,6 +25946,13 @@ SLKA-25410:
SLKA-25413:
name: "SD Gundam G - Generation Spirits"
region: "NTSC-K"
SLKA-25414:
name: "Guitar Hero III - Legends of Rock"
region: "NTSC-K"
roundModes:
vu1RoundMode: 0 # Crashes without.
gsHWFixes:
textureInsideRT: 1 # Crowd textures.
SLKA-25422:
name: "Silent Hill - Origins"
region: "NTSC-K"
@@ -30850,7 +30958,7 @@ SLPM-65470:
name: "Firefighter F.D. 18"
region: "NTSC-J"
SLPM-65471:
name: "Firefighter F.D. 18"
name: "Need for Speed - Underground"
region: "NTSC-J"
SLPM-65472:
name: "Train Simulator & Densha de Go! Tokyo Kyuukouhen"
@@ -31984,6 +32092,9 @@ SLPM-65798:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPM-65799:
name: "New Jinsei Game"
region: "NTSC-J"
@@ -32159,6 +32270,8 @@ SLPM-65845:
SLPM-65846:
name: "Lord of the Rings, The - Uchitsu Kuni Daisanki"
region: "NTSC-J"
gsHWFixes:
halfPixelOffset: 2 # Fixes ghosting.
SLPM-65847:
name: "Soriaro no Fuukin Remix [First Print - Limited Edition]"
region: "NTSC-J"
@@ -33023,6 +33136,8 @@ SLPM-66090:
region: "NTSC-J"
gsHWFixes:
PCRTCOverscan: 1 # Fixes offscreen image.
nativePaletteDraw: 1 # Fixes the look of screen effects.
halfPixelOffset: 2 # Corrects the alignment of screen effects.
SLPM-66091:
name: "Shinobido Imashime"
region: "NTSC-J"
@@ -33464,6 +33579,9 @@ SLPM-66191:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPM-66192:
name: "Izumo 2"
region: "NTSC-J"
@@ -33499,6 +33617,9 @@ SLPM-66204:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPM-66205:
name: "Front Mission 5 - Scars of the War"
region: "NTSC-J"
@@ -35082,6 +35203,9 @@ SLPM-66600:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPM-66601:
name: "SSX On Tour [EA Best Hits]"
region: "NTSC-J"
@@ -35381,6 +35505,9 @@ SLPM-66674:
region: "NTSC-J"
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPM-66675:
name: "Kingdom Hearts II - Final Mix +"
region: "NTSC-J"
@@ -36009,6 +36136,9 @@ SLPM-66837:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPM-66838:
name: "Fantastic Fortune 2 - Triple Star [Best Hit Selection]"
region: "NTSC-J"
@@ -36801,6 +36931,7 @@ SLPM-68019:
- SoftwareRendererFMVHack # Fixes vertical lines in FMVs.
gsHWFixes:
halfPixelOffset: 1 # Corrects post processing position.
cpuCLUTRender: 1 # Reduces the bloomy blur of characters.
clampModes:
vu1ClampMode: 3 # Fixes SPS on characters.
SLPM-68503:
@@ -37479,6 +37610,9 @@ SLPS-20065:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPS-20066:
name: "Rhapsody 3 - Tenshi no Present - The Marl Kingdom Stories"
region: "NTSC-J"
@@ -37542,6 +37676,9 @@ SLPS-20091:
SLPS-20092:
name: "Tiger Woods PGA Tour 2001"
region: "NTSC-J"
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPS-20094:
name: "Shinseiki Evangelion - Typing Project-E [TVware Information Revolution Series]"
region: "NTSC-J"
@@ -38982,6 +39119,9 @@ SLPS-25079:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPS-25080:
name: "Uchuu Senkan Yamato - Iscandar he no Tsuioku [Special Edition]"
region: "NTSC-J"
@@ -39203,6 +39343,9 @@ SLPS-25142:
region: "NTSC-J"
gameFixes:
- VUSyncHack # Fixes the inverted legs.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPS-25143:
name: "dot hack - Mutation Part 2"
region: "NTSC-J"
@@ -39259,6 +39402,9 @@ SLPS-25157:
region: "NTSC-J"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLPS-25158:
name: "dot hack - Outbreak Part 3"
region: "NTSC-J"
@@ -41791,6 +41937,7 @@ SLPS-25837:
- SoftwareRendererFMVHack # Fixes vertical lines in FMVs.
gsHWFixes:
halfPixelOffset: 1 # Corrects post processing position.
cpuCLUTRender: 1 # Reduces the bloomy blur of characters.
clampModes:
vu1ClampMode: 3 # Fixes SPS on characters.
SLPS-25838:
@@ -41804,6 +41951,8 @@ SLPS-25840:
region: "NTSC-J"
roundModes:
vu1RoundMode: 0 # Crashes without.
gsHWFixes:
textureInsideRT: 1 # Crowd textures.
SLPS-25841:
name: "Tales of Destiny [Director's Cut] [Premium Box]"
region: "NTSC-J"
@@ -41976,10 +42125,12 @@ SLPS-25889:
roundModes:
vu1RoundMode: 0 # Fixes VU size spam and potential graphical issues with GH3 engine.
SLPS-25890:
name: "Guitar Hero III - Legends of Rock [with Guitar]"
name: "Guitar Hero III - Legends of Rock"
region: "NTSC-J"
roundModes:
vu1RoundMode: 0 # Crashes without.
gsHWFixes:
textureInsideRT: 1 # Crowd textures.
SLPS-25892:
name: "Nogizaka Haruka no Himitsu - Cosplay Hajimemashita"
region: "NTSC-J"
@@ -43196,6 +43347,9 @@ SLUS-20093:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20094:
name: "X Squad"
region: "NTSC-U"
@@ -43248,6 +43402,9 @@ SLUS-20104:
name: "Tiger Woods PGA Tour 2001"
region: "NTSC-U"
compat: 5
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20105:
name: "MDK 2 - Armageddon"
region: "NTSC-U"
@@ -43809,6 +43966,9 @@ SLUS-20241:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20242:
name: "Legends of Wrestling"
region: "NTSC-U"
@@ -43883,6 +44043,9 @@ SLUS-20263:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20264:
name: "F1 2001"
region: "NTSC-U"
@@ -44274,6 +44437,15 @@ SLUS-20347:
eeClampMode: 3 # Fixes invisible characters in various scenes.
gsHWFixes:
partialTargetInvalidation: 1 # Fixes black screens on judgement wheel.
patches:
default:
content: |-
author=refraction
// Fixes bad clip flag arrangement
patch=1,EE,00249e10,word,48509000
patch=1,EE,00249e14,word,4BDD69FF
patch=1,EE,00249e9c,word,48509000
patch=1,EE,00249ea0,word,4BDD69FF
SLUS-20348:
name: "Monopoly Party"
region: "NTSC-U"
@@ -44344,6 +44516,9 @@ SLUS-20364:
region: "NTSC-U"
gameFixes:
- VUSyncHack # Fixes the inverted legs.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20365:
name: "Pirates - Legend of Black Kat"
region: "NTSC-U"
@@ -45147,11 +45322,17 @@ SLUS-20529:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20530:
name: "NCAA Football 2003"
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20531:
name: "NHL 2003"
region: "NTSC-U"
@@ -45379,6 +45560,9 @@ SLUS-20572:
name: "Tiger Woods PGA Tour 2003"
region: "NTSC-U"
compat: 5
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20573:
name: "Sims, The"
region: "NTSC-U"
@@ -46087,6 +46271,9 @@ SLUS-20719:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
memcardFilters:
- "PSCD10088" # Enables EA Sports BIO.
- "SLUS-20719"
@@ -46271,6 +46458,9 @@ SLUS-20752:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
memcardFilters:
- "PSCD10088" # Enables EA Sports BIO.
- "SLUS-20752"
@@ -46309,6 +46499,9 @@ SLUS-20757:
compat: 5
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
memcardFilters:
- "PSCD10088" # Enables EA Sports BIO.
- "SLUS-20757"
@@ -47545,6 +47738,9 @@ SLUS-20991:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-20992:
name: "Catwoman"
region: "NTSC-U"
@@ -47581,6 +47777,9 @@ SLUS-21000:
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21001:
name: "NHL 2005"
region: "NTSC-U"
@@ -47590,6 +47789,9 @@ SLUS-21002:
compat: 5
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21003:
name: "NASCAR 2005 - Chase for the Cup"
region: "NTSC-U"
@@ -47738,6 +47940,9 @@ SLUS-21025:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21026:
name: "Battlefield 2 - Modern Combat"
region: "NTSC-U"
@@ -47755,7 +47960,7 @@ SLUS-21027:
region: "NTSC-U"
compat: 5
gsHWFixes:
halfPixelOffset: 1 # Fixes ghosting in cutscenes.
halfPixelOffset: 2 # Fixes ghosting.
SLUS-21028:
name: "World Championship Poker"
region: "NTSC-U"
@@ -48013,7 +48218,8 @@ SLUS-21075:
region: "NTSC-U"
compat: 5
gsHWFixes:
halfPixelOffset: 1 # Fixes blurriness.
halfPixelOffset: 2 # Reduces blurriness. Normal Vertex works better, but causes some lights to disappear.
roundSprite: 1 # Further reduces blurriness.
beforeDraw: "OI_HauntingGround" # Fix bloom.
SLUS-21076:
name: "Atari Anthology"
@@ -48588,6 +48794,8 @@ SLUS-21191:
- IbitHack # Fixes constant recompilation problems.
gsHWFixes:
PCRTCOverscan: 1 # Fixes offscreen image.
nativePaletteDraw: 1 # Fixes the look of screen effects.
halfPixelOffset: 2 # Corrects the alignment of screen effects.
SLUS-21192:
name: "Cabela's Outdoor Adventures 2006"
region: "NTSC-U"
@@ -48715,11 +48923,17 @@ SLUS-21213:
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21214:
name: "NCAA Football 2006"
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21215:
name: "Warriors, The"
region: "NTSC-U"
@@ -49052,6 +49266,9 @@ SLUS-21264:
compat: 5
clampModes:
vuClampMode: 3 # Fix black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21265:
name: "Sims 2, The"
region: "NTSC-U"
@@ -50262,6 +50479,9 @@ SLUS-21459:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21460:
name: "NBA Live '07"
region: "NTSC-U"
@@ -50343,11 +50563,17 @@ SLUS-21476:
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21477:
name: "Madden NFL '07 [Hall of Fame Edition]"
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21478:
name: "Pirates - Legend of the Black Buccaneer"
region: "NTSC-U"
@@ -50388,6 +50614,9 @@ SLUS-21483:
compat: 5
clampModes:
vuClampMode: 2 # Fix black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21484:
name: "Flushed Away"
region: "NTSC-U"
@@ -50944,6 +51173,9 @@ SLUS-21620:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21621:
name: "Shin Megami Tensei - Persona 3 FES"
region: "NTSC-U"
@@ -51044,6 +51276,9 @@ SLUS-21638:
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21639:
name: "NASCAR '08"
region: "NTSC-U"
@@ -51086,6 +51321,9 @@ SLUS-21646:
compat: 5
clampModes:
vuClampMode: 2 # Fix black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21647:
name: "NHL '08"
region: "NTSC-U"
@@ -51344,8 +51582,13 @@ SLUS-21704:
region: "NTSC-U"
compat: 3
SLUS-21705:
name: "Madden NFL 08"
name: "Madden NFL 08 en Español"
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21706:
name: "Alvin and the Chipmunks"
region: "NTSC-U"
@@ -51587,6 +51830,9 @@ SLUS-21752:
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21753:
name: "Monopoly"
region: "NTSC-U"
@@ -51677,6 +51923,9 @@ SLUS-21770:
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21771:
name: "NHL 2009"
region: "NTSC-U"
@@ -51687,6 +51936,9 @@ SLUS-21772:
compat: 5
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21773:
name: "PDC World Championship Darts 2008"
region: "NTSC-U"
@@ -52139,6 +52391,9 @@ SLUS-21877:
compat: 5
clampModes:
vuClampMode: 2 # Fixes black textures on characters.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21878:
name: "Ice Age 3 - Dawn of the Dinosaurs"
region: "NTSC-U"
@@ -52214,12 +52469,18 @@ SLUS-21892:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21893:
name: "Madden NFL 10"
region: "NTSC-U"
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21895:
name: "Astro Boy - The Video Game"
region: "NTSC-U"
@@ -52385,6 +52646,9 @@ SLUS-21932:
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21933:
name: "Despicable Me"
region: "NTSC-U"
@@ -52404,6 +52668,9 @@ SLUS-21937:
compat: 5
clampModes:
vuClampMode: 3 # Shows the crowd in the back.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21938:
name: "Ben 10 Ultimate Alien - Cosmic Destruction"
region: "NTSC-U"
@@ -52444,6 +52711,9 @@ SLUS-21946:
compat: 5
clampModes:
vuClampMode: 3 # Missing geometry with microVU.
gsHWFixes:
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
trilinearFiltering: 1
SLUS-21947:
name: "FIFA Soccer 12"
region: "NTSC-U"
@@ -53261,6 +53531,11 @@ SLUS-29174:
SLUS-29175:
name: "Namco Transmission Demo Disc Vol. 3.2"
region: "NTSC-U"
SLUS-29177:
name: "Guitar Hero [Demo]"
region: "NTSC-U"
roundModes:
vu1RoundMode: 1 # Fixes cut-off numbers and restores missing whitespace inside combo meter.
SLUS-29178:
name: "TOCA Race Driver 3 [Demo]"
region: "NTSC-U"
File diff suppressed because it is too large Load Diff
+26 -1
View File
@@ -85,11 +85,13 @@ static double s_last_render_passes = 0;
static double s_last_barriers = 0;
static double s_last_copies = 0;
static double s_last_uploads = 0;
static double s_last_readbacks = 0;
static u64 s_total_draws = 0;
static u64 s_total_render_passes = 0;
static u64 s_total_barriers = 0;
static u64 s_total_copies = 0;
static u64 s_total_uploads = 0;
static u64 s_total_readbacks = 0;
static u32 s_total_frames = 0;
bool GSRunner::InitializeConfig()
@@ -255,7 +257,7 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
{
}
void Host::SetRelativeMouseMode(bool enabled)
void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
{
}
@@ -293,6 +295,7 @@ void Host::BeginPresentFrame()
update_stat(GSPerfMon::Barriers, s_total_barriers, s_last_barriers);
update_stat(GSPerfMon::TextureCopies, s_total_copies, s_last_copies);
update_stat(GSPerfMon::TextureUploads, s_total_uploads, s_last_uploads);
update_stat(GSPerfMon::Readbacks, s_total_readbacks, s_last_readbacks);
s_total_frames++;
std::atomic_thread_fence(std::memory_order_release);
}
@@ -641,9 +644,15 @@ void GSRunner::DumpStats()
Console.WriteLn(fmt::format("@HWSTAT@ Barriers: {} (avg {})", s_total_barriers, static_cast<u64>(std::ceil(s_total_barriers / static_cast<double>(s_total_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Copies: {} (avg {})", s_total_copies, static_cast<u64>(std::ceil(s_total_copies / static_cast<double>(s_total_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Uploads: {} (avg {})", s_total_uploads, static_cast<u64>(std::ceil(s_total_uploads / static_cast<double>(s_total_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Readbacks: {} (avg {})", s_total_readbacks, static_cast<u64>(std::ceil(s_total_readbacks / static_cast<double>(s_total_frames)))));
Console.WriteLn("============================================");
}
#ifdef _WIN32
// We can't handle unicode in filenames if we don't use wmain on Win32.
#define main real_main
#endif
int main(int argc, char* argv[])
{
GSRunner::InitializeConsole();
@@ -808,4 +817,20 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
int wmain(int argc, wchar_t** argv)
{
std::vector<std::string> u8_args;
u8_args.reserve(static_cast<size_t>(argc));
for (int i = 0; i < argc; i++)
u8_args.push_back(StringUtil::WideStringToUTF8String(argv[i]));
std::vector<char*> u8_argptrs;
u8_argptrs.reserve(u8_args.size());
for (int i = 0; i < argc; i++)
u8_argptrs.push_back(u8_args[i].data());
u8_argptrs.push_back(nullptr);
return real_main(argc, u8_argptrs.data());
}
#endif // _WIN32
+19
View File
@@ -380,6 +380,8 @@ void AutoUpdaterDialog::getChangesComplete(QNetworkReply* reply)
changes_html.prepend(tr("<h2>Save State Warning</h2><p>Installing this update will make your save states "
"<b>incompatible</b>. Please ensure you have saved your games to a Memory Card "
"before installing this update or you will lose progress.</p>"));
m_update_will_break_save_states = true;
}
if (update_increases_settings_version)
@@ -407,6 +409,23 @@ void AutoUpdaterDialog::getChangesComplete(QNetworkReply* reply)
void AutoUpdaterDialog::downloadUpdateClicked()
{
if (m_update_will_break_save_states)
{
QMessageBox msgbox;
msgbox.setIcon(QMessageBox::Critical);
msgbox.setWindowTitle(tr("Savestate Warning"));
msgbox.setText(tr("<h1>WARNING</h1><p style='font-size:12pt;'>Installing this update will make your <b>save states incompatible</b>.</p><p>Do you wish to continue?</p>"));
msgbox.addButton(QMessageBox::Yes);
msgbox.addButton(QMessageBox::No);
msgbox.setDefaultButton(QMessageBox::No);
// This makes the box wider, for some reason sizing boxes in Qt is hard - Source: The internet.
QSpacerItem* horizontalSpacer = new QSpacerItem(500, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
QGridLayout* layout = (QGridLayout*)msgbox.layout();
layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
if (msgbox.exec() != QMessageBox::Yes)
return;
}
m_display_messages = true;
QUrl url(m_download_url);
QNetworkRequest request(url);
+2
View File
@@ -55,6 +55,7 @@ target_sources(pcsx2-qt PRIVATE
Settings/BIOSSettingsWidget.ui
Settings/ControllerBindingWidget.ui
Settings/ControllerBindingWidget_DualShock2.ui
Settings/ControllerBindingWidget_Guitar.ui
Settings/ControllerBindingWidgets.cpp
Settings/ControllerBindingWidgets.h
Settings/ControllerLEDSettingsDialog.ui
@@ -133,6 +134,7 @@ target_sources(pcsx2-qt PRIVATE
Settings/SettingsDialog.ui
Settings/USBBindingWidget_DrivingForce.ui
Settings/USBBindingWidget_GTForce.ui
Settings/USBBindingWidget_GunCon2.ui
Debugger/CpuWidget.cpp
Debugger/CpuWidget.h
Debugger/CpuWidget.ui
+7 -5
View File
@@ -414,7 +414,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
connect(thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested);
connect(thread, &EmuThread::onRelativeMouseModeRequested, this, &MainWindow::relativeMouseModeRequested);
connect(thread, &EmuThread::onMouseModeRequested, this, &MainWindow::mouseModeRequested);
connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
connect(thread, &EmuThread::onVMPaused, this, &MainWindow::onVMPaused);
@@ -887,7 +887,8 @@ bool MainWindow::isRenderingToMain() const
bool MainWindow::shouldHideMouseCursor() const
{
return (isRenderingFullscreen() && Host::GetBoolSettingValue("UI", "HideMouseCursor", false)) || m_relative_mouse_mode;
return ((isRenderingFullscreen() && Host::GetBoolSettingValue("UI", "HideMouseCursor", false)) ||
m_relative_mouse_mode || m_hide_mouse_cursor);
}
bool MainWindow::shouldHideMainWindow() const
@@ -2003,12 +2004,13 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height)
QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height);
}
void MainWindow::relativeMouseModeRequested(bool enabled)
void MainWindow::mouseModeRequested(bool relative_mode, bool hide_cursor)
{
if (m_relative_mouse_mode == enabled)
if (m_relative_mouse_mode == relative_mode && m_hide_mouse_cursor == hide_cursor)
return;
m_relative_mouse_mode = enabled;
m_relative_mouse_mode = relative_mode;
m_hide_mouse_cursor = hide_cursor;
if (m_display_widget && !s_vm_paused)
updateDisplayWidgetCursor();
}
+2 -1
View File
@@ -124,7 +124,7 @@ private Q_SLOTS:
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void displayResizeRequested(qint32 width, qint32 height);
void relativeMouseModeRequested(bool enabled);
void mouseModeRequested(bool relative_mode, bool hide_cursor);
void releaseRenderWindow();
void focusDisplayWidget();
@@ -293,6 +293,7 @@ private:
bool m_display_created = false;
bool m_relative_mouse_mode = false;
bool m_hide_mouse_cursor = false;
bool m_was_paused_on_surface_loss = false;
bool m_was_disc_change_request = false;
bool m_is_closing = false;
+2 -3
View File
@@ -40,7 +40,6 @@
#include "pcsx2/Input/InputManager.h"
#include "pcsx2/LogSink.h"
#include "pcsx2/MTGS.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/SysForwardDefs.h"
#include "pcsx2/VMManager.h"
@@ -1469,9 +1468,9 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
emit g_emu_thread->onInputDeviceDisconnected(identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()));
}
void Host::SetRelativeMouseMode(bool enabled)
void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
{
emit g_emu_thread->onRelativeMouseModeRequested(enabled);
emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor);
}
//////////////////////////////////////////////////////////////////////////
+1 -1
View File
@@ -119,7 +119,7 @@ Q_SIGNALS:
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested();
void onRelativeMouseModeRequested(bool enabled);
void onMouseModeRequested(bool relative_mode, bool hide_cursor);
/// Called when the VM is starting initialization, but has not been completed yet.
void onVMStarting();
@@ -0,0 +1,317 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ControllerBindingWidget_Guitar</class>
<widget class="QWidget" name="ControllerBindingWidget_Guitar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1100</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1101</width>
<height>322</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<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="maximumSize">
<size>
<width>800</width>
<height>320</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources/resources.qrc">:/images/guitar.png</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>
</widget>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>320</y>
<width>1101</width>
<height>181</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="6">
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Yellow</string>
</property>
<widget class="InputBindingWidget" name="Yellow">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Start</string>
</property>
<widget class="InputBindingWidget" name="Start">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="0" column="7">
<widget class="QGroupBox" name="groupBox_8">
<property name="title">
<string>Red</string>
</property>
<widget class="InputBindingWidget" name="Red">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="0" column="8">
<widget class="QGroupBox" name="groupBox_9">
<property name="title">
<string>Green</string>
</property>
<widget class="InputBindingWidget" name="Green">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Orange</string>
</property>
<widget class="InputBindingWidget" name="Orange">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Select</string>
</property>
<widget class="InputBindingWidget" name="Select">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="0" column="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Strum Up</string>
</property>
<widget class="InputBindingWidget" name="Up">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="1" column="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Strum Down</string>
</property>
<widget class="InputBindingWidget" name="Down">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="0" column="5">
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Blue</string>
</property>
<widget class="InputBindingWidget" name="Blue">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string>Whammy Bar</string>
</property>
<widget class="InputBindingWidget" name="Whammy">
<property name="geometry">
<rect>
<x>24</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Tilt</string>
</property>
<widget class="InputBindingWidget" name="Tilt">
<property name="geometry">
<rect>
<x>20</x>
<y>30</y>
<width>90</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
</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>
+69 -28
View File
@@ -26,7 +26,7 @@
#include "common/StringUtil.h"
#include "pcsx2/Host.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "pcsx2/SIO/Pad/Pad.h"
#include "Settings/ControllerBindingWidgets.h"
#include "Settings/ControllerSettingsDialog.h"
@@ -38,6 +38,7 @@
#include "ui_USBBindingWidget_DrivingForce.h"
#include "ui_USBBindingWidget_GTForce.h"
#include "ui_USBBindingWidget_GunCon2.h"
ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
: QWidget(parent)
@@ -52,7 +53,7 @@ ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSett
onTypeChanged();
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
m_dialog->getProfileSettingsInterface(), m_ui.controllerType, m_config_section, "Type", PAD::GetDefaultPadType(port));
m_dialog->getProfileSettingsInterface(), m_ui.controllerType, m_config_section, "Type", Pad::GetDefaultPadType(port));
connect(m_ui.controllerType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ControllerBindingWidget::onTypeChanged);
connect(m_ui.bindings, &QPushButton::clicked, this, &ControllerBindingWidget::onBindingsClicked);
@@ -71,14 +72,14 @@ QIcon ControllerBindingWidget::getIcon() const
void ControllerBindingWidget::populateControllerTypes()
{
for (const auto& [name, display_name] : PAD::GetControllerTypeNames())
m_ui.controllerType->addItem(qApp->translate("Pad", display_name), QString::fromStdString(name));
for (const auto& [name, display_name] : Pad::GetControllerTypeNames())
m_ui.controllerType->addItem(QString::fromUtf8(display_name), QString::fromUtf8(name));
}
void ControllerBindingWidget::onTypeChanged()
{
const bool is_initializing = (m_ui.stackedWidget->count() == 0);
m_controller_type = m_dialog->getStringValue(m_config_section.c_str(), "Type", PAD::GetDefaultPadType(m_port_number));
m_controller_type = m_dialog->getStringValue(m_config_section.c_str(), "Type", Pad::GetDefaultPadType(m_port_number));
if (m_bindings_widget)
{
@@ -99,25 +100,32 @@ void ControllerBindingWidget::onTypeChanged()
m_macros_widget = nullptr;
}
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_controller_type);
const bool has_settings = (cinfo && cinfo->num_settings > 0);
const bool has_macros = (cinfo && cinfo->num_bindings > 0);
const Pad::ControllerInfo* cinfo = Pad::GetControllerInfo(m_controller_type);
const bool has_settings = (cinfo && !cinfo->settings.empty());
const bool has_macros = (cinfo && !cinfo->bindings.empty());
m_ui.settings->setEnabled(has_settings);
m_ui.macros->setEnabled(has_macros);
if (m_controller_type == "DualShock2")
{
m_bindings_widget = ControllerBindingWidget_DualShock2::createInstance(this);
}
else if (m_controller_type == "Guitar")
{
m_bindings_widget = ControllerBindingWidget_Guitar::createInstance(this);
}
else
{
m_bindings_widget = new ControllerBindingWidget_Base(this);
}
m_ui.stackedWidget->addWidget(m_bindings_widget);
m_ui.stackedWidget->setCurrentWidget(m_bindings_widget);
if (has_settings)
{
const std::span<const SettingInfo> settings(cinfo->settings, cinfo->num_settings);
m_settings_widget = new ControllerCustomSettingsWidget(
settings, m_config_section, std::string(), "Pad", getDialog(), m_ui.stackedWidget);
cinfo->settings, m_config_section, std::string(), "Pad", getDialog(), m_ui.stackedWidget);
m_ui.stackedWidget->addWidget(m_settings_widget);
}
@@ -210,13 +218,13 @@ void ControllerBindingWidget::onClearBindingsClicked()
{
{
auto lock = Host::GetSettingsLock();
PAD::ClearPortBindings(*Host::Internal::GetBaseSettingsLayer(), m_port_number);
Pad::ClearPortBindings(*Host::Internal::GetBaseSettingsLayer(), m_port_number);
}
Host::CommitBaseSettingChanges();
}
else
{
PAD::ClearPortBindings(*m_dialog->getProfileSettingsInterface(), m_port_number);
Pad::ClearPortBindings(*m_dialog->getProfileSettingsInterface(), m_port_number);
m_dialog->getProfileSettingsInterface()->Save();
}
@@ -240,14 +248,14 @@ void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
{
{
auto lock = Host::GetSettingsLock();
result = PAD::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping);
result = Pad::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping);
}
if (result)
Host::CommitBaseSettingChanges();
}
else
{
result = PAD::MapController(*m_dialog->getProfileSettingsInterface(), m_port_number, mapping);
result = Pad::MapController(*m_dialog->getProfileSettingsInterface(), m_port_number, mapping);
if (result)
{
m_dialog->getProfileSettingsInterface()->Save();
@@ -312,7 +320,7 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare
ControllerSettingsDialog* dialog = m_bwidget->getDialog();
const std::string& section = m_bwidget->getConfigSection();
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_bwidget->getControllerType());
const Pad::ControllerInfo* cinfo = Pad::GetControllerInfo(m_bwidget->getControllerType());
if (!cinfo)
{
// Shouldn't ever happen.
@@ -325,20 +333,19 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare
for (const std::string_view& button : buttons_split)
{
for (u32 i = 0; i < cinfo->num_bindings; i++)
for (const InputBindingInfo& bi : cinfo->bindings)
{
if (button == cinfo->bindings[i].name)
if (button == bi.name)
{
m_binds.push_back(&cinfo->bindings[i]);
m_binds.push_back(&bi);
break;
}
}
}
// populate list view
for (u32 i = 0; i < cinfo->num_bindings; i++)
for (const InputBindingInfo& bi : cinfo->bindings)
{
const InputBindingInfo& bi = cinfo->bindings[i];
if (bi.bind_type == InputBindingInfo::Type::Motor)
continue;
@@ -350,8 +357,12 @@ ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* pare
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(
dialog->getProfileSettingsInterface(), m_ui.pressure, section, fmt::format("Macro{}Pressure", index + 1u), 100.0f, 1.0f);
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(
dialog->getProfileSettingsInterface(), m_ui.deadzone, section, fmt::format("Macro{}Deadzone", index + 1u), 100.0f, 0.0f);
connect(m_ui.pressure, &QSlider::valueChanged, this, &ControllerMacroEditWidget::onPressureChanged);
connect(m_ui.deadzone, &QSlider::valueChanged, this, &ControllerMacroEditWidget::onDeadzoneChanged);
onPressureChanged();
onDeadzoneChanged();
m_frequency = dialog->getIntValue(section.c_str(), fmt::format("Macro{}Frequency", index + 1u).c_str(), 0);
updateFrequencyText();
@@ -384,6 +395,11 @@ void ControllerMacroEditWidget::onPressureChanged()
m_ui.pressureValue->setText(tr("%1%").arg(m_ui.pressure->value()));
}
void ControllerMacroEditWidget::onDeadzoneChanged()
{
m_ui.deadzoneValue->setText(tr("%1%").arg(m_ui.deadzone->value()));
}
void ControllerMacroEditWidget::onSetFrequencyClicked()
{
bool okay;
@@ -423,12 +439,12 @@ void ControllerMacroEditWidget::updateFrequencyText()
void ControllerMacroEditWidget::updateBinds()
{
ControllerSettingsDialog* dialog = m_bwidget->getDialog();
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_bwidget->getControllerType());
const Pad::ControllerInfo* cinfo = Pad::GetControllerInfo(m_bwidget->getControllerType());
if (!cinfo)
return;
std::vector<const InputBindingInfo*> new_binds;
for (u32 i = 0, bind_index = 0; i < cinfo->num_bindings; i++)
for (u32 i = 0, bind_index = 0; i < static_cast<u32>(cinfo->bindings.size()); i++)
{
const InputBindingInfo& bi = cinfo->bindings[i];
if (bi.bind_type == InputBindingInfo::Type::Motor)
@@ -793,16 +809,15 @@ QIcon ControllerBindingWidget_Base::getIcon() const
void ControllerBindingWidget_Base::initBindingWidgets()
{
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(getControllerType());
const Pad::ControllerInfo* cinfo = Pad::GetControllerInfo(getControllerType());
if (!cinfo)
return;
const std::string& config_section = getConfigSection();
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
for (u32 i = 0; i < cinfo->num_bindings; i++)
for (const InputBindingInfo& bi : cinfo->bindings)
{
const InputBindingInfo& bi = cinfo->bindings[i];
if (bi.bind_type == InputBindingInfo::Type::Axis || bi.bind_type == InputBindingInfo::Type::HalfAxis ||
bi.bind_type == InputBindingInfo::Type::Button || bi.bind_type == InputBindingInfo::Type::Pointer ||
bi.bind_type == InputBindingInfo::Type::Device)
@@ -820,7 +835,7 @@ void ControllerBindingWidget_Base::initBindingWidgets()
switch (cinfo->vibration_caps)
{
case PAD::VibrationCapabilities::LargeSmallMotors:
case Pad::VibrationCapabilities::LargeSmallMotors:
{
InputVibrationBindingWidget* widget = findChild<InputVibrationBindingWidget*>(QStringLiteral("LargeMotor"));
if (widget)
@@ -832,7 +847,7 @@ void ControllerBindingWidget_Base::initBindingWidgets()
}
break;
case PAD::VibrationCapabilities::SingleMotor:
case Pad::VibrationCapabilities::SingleMotor:
{
InputVibrationBindingWidget* widget = findChild<InputVibrationBindingWidget*>(QStringLiteral("Motor"));
if (widget)
@@ -840,7 +855,7 @@ void ControllerBindingWidget_Base::initBindingWidgets()
}
break;
case PAD::VibrationCapabilities::NoVibration:
case Pad::VibrationCapabilities::NoVibration:
default:
break;
}
@@ -867,6 +882,27 @@ ControllerBindingWidget_Base* ControllerBindingWidget_DualShock2::createInstance
return new ControllerBindingWidget_DualShock2(parent);
}
ControllerBindingWidget_Guitar::ControllerBindingWidget_Guitar(ControllerBindingWidget* parent)
: ControllerBindingWidget_Base(parent)
{
m_ui.setupUi(this);
initBindingWidgets();
}
ControllerBindingWidget_Guitar::~ControllerBindingWidget_Guitar()
{
}
QIcon ControllerBindingWidget_Guitar::getIcon() const
{
return QIcon::fromTheme("guitar-line");
}
ControllerBindingWidget_Base* ControllerBindingWidget_Guitar::createInstance(ControllerBindingWidget* parent)
{
return new ControllerBindingWidget_Guitar(parent);
}
//////////////////////////////////////////////////////////////////////////
USBDeviceWidget::USBDeviceWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
@@ -1240,6 +1276,11 @@ USBBindingWidget* USBBindingWidget::createInstance(
has_template = true;
}
}
else if (type == "guncon2")
{
Ui::USBBindingWidget_GunCon2().setupUi(widget);
has_template = true;
}
if (has_template)
widget->bindWidgets(bindings);
+20 -2
View File
@@ -15,7 +15,7 @@
#pragma once
#include "PAD/Host/PAD.h"
#include "pcsx2/SIO/Pad/PadTypes.h"
#include <QtWidgets/QWidget>
@@ -23,6 +23,7 @@
#include "ui_ControllerBindingWidget.h"
#include "ui_ControllerBindingWidget_DualShock2.h"
#include "ui_ControllerBindingWidget_Guitar.h"
#include "ui_ControllerMacroWidget.h"
#include "ui_ControllerMacroEditWidget.h"
#include "ui_USBDeviceWidget.h"
@@ -91,7 +92,7 @@ public:
void updateListItem(u32 index);
private:
static constexpr u32 NUM_MACROS = PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER;
static constexpr u32 NUM_MACROS = Pad::NUM_MACRO_BUTTONS_PER_CONTROLLER;
void createWidgets(ControllerBindingWidget* parent);
@@ -114,6 +115,7 @@ public:
private Q_SLOTS:
void onPressureChanged();
void onDeadzoneChanged();
void onSetFrequencyClicked();
void updateBinds();
@@ -193,6 +195,22 @@ private:
Ui::ControllerBindingWidget_DualShock2 m_ui;
};
class ControllerBindingWidget_Guitar final : public ControllerBindingWidget_Base
{
Q_OBJECT
public:
ControllerBindingWidget_Guitar(ControllerBindingWidget* parent);
~ControllerBindingWidget_Guitar();
QIcon getIcon() const override;
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
private:
Ui::ControllerBindingWidget_Guitar m_ui;
};
//////////////////////////////////////////////////////////////////////////
class USBDeviceWidget final : public QWidget
+44 -1
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>691</width>
<height>547</height>
<height>433</height>
</rect>
</property>
<property name="windowTitle">
@@ -83,6 +83,9 @@
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item>
@@ -123,6 +126,46 @@
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="deadzoneLayout" stretch="0,1,0">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Deadzone:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="deadzone">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deadzoneValue">
<property name="text">
<string>100%</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
+10 -10
View File
@@ -22,8 +22,8 @@
#include "Settings/HotkeySettingsWidget.h"
#include "pcsx2/INISettingsInterface.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "pcsx2/Sio.h"
#include "pcsx2/SIO/Pad/Pad.h"
#include "pcsx2/SIO/Sio.h"
#include "pcsx2/VMManager.h"
#include "common/Assertions.h"
@@ -130,7 +130,7 @@ void ControllerSettingsDialog::onNewProfileClicked()
{
// from global
auto lock = Host::GetSettingsLock();
PAD::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), true, true, false);
Pad::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), true, true, false);
USB::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), true, true);
}
else
@@ -138,7 +138,7 @@ void ControllerSettingsDialog::onNewProfileClicked()
// from profile
const bool copy_hotkey_bindings = m_profile_interface->GetBoolValue("Pad", "UseProfileHotkeyBindings", false);
temp_si.SetBoolValue("Pad", "UseProfileHotkeyBindings", copy_hotkey_bindings);
PAD::CopyConfiguration(&temp_si, *m_profile_interface, true, true, copy_hotkey_bindings);
Pad::CopyConfiguration(&temp_si, *m_profile_interface, true, true, copy_hotkey_bindings);
USB::CopyConfiguration(&temp_si, *m_profile_interface, true, true);
}
}
@@ -167,7 +167,7 @@ void ControllerSettingsDialog::onLoadProfileClicked()
{
auto lock = Host::GetSettingsLock();
PAD::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_profile_interface, true, true, false);
Pad::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_profile_interface, true, true, false);
USB::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_profile_interface, true, true);
}
Host::CommitBaseSettingChanges();
@@ -403,8 +403,8 @@ void ControllerSettingsDialog::createWidgets()
m_port_bindings[global_slot] = new ControllerBindingWidget(m_ui.settingsContainer, this, global_slot);
m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]);
const PAD::ControllerInfo* ci = PAD::GetControllerInfo(m_port_bindings[global_slot]->getControllerType());
const QString display_name(ci ? qApp->translate("Pad", ci->display_name) : QStringLiteral("Unknown"));
const Pad::ControllerInfo* ci = Pad::GetControllerInfo(m_port_bindings[global_slot]->getControllerType());
const QString display_name(QString::fromUtf8(ci ? ci->GetLocalizedName() : "Unknown"));
QListWidgetItem* item = new QListWidgetItem();
//: Controller Port is an official term from Sony. Find the official translation for your language inside the console's manual.
@@ -459,8 +459,8 @@ void ControllerSettingsDialog::updateListDescription(u32 global_slot, Controller
const auto [port, slot] = sioConvertPadToPortAndSlot(global_slot);
const bool mtap_enabled = getBoolValue("Pad", (port == 0) ? "MultitapPort1" : "MultitapPort2", false);
const PAD::ControllerInfo* ci = PAD::GetControllerInfo(widget->getControllerType());
const QString display_name(ci ? qApp->translate("Pad", ci->display_name) : QStringLiteral("Unknown"));
const Pad::ControllerInfo* ci = Pad::GetControllerInfo(widget->getControllerType());
const QString display_name = QString::fromUtf8(ci ? ci->GetLocalizedName() : "Unknown");
//: Controller Port is an official term from Sony. Find the official translation for your language inside the console's manual.
item->setText(mtap_enabled ? (tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
@@ -492,7 +492,7 @@ void ControllerSettingsDialog::updateListDescription(u32 port, USBDeviceWidget*
void ControllerSettingsDialog::refreshProfileList()
{
const std::vector<std::string> names(PAD::GetInputProfileNames());
const std::vector<std::string> names = Pad::GetInputProfileNames();
QSignalBlocker sb(m_ui.currentProfile);
m_ui.currentProfile->clear();
+218 -2
View File
@@ -15,21 +15,28 @@
#include "PrecompiledHeader.h"
#include "pcsx2/SIO/Pad/Pad.h"
#include "GameSummaryWidget.h"
#include "SettingsDialog.h"
#include "MainWindow.h"
#include "QtHost.h"
#include "QtProgressCallback.h"
#include "QtUtils.h"
#include "pcsx2/CDVD/IsoHasher.h"
#include "pcsx2/GameDatabase.h"
#include "pcsx2/GameList.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "common/Error.h"
#include "common/MD5Digest.h"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include "fmt/format.h"
#include <QtCore/QDir>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsDialog* dialog, QWidget* parent)
: m_dialog(dialog)
@@ -51,15 +58,18 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsDialo
populateInputProfiles();
populateDetails(entry);
populateDiscPath(entry);
populateTrackList(entry);
connect(m_ui.inputProfile, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onInputProfileChanged);
connect(m_ui.verify, &QAbstractButton::clicked, this, &GameSummaryWidget::onVerifyClicked);
connect(m_ui.searchHash, &QAbstractButton::clicked, this, &GameSummaryWidget::onSearchHashClicked);
}
GameSummaryWidget::~GameSummaryWidget() = default;
void GameSummaryWidget::populateInputProfiles()
{
for (const std::string& name : PAD::GetInputProfileNames())
for (const std::string& name : Pad::GetInputProfileNames())
m_ui.inputProfile->addItem(QString::fromStdString(name));
}
@@ -137,3 +147,209 @@ void GameSummaryWidget::onDiscPathBrowseClicked()
// let the signal take care of it
m_ui.discPath->setText(QDir::toNativeSeparators(filename));
}
void GameSummaryWidget::populateTrackList(const GameList::Entry* entry)
{
if (entry->type != GameList::EntryType::PS1Disc && entry->type != GameList::EntryType::PS2Disc)
{
m_ui.verify->setEnabled(false);
m_ui.verifyResult->setPlainText(tr("Game is not a CD/DVD."));
return;
}
if (QtHost::IsVMValid())
{
m_ui.verify->setEnabled(false);
m_ui.verifyResult->setPlainText(tr("Track list unavailable while virtual machine is running."));
return;
}
IsoHasher hasher;
Error error;
if (!hasher.Open(m_entry_path, &error))
{
m_ui.verify->setEnabled(false);
m_ui.verifyResult->setPlainText(QString::fromStdString(error.GetDescription()));
return;
}
const auto AddColumn = [this](const QString& text) {
QTableWidgetItem* item = new QTableWidgetItem(text);
const int column = m_ui.tracks->columnCount();
m_ui.tracks->insertColumn(column);
m_ui.tracks->setHorizontalHeaderItem(column, item);
};
const auto SetColumn = [this](int row, int column, const QString& text) {
QTableWidgetItem* item = new QTableWidgetItem(text);
m_ui.tracks->setItem(row, column, item);
};
// columns depend on CD vs DVD.
AddColumn(tr("#"));
if (hasher.IsCD())
{
AddColumn(tr("Mode"));
AddColumn(tr("Start"));
AddColumn(tr("Sectors"));
AddColumn(tr("Size"));
AddColumn(tr("MD5"));
AddColumn(tr("Status"));
}
else
{
AddColumn(tr("Start"));
AddColumn(tr("Sectors"));
AddColumn(tr("Size"));
AddColumn(tr("MD5"));
AddColumn(tr("Status"));
}
for (const IsoHasher::Track& track : hasher.GetTracks())
{
const int row = m_ui.tracks->rowCount();
m_ui.tracks->insertRow(row);
SetColumn(row, 0, tr("%1").arg(track.number));
if (hasher.IsCD())
{
SetColumn(row, 1, QtUtils::StringViewToQString(IsoHasher::GetTrackTypeString(track.type)));
SetColumn(row, 2, tr("%1").arg(track.start_lsn));
SetColumn(row, 3, tr("%1").arg(track.sectors));
SetColumn(row, 4, tr("%1").arg(track.size));
SetColumn(row, 5, tr("<not computed>"));
SetColumn(row, 6, QString());
}
else
{
SetColumn(row, 1, tr("%1").arg(track.start_lsn));
SetColumn(row, 2, tr("%1").arg(track.sectors));
SetColumn(row, 3, tr("%1").arg(track.size));
SetColumn(row, 4, tr("<not computed>"));
SetColumn(row, 5, QString());
}
}
if (hasher.IsCD())
QtUtils::ResizeColumnsForTableView(m_ui.tracks, {20, 60, 70, 70, 100, 220, 40});
else
QtUtils::ResizeColumnsForTableView(m_ui.tracks, {20, 100, 100, 100, 220, 40});
}
void GameSummaryWidget::onVerifyClicked()
{
// Can't do this while a VM is running because of stupid CDVD.
if (QtHost::IsVMValid())
{
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"), tr("Cannot verify image while a game is running."));
return;
}
IsoHasher hasher;
Error error;
if (!hasher.Open(m_entry_path, &error))
{
setVerifyResult(QString::fromStdString(error.GetDescription()));
return;
}
QtModalProgressCallback callback(this);
hasher.ComputeHashes(&callback);
if (callback.IsCancelled())
return;
const int hash_column = hasher.IsCD() ? 5 : 4;
int row = 0;
// convert to database format
std::vector<GameDatabase::TrackHash> thashes;
thashes.reserve(hasher.GetTrackCount());
for (const IsoHasher::Track& track : hasher.GetTracks())
{
GameDatabase::TrackHash thash;
thash.size = track.size;
if (track.hash.empty() || !thash.parseHash(track.hash))
{
m_ui.verify->setEnabled(false);
m_ui.verifyResult->setPlainText(tr("One or more tracks is missing."));
return;
}
// Use the first track's hash as the redump search term.
if (m_redump_search_keyword.empty())
m_redump_search_keyword = thash.toString();
thashes.push_back(thash);
}
// match the hashes. can't use vector<bool> here because it's not an actual array
std::unique_ptr<bool[]> val_results = std::make_unique<bool[]>(hasher.GetTrackCount());
std::string match_error;
const GameDatabase::HashDatabaseEntry* hentry =
GameDatabase::lookupHash(thashes.data(), thashes.size(), val_results.get(), &match_error);
// fill the UI with both the hashes and validation results
for (u32 i = 0; i < hasher.GetTrackCount(); i++)
{
QTableWidgetItem* const hash_item = m_ui.tracks->item(row, hash_column);
QTableWidgetItem* const status_item = m_ui.tracks->item(row, hash_column + 1);
const bool result = val_results[i];
const QBrush brush(result ? QColor(0, 200, 0) : QColor(200, 0, 0));
hash_item->setText(QString::fromStdString(hasher.GetTrack(i).hash));
hash_item->setForeground(brush);
status_item->setText(result ? QStringLiteral("\u2713") : QStringLiteral("\u2715"));
status_item->setForeground(brush);
row++;
}
if (hentry)
{
if (!hentry->version.empty())
{
setVerifyResult(tr("Verified as %1 [%2] (Version %3).")
.arg(QString::fromStdString(hentry->name))
.arg(QString::fromStdString(hentry->serial))
.arg(QString::fromStdString(hentry->version)));
}
else
{
setVerifyResult(tr("Verified as %1 [%2].")
.arg(QString::fromStdString(hentry->name))
.arg(QString::fromStdString(hentry->serial)));
}
}
else
{
setVerifyResult(QString::fromStdString(match_error));
}
}
void GameSummaryWidget::onSearchHashClicked()
{
if (m_redump_search_keyword.empty())
return;
QtUtils::OpenURL(this, fmt::format("http://redump.org/discs/quicksearch/{}", m_redump_search_keyword).c_str());
}
void GameSummaryWidget::setVerifyResult(QString error)
{
m_ui.verify->setVisible(false);
m_ui.verifyButtonLayout->removeWidget(m_ui.verify);
m_ui.verify->deleteLater();
m_ui.verify = nullptr;
m_ui.verifyButtonLayout->removeItem(m_ui.verifyButtonSpacer);
delete m_ui.verifyButtonSpacer;
m_ui.verifyButtonSpacer = nullptr;
m_ui.verifyLayout->removeItem(m_ui.verifyButtonLayout);
m_ui.verifyButtonLayout->deleteLater();
m_ui.verifyButtonLayout = nullptr;
m_ui.verifyLayout->update();
updateGeometry();
m_ui.verifyResult->setPlainText(error);
m_ui.verifyResult->setVisible(true);
m_ui.searchHash->setVisible(true);
}
+10 -4
View File
@@ -34,16 +34,22 @@ public:
GameSummaryWidget(const GameList::Entry* entry, SettingsDialog* dialog, QWidget* parent);
~GameSummaryWidget();
private Q_SLOTS:
void onInputProfileChanged(int index);
void onDiscPathChanged(const QString& value);
void onDiscPathBrowseClicked();
void onVerifyClicked();
void onSearchHashClicked();
private:
void populateInputProfiles();
void populateDetails(const GameList::Entry* entry);
void populateDiscPath(const GameList::Entry* entry);
void onInputProfileChanged(int index);
void onDiscPathChanged(const QString& value);
void onDiscPathBrowseClicked();
void populateTrackList(const GameList::Entry* entry);
void setVerifyResult(QString error);
Ui::GameSummaryWidget m_ui;
SettingsDialog* m_dialog;
std::string m_entry_path;
std::string m_redump_search_keyword;
};
+87 -3
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>692</width>
<height>562</height>
<width>641</width>
<height>467</height>
</rect>
</property>
<layout class="QFormLayout" name="detailsFormLayout">
@@ -413,7 +413,7 @@
</item>
</layout>
</item>
<item row="9" column="1">
<item row="10" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -426,6 +426,90 @@
</property>
</spacer>
</item>
<item row="9" column="0" colspan="2">
<layout class="QVBoxLayout" name="verifyLayout">
<item>
<widget class="QTableWidget" name="tracks">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="verifyButtonLayout">
<item>
<spacer name="verifyButtonSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="verify">
<property name="text">
<string>Verify</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="2">
<spacer name="verticalSpacer_2">
<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="0" rowspan="3">
<widget class="QPlainTextEdit" name="verifyResult">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>60</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="searchHash">
<property name="text">
<string>Search on Redump.org...</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
+1 -1
View File
@@ -23,7 +23,7 @@
#include "MemoryCardConvertWorker.h"
#include "pcsx2/MemoryCardFile.h"
#include "pcsx2/SIO/Memcard/MemoryCardFile.h"
class MemoryCardConvertDialog final : public QDialog
{
+2 -2
View File
@@ -7,8 +7,8 @@
#include "QtProgressCallback.h"
#include "pcsx2/MemoryCardFile.h"
#include "pcsx2/MemoryCardFolder.h"
#include "pcsx2/SIO/Memcard/MemoryCardFile.h"
#include "pcsx2/SIO/Memcard/MemoryCardFolder.h"
class MemoryCardConvertWorker : public QtAsyncProgressThread
{
+1 -1
View File
@@ -24,7 +24,7 @@
#include "Settings/MemoryCardCreateDialog.h"
#include "pcsx2/MemoryCardFile.h"
#include "pcsx2/SIO/Memcard/MemoryCardFile.h"
#include "pcsx2/System.h"
MemoryCardCreateDialog::MemoryCardCreateDialog(QWidget* parent /* = nullptr */)
@@ -32,7 +32,7 @@
#include "SettingWidgetBinder.h"
#include "SettingsDialog.h"
#include "pcsx2/MemoryCardFile.h"
#include "pcsx2/SIO/Memcard/MemoryCardFile.h"
static constexpr const char* CONFIG_SECTION = "MemoryCards";
+4 -2
View File
@@ -79,8 +79,10 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
if (isPerGameSettings())
{
QString summary = tr("<strong>Summary</strong><hr>Eventually this will be where we can see patches and compute "
"hashes/verify dumps/etc.");
QString summary = tr("<strong>Summary</strong><hr>This page shows details about the selected game. Changing the Input "
"Profile will set the controller binding scheme for this game to whichever profile is chosen, instead "
"of the default (Shared) configuration. The track list and dump verification can be used to determine "
"if your disc image matches a known good dump. If it does not match, the game may be broken.");
if (game)
{
addWidget(new GameSummaryWidget(game, this, m_ui.settingsContainer), tr("Summary"),
@@ -0,0 +1,779 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>USBBindingWidget_GunCon2</class>
<widget class="QWidget" name="USBBindingWidget_GunCon2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>400</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>1000</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_16">
<item row="2" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_16">
<property name="title">
<string>Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_19">
<property name="title">
<string>A</string>
</property>
<layout class="QGridLayout" name="gridLayout_19">
<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="A">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="groupBox_20">
<property name="title">
<string>C</string>
</property>
<layout class="QGridLayout" name="gridLayout_20">
<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="C">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</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="2">
<widget class="QGroupBox" name="groupBox_23">
<property name="title">
<string>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="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</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" colspan="2">
<widget class="QGroupBox" name="groupBox_35">
<property name="title">
<string>Select</string>
</property>
<layout class="QGridLayout" name="gridLayout_35">
<item row="0" column="0">
<widget class="InputBindingWidget" name="Select">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</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" colspan="2">
<widget class="QGroupBox" name="groupBox_18">
<property name="title">
<string>B</string>
</property>
<layout class="QGridLayout" name="gridLayout_18">
<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="B">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="3" rowspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>D-Pad</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="3" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Down</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<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="Down">
<property name="minimumSize">
<size>
<width>125</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>125</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Left</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<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="Left">
<property name="minimumSize">
<size>
<width>125</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>125</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" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Up</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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="Up">
<property name="minimumSize">
<size>
<width>125</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>125</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Right</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<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="Right">
<property name="minimumSize">
<size>
<width>125</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>125</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_15">
<property name="title">
<string>Pointer Setup</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;p&gt;By default, GunCon2 will use the mouse pointer. To use the mouse, you &lt;strong&gt;do not&lt;/strong&gt; need to configure any bindings apart from the trigger and buttons.&lt;/p&gt;
&lt;p&gt;If you want to use a controller, or lightgun which simulates a controller instead of a mouse, then you should bind it to Relative Aiming. Otherwise, Relative Aiming should be &lt;strong&gt;left unbound&lt;/strong&gt;.&lt;/p&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="4" rowspan="3">
<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 row="0" column="0" rowspan="3">
<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>
<item row="0" column="2" rowspan="2">
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string extracomment="Try to use Sony's official terminology for this. A good place to start would be in the console or the DualShock 2's manual. If this element was officially translated to your language by Sony in later DualShocks, you may use that term.">Relative Aiming</string>
</property>
<layout class="QGridLayout" name="gridLayout_11">
<item row="3" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Down</string>
</property>
<layout class="QGridLayout" name="gridLayout_12">
<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="RelativeDown">
<property name="minimumSize">
<size>
<width>100</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="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_12">
<property name="title">
<string>Left</string>
</property>
<layout class="QGridLayout" name="gridLayout_13">
<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="RelativeLeft">
<property name="minimumSize">
<size>
<width>100</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" colspan="2">
<widget class="QGroupBox" name="groupBox_13">
<property name="title">
<string>Up</string>
</property>
<layout class="QGridLayout" name="gridLayout_14">
<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="RelativeUp">
<property name="minimumSize">
<size>
<width>100</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="2" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_14">
<property name="title">
<string>Right</string>
</property>
<layout class="QGridLayout" name="gridLayout_15">
<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="RelativeRight">
<property name="minimumSize">
<size>
<width>100</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>
</widget>
</item>
<item row="1" column="1" rowspan="2">
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_7">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<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="Trigger">
<property name="minimumSize">
<size>
<width>125</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>125</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_8">
<property name="title">
<string>Shoot Offscreen</string>
</property>
<layout class="QGridLayout" name="gridLayout_7">
<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="ShootOffscreen">
<property name="minimumSize">
<size>
<width>125</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>125</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="2">
<widget class="QGroupBox" name="groupBox_9">
<property name="title">
<string>Calibration Shot</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="1">
<widget class="InputBindingWidget" name="Recalibrate">
<property name="minimumSize">
<size>
<width>125</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>125</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="text">
<string>Calibration shot is required to pass the setup screen in some games.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" 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>
</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>
+5 -5
View File
@@ -15,7 +15,7 @@
#include "PrecompiledHeader.h"
#include "PAD/Host/PAD.h"
#include "pcsx2/SIO/Pad/Pad.h"
#include "QtHost.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
@@ -414,10 +414,10 @@ void SetupWizardDialog::setupControllerPage()
const std::string section = fmt::format("Pad{}", port + 1);
const PadWidgets& w = pad_widgets[port];
for (const auto& [name, display_name] : PAD::GetControllerTypeNames())
w.type_combo->addItem(qApp->translate("Pad", display_name), QString::fromStdString(name));
for (const auto& [name, display_name] : Pad::GetControllerTypeNames())
w.type_combo->addItem(QString::fromUtf8(display_name), QString::fromUtf8(name));
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
nullptr, w.type_combo, section, "Type", PAD::GetDefaultPadType(port));
nullptr, w.type_combo, section, "Type", Pad::GetDefaultPadType(port));
w.mapping_result->setText((port == 0) ? tr("Default (Keyboard)") : tr("Default (None)"));
@@ -473,7 +473,7 @@ void SetupWizardDialog::doDeviceAutomaticBinding(u32 port, QLabel* update_label,
bool result;
{
auto lock = Host::GetSettingsLock();
result = PAD::MapController(*Host::Internal::GetBaseSettingsLayer(), port, mapping);
result = Pad::MapController(*Host::Internal::GetBaseSettingsLayer(), port, mapping);
}
if (!result)
return;
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -306,6 +306,9 @@
<QtUi Include="Settings\ControllerBindingWidget_DualShock2.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\ControllerBindingWidget_Guitar.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\ControllerBindingWidget.ui">
<FileType>Document</FileType>
</QtUi>
@@ -371,6 +374,9 @@
</QtUi>
</ItemGroup>
<ItemGroup>
<QtUi Include="Settings\USBBindingWidget_GunCon2.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="SetupWizardDialog.ui">
<FileType>Document</FileType>
</QtUi>
@@ -392,8 +398,12 @@
<QtUi Include="Settings\GamePatchSettingsWidget.ui">
<FileType>Document</FileType>
</QtUi>
<None Include="Settings\USBBindingWidget_DrivingForce.ui" />
<None Include="Settings\USBBindingWidget_GTForce.ui" />
<QtUi Include="Settings\USBBindingWidget_DrivingForce.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\USBBindingWidget_GTForce.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\USBDeviceWidget.ui">
<FileType>Document</FileType>
</QtUi>
+12 -6
View File
@@ -531,6 +531,9 @@
<QtUi Include="Settings\ControllerBindingWidget_DualShock2.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\ControllerBindingWidget_Guitar.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\ControllerBindingWidget.ui">
<Filter>Settings</Filter>
</QtUi>
@@ -621,17 +624,20 @@
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="SetupWizardDialog.ui" />
<QtUi Include="Settings\USBBindingWidget_DrivingForce.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\USBBindingWidget_GTForce.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\USBBindingWidget_GunCon2.ui">
<Filter>Settings</Filter>
</QtUi>
</ItemGroup>
<ItemGroup>
<None Include="Settings\FolderSettingsWidget.ui">
<Filter>Settings</Filter>
</None>
<None Include="Settings\USBBindingWidget_GTForce.ui">
<Filter>Settings</Filter>
</None>
<None Include="Settings\USBBindingWidget_DrivingForce.ui">
<Filter>Settings</Filter>
</None>
</ItemGroup>
<ItemGroup>
<QtTs Include="Translations\pcsx2-qt_en.ts">
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="guitar_PCSX2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 493.66 474.14"><defs><style>.cls-1,.cls-2{stroke:#000;stroke-miterlimit:10;}.cls-2{stroke-width:20px;}</style></defs><g id="By_maxihplay"><path id="corpo" class="cls-1" d="M188.41,192.55c-.75,14.56,6.75,47.85,20.03,67.76,19.32,28.97,42.12,37.78,54.54,43.83-17.76,8.99-43.47,27.14-53.18,55-1.18,3.26-1.97,6.71-2.27,10.24-3.93,46.09-40.35,63.32-70.26,64.13-14.57,.4-29.14-2.93-42.13-9.52-14.19-7.25-26.02-17.98-35.01-32.02-.41-.58-.81-1.15-1.22-1.73-10.27-14.2-16.49-30.76-17.93-47.84-1.42-16.1,1.39-31.83,7.86-45.71,2.65-5.68,10.86-21.28,28.05-31.02,12.09-6.86,22.94-7.51,28.71-8.28,17.19-2.32,61.18-15.77,82.82-64.84Zm19.53-71.29c-38.91,1.06-45.85,41.06-64.11,71.01-14.61,23.88-38.65,24.92-40.31,24.97h-.1C10.15,223.2-29.95,335.62,26.37,413.63c26.97,41.92,70.63,61.09,111.99,59.96,53.67-1.46,103.78-36.96,109.18-100.8h0c19.93-58.65,105.53-15.58,93.41-112.5-23.14,8.75-40.07,12.44-52.19,12.77-26.98,.73-31.85-18.13-27.99-30.95l-25.37-31.05c-20.51-52.62-2.68-69.18,10.16-86.16-6.4-1.49-32.54-3.87-37.62-3.63h0Z"/><path id="cordas" class="cls-2" d="M103.87,365.16l-2.97-3.25c-3.52-3.71-3.25-9.64,.56-13.17l65.75-61.02c3.8-3.52,9.64-3.25,13.17,.46l2.97,3.25c3.52,3.71,3.25,9.64-.56,13.17l-65.75,61.02c-3.71,3.52-9.64,3.25-13.17-.46h0Z"/><polygon id="braco" class="cls-2" points="239.3 255.5 223.65 236.65 365.87 104.18 382.89 121.88 239.3 255.5"/><path class="cls-1" d="M485.96,27.45l-16.94-18.24c-10.07-10.97-27.21-11.69-38.25-1.56l-68.38,63.39c-10.94,10.08-11.64,27.19-1.51,38.22l16.94,18.24c5.31,5.78,12.58,8.71,19.88,8.71,6.56,0,13.14-2.37,18.37-7.16l68.35-63.35c5.33-4.89,8.42-11.56,8.72-18.79,.29-7.2-2.23-14.09-7.17-19.47Z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="guitar_PCSX2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 492.66 473.14"><defs><style>.cls-1,.cls-2{fill:#fff;}.cls-2{stroke:#fff;stroke-miterlimit:10;stroke-width:20px;}</style></defs><g id="By_maxihplay"><path id="corpo" class="cls-1" d="M187.91,192.05c-.75,14.56,6.75,47.85,20.03,67.76,19.32,28.97,42.12,37.78,54.54,43.83-17.76,8.99-43.47,27.14-53.18,55-1.18,3.26-1.97,6.71-2.27,10.24-3.93,46.09-40.35,63.32-70.26,64.13-14.57,.4-29.14-2.93-42.13-9.52-14.19-7.25-26.02-17.98-35.01-32.02-.41-.58-.81-1.15-1.22-1.73-10.27-14.2-16.49-30.76-17.93-47.84-1.42-16.1,1.39-31.83,7.86-45.71,2.65-5.68,10.86-21.28,28.05-31.02,12.09-6.86,22.94-7.51,28.71-8.28,17.19-2.32,61.18-15.77,82.82-64.84Zm19.53-71.29c-38.91,1.06-45.85,41.06-64.11,71.01-14.61,23.88-38.65,24.92-40.31,24.97h-.1C9.65,222.7-30.45,335.12,25.87,413.13c26.97,41.92,70.63,61.09,111.99,59.96,53.67-1.46,103.78-36.96,109.18-100.8h0c19.93-58.65,105.53-15.58,93.41-112.5-23.14,8.75-40.07,12.44-52.19,12.77-26.98,.73-31.85-18.13-27.99-30.95l-25.37-31.05c-20.51-52.62-2.68-69.18,10.16-86.16-6.4-1.49-32.54-3.87-37.62-3.63h0Z"/><path id="cordas" class="cls-2" d="M103.37,364.66l-2.97-3.25c-3.52-3.71-3.25-9.64,.56-13.17l65.75-61.02c3.8-3.52,9.64-3.25,13.17,.46l2.97,3.25c3.52,3.71,3.25,9.64-.56,13.17l-65.75,61.02c-3.71,3.52-9.64,3.25-13.17-.46h0Z"/><polygon id="braco" class="cls-2" points="238.8 255 223.15 236.15 365.37 103.68 382.39 121.38 238.8 255"/><path class="cls-1" d="M485.46,26.95l-16.94-18.24c-10.07-10.97-27.21-11.69-38.25-1.56l-68.38,63.39c-10.94,10.08-11.64,27.19-1.51,38.22l16.94,18.24c5.31,5.78,12.58,8.71,19.88,8.71,6.56,0,13.14-2.37,18.37-7.16l68.35-63.35c5.33-4.89,8.42-11.56,8.72-18.79,.29-7.2-2.23-14.09-7.17-19.47Z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+148 -145
View File
@@ -1,147 +1,150 @@
<RCC>
<qresource>
<file>icons/AppIcon64.png</file>
<file>icons/applications-system-24.png</file>
<file>icons/black/index.theme</file>
<file>icons/black/svg/arrow-left-right-line.svg</file>
<file>icons/black/svg/artboard-2-line.svg</file>
<file>icons/black/svg/at.svg</file>
<file>icons/black/svg/band-aid-line.svg</file>
<file>icons/black/svg/brush-line.svg</file>
<file>icons/black/svg/cheats-line.svg</file>
<file>icons/black/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/black/svg/chip-line.svg</file>
<file>icons/black/svg/close-line.svg</file>
<file>icons/black/svg/controller-line.svg</file>
<file>icons/black/svg/controller-strike-line.svg</file>
<file>icons/black/svg/delete-back-2-line.svg</file>
<file>icons/black/svg/disc-2-line.svg</file>
<file>icons/black/svg/disc-eject-line.svg</file>
<file>icons/black/svg/discord.svg</file>
<file>icons/black/svg/door-open-line.svg</file>
<file>icons/black/svg/download-2-line.svg</file>
<file>icons/black/svg/eject-line.svg</file>
<file>icons/black/svg/emulation-line.svg</file>
<file>icons/black/svg/file-add-line.svg</file>
<file>icons/black/svg/file-line.svg</file>
<file>icons/black/svg/file-list-line.svg</file>
<file>icons/black/svg/file-reduce-line.svg</file>
<file>icons/black/svg/file-search-line.svg</file>
<file>icons/black/svg/file-settings-line.svg</file>
<file>icons/black/svg/filter-line.svg</file>
<file>icons/black/svg/flashlight-line.svg</file>
<file>icons/black/svg/floppy-in-line.svg</file>
<file>icons/black/svg/floppy-out-line.svg</file>
<file>icons/black/svg/folder-add-line.svg</file>
<file>icons/black/svg/folder-open-line.svg</file>
<file>icons/black/svg/folder-reduce-line.svg</file>
<file>icons/black/svg/folder-settings-line.svg</file>
<file>icons/black/svg/fullscreen-line.svg</file>
<file>icons/black/svg/function-line.svg</file>
<file>icons/black/svg/github.svg</file>
<file>icons/black/svg/global-line.svg</file>
<file>icons/black/svg/heart-circle-line.svg</file>
<file>icons/black/svg/heart-monitor-line.svg</file>
<file>icons/black/svg/image-fill.svg</file>
<file>icons/black/svg/interface-line.svg</file>
<file>icons/black/svg/keyboard-line.svg</file>
<file>icons/black/svg/lightbulb-line.svg</file>
<file>icons/black/svg/list-check.svg</file>
<file>icons/black/svg/login-box-line.svg</file>
<file>icons/black/svg/memcard-line.svg</file>
<file>icons/black/svg/mouse-line.svg</file>
<file>icons/black/svg/pause-line.svg</file>
<file>icons/black/svg/play-line.svg</file>
<file>icons/black/svg/price-tag-3-line.svg</file>
<file>icons/black/svg/refresh-line.svg</file>
<file>icons/black/svg/restart-line.svg</file>
<file>icons/black/svg/save-3-line.svg</file>
<file>icons/black/svg/screenshot-2-line.svg</file>
<file>icons/black/svg/settings-3-line.svg</file>
<file>icons/black/svg/shut-down-line.svg</file>
<file>icons/black/svg/tools-line.svg</file>
<file>icons/black/svg/trophy-line.svg</file>
<file>icons/black/svg/tv-2-line.svg</file>
<file>icons/black/svg/usb-fill.svg</file>
<file>icons/black/svg/volume-up-line.svg</file>
<file>icons/black/svg/warning-line.svg</file>
<file>icons/black/svg/window-2-line.svg</file>
<file>icons/black/svg/zoom-in-line.svg</file>
<file>icons/black/svg/zoom-out-line.svg</file>
<file>icons/logo.png</file>
<file>icons/media-optical-24.png</file>
<file>icons/media-optical-gear-24.png</file>
<file>icons/media-optical.png</file>
<file>icons/QT.png</file>
<file>icons/update.png</file>
<file>icons/white/index.theme</file>
<file>icons/white/svg/arrow-left-right-line.svg</file>
<file>icons/white/svg/artboard-2-line.svg</file>
<file>icons/white/svg/at.svg</file>
<file>icons/white/svg/band-aid-line.svg</file>
<file>icons/white/svg/brush-line.svg</file>
<file>icons/white/svg/cheats-line.svg</file>
<file>icons/white/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/white/svg/chip-line.svg</file>
<file>icons/white/svg/close-line.svg</file>
<file>icons/white/svg/controller-line.svg</file>
<file>icons/white/svg/controller-strike-line.svg</file>
<file>icons/white/svg/delete-back-2-line.svg</file>
<file>icons/white/svg/disc-2-line.svg</file>
<file>icons/white/svg/disc-eject-line.svg</file>
<file>icons/white/svg/discord.svg</file>
<file>icons/white/svg/door-open-line.svg</file>
<file>icons/white/svg/download-2-line.svg</file>
<file>icons/white/svg/eject-line.svg</file>
<file>icons/white/svg/emulation-line.svg</file>
<file>icons/white/svg/file-add-line.svg</file>
<file>icons/white/svg/file-line.svg</file>
<file>icons/white/svg/file-list-line.svg</file>
<file>icons/white/svg/file-reduce-line.svg</file>
<file>icons/white/svg/file-search-line.svg</file>
<file>icons/white/svg/file-settings-line.svg</file>
<file>icons/white/svg/filter-line.svg</file>
<file>icons/white/svg/flashlight-line.svg</file>
<file>icons/white/svg/floppy-in-line.svg</file>
<file>icons/white/svg/floppy-out-line.svg</file>
<file>icons/white/svg/folder-add-line.svg</file>
<file>icons/white/svg/folder-open-line.svg</file>
<file>icons/white/svg/folder-reduce-line.svg</file>
<file>icons/white/svg/folder-settings-line.svg</file>
<file>icons/white/svg/fullscreen-line.svg</file>
<file>icons/white/svg/function-line.svg</file>
<file>icons/white/svg/github.svg</file>
<file>icons/white/svg/global-line.svg</file>
<file>icons/white/svg/heart-circle-line.svg</file>
<file>icons/white/svg/heart-monitor-line.svg</file>
<file>icons/white/svg/image-fill.svg</file>
<file>icons/white/svg/interface-line.svg</file>
<file>icons/white/svg/keyboard-line.svg</file>
<file>icons/white/svg/lightbulb-line.svg</file>
<file>icons/white/svg/list-check.svg</file>
<file>icons/white/svg/login-box-line.svg</file>
<file>icons/white/svg/memcard-line.svg</file>
<file>icons/white/svg/mouse-line.svg</file>
<file>icons/white/svg/pause-line.svg</file>
<file>icons/white/svg/play-line.svg</file>
<file>icons/white/svg/price-tag-3-line.svg</file>
<file>icons/white/svg/refresh-line.svg</file>
<file>icons/white/svg/restart-line.svg</file>
<file>icons/white/svg/save-3-line.svg</file>
<file>icons/white/svg/screenshot-2-line.svg</file>
<file>icons/white/svg/settings-3-line.svg</file>
<file>icons/white/svg/shut-down-line.svg</file>
<file>icons/white/svg/tools-line.svg</file>
<file>icons/white/svg/trophy-line.svg</file>
<file>icons/white/svg/tv-2-line.svg</file>
<file>icons/white/svg/usb-fill.svg</file>
<file>icons/white/svg/volume-up-line.svg</file>
<file>icons/white/svg/warning-line.svg</file>
<file>icons/white/svg/window-2-line.svg</file>
<file>icons/white/svg/zoom-in-line.svg</file>
<file>icons/white/svg/zoom-out-line.svg</file>
<file>images/DrivingForce.png</file>
<file>images/dualshock-2.png</file>
<file>images/GTForce.png</file>
</qresource>
<qresource>
<file>icons/AppIcon64.png</file>
<file>icons/applications-system-24.png</file>
<file>icons/black/index.theme</file>
<file>icons/black/svg/arrow-left-right-line.svg</file>
<file>icons/black/svg/artboard-2-line.svg</file>
<file>icons/black/svg/at.svg</file>
<file>icons/black/svg/band-aid-line.svg</file>
<file>icons/black/svg/brush-line.svg</file>
<file>icons/black/svg/cheats-line.svg</file>
<file>icons/black/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/black/svg/chip-line.svg</file>
<file>icons/black/svg/close-line.svg</file>
<file>icons/black/svg/controller-line.svg</file>
<file>icons/black/svg/controller-strike-line.svg</file>
<file>icons/black/svg/delete-back-2-line.svg</file>
<file>icons/black/svg/disc-2-line.svg</file>
<file>icons/black/svg/disc-eject-line.svg</file>
<file>icons/black/svg/discord.svg</file>
<file>icons/black/svg/door-open-line.svg</file>
<file>icons/black/svg/download-2-line.svg</file>
<file>icons/black/svg/eject-line.svg</file>
<file>icons/black/svg/emulation-line.svg</file>
<file>icons/black/svg/file-add-line.svg</file>
<file>icons/black/svg/file-line.svg</file>
<file>icons/black/svg/file-list-line.svg</file>
<file>icons/black/svg/file-reduce-line.svg</file>
<file>icons/black/svg/file-search-line.svg</file>
<file>icons/black/svg/file-settings-line.svg</file>
<file>icons/black/svg/filter-line.svg</file>
<file>icons/black/svg/flashlight-line.svg</file>
<file>icons/black/svg/floppy-in-line.svg</file>
<file>icons/black/svg/floppy-out-line.svg</file>
<file>icons/black/svg/folder-add-line.svg</file>
<file>icons/black/svg/folder-open-line.svg</file>
<file>icons/black/svg/folder-reduce-line.svg</file>
<file>icons/black/svg/guitar-line.svg</file>
<file>icons/black/svg/folder-settings-line.svg</file>
<file>icons/black/svg/fullscreen-line.svg</file>
<file>icons/black/svg/function-line.svg</file>
<file>icons/black/svg/github.svg</file>
<file>icons/black/svg/global-line.svg</file>
<file>icons/black/svg/heart-circle-line.svg</file>
<file>icons/black/svg/heart-monitor-line.svg</file>
<file>icons/black/svg/image-fill.svg</file>
<file>icons/black/svg/interface-line.svg</file>
<file>icons/black/svg/keyboard-line.svg</file>
<file>icons/black/svg/lightbulb-line.svg</file>
<file>icons/black/svg/list-check.svg</file>
<file>icons/black/svg/login-box-line.svg</file>
<file>icons/black/svg/memcard-line.svg</file>
<file>icons/black/svg/mouse-line.svg</file>
<file>icons/black/svg/pause-line.svg</file>
<file>icons/black/svg/play-line.svg</file>
<file>icons/black/svg/price-tag-3-line.svg</file>
<file>icons/black/svg/refresh-line.svg</file>
<file>icons/black/svg/restart-line.svg</file>
<file>icons/black/svg/save-3-line.svg</file>
<file>icons/black/svg/screenshot-2-line.svg</file>
<file>icons/black/svg/settings-3-line.svg</file>
<file>icons/black/svg/shut-down-line.svg</file>
<file>icons/black/svg/tools-line.svg</file>
<file>icons/black/svg/trophy-line.svg</file>
<file>icons/black/svg/tv-2-line.svg</file>
<file>icons/black/svg/usb-fill.svg</file>
<file>icons/black/svg/volume-up-line.svg</file>
<file>icons/black/svg/warning-line.svg</file>
<file>icons/black/svg/window-2-line.svg</file>
<file>icons/black/svg/zoom-in-line.svg</file>
<file>icons/black/svg/zoom-out-line.svg</file>
<file>icons/logo.png</file>
<file>icons/media-optical-24.png</file>
<file>icons/media-optical-gear-24.png</file>
<file>icons/media-optical.png</file>
<file>icons/QT.png</file>
<file>icons/update.png</file>
<file>icons/white/index.theme</file>
<file>icons/white/svg/arrow-left-right-line.svg</file>
<file>icons/white/svg/artboard-2-line.svg</file>
<file>icons/white/svg/at.svg</file>
<file>icons/white/svg/band-aid-line.svg</file>
<file>icons/white/svg/brush-line.svg</file>
<file>icons/white/svg/cheats-line.svg</file>
<file>icons/white/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/white/svg/chip-line.svg</file>
<file>icons/white/svg/close-line.svg</file>
<file>icons/white/svg/controller-line.svg</file>
<file>icons/white/svg/controller-strike-line.svg</file>
<file>icons/white/svg/delete-back-2-line.svg</file>
<file>icons/white/svg/disc-2-line.svg</file>
<file>icons/white/svg/disc-eject-line.svg</file>
<file>icons/white/svg/discord.svg</file>
<file>icons/white/svg/door-open-line.svg</file>
<file>icons/white/svg/download-2-line.svg</file>
<file>icons/white/svg/eject-line.svg</file>
<file>icons/white/svg/emulation-line.svg</file>
<file>icons/white/svg/file-add-line.svg</file>
<file>icons/white/svg/file-line.svg</file>
<file>icons/white/svg/file-list-line.svg</file>
<file>icons/white/svg/file-reduce-line.svg</file>
<file>icons/white/svg/file-search-line.svg</file>
<file>icons/white/svg/file-settings-line.svg</file>
<file>icons/white/svg/filter-line.svg</file>
<file>icons/white/svg/flashlight-line.svg</file>
<file>icons/white/svg/floppy-in-line.svg</file>
<file>icons/white/svg/floppy-out-line.svg</file>
<file>icons/white/svg/folder-add-line.svg</file>
<file>icons/white/svg/folder-open-line.svg</file>
<file>icons/white/svg/folder-reduce-line.svg</file>
<file>icons/white/svg/folder-settings-line.svg</file>
<file>icons/white/svg/fullscreen-line.svg</file>
<file>icons/white/svg/function-line.svg</file>
<file>icons/white/svg/github.svg</file>
<file>icons/white/svg/global-line.svg</file>
<file>icons/white/svg/guitar-line.svg</file>
<file>icons/white/svg/heart-circle-line.svg</file>
<file>icons/white/svg/heart-monitor-line.svg</file>
<file>icons/white/svg/image-fill.svg</file>
<file>icons/white/svg/interface-line.svg</file>
<file>icons/white/svg/keyboard-line.svg</file>
<file>icons/white/svg/lightbulb-line.svg</file>
<file>icons/white/svg/list-check.svg</file>
<file>icons/white/svg/login-box-line.svg</file>
<file>icons/white/svg/memcard-line.svg</file>
<file>icons/white/svg/mouse-line.svg</file>
<file>icons/white/svg/pause-line.svg</file>
<file>icons/white/svg/play-line.svg</file>
<file>icons/white/svg/price-tag-3-line.svg</file>
<file>icons/white/svg/refresh-line.svg</file>
<file>icons/white/svg/restart-line.svg</file>
<file>icons/white/svg/save-3-line.svg</file>
<file>icons/white/svg/screenshot-2-line.svg</file>
<file>icons/white/svg/settings-3-line.svg</file>
<file>icons/white/svg/shut-down-line.svg</file>
<file>icons/white/svg/tools-line.svg</file>
<file>icons/white/svg/trophy-line.svg</file>
<file>icons/white/svg/tv-2-line.svg</file>
<file>icons/white/svg/usb-fill.svg</file>
<file>icons/white/svg/volume-up-line.svg</file>
<file>icons/white/svg/warning-line.svg</file>
<file>icons/white/svg/window-2-line.svg</file>
<file>icons/white/svg/zoom-in-line.svg</file>
<file>icons/white/svg/zoom-out-line.svg</file>
<file>images/DrivingForce.png</file>
<file>images/dualshock-2.png</file>
<file>images/GTForce.png</file>
<file>images/guitar.png</file>
</qresource>
</RCC>
+1 -1
View File
@@ -30,7 +30,7 @@
#include "IopHw.h"
#include "IopDma.h"
#include "VMManager.h"
#include "Sio.h"
#include "SIO/Sio.h"
#include "common/Error.h"
#include "common/FileSystem.h"
+200
View File
@@ -0,0 +1,200 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "CDVD/CDVDcommon.h"
#include "CDVD/IsoHasher.h"
#include "Host.h"
#include "common/Error.h"
#include "common/MD5Digest.h"
#include "common/StringUtil.h"
#include "fmt/core.h"
#include <algorithm>
IsoHasher::IsoHasher() = default;
IsoHasher::~IsoHasher()
{
Close();
}
std::string_view IsoHasher::GetTrackTypeString(u32 type)
{
switch (type)
{
case CDVD_AUDIO_TRACK:
return TRANSLATE_SV("CDVD", "Audio");
case CDVD_MODE1_TRACK:
return TRANSLATE_SV("CDVD", "Mode 1");
case CDVD_MODE2_TRACK:
return TRANSLATE_SV("CDVD", "Mode 2");
default:
return TRANSLATE_SV("CDVD", "Unknown");
}
}
bool IsoHasher::Open(std::string iso_path, Error* error)
{
Close();
CDVDsys_SetFile(CDVD_SourceType::Iso, std::move(iso_path));
CDVDsys_ChangeSource(CDVD_SourceType::Iso);
m_is_open = DoCDVDopen();
if (!m_is_open)
{
Error::SetString(error, "Failed to open CDVD.");
return false;
}
const s32 type = DoCDVDdetectDiskType();
switch (type)
{
case CDVD_TYPE_PSCD:
case CDVD_TYPE_PSCDDA:
case CDVD_TYPE_PS2CD:
case CDVD_TYPE_PS2CDDA:
m_is_cd = true;
break;
case CDVD_TYPE_PS2DVD:
m_is_cd = false;
break;
default:
Error::SetString(error, fmt::format("Unknown CDVD disk type {}", type));
return false;
}
cdvdTN tn;
if (CDVD->getTN(&tn) < 0)
{
Error::SetString(error, "Failed to get track count.");
return false;
}
for (u8 track = tn.strack; track <= tn.etrack; track++)
{
cdvdTD td, next_td;
if (CDVD->getTD(track, &td) < 0 || CDVD->getTD((track == tn.etrack) ? 0 : (track + 1), &next_td) < 0)
{
Error::SetString(error, fmt::format("Failed to get track range for {}", static_cast<unsigned>(track)));
return false;
}
// sanity check..
if (next_td.lsn < td.lsn)
{
Error::SetString(error,
fmt::format("Invalid track range for {} ({},{})", static_cast<unsigned>(track), td.lsn, next_td.lsn));
return false;
}
Track strack;
strack.number = track;
strack.type = td.type;
strack.start_lsn = td.lsn;
strack.sectors = next_td.lsn - td.lsn;
strack.size = static_cast<u64>(strack.sectors) * (m_is_cd ? 2352 : 2048);
m_tracks.push_back(std::move(strack));
}
return true;
}
void IsoHasher::Close()
{
if (!m_is_open)
return;
DoCDVDclose();
m_tracks.clear();
m_is_cd = false;
m_is_open = false;
}
void IsoHasher::ComputeHashes(ProgressCallback* callback)
{
callback->SetProgressRange(GetTrackCount());
callback->SetProgressValue(0);
callback->SetCancellable(true);
for (u32 index = 0; index < GetTrackCount(); index++)
{
Track& track = m_tracks[index];
if (!track.hash.empty())
{
callback->SetProgressValue(index + 1);
continue;
}
callback->PushState();
const bool result = ComputeTrackHash(track, callback);
callback->PopState();
if (!result)
break;
callback->SetProgressValue(index + 1);
callback->IncrementProgressValue();
}
callback->SetProgressValue(GetTrackCount());
}
bool IsoHasher::ComputeTrackHash(Track& track, ProgressCallback* callback)
{
// use 2048 byte reads for DVDs, otherwise 2352 raw.
const int read_mode = m_is_cd ? CDVD_MODE_2352 : CDVD_MODE_2048;
const u32 sector_size = m_is_cd ? 2352 : 2048;
std::vector<u8> sector_buffer(sector_size);
const u32 update_interval = std::max<u32>(track.sectors / 100u, 1u);
callback->SetFormattedStatusText("Computing hash for track %u...", track.number);
callback->SetProgressRange(track.sectors);
MD5Digest md5;
for (u32 i = 0; i < track.sectors; i++)
{
if (callback->IsCancelled())
return false;
const u32 lsn = track.start_lsn + i;
if (DoCDVDreadSector(sector_buffer.data(), lsn, read_mode) != 0)
{
callback->DisplayFormattedModalError("Read error at LSN %u", lsn);
return false;
}
md5.Update(sector_buffer.data(), sector_size);
if ((i % update_interval) == 0)
callback->SetProgressValue(i);
}
u8 digest[16];
md5.Final(digest);
track.hash =
fmt::format("{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], digest[8],
digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]);
callback->SetProgressValue(track.sectors);
return true;
}
+61
View File
@@ -0,0 +1,61 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
#include "common/ProgressCallback.h"
#include <string>
#include <vector>
class Error;
class IsoHasher
{
public:
struct Track
{
u32 number;
u32 type;
u32 start_lsn;
u32 sectors;
u64 size;
std::string hash;
};
public:
IsoHasher();
~IsoHasher();
static std::string_view GetTrackTypeString(u32 type);
const u32 GetTrackCount() const { return static_cast<u32>(m_tracks.size()); }
const Track& GetTrack(u32 n) const { return m_tracks.at(n); }
const std::vector<Track>& GetTracks() const { return m_tracks; }
bool IsCD() const { return m_is_cd; }
bool Open(std::string iso_path, Error* error = nullptr);
void Close();
void ComputeHashes(ProgressCallback* callback = ProgressCallback::NullProgressCallback);
private:
bool ComputeTrackHash(Track& track, ProgressCallback* callback);
std::vector<Track> m_tracks;
bool m_is_open = false;
bool m_is_cd = false;
};
+6
View File
@@ -30,6 +30,12 @@ IsoReader::IsoReader() = default;
IsoReader::~IsoReader() = default;
std::string_view IsoReader::RemoveVersionIdentifierFromPath(const std::string_view& path)
{
const std::string_view::size_type pos = path.find(';');
return (pos != std::string_view::npos) ? path.substr(0, pos) : path;
}
bool IsoReader::Open(Error* error)
{
if (!ReadPVD(error))
+2
View File
@@ -148,6 +148,8 @@ public:
IsoReader();
~IsoReader();
static std::string_view RemoveVersionIdentifierFromPath(const std::string_view& path);
const ISOPrimaryVolumeDescriptor& GetPVD() const { return m_pvd; }
// TODO: Eventually we'll want to pass a handle to the currently-open file here.
+28 -17
View File
@@ -120,14 +120,10 @@ set(pcsx2Sources
PINE.cpp
Mdec.cpp
Memory.cpp
MemoryCardFile.cpp
MemoryCardFolder.cpp
MemoryCardProtocol.cpp
MMI.cpp
MTGS.cpp
MTVU.cpp
MultipartFileReader.cpp
MultitapProtocol.cpp
Patch.cpp
Pcsx2Config.cpp
PerformanceMetrics.cpp
@@ -144,7 +140,13 @@ set(pcsx2Sources
Sif0.cpp
Sif1.cpp
sif2.cpp
Sio.cpp
SIO/Sio.cpp
SIO/Sio2.cpp
SIO/Sio0.cpp
SIO/Multitap/MultitapProtocol.cpp
SIO/Memcard/MemoryCardFile.cpp
SIO/Memcard/MemoryCardFolder.cpp
SIO/Memcard/MemoryCardProtocol.cpp
SourceLog.cpp
SPR.cpp
StateWrapper.cpp
@@ -205,11 +207,7 @@ set(pcsx2Headers
MTGS.h
MTVU.h
Memory.h
MemoryCardFile.h
MemoryCardFolder.h
MemoryCardProtocol.h
MemoryTypes.h
MultitapProtocol.h
Patch.h
PCSX2Base.h
PerformanceMetrics.h
@@ -222,7 +220,14 @@ set(pcsx2Headers
Sifcmd.h
Sif.h
SingleRegisterTypes.h
Sio.h
SIO/Sio.h
SIO/Sio2.h
SIO/Sio0.h
SIO/SioTypes.h
SIO/Multitap/MultitapProtocol.h
SIO/Memcard/MemoryCardFile.h
SIO/Memcard/MemoryCardFolder.h
SIO/Memcard/MemoryCardProtocol.h
SPR.h
StateWrapper.h
SysForwardDefs.h
@@ -247,6 +252,7 @@ set(pcsx2CDVDSources
CDVD/CDVDisoReader.cpp
CDVD/CDVDdiscThread.cpp
CDVD/InputIsoFile.cpp
CDVD/IsoHasher.cpp
CDVD/IsoReader.cpp
CDVD/OutputIsoFile.cpp
CDVD/ChunksCache.cpp
@@ -271,6 +277,7 @@ set(pcsx2CDVDHeaders
CDVD/GzippedFileReader.h
CDVD/ThreadedFileReader.h
CDVD/IsoFileFormats.h
CDVD/IsoHasher.h
CDVD/IsoReader.h
CDVD/zlib_indexed.h
)
@@ -475,15 +482,19 @@ endif()
# Host PAD
set(pcsx2PADSources
PAD/Host/KeyStatus.cpp
PAD/Host/PAD.cpp
PAD/Host/StateManagement.cpp
SIO/Pad/Pad.cpp
SIO/Pad/PadBase.cpp
SIO/Pad/PadDualshock2.cpp
SIO/Pad/PadGuitar.cpp
SIO/Pad/PadNotConnected.cpp
)
set(pcsx2PADHeaders
PAD/Host/Global.h
PAD/Host/KeyStatus.h
PAD/Host/PAD.h
PAD/Host/StateManagement.h
SIO/Pad/Pad.h
SIO/Pad/PadBase.h
SIO/Pad/PadDualshock2.h
SIO/Pad/PadGuitar.h
SIO/Pad/PadNotConnected.h
SIO/Pad/PadTypes.h
)
# GS sources
+9 -21
View File
@@ -30,9 +30,8 @@
#include "PerformanceMetrics.h"
#include "Patch.h"
#include "ps2/HwInternal.h"
#include "Sio.h"
#include "SIO/Sio.h"
#include "SPU2/spu2.h"
#include "PAD/Host/PAD.h"
#include "Recording/InputRecording.h"
#include "VMManager.h"
#include "VUmicro.h"
@@ -548,20 +547,6 @@ static __fi void DoFMVSwitch()
RendererSwitched = false;
}
// Convenience function to update UI thread and set patches.
static __fi void VSyncUpdateCore()
{
DoFMVSwitch();
VMManager::Internal::VSyncOnCPUThread();
}
static __fi void VSyncCheckExit()
{
if (VMManager::Internal::IsExecutionInterrupted())
Cpu->ExitExecution();
}
// Framelimiter - Measures the delta time between calls and stalls until a
// certain amount of time passes if such time hasn't passed yet.
static __fi void frameLimit()
@@ -606,14 +591,13 @@ static __fi void frameLimit()
static __fi void VSyncStart(u32 sCycle)
{
// Update vibration at the end of a frame.
VSyncUpdateCore();
PAD::Update();
// End-of-frame tasks.
DoFMVSwitch();
VMManager::Internal::VSyncOnCPUThread();
frameLimit(); // limit FPS
gsPostVsyncStart(); // MUST be after framelimit; doing so before causes funk with frame times!
VSyncCheckExit();
if(EmuConfig.Trace.Enabled && EmuConfig.Trace.EE.m_EnableAll)
SysTrace.EE.Counters.Write( " ================ EE COUNTER VSYNC START (frame: %d) ================", g_FrameCount );
@@ -644,6 +628,10 @@ static __fi void VSyncStart(u32 sCycle)
// Therefore, there needs to be some delay in order for it to see the interrupt flag before the interrupt is acknowledged, likely helped on real hardware by the pipelines.
// Without the patch and fixing this, the games have other issues, so I'm not going to rush to fix it.
// Refraction
// Bail out before the next frame starts if we're paused, or the CPU has changed
if (VMManager::Internal::IsExecutionInterrupted())
Cpu->ExitExecution();
}
static __fi void GSVSync()
+3 -1
View File
@@ -187,7 +187,9 @@ bool AdapterUtils::GetAdapter(const std::string& name, Adapter* adapter, Adapter
do
{
if (pAdapter->ifa_addr->sa_family == AF_INET && strcmp(pAdapter->ifa_name, name.c_str()) == 0)
if (pAdapter->ifa_addr != nullptr &&
pAdapter->ifa_addr->sa_family == AF_INET &&
strcmp(pAdapter->ifa_name, name.c_str()) == 0)
break;
pAdapter = pAdapter->ifa_next;
+52 -1
View File
@@ -2720,6 +2720,55 @@ void GSState::GrowVertexBuffer()
m_index.buff = index;
}
bool GSState::TrianglesAreQuads() const
{
// If this is a quad, there should only be two distinct values for both X and Y, which
// also happen to be the minimum/maximum bounds of the primitive.
const GSVertex* const v = m_vertex.buff;
for (u32 idx = 0; idx < m_index.tail; idx += 6)
{
const u16* const i = m_index.buff + idx;
// Degenerate triangles should've been culled already, so we can check indices.
u32 extra_verts = 0;
for (u32 j = 3; j < 6; j++)
{
const u16 idx = i[j];
if (idx != i[0] && idx != i[1] && idx != i[2])
extra_verts++;
}
if (extra_verts == 1)
return true;
// As a fallback, they might've used different vertices with a tri list, not strip.
// Note that this won't work unless the quad is axis-aligned.
u16 distinct_x_values[2] = {v[i[0]].XYZ.X};
u16 distinct_y_values[2] = {v[i[0]].XYZ.Y};
u32 num_distinct_x_values = 1, num_distinct_y_values = 1;
for (u32 j = 1; j < 6; j++)
{
const GSVertex& jv = v[i[j]];
if (jv.XYZ.X != distinct_x_values[0] && jv.XYZ.X != distinct_x_values[1])
{
if (num_distinct_x_values > 1)
return false;
distinct_x_values[num_distinct_x_values++] = jv.XYZ.X;
}
if (jv.XYZ.Y != distinct_y_values[0] && jv.XYZ.Y != distinct_y_values[1])
{
if (num_distinct_y_values > 1)
return false;
distinct_y_values[num_distinct_y_values++] = jv.XYZ.Y;
}
}
}
return true;
}
GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
{
// Either 1 triangle or 1 line or 3 POINTs
@@ -2727,7 +2776,9 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
if (m_vertex.next < 4)
return PRIM_OVERLAP_NO;
if (m_vt.m_primclass != GS_SPRITE_CLASS)
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
return (m_index.tail == 6 && TrianglesAreQuads()) ? PRIM_OVERLAP_NO : PRIM_OVERLAP_UNKNOW;
else if (m_vt.m_primclass != GS_SPRITE_CLASS)
return PRIM_OVERLAP_UNKNOW; // maybe, maybe not
// Check intersection of sprite primitive only
+1 -1
View File
@@ -228,7 +228,6 @@ public:
bool m_isPackedUV_HackFlag = false;
bool m_channel_shuffle = false;
u8 m_scanmask_used = 0;
u8 m_force_preload = 0;
u32 m_dirty_gs_regs = 0;
int m_backed_up_ctx = 0;
std::vector<GSUploadQueue> m_draw_transfers;
@@ -419,6 +418,7 @@ public:
void DumpVertices(const std::string& filename);
bool TrianglesAreQuads() const;
PRIM_OVERLAP PrimitiveOverlap();
GIFRegTEX0 GetTex0Layer(u32 lod);
};
+95 -12
View File
@@ -216,20 +216,103 @@ bool GSHwHack::GSC_Tekken5(GSRendererHW& r, int& skip)
bool GSHwHack::GSC_BurnoutGames(GSRendererHW& r, int& skip)
{
if (RFBW == 2 && std::abs(static_cast<int>(RFBP) - static_cast<int>(RZBP)) <= static_cast<int>(BLOCKS_PER_PAGE) && (!RTME || RTPSM != PSMT8))
{
skip = 2;
return true;
}
// Burnout has a... creative way of achieving its bloom effect, to avoid horizontal page breaks.
// First they double strip clear a single page column to (191, 191, 191), then blend the main
// framebuffer into this column, with (Cs - Cd) * 2. So anything lower than 191 clamps to zero,
// and anything larger boosts up a bit. Then that column gets downsampled to half size, makes
// sense right? The fun bit is when they move to the next page, instead of being sensible and
// using another double strip clear, they write Z to the next page as part of the blended draw,
// setting it to 191 (in Z24 terms). Then the buffers are swapped for the next column, 0x1a40
// and 0x1a60 in US.
//
// We _could_ handle that, except for the fact that instead of pointing the texture at 0x1a60
// for the downsample of the second column, they point it at 0x1a40, and offset the coordinates
// by a page. This would need "tex outside RT", and no way that's happening.
//
// So, I present to you, dear reader, the first state machine within a CRC hack, in all its
// disgusting glory. This effectively reduces the multi-pass effect to a single pass, by replacing
// the column-wide draws with a fullscreen sprite, and skipping the extra passes.
//
// After this, they do a blur on the buffer, which is fine, because all the buffer swap BS has
// finished, so we can return to normal.
// We don't check if we already have a skip here, because it gets confused when auto flush is on.
if (RTME && (RFBP == 0x01dc0 || RFBP == 0x01c00 || RFBP == 0x01f00 || RFBP == 0x01d40 || RFBP == 0x02200 || RFBP == 0x02000) && RFPSM == RTPSM && (RTBP0 == 0x01dc0 || RTBP0 == 0x01c00 || RTBP0 == 0x01f00 || RTBP0 == 0x01d40 || RTBP0 == 0x02200 || RTBP0 == 0x02000) && RTPSM == PSMCT32)
static u32 state = 0;
static GIFRegTEX0 main_fb;
static GSVector2i main_fb_size;
static GIFRegTEX0 downsample_fb;
static GIFRegTEX0 bloom_fb;
switch (state)
{
// 0x01dc0 01c00(MP) ntsc, 0x01f00 0x01d40(MP) ntsc progressive, 0x02200(MP) pal.
// Yellow stripes.
// Multiplayer tested only on Takedown.
skip = 3;
return true;
case 0: // waiting for double striped clear
{
if (RFBW != 2 || RFBP != RZBP || RTME)
break;
// Need a backed up context to grab the framebuffer.
if (r.m_backed_up_ctx < 0)
break;
// Next draw should contain our source.
GSTextureCache::Target* tgt = g_texture_cache->LookupTarget(r.m_env.CTXT[r.m_backed_up_ctx].TEX0,
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget);
if (!tgt)
break;
// Clear temp render target.
main_fb = tgt->m_TEX0;
main_fb_size = tgt->GetUnscaledSize();
r.m_cached_ctx.FRAME.FBW = tgt->m_TEX0.TBW;
r.m_cached_ctx.ZBUF.ZMSK = true;
r.ReplaceVerticesWithSprite(GSVector4i::loadh(main_fb_size), main_fb_size);
bloom_fb = GIFRegTEX0::Create(RFBP, RFBW, RFPSM);
state = 1;
GL_INS("GSC_BurnoutGames(): Initial double-striped clear.");
return true;
}
case 1: // reverse blend to extract bright pixels
{
r.ReplaceVerticesWithSprite(GSVector4i::loadh(main_fb_size), main_fb_size);
r.m_cached_ctx.ZBUF.ZMSK = true;
state = 2;
GL_INS("GSC_BurnoutGames(): Extract Bright Pixels.");
return true;
}
case 2: // downsample
{
const GSVector4i downsample_rect = GSVector4i(0, 0, main_fb_size.x / 2, main_fb_size.y / 2);
r.ReplaceVerticesWithSprite(downsample_rect, GSVector4i::loadh(main_fb_size), main_fb_size, downsample_rect);
downsample_fb = GIFRegTEX0::Create(RFBP, RFBW, RFPSM);
state = 3;
GL_INS("GSC_BurnoutGames(): Downsampling.");
return true;
}
case 3:
{
// Kill the downsample source, because we made it way larger than it was supposed to be.
// That way we don't risk confusing any other targets.
g_texture_cache->InvalidateVideoMemType(GSTextureCache::RenderTarget, bloom_fb.TBP0);
state = 4;
[[fallthrough]];
}
case 4: // Skip until it's downsampled again.
{
if (!RTME || RTBP0 != downsample_fb.TBP0)
{
GL_INS("GSC_BurnoutGames(): Skipping extra pass.");
skip = 1;
return true;
}
// Finally, we're done, let the game take over.
GL_INS("GSC_BurnoutGames(): Bloom effect done.");
skip = 0;
state = 0;
return true;
}
}
return GSC_BlackAndBurnoutSky(r, skip);
+97 -115
View File
@@ -95,15 +95,11 @@ float GSRendererHW::GetUpscaleMultiplier()
void GSRendererHW::Reset(bool hardware_reset)
{
// Force targets to preload for 2 frames (for 30fps games).
static constexpr u8 TARGET_PRELOAD_FRAMES = 2;
// Read back on CSR Reset, conditional downloading on render swap etc handled elsewhere.
if (!hardware_reset)
g_texture_cache->ReadbackAll();
g_texture_cache->RemoveAll();
m_force_preload = TARGET_PRELOAD_FRAMES;
GSRenderer::Reset(hardware_reset);
}
@@ -117,22 +113,10 @@ void GSRendererHW::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
void GSRendererHW::VSync(u32 field, bool registers_written, bool idle_frame)
{
if (m_force_preload > 0)
{
m_force_preload--;
if (m_force_preload == 0)
{
for (auto iter = m_draw_transfers.rbegin(); iter != m_draw_transfers.rend(); iter++)
{
if ((s_n - iter->draw) > 5)
{
m_draw_transfers.erase(m_draw_transfers.begin(), std::next(iter).base());
break;
}
}
}
}
else if (!idle_frame)
if (GSConfig.LoadTextureReplacements)
GSTextureReplacements::ProcessAsyncLoadedTextures();
if (!idle_frame)
{
// If it did draws very recently, we should keep the recent stuff in case it hasn't been preloaded/used yet.
// Rocky Legend does this with the main menu FMV's.
@@ -151,24 +135,16 @@ void GSRendererHW::VSync(u32 field, bool registers_written, bool idle_frame)
{
m_draw_transfers.clear();
}
}
if (GSConfig.LoadTextureReplacements)
GSTextureReplacements::ProcessAsyncLoadedTextures();
// Don't age the texture cache when no draws or EE writes have occurred.
// Xenosaga needs its targets kept around while it's loading, because it uses them for a fade transition.
if (idle_frame)
{
GL_INS("No draws or transfers, not aging TC");
g_texture_cache->IncAge();
}
else
{
g_texture_cache->IncAge();
// Don't age the texture cache when no draws or EE writes have occurred.
// Xenosaga needs its targets kept around while it's loading, because it uses them for a fade transition.
GL_INS("No draws or transfers, not aging TC");
}
GSRenderer::VSync(field, registers_written, idle_frame);
if (g_texture_cache->GetHashCacheMemoryUsage() > 1024 * 1024 * 1024)
{
Host::AddKeyedOSDMessage("HashCacheOverflow",
@@ -182,6 +158,8 @@ void GSRendererHW::VSync(u32 field, bool registers_written, bool idle_frame)
m_skip = 0;
m_skip_offset = 0;
GSRenderer::VSync(field, registers_written, idle_frame);
}
GSTexture* GSRendererHW::GetOutput(int i, float& scale, int& y_offset)
@@ -1089,7 +1067,12 @@ bool GSRendererHW::CheckNextDrawForSplitClear(const GSVector4i& r, u32* pages_co
{
const u32 end_block = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, r);
if (pages_covered_by_this_draw)
*pages_covered_by_this_draw = ((end_block - m_cached_ctx.FRAME.Block()) + (BLOCKS_PER_PAGE)) / BLOCKS_PER_PAGE;
{
if (end_block < m_cached_ctx.FRAME.Block())
*pages_covered_by_this_draw = (((MAX_BLOCKS - end_block) + m_cached_ctx.FRAME.Block()) + (BLOCKS_PER_PAGE)) / BLOCKS_PER_PAGE;
else
*pages_covered_by_this_draw = ((end_block - m_cached_ctx.FRAME.Block()) + (BLOCKS_PER_PAGE)) / BLOCKS_PER_PAGE;
}
// must be changing FRAME
if (m_backed_up_ctx < 0 || (m_dirty_gs_regs & (1u << DIRTY_REG_FRAME)) == 0)
@@ -1848,7 +1831,7 @@ void GSRendererHW::Draw()
}
const bool process_texture = PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_cached_ctx.TEX0.TCC);
const u32 frame_end_bp = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r);
const u32 frame_end_bp = GSLocalMemory::GetUnwrappedEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r);
bool preserve_rt_color =
((!no_rt && (!IsDiscardingDstColor() || !PrimitiveCoversWithoutGaps() || !all_depth_tests_pass)) || // Using Dst Color or draw has gaps
(process_texture && m_cached_ctx.TEX0.TBP0 >= m_cached_ctx.FRAME.Block() &&
@@ -2485,7 +2468,7 @@ void GSRendererHW::Draw()
// Grandia Xtreme, Onimusha Warlord.
if (!new_rect && new_height && old_end_block != rt->m_end_block)
{
old_rt = g_texture_cache->FindTargetOverlap(old_end_block, rt->m_end_block, GSTextureCache::RenderTarget, m_cached_ctx.FRAME.PSM);
old_rt = g_texture_cache->FindTargetOverlap(rt, GSTextureCache::RenderTarget, m_cached_ctx.FRAME.PSM);
if (old_rt && old_rt != rt && GSUtil::HasSharedBits(old_rt->m_TEX0.PSM, rt->m_TEX0.PSM))
{
@@ -2523,7 +2506,7 @@ void GSRendererHW::Draw()
if (!new_rect && new_height && old_end_block != ds->m_end_block)
{
old_ds = g_texture_cache->FindTargetOverlap(old_end_block, ds->m_end_block, GSTextureCache::DepthStencil, m_cached_ctx.ZBUF.PSM);
old_ds = g_texture_cache->FindTargetOverlap(ds, GSTextureCache::DepthStencil, m_cached_ctx.ZBUF.PSM);
if (old_ds && old_ds != ds && GSUtil::HasSharedBits(old_ds->m_TEX0.PSM, ds->m_TEX0.PSM))
{
@@ -3362,7 +3345,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
return true;
}
void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool& blending_alpha_pass)
void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, bool& DATE_PRIMID, bool& DATE_BARRIER, bool& blending_alpha_pass)
{
// AA1: Don't enable blending on AA1, not yet implemented on hardware mode,
// it requires coverage sample so it's safer to turn it off instead.
@@ -3394,6 +3377,13 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
m_conf.ps.blend_c = ALPHA.C;
m_conf.ps.blend_d = ALPHA.D;
#ifdef ENABLE_OGL_DEBUG
static constexpr const char* col[3] = {"Cs", "Cd", "0"};
static constexpr const char* alpha[3] = {"As", "Ad", "Af"};
GL_INS("EmulateBlending(): (%s - %s) * %s + %s", col[ALPHA.A], col[ALPHA.B], alpha[ALPHA.C], col[ALPHA.D]);
GL_INS("Draw AlphaMinMax: %d-%d, RT AlphaMinMax: %d-%d", GetAlphaMinMax().min, GetAlphaMinMax().max, rt_alpha_min, rt_alpha_max);
#endif
// When AA1 is enabled and Alpha Blending is disabled, alpha blending done with coverage instead of alpha.
// We use a COV value of 128 (full coverage) in triangles (except the edge geometry, which we can't do easily).
if (IsCoverageAlpha())
@@ -3413,10 +3403,12 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
const bool alpha_c0_one = (m_conf.ps.blend_c == 0 && (GetAlphaMinMax().min == 128) && (GetAlphaMinMax().max == 128));
const bool alpha_c0_high_min_one = (m_conf.ps.blend_c == 0 && GetAlphaMinMax().min > 128);
const bool alpha_c0_high_max_one = (m_conf.ps.blend_c == 0 && GetAlphaMinMax().max > 128);
const bool alpha_c1_zero = (m_conf.ps.blend_c == 1 && rt_alpha_min == 0 && rt_alpha_max == 0);
const bool alpha_c1_one = (m_conf.ps.blend_c == 1 && rt_alpha_min == 128 && rt_alpha_max == 128);
const bool alpha_c2_zero = (m_conf.ps.blend_c == 2 && AFIX == 0u);
const bool alpha_c2_one = (m_conf.ps.blend_c == 2 && AFIX == 128u);
const bool alpha_c2_high_one = (m_conf.ps.blend_c == 2 && AFIX > 128u);
const bool alpha_one = alpha_c0_one || alpha_c2_one;
const bool alpha_one = alpha_c0_one || alpha_c1_one || alpha_c2_one;
// Optimize blending equations, must be done before index calculation
if ((m_conf.ps.blend_a == m_conf.ps.blend_b) || ((m_conf.ps.blend_b == m_conf.ps.blend_d) && alpha_one))
@@ -3435,7 +3427,7 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
m_conf.ps.blend_b = 0;
m_conf.ps.blend_c = 0;
}
else if (alpha_c0_zero || alpha_c2_zero)
else if (alpha_c0_zero || alpha_c1_zero || alpha_c2_zero)
{
// C == 0.0f
// (A - B) * C, result will be 0.0f so set A B to Cs
@@ -3461,7 +3453,8 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
// Ad cases, alpha write is masked, one barrier is enough, for d3d11 read the fb
// Replace Ad with As, blend flags will be used from As since we are chaging the blend_index value.
// Must be done before index calculation, after blending equation optimizations
bool blend_ad_alpha_masked = (m_conf.ps.blend_c == 1) && (m_cached_ctx.FRAME.FBMSK & 0xFF000000) == 0xFF000000;
const bool blend_ad = m_conf.ps.blend_c == 1;
bool blend_ad_alpha_masked = blend_ad && (m_cached_ctx.FRAME.FBMSK & 0xFF000000) == 0xFF000000;
if (((GSConfig.AccurateBlendingUnit >= AccBlendLevel::Basic) || (COLCLAMP.CLAMP == 0))
&& g_gs_device->Features().texture_barrier && blend_ad_alpha_masked)
m_conf.ps.blend_c = 0;
@@ -3523,13 +3516,15 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
const bool prefer_sw_blend = m_conf.require_full_barrier || (one_barrier && m_prim_overlap == PRIM_OVERLAP_NO);
// SW Blend is (nearly) free. Let's use it.
const bool no_prim_overlap = features.framebuffer_fetch ? (m_vt.m_primclass == GS_SPRITE_CLASS) : (m_prim_overlap == PRIM_OVERLAP_NO);
const bool no_prim_overlap = (m_prim_overlap == PRIM_OVERLAP_NO);
const bool impossible_or_free_blend = (blend_flag & BLEND_A_MAX) // Impossible blending
|| blend_non_recursive // Free sw blending, doesn't require barriers or reading fb
|| accumulation_blend // Mix of hw/sw blending
|| no_prim_overlap // Blend can be done in a single draw
|| (m_conf.require_full_barrier) // Another effect (for example fbmask) already requires a full barrier
|| (one_barrier && features.framebuffer_fetch); // On fbfetch, one barrier is like full barrier
|| (m_conf.require_full_barrier) // Another effect (for example fbmask) already requires a full barrier
// Blend can be done in a single draw, and we already need a barrier
// On fbfetch, one barrier is like full barrier
|| (one_barrier && (no_prim_overlap || features.framebuffer_fetch))
|| ((alpha_c2_high_one || alpha_c0_high_max_one) && no_prim_overlap);
switch (GSConfig.AccurateBlendingUnit)
{
@@ -3557,6 +3552,8 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
// If prims don't overlap prefer full sw blend on blend_ad_alpha_masked cases.
accumulation_blend &= !(prefer_sw_blend || (blend_ad_alpha_masked && m_prim_overlap == PRIM_OVERLAP_NO));
sw_blending |= impossible_or_free_blend;
// Ad blends are completely wrong without sw blend (Ad is 0.5 not 1 for 128). We can spare a barrier for it.
sw_blending |= blend_ad && no_prim_overlap;
// Try to do hw blend for clr2 case.
sw_blending &= !clr_blend1_2;
// Do not run BLEND MIX if sw blending is already present, it's less accurate
@@ -4429,7 +4426,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c
}
bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextureCache::Source* tex,
const TextureMinMaxResult& tmm) const
const TextureMinMaxResult& tmm)
{
// Minimum blending or no barriers -> we can't use tex-is-fb.
if (GSConfig.AccurateBlendingUnit == AccBlendLevel::Minimum || !g_gs_device->Features().texture_barrier)
@@ -4456,7 +4453,7 @@ bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextu
}
else if (clamp == CLAMP_REGION_REPEAT)
{
const u32 req_tbits = (tmax > 1) ? std::bit_ceil(static_cast<u32>(tmax - 1) - 1) : 0x1;
const u32 req_tbits = (tmax > 1) ? (std::bit_ceil(static_cast<u32>(tmax - 1)) - 1) : 0x1;
if ((min & req_tbits) != req_tbits)
{
GL_CACHE("Can't use tex-is-fb because of REGION_REPEAT [%d, %d] with TMM of [%d, %d] and tbits of %d",
@@ -4477,21 +4474,8 @@ bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextu
// Texture is actually the frame buffer. Stencil emulation to compute shadow (Jak series/tri-ace game)
// Will hit the "m_ps_sel.tex_is_fb = 1" path in the draw
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
{
// This pattern is used by several games to emulate a stencil (shadow)
// Ratchet & Clank, Jak do alpha integer multiplication (tfx) which is mostly equivalent to +1/-1
// Tri-Ace (Star Ocean 3/RadiataStories/VP2) uses a palette to handle the +1/-1
if (m_cached_ctx.FRAME.FBMSK == 0x00FFFFFF)
{
GL_CACHE("Tex-is-fb hack for Jak");
return true;
}
GL_CACHE("Triangle draw, not using tex-is-fb");
return false;
}
else if (m_vt.m_primclass == GS_SPRITE_CLASS)
const bool is_quads = (m_vt.m_primclass == GS_SPRITE_CLASS || m_prim_overlap == PRIM_OVERLAP_NO);
if (is_quads)
{
// No bilinear for tex-is-fb.
if (m_vt.IsLinear())
@@ -4524,6 +4508,21 @@ bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextu
return false;
}
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
{
// This pattern is used by several games to emulate a stencil (shadow)
// Ratchet & Clank, Jak do alpha integer multiplication (tfx) which is mostly equivalent to +1/-1
// Tri-Ace (Star Ocean 3/RadiataStories/VP2) uses a palette to handle the +1/-1
if (m_cached_ctx.FRAME.FBMSK == 0x00FFFFFF)
{
GL_CACHE("Tex-is-fb hack for Jak");
return true;
}
GL_CACHE("Triangle draw, not using tex-is-fb");
return false;
}
return false;
}
@@ -4622,14 +4621,12 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
// Upscaling hack to avoid various line/grid issues
MergeSprite(tex);
const GSDevice::FeatureSupport features(g_gs_device->Features());
if (!features.framebuffer_fetch)
m_prim_overlap = PrimitiveOverlap();
else
m_prim_overlap = PRIM_OVERLAP_UNKNOW;
m_prim_overlap = PrimitiveOverlap();
EmulateTextureShuffleAndFbmask(rt);
const GSDevice::FeatureSupport features = g_gs_device->Features();
// DATE: selection of the algorithm. Must be done before blending because GL42 is not compatible with blending
if (DATE)
{
@@ -4726,8 +4723,20 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
}
// Blend
int blend_alpha_min = 0, blend_alpha_max = 255;
if (rt)
{
blend_alpha_min = rt->m_alpha_min;
blend_alpha_max = rt->m_alpha_max;
const bool is_24_bit = (GSLocalMemory::m_psm[rt->m_TEX0.PSM].trbpp == 24);
if (is_24_bit)
{
// C24/Z24 - alpha is 1.
blend_alpha_min = 128;
blend_alpha_max = 128;
}
if (!m_channel_shuffle && !m_texture_shuffle)
{
const int fba_value = m_prev_env.CTXT[m_prev_env.PRIM.CTXT].FBA.FBA * 128;
@@ -4749,7 +4758,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
rt->m_alpha_max |= std::max(GetAlphaMinMax().max | fba_value, rt->m_alpha_max);
rt->m_alpha_min = std::min(GetAlphaMinMax().min | fba_value, rt->m_alpha_min);
}
else
else if (!is_24_bit)
{
// If both are zero then we probably don't know what the alpha is.
if (rt->m_alpha_max == 0 && rt->m_alpha_min == 0)
@@ -4764,6 +4773,16 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
rt->m_alpha_max = 255;
rt->m_alpha_min = 0;
}
GL_INS("RT Alpha Range: %d-%d => %d-%d", blend_alpha_min, blend_alpha_max, rt->m_alpha_min, rt->m_alpha_max);
// If there's no overlap, the values in the RT before FB write will be the old values.
if (m_prim_overlap != PRIM_OVERLAP_NO)
{
// Otherwise, it may be a mix of the old/new values.
blend_alpha_min = std::min(blend_alpha_min, rt->m_alpha_min);
blend_alpha_max = std::max(blend_alpha_max, rt->m_alpha_max);
}
}
// Not gonna spend too much time with this, it's not likely to be used much, can't be less accurate than it was.
@@ -4771,12 +4790,13 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
{
ds->m_alpha_max = std::max(ds->m_alpha_max, static_cast<int>(m_vt.m_max.p.z) >> 24);
ds->m_alpha_min = std::min(ds->m_alpha_min, static_cast<int>(m_vt.m_min.p.z) >> 24);
GL_INS("New DS Alpha Range: %d-%d", ds->m_alpha_min, ds->m_alpha_max);
}
bool blending_alpha_pass = false;
if ((!IsOpaque() || m_context->ALPHA.IsBlack()) && rt && (m_conf.colormask.wrgba & 0x7))
{
EmulateBlending(DATE_PRIMID, DATE_BARRIER, blending_alpha_pass);
EmulateBlending(blend_alpha_min, blend_alpha_max, DATE_PRIMID, DATE_BARRIER, blending_alpha_pass);
}
else
{
@@ -5221,12 +5241,12 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
if (HasEEUpload(r))
return CLUTDrawTestResult::CLUTDrawOnCPU;
GSTextureCache::Target* tgt = g_texture_cache->GetExactTarget(
m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.TBW, GSTextureCache::RenderTarget, m_cached_ctx.TEX0.TBP0);
const GSTextureCache::Target* tgt = g_texture_cache->FindOverlappingTarget(
m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.TBW, m_cached_ctx.TEX0.PSM, r);
if (tgt)
{
bool is_dirty = false;
for (GSDirtyRect& rc : tgt->m_dirty)
for (const GSDirtyRect& rc : tgt->m_dirty)
{
if (!rc.GetDirtyRect(m_cached_ctx.TEX0).rintersect(r).rempty())
{
@@ -5454,7 +5474,8 @@ bool GSRendererHW::DetectDoubleHalfClear(bool& no_rt, bool& no_ds)
// GTA: LCS does this setup, along with a few other games. Thankfully if it's a zero clear, we'll clear both
// separately, and the end result is the same because it gets invalidated. That's better than falsely detecting
// double half clears, and ending up with 1024 high render targets which really shouldn't be.
if (frame_psm.fmt != zbuf_psm.fmt && m_cached_ctx.FRAME.FBMSK != ((zbuf_psm.fmt == 1) ? 0xFF000000u : 0))
if ((frame_psm.fmt != zbuf_psm.fmt && m_cached_ctx.FRAME.FBMSK != ((zbuf_psm.fmt == 1) ? 0xFF000000u : 0)) ||
!GSUtil::HasCompatibleBits(m_cached_ctx.FRAME.PSM & ~0x30, m_cached_ctx.ZBUF.PSM & ~0x30)) // Bit depth is not the same (i.e. 32bit + 16bit).
{
GL_INS("Inconsistent FRAME [%s, %08x] and ZBUF [%s] formats, not using double-half clear.",
psm_str(m_cached_ctx.FRAME.PSM), m_cached_ctx.FRAME.FBMSK, psm_str(m_cached_ctx.ZBUF.PSM));
@@ -5814,52 +5835,13 @@ bool GSRendererHW::PrimitiveCoversWithoutGaps()
if (m_vt.m_primclass == GS_POINT_CLASS)
{
m_primitive_covers_without_gaps = true;
return true;
m_primitive_covers_without_gaps = (m_vertex.next < 2);
return m_primitive_covers_without_gaps.value();
}
else if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
{
if (m_index.tail != 6)
{
m_primitive_covers_without_gaps = false;
return false;
}
// If this is a quad, there should only be two distinct values for both X and Y, which
// also happen to be the minimum/maximum bounds of the primitive.
const GSVertex* const v = m_vertex.buff;
const u16* const i = m_index.buff;
u16 distinct_x_values[2] = {v[i[0]].XYZ.X};
u16 distinct_y_values[2] = {v[i[0]].XYZ.Y};
u32 num_distinct_x_values = 1, num_distinct_y_values = 1;
for (u32 j = 1; j < 6; j++)
{
const GSVertex& jv = v[i[j]];
if (jv.XYZ.X != distinct_x_values[0] && jv.XYZ.X != distinct_x_values[1])
{
if (num_distinct_x_values > 1)
{
m_primitive_covers_without_gaps = false;
return false;
}
distinct_x_values[num_distinct_x_values++] = jv.XYZ.X;
}
if (jv.XYZ.Y != distinct_y_values[0] && jv.XYZ.Y != distinct_y_values[1])
{
if (num_distinct_y_values > 1)
{
m_primitive_covers_without_gaps = false;
return false;
}
distinct_y_values[num_distinct_y_values++] = jv.XYZ.Y;
}
}
m_primitive_covers_without_gaps = true;
return true;
m_primitive_covers_without_gaps = (m_index.tail == 6 && TrianglesAreQuads());
return m_primitive_covers_without_gaps.value();
}
else if (m_vt.m_primclass != GS_SPRITE_CLASS)
{
+2 -2
View File
@@ -86,7 +86,7 @@ private:
void SetupIA(float target_scale, float sx, float sy);
void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt);
bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only);
void EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool& blending_alpha_pass);
void EmulateBlending(int rt_alpha_min, int rt_alpha_max, bool& DATE_PRIMID, bool& DATE_BARRIER, bool& blending_alpha_pass);
void EmulateTextureSampler(const GSTextureCache::Target* rt, const GSTextureCache::Target* ds,
GSTextureCache::Source* tex, const TextureMinMaxResult& tmm, GSTexture*& src_copy);
@@ -94,7 +94,7 @@ private:
const GSTextureCache::Source* tex, const TextureMinMaxResult& tmm, GSTextureCache::SourceRegion& source_region,
bool& target_region, GSVector2i& unscaled_size, float& scale, GSTexture*& src_copy);
bool CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextureCache::Source* tex,
const TextureMinMaxResult& tmm) const;
const TextureMinMaxResult& tmm);
void EmulateZbuffer(const GSTextureCache::Target* ds);
void EmulateATST(float& AREF, GSHWDrawConfig::PSSelector& ps, bool pass_2);
+54 -17
View File
@@ -1222,14 +1222,13 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
return src;
}
GSTextureCache::Target* GSTextureCache::FindTargetOverlap(u32 bp, u32 end_block, int type, int psm)
GSTextureCache::Target* GSTextureCache::FindTargetOverlap(Target* target, int type, int psm)
{
u32 end_block_bp = end_block < bp ? (MAX_BP + 1) : end_block;
for (auto t : m_dst[type])
{
// Only checks that the texure starts at the requested bp, which shares data. Size isn't considered.
if (t->m_TEX0.TBP0 >= bp && t->m_TEX0.TBP0 < end_block_bp && GSUtil::HasCompatibleBits(t->m_TEX0.PSM, psm))
if (t != target && t->m_TEX0.TBW == target->m_TEX0.TBW && t->m_TEX0.TBP0 >= target->m_TEX0.TBP0 &&
t->UnwrappedEndBlock() <= target->UnwrappedEndBlock() && GSUtil::HasCompatibleBits(t->m_TEX0.PSM, psm))
return t;
}
return nullptr;
@@ -1640,7 +1639,6 @@ void GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
if (TEX0.TBW > 0 && supported_fmt)
{
const bool forced_preload = GSRendererHW::GetInstance()->m_force_preload > 0;
const GSVector4i newrect = GSVector4i::loadh(size);
const u32 rect_end = GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, newrect);
@@ -1648,7 +1646,7 @@ void GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
rgba._u32 = GSUtil::GetChannelMask(TEX0.PSM);
dst->UpdateValidity(GSVector4i::loadh(valid_size));
if (!is_frame && !forced_preload && !preload)
if (!is_frame && !preload)
{
if (preserve_target || !draw_rect.eq(dst->m_valid))
{
@@ -1657,9 +1655,7 @@ void GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
for (iter = GSRendererHW::GetInstance()->m_draw_transfers.begin(); iter != GSRendererHW::GetInstance()->m_draw_transfers.end(); )
{
u32 transfer_end = GSLocalMemory::GetEndBlockAddress(iter->blit.DBP, iter->blit.DBW, iter->blit.DPSM, iter->rect);
if (transfer_end < iter->blit.DBP)
transfer_end += MAX_BLOCKS;
const u32 transfer_end = GSLocalMemory::GetUnwrappedEndBlockAddress(iter->blit.DBP, iter->blit.DBW, iter->blit.DPSM, iter->rect);
// If the format, and location doesn't overlap
if (transfer_end >= TEX0.TBP0 && iter->blit.DBP <= rect_end && GSUtil::HasCompatibleBits(iter->blit.DPSM, TEX0.PSM))
@@ -1748,6 +1744,29 @@ void GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
{
dst->UpdateValidity(GSVector4i::loadh(valid_size));
}
for (int type = 0; type < 2; type++)
{
auto& list = m_dst[type];
for (auto i = list.begin(); i != list.end();)
{
auto j = i;
Target* t = *j;
// could be overwriting a double buffer, so if it's the second half of it, just reduce the size down to half.
if (dst != t && t->m_TEX0.TBW == dst->m_TEX0.TBW &&
t->m_TEX0.PSM == dst->m_TEX0.PSM &&
((((t->m_end_block + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == dst->m_TEX0.TBP0)
{
//DevCon.Warning("Found one %x->%x BW %d PSM %x (new target %x->%x BW %d PSM %x)", t->m_TEX0.TBP0, t->m_end_block, t->m_TEX0.TBW, t->m_TEX0.PSM, dst->m_TEX0.TBP0, dst->m_end_block, dst->m_TEX0.TBW, dst->m_TEX0.PSM);
GSVector4i new_valid = t->m_valid;
new_valid.w /= 2;
t->ResizeValidity(new_valid);
return;
}
i++;
}
}
}
GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale)
@@ -2181,7 +2200,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
// Unfortunately sometimes the draw rect is incorrect, and since the end block gets the rect -1, it'll underflow,
// so we need to prevent that from happening. Just make it a single block in that case, and hope for the best.
const u32 start_bp = GSLocalMemory::GetStartBlockAddress(off.bp(), off.bw(), off.psm(), rect);
const u32 end_bp = rect.rempty() ? start_bp : GSLocalMemory::GetEndBlockAddress(off.bp(), off.bw(), off.psm(), rect);
const u32 end_bp = rect.rempty() ? start_bp : GSLocalMemory::GetUnwrappedEndBlockAddress(off.bp(), off.bw(), off.psm(), rect);
// Ideally in the future we can turn this on unconditionally, but for now it breaks too much.
const bool check_inside_target = (GSConfig.UserHacks_TargetPartialInvalidation ||
@@ -2348,7 +2367,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
else
{
if (GSLocalMemory::m_psm[psm].bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp ||
((100.0f / static_cast<float>(t->m_end_block - t->m_TEX0.TBP0)) * static_cast<float>(end_bp - bp)) < 20.0f)
((100.0f / static_cast<float>(t->UnwrappedEndBlock() - t->m_TEX0.TBP0)) * static_cast<float>(end_bp - bp)) < 20.0f)
{
SurfaceOffset so = ComputeSurfaceOffset(off, r, t);
if (so.is_valid)
@@ -2421,7 +2440,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
// TODO Use ComputeSurfaceOffset below.
if (GSUtil::HasSharedBits(psm, t->m_TEX0.PSM))
{
if (t->m_TEX0.TBP0 >= start_bp && t->m_end_block <= end_bp)
if (t->m_TEX0.TBP0 >= start_bp && t->UnwrappedEndBlock() <= end_bp)
{
// If we're clearing C24 but the target is C32, then we need to dirty instead.
if (rgba._u32 != GSUtil::GetChannelMask(t->m_TEX0.PSM))
@@ -2960,7 +2979,7 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
t->m_drawn_since_read.z = targetr.x;
}
if (exact_bp)
if (exact_bp && read_end <= t->m_end_block)
return;
}
}
@@ -3320,6 +3339,26 @@ GSTextureCache::Target* GSTextureCache::GetTargetWithSharedBits(u32 BP, u32 PSM)
return nullptr;
}
GSTextureCache::Target* GSTextureCache::FindOverlappingTarget(u32 BP, u32 end_bp) const
{
for (int i = 0; i < 2; i++)
{
for (Target* tgt : m_dst[i])
{
if (CheckOverlap(tgt->m_TEX0.TBP0, tgt->m_end_block, BP, end_bp))
return tgt;
}
}
return nullptr;
}
GSTextureCache::Target* GSTextureCache::FindOverlappingTarget(u32 BP, u32 BW, u32 PSM, GSVector4i rc) const
{
const u32 end_bp = GSLocalMemory::GetUnwrappedEndBlockAddress(BP, BW, PSM, rc);
return FindOverlappingTarget(BP, end_bp);
}
GSVector2i GSTextureCache::GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width, s32 min_height)
{
TargetHeightElem search = {};
@@ -5151,8 +5190,6 @@ void GSTextureCache::Target::Update()
g_gs_device->DrawMultiStretchRects(drects, ndrects, m_texture, shader);
}
UpdateValidity(total_rect);
// We don't know what the dirty alpha is gonna be, so assume max.
m_alpha_min = 0;
m_alpha_max = 255;
g_gs_device->Recycle(t);
@@ -5806,8 +5843,8 @@ bool GSTextureCache::SurfaceOffsetKeyEqual::operator()(const GSTextureCache::Sur
{
for (size_t i = 0; i < lhs.elems.size(); ++i)
{
const SurfaceOffsetKeyElem& lhs_elem = lhs.elems.at(i);
const SurfaceOffsetKeyElem& rhs_elem = rhs.elems.at(i);
const SurfaceOffsetKeyElem& lhs_elem = lhs.elems[i];
const SurfaceOffsetKeyElem& rhs_elem = rhs.elems[i];
if (lhs_elem.bp != rhs_elem.bp
|| lhs_elem.bw != rhs_elem.bw
|| lhs_elem.psm != rhs_elem.psm
+4 -2
View File
@@ -469,14 +469,14 @@ public:
bool CanTranslate(u32 bp, u32 bw, u32 spsm, GSVector4i r, u32 dbp, u32 dpsm, u32 dbw);
GSVector4i TranslateAlignedRectByPage(Target* t, u32 sbp, u32 spsm, u32 sbw, GSVector4i src_r, bool is_invalidation = false);
void DirtyRectByPage(u32 sbp, u32 spsm, u32 sbw, Target* t, GSVector4i src_r);
void DirtyRectByPageOld(u32 sbp, u32 spsm, u32 sbw, Target* t, GSVector4i src_r);
GSTexture* LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2i& offset, float* scale, const GSVector2i& size);
std::shared_ptr<Palette> LookupPaletteObject(const u32* clut, u16 pal, bool need_gs_texture);
Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const GSVector2i* lod, const bool possible_shuffle);
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const bool possible_shuffle, bool palette = false);
Target* FindTargetOverlap(u32 bp, u32 end_block, int type, int psm);
Target* FindTargetOverlap(Target* target, int type, int psm);
Target* LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used = true, u32 fbmask = 0,
bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_target = true,
const GSVector4i draw_rc = GSVector4i::zero());
@@ -488,6 +488,8 @@ public:
/// Looks up a target in the cache, and only returns it if the BP/BW match exactly.
Target* GetExactTarget(u32 BP, u32 BW, int type, u32 end_bp);
Target* GetTargetWithSharedBits(u32 BP, u32 PSM) const;
Target* FindOverlappingTarget(u32 BP, u32 end_bp) const;
Target* FindOverlappingTarget(u32 BP, u32 BW, u32 PSM, GSVector4i rc) const;
GSVector2i GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width, s32 min_height);
bool Has32BitTarget(u32 bp);
+240 -1
View File
@@ -1,5 +1,5 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2020 PCSX2 Dev Team
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
@@ -21,6 +21,7 @@
#include "IconsFontAwesome5.h"
#include "vtlb.h"
#include "common/Error.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
@@ -973,3 +974,241 @@ const GameDatabaseSchema::GameEntry* GameDatabase::findGame(const std::string_vi
auto iter = s_game_db.find(StringUtil::toLower(serial));
return (iter != s_game_db.end()) ? &iter->second : nullptr;
}
bool GameDatabase::TrackHash::parseHash(const std::string_view& str)
{
constexpr u32 expected_length = SIZE * 2;
if (str.length() != expected_length)
return false;
std::memset(data, 0, sizeof(data));
for (u32 i = 0; i < SIZE * 2; i++)
{
const char ch = str[i];
u8 b;
if (ch >= '0' && ch <= '9')
b = static_cast<u8>(ch - '0');
else if (ch >= 'a' && ch <= 'f')
b = static_cast<u8>(ch - 'a') + 0xa;
else if (ch >= 'A' && ch <= 'F')
b = static_cast<u8>(ch - 'A') + 0xa;
else
return false;
data[i / 2] |= ((i % 2) == 0) ? (b << 4) : b;
}
return true;
}
std::string GameDatabase::TrackHash::toString() const
{
return fmt::format(
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
}
struct TrackHashHasher
{
size_t operator()(const GameDatabase::TrackHash& hash) const
{
return std::hash<std::string_view>()(std::string_view(reinterpret_cast<const char*>(hash.data),
GameDatabase::TrackHash::SIZE));
}
};
static constexpr char HASHDB_YAML_FILE_NAME[] = "RedumpDatabase.yaml";
std::unordered_map<GameDatabase::TrackHash, u32, TrackHashHasher> s_track_hash_to_entry_map;
std::vector<GameDatabase::HashDatabaseEntry> s_hash_database;
static bool parseHashDatabaseEntry(const c4::yml::NodeRef& node)
{
if (!node.has_child("name") || !node.has_child("hashes"))
{
Console.Warning("[HashDatabase] Incomplete entry found.");
return false;
}
GameDatabase::HashDatabaseEntry entry;
node["name"] >> entry.name;
if (node.has_child("version"))
node["version"] >> entry.version;
if (node.has_child("serial"))
node["serial"] >> entry.serial;
const u32 index = static_cast<u32>(s_hash_database.size());
for (const ryml::NodeRef& n : node["hashes"].children())
{
if (!n.is_map() || !n.has_child("size") || !n.has_child("md5"))
{
Console.Error(fmt::format("[HashDatabase] Incomplete hash definition in {}", entry.name));
return false;
}
GameDatabase::TrackHash th;
std::string md5;
n["md5"] >> md5;
n["size"] >> th.size;
if (!th.parseHash(md5))
{
Console.Error(fmt::format("[HashDatabase] Failed to parse hash in {}: '{}'", entry.name, md5));
return false;
}
if (entry.tracks.empty() && s_track_hash_to_entry_map.find(th) != s_track_hash_to_entry_map.end())
Console.Warning(fmt::format("[HashDatabase] Duplicate first track hash in {}", entry.name));
entry.tracks.push_back(th);
s_track_hash_to_entry_map.emplace(th, index);
}
s_hash_database.push_back(std::move(entry));
return true;
}
bool GameDatabase::loadHashDatabase()
{
if (!s_hash_database.empty())
return true;
ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void*) {
Console.Error(fmt::format(
"[HashDatabase YAML] Parsing error at {}:{} (bufpos={}): {}", loc.line, loc.col, loc.offset, msg));
};
ryml::set_callbacks(rymlCallbacks);
c4::set_error_callback([](const char* msg, size_t msg_size) {
Console.Error(fmt::format("[HashDatabase YAML] Internal Parsing error: {}", std::string_view(msg, msg_size)));
});
Common::Timer load_timer;
auto buf = Host::ReadResourceFileToString(HASHDB_YAML_FILE_NAME);
if (!buf.has_value())
{
Console.Error("[GameDB] Unable to open hash database file, file does not exist.");
return false;
}
ryml::Tree tree = ryml::parse_in_arena(c4::to_csubstr(buf.value()));
ryml::NodeRef root = tree.rootref();
bool okay = true;
for (const ryml::NodeRef& n : root.children())
{
if (!parseHashDatabaseEntry(n))
{
okay = false;
break;
}
}
ryml::reset_callbacks();
if (!okay)
{
s_track_hash_to_entry_map.clear();
s_hash_database.clear();
return false;
}
Console.WriteLn(Color_StrongGreen, "[HashDatabase] Loaded YAML in %.0f ms", load_timer.GetTimeMilliseconds());
return true;
}
void GameDatabase::unloadHashDatabase()
{
s_track_hash_to_entry_map.clear();
s_hash_database.clear();
}
static size_t getTrackIndex(const GameDatabase::TrackHash* tracks, size_t num_tracks, const GameDatabase::TrackHash& track)
{
for (size_t i = 0; i < num_tracks; i++)
{
if (tracks[i] == track)
return i;
}
return num_tracks;
}
const GameDatabase::HashDatabaseEntry* GameDatabase::lookupHash(
const TrackHash* tracks, size_t num_tracks, bool* tracks_matched, std::string* match_error)
{
loadHashDatabase();
if (num_tracks == 0)
{
*match_error = TRANSLATE_STR("GameDatabase", "No tracks provided.");
std::memset(tracks_matched, 0, sizeof(bool) * num_tracks);
return nullptr;
}
// match the first track, for DVDs this will be all there is anyway
const auto data_iter = s_track_hash_to_entry_map.find(tracks[0]);
if (data_iter == s_track_hash_to_entry_map.end())
{
*match_error = fmt::format(TRANSLATE_FS("GameDatabase", "Hash {} is not in database."), tracks[0].toString());
std::memset(tracks_matched, 0, sizeof(bool) * num_tracks);
return nullptr;
}
// make sure they're not missing the data track
const GameDatabase::HashDatabaseEntry* candidate = &s_hash_database[data_iter->second];
if (getTrackIndex(candidate->tracks.data(), candidate->tracks.size(), tracks[0]) != 0)
{
*match_error = TRANSLATE_STR("GameDatabase", "Data track number does not match data track in database.");
std::memset(tracks_matched, 0, sizeof(bool) * num_tracks);
return nullptr;
}
// first track is okay!
tracks_matched[0] = true;
match_error->clear();
// now check any audio tracks...
bool all_okay = true;
for (size_t track = 1; track < num_tracks; track++)
{
const auto audio_iter = s_track_hash_to_entry_map.find(tracks[track]);
if (audio_iter != s_track_hash_to_entry_map.end())
{
fmt::format_to(std::back_inserter(*match_error),
TRANSLATE_FS("GameDatabase", "Track {} with hash {} is not found in database.\n"), track + 1,
tracks[track].toString());
tracks_matched[track] = false;
all_okay = false;
continue;
}
// same game?
if (audio_iter->second != data_iter->second)
{
fmt::format_to(std::back_inserter(*match_error),
TRANSLATE_FS("GameDatabase", "Track {} with hash {} is for a different game ({}).\n"), track + 1,
tracks[track].toString(), s_hash_database[audio_iter->second].name);
tracks_matched[track] = false;
all_okay = false;
continue;
}
// make sure it's the correct track number
if (getTrackIndex(candidate->tracks.data(), candidate->tracks.size(), tracks[track]) != track)
{
fmt::format_to(std::back_inserter(*match_error),
TRANSLATE_FS("GameDatabase", "Track {} with hash {} does not match database track..\n"), track + 1,
tracks[track].toString());
tracks_matched[track] = false;
all_okay = false;
continue;
}
tracks_matched[track] = true;
}
if (!match_error->empty() && match_error->back() == '\n')
match_error->pop_back();
return all_okay ? candidate : nullptr;
}
+35 -1
View File
@@ -21,6 +21,7 @@
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
enum GamefixId;
@@ -133,10 +134,43 @@ namespace GameDatabaseSchema
/// Returns true if the current config value for the specified hw fix id matches the value.
static bool configMatchesHWFix(const Pcsx2Config::GSOptions& config, GSHWFixId id, int value);
};
};
}; // namespace GameDatabaseSchema
namespace GameDatabase
{
void ensureLoaded();
const GameDatabaseSchema::GameEntry* findGame(const std::string_view& serial);
struct TrackHash
{
static constexpr u32 SIZE = 16;
bool parseHash(const std::string_view& str);
std::string toString() const;
#define MAKE_OPERATOR(op) \
bool operator op(const TrackHash& hash) const { return (std::memcmp(data, hash.data, sizeof(data)) op 0); }
MAKE_OPERATOR(==);
MAKE_OPERATOR(!=);
MAKE_OPERATOR(<);
MAKE_OPERATOR(<=);
MAKE_OPERATOR(>);
MAKE_OPERATOR(>=);
#undef MAKE_OPERATOR
u8 data[SIZE];
u64 size;
};
struct HashDatabaseEntry
{
std::string serial;
std::string name;
std::string version;
std::vector<TrackHash> tracks;
};
bool loadHashDatabase();
void unloadHashDatabase();
const HashDatabaseEntry* lookupHash(const TrackHash* tracks, size_t num_tracks, bool* tracks_matched, std::string* match_error);
}; // namespace GameDatabase
-2
View File
@@ -19,8 +19,6 @@
#include "GS/Renderers/HW/GSTextureReplacements.h"
#include "Host.h"
#include "LayeredSettingsInterface.h"
#include "MemoryCardFile.h"
#include "Sio.h"
#include "VMManager.h"
#include "common/Assertions.h"
+36 -36
View File
@@ -29,10 +29,7 @@
#include "ImGui/ImGuiFullscreen.h"
#include "ImGui/ImGuiManager.h"
#include "Input/InputManager.h"
#include "MemoryCardFile.h"
#include "MTGS.h"
#include "PAD/Host/PAD.h"
#include "Sio.h"
#include "USB/USB.h"
#include "VMManager.h"
#include "ps2/BiosTools.h"
@@ -50,6 +47,10 @@
#include "common/Threading.h"
#include "common/Timer.h"
#include "SIO/Memcard/MemoryCardFile.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Sio.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "IconsFontAwesome5.h"
@@ -2271,7 +2272,7 @@ void FullscreenUI::StartAutomaticBinding(u32 port)
Host::RunOnCPUThread([port, name = std::move(names[index])]() {
auto lock = Host::GetSettingsLock();
SettingsInterface* bsi = GetEditingSettingsInterface();
const bool result = PAD::MapController(*bsi, port, InputManager::GetGenericBindingMapping(name));
const bool result = Pad::MapController(*bsi, port, InputManager::GetGenericBindingMapping(name));
SetSettingsChanged(bsi);
@@ -3648,7 +3649,7 @@ void FullscreenUI::CopyGlobalControllerSettingsToGame()
SettingsInterface* dsi = GetEditingSettingsInterface(true);
SettingsInterface* ssi = GetEditingSettingsInterface(false);
PAD::CopyConfiguration(dsi, *ssi, true, true, false);
Pad::CopyConfiguration(dsi, *ssi, true, true, false);
USB::CopyConfiguration(dsi, *ssi, true, true);
SetSettingsChanged(dsi);
@@ -3659,15 +3660,15 @@ void FullscreenUI::ResetControllerSettings()
{
SettingsInterface* dsi = GetEditingSettingsInterface();
PAD::SetDefaultControllerConfig(*dsi);
PAD::SetDefaultHotkeyConfig(*dsi);
Pad::SetDefaultControllerConfig(*dsi);
Pad::SetDefaultHotkeyConfig(*dsi);
USB::SetDefaultConfiguration(dsi);
ShowToast(std::string(), "Controller settings reset to default.");
}
void FullscreenUI::DoLoadInputProfile()
{
std::vector<std::string> profiles(PAD::GetInputProfileNames());
std::vector<std::string> profiles = Pad::GetInputProfileNames();
if (profiles.empty())
{
ShowToast(std::string(), "No input profiles available.");
@@ -3693,7 +3694,7 @@ void FullscreenUI::DoLoadInputProfile()
auto lock = Host::GetSettingsLock();
SettingsInterface* dsi = GetEditingSettingsInterface();
PAD::CopyConfiguration(dsi, ssi, true, true, IsEditingGameSettings(dsi));
Pad::CopyConfiguration(dsi, ssi, true, true, IsEditingGameSettings(dsi));
USB::CopyConfiguration(dsi, ssi, true, true);
SetSettingsChanged(dsi);
ShowToast(std::string(), fmt::format("Input profile '{}' loaded.", title));
@@ -3707,7 +3708,7 @@ void FullscreenUI::DoSaveInputProfile(const std::string& name)
auto lock = Host::GetSettingsLock();
SettingsInterface* ssi = GetEditingSettingsInterface();
PAD::CopyConfiguration(&dsi, *ssi, true, true, IsEditingGameSettings(ssi));
Pad::CopyConfiguration(&dsi, *ssi, true, true, IsEditingGameSettings(ssi));
USB::CopyConfiguration(&dsi, *ssi, true, true);
if (dsi.Save())
ShowToast(std::string(), fmt::format("Input profile '{}' saved.", name));
@@ -3717,7 +3718,7 @@ void FullscreenUI::DoSaveInputProfile(const std::string& name)
void FullscreenUI::DoSaveInputProfile()
{
std::vector<std::string> profiles(PAD::GetInputProfileNames());
std::vector<std::string> profiles = Pad::GetInputProfileNames();
ImGuiFullscreen::ChoiceDialogOptions coptions;
coptions.reserve(profiles.size() + 1);
@@ -3832,8 +3833,8 @@ void FullscreenUI::DrawControllerSettingsPage()
// we reorder things a little to make it look less silly for mtap
static constexpr const std::array<char, 4> mtap_slot_names = {{'A', 'B', 'C', 'D'}};
static constexpr const std::array<u32, PAD::NUM_CONTROLLER_PORTS> mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}};
static constexpr const std::array<const char*, PAD::NUM_CONTROLLER_PORTS> sections = {
static constexpr const std::array<u32, Pad::NUM_CONTROLLER_PORTS> mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}};
static constexpr const std::array<const char*, Pad::NUM_CONTROLLER_PORTS> sections = {
{"Pad1", "Pad2", "Pad3", "Pad4", "Pad5", "Pad6", "Pad7", "Pad8"}};
// create the ports
@@ -3851,11 +3852,11 @@ void FullscreenUI::DrawControllerSettingsPage()
.c_str());
const char* section = sections[global_slot];
const std::string type(bsi->GetStringValue(section, "Type", PAD::GetDefaultPadType(global_slot)));
const PAD::ControllerInfo* ci = PAD::GetControllerInfo(type);
const std::string type(bsi->GetStringValue(section, "Type", Pad::GetDefaultPadType(global_slot)));
const Pad::ControllerInfo* ci = Pad::GetControllerInfo(type);
if (MenuButton(ICON_FA_GAMEPAD " Controller Type", ci ? ci->display_name : "Unknown"))
{
const std::vector<std::pair<const char*, const char*>> raw_options = PAD::GetControllerTypeNames();
const std::vector<std::pair<const char*, const char*>> raw_options = Pad::GetControllerTypeNames();
ImGuiFullscreen::ChoiceDialogOptions options;
options.reserve(raw_options.size());
for (auto& it : raw_options)
@@ -3876,7 +3877,7 @@ void FullscreenUI::DrawControllerSettingsPage()
});
}
if (!ci || ci->num_bindings == 0)
if (!ci || ci->bindings.empty())
{
ImGui::PopID();
continue;
@@ -3885,20 +3886,17 @@ void FullscreenUI::DrawControllerSettingsPage()
if (MenuButton(ICON_FA_MAGIC " Automatic Mapping", "Attempts to map the selected port to a chosen controller."))
StartAutomaticBinding(global_slot);
for (u32 i = 0; i < ci->num_bindings; i++)
{
const InputBindingInfo& bi = ci->bindings[i];
DrawInputBindingButton(bsi, bi.bind_type, section, bi.name, bi.display_name, true);
}
for (const InputBindingInfo& bi : ci->bindings)
DrawInputBindingButton(bsi, bi.bind_type, section, bi.name, Host::TranslateToCString("Pad", bi.display_name), true);
MenuHeading((mtap_enabled[mtap_port] ?
fmt::format(ICON_FA_MICROCHIP " Controller Port {}{} Macros", mtap_port + 1, mtap_slot_names[mtap_slot]) :
fmt::format(ICON_FA_MICROCHIP " Controller Port {} Macros", mtap_port + 1))
.c_str());
static bool macro_button_expanded[PAD::NUM_CONTROLLER_PORTS][PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER] = {};
static bool macro_button_expanded[Pad::NUM_CONTROLLER_PORTS][Pad::NUM_MACRO_BUTTONS_PER_CONTROLLER] = {};
for (u32 macro_index = 0; macro_index < PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_index++)
for (u32 macro_index = 0; macro_index < Pad::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_index++)
{
bool& expanded = macro_button_expanded[global_slot][macro_index];
expanded ^= MenuHeadingButton(fmt::format(ICON_FA_MICROCHIP " Macro Button {}", macro_index + 1).c_str(),
@@ -3914,25 +3912,23 @@ void FullscreenUI::DrawControllerSettingsPage()
{
std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
ImGuiFullscreen::ChoiceDialogOptions options;
for (u32 i = 0; i < ci->num_bindings; i++)
for (const InputBindingInfo& bi : ci->bindings)
{
const InputBindingInfo& bi = ci->bindings[i];
if (bi.bind_type != InputBindingInfo::Type::Button && bi.bind_type != InputBindingInfo::Type::Axis &&
bi.bind_type != InputBindingInfo::Type::HalfAxis)
{
continue;
}
options.emplace_back(bi.display_name, std::any_of(buttons_split.begin(), buttons_split.end(),
[bi](const std::string_view& it) { return (it == bi.name); }));
options.emplace_back(Host::TranslateToCString("Pad", bi.display_name), std::any_of(buttons_split.begin(), buttons_split.end(),
[bi](const std::string_view& it) { return (it == bi.name); }));
}
OpenChoiceDialog(fmt::format("Select Macro {} Binds", macro_index + 1).c_str(), true, std::move(options),
[section, macro_index, ci](s32 index, const std::string& title, bool checked) {
// convert display name back to bind name
std::string_view to_modify;
for (u32 j = 0; j < ci->num_bindings; j++)
for (const InputBindingInfo& bi : ci->bindings)
{
const InputBindingInfo& bi = ci->bindings[j];
if (bi.display_name == title)
{
to_modify = bi.name;
@@ -3982,6 +3978,10 @@ void FullscreenUI::DrawControllerSettingsPage()
DrawFloatSpinBoxSetting(bsi, ICON_FA_ARROW_DOWN " Pressure", "Determines how much pressure is simulated when macro is active.",
section, pressure_key.c_str(), 1.0f, 0.01f, 1.0f, 0.01f, 100.0f, "%.0f%%");
const std::string deadzone_key(fmt::format("Macro{}Deadzone", macro_index + 1));
DrawFloatSpinBoxSetting(bsi, ICON_FA_ARROW_DOWN " Pressure", "Determines the pressure required to activate the macro.",
section, deadzone_key.c_str(), 0.0f, 0.00f, 1.0f, 0.01f, 100.0f, "%.0f%%");
ImGui::SetNextWindowSize(LayoutScale(500.0f, 180.0f));
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
@@ -4016,18 +4016,15 @@ void FullscreenUI::DrawControllerSettingsPage()
ImGui::PopFont();
}
if (ci->num_settings > 0)
if (!ci->settings.empty())
{
MenuHeading((mtap_enabled[mtap_port] ?
fmt::format(ICON_FA_SLIDERS_H " Controller Port {}{} Settings", mtap_port + 1, mtap_slot_names[mtap_slot]) :
fmt::format(ICON_FA_SLIDERS_H " Controller Port {} Settings", mtap_port + 1))
.c_str());
for (u32 i = 0; i < ci->num_settings; i++)
{
const SettingInfo& si = ci->settings[i];
for (const SettingInfo& si : ci->settings)
DrawSettingInfoSetting(bsi, section, si.name, si);
}
}
ImGui::PopID();
@@ -4107,7 +4104,10 @@ void FullscreenUI::DrawControllerSettingsPage()
const std::string section(USB::GetConfigSection(port));
for (const InputBindingInfo& bi : bindings)
DrawInputBindingButton(bsi, bi.bind_type, section.c_str(), USB::GetConfigSubKey(type, bi.name).c_str(), bi.display_name);
{
DrawInputBindingButton(bsi, bi.bind_type, section.c_str(), USB::GetConfigSubKey(type, bi.name).c_str(),
Host::TranslateToCString("USB", bi.display_name));
}
}
const std::span<const SettingInfo> settings(USB::GetDeviceSettings(type, subtype));
+147 -1
View File
@@ -38,6 +38,7 @@
#include "fmt/core.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "common/Image.h"
#include <chrono>
#include <cmath>
@@ -47,6 +48,17 @@
namespace ImGuiManager
{
struct SoftwareCursor
{
std::string image_path;
std::unique_ptr<GSTexture> texture;
u32 color;
float scale;
float extent_x;
float extent_y;
std::pair<float, float> pos;
};
static void SetStyle();
static void SetKeyMap();
static bool LoadFontData();
@@ -57,6 +69,11 @@ namespace ImGuiManager
static bool AddIconFonts(float size);
static void AcquirePendingOSDMessages();
static void DrawOSDMessages();
static void CreateSoftwareCursorTextures();
static void UpdateSoftwareCursorTexture(u32 index);
static void DestroySoftwareCursorTextures();
static void DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<float, float>& pos);
static void DrawSoftwareCursors();
} // namespace ImGuiManager
static float s_global_scale = 1.0f;
@@ -73,6 +90,8 @@ static std::vector<u8> s_standard_font_data;
static std::vector<u8> s_fixed_font_data;
static std::vector<u8> s_icon_font_data;
static float s_window_width;
static float s_window_height;
static Common::Timer s_last_render_time;
// cached copies of WantCaptureKeyboard/Mouse, used to know when to dispatch events
@@ -86,6 +105,8 @@ static std::unordered_map<u32, ImGuiKey> s_imgui_key_map;
// need to keep track of this, so we can reinitialize on renderer switch
static bool s_fullscreen_ui_was_initialized = false;
static std::array<ImGuiManager::SoftwareCursor, InputManager::MAX_SOFTWARE_CURSORS> s_software_cursors = {};
void ImGuiManager::SetFontPath(std::string path)
{
s_font_path = std::move(path);
@@ -146,6 +167,7 @@ bool ImGuiManager::Initialize()
if (add_fullscreen_fonts)
InitializeFullscreenUI();
CreateSoftwareCursorTextures();
return true;
}
@@ -157,6 +179,8 @@ bool ImGuiManager::InitializeFullscreenUI()
void ImGuiManager::Shutdown(bool clear_state)
{
DestroySoftwareCursorTextures();
FullscreenUI::Shutdown(clear_state);
ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr);
if (clear_state)
@@ -174,12 +198,24 @@ void ImGuiManager::Shutdown(bool clear_state)
UnloadFontData();
}
float ImGuiManager::GetWindowWidth()
{
return s_window_width;
}
float ImGuiManager::GetWindowHeight()
{
return s_window_height;
}
void ImGuiManager::WindowResized()
{
const u32 new_width = g_gs_device ? g_gs_device->GetWindowWidth() : 0;
const u32 new_height = g_gs_device ? g_gs_device->GetWindowHeight() : 0;
ImGui::GetIO().DisplaySize = ImVec2(static_cast<float>(new_width), static_cast<float>(new_height));
s_window_width = static_cast<float>(new_width);
s_window_height = static_cast<float>(new_height);
ImGui::GetIO().DisplaySize = ImVec2(s_window_width, s_window_height);
UpdateScale();
@@ -672,6 +708,7 @@ void ImGuiManager::RenderOSD()
AcquirePendingOSDMessages();
DrawOSDMessages();
DrawSoftwareCursors();
}
float ImGuiManager::GetGlobalScale()
@@ -810,3 +847,112 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value
return true;
}
void ImGuiManager::CreateSoftwareCursorTextures()
{
for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++)
{
if (!s_software_cursors[i].image_path.empty())
UpdateSoftwareCursorTexture(i);
}
}
void ImGuiManager::DestroySoftwareCursorTextures()
{
for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++)
{
s_software_cursors[i].texture.reset();
}
}
void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
{
SoftwareCursor& sc = s_software_cursors[index];
if (sc.image_path.empty())
{
sc.texture.reset();
return;
}
Common::RGBA8Image image;
if (!image.LoadFromFile(sc.image_path.c_str()))
{
Console.Error("Failed to load software cursor %u image '%s'", index, sc.image_path.c_str());
return;
}
sc.texture = std::unique_ptr<GSTexture>(g_gs_device->CreateTexture(image.GetWidth(), image.GetHeight(), 1, GSTexture::Format::Color));
if (!sc.texture)
{
Console.Error(
"Failed to upload %ux%u software cursor %u image '%s'", image.GetWidth(), image.GetHeight(), index, sc.image_path.c_str());
return;
}
sc.texture->Update(GSVector4i(0, 0, image.GetWidth(), image.GetHeight()), image.GetPixels(), image.GetByteStride(), 0);
sc.extent_x = std::ceil(static_cast<float>(image.GetWidth()) * sc.scale * s_global_scale) / 2.0f;
sc.extent_y = std::ceil(static_cast<float>(image.GetHeight()) * sc.scale * s_global_scale) / 2.0f;
}
void ImGuiManager::DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<float, float>& pos)
{
if (!sc.texture)
return;
const ImVec2 min(pos.first - sc.extent_x, pos.second - sc.extent_y);
const ImVec2 max(pos.first + sc.extent_x, pos.second + sc.extent_y);
ImDrawList* dl = ImGui::GetForegroundDrawList();
dl->AddImage(
reinterpret_cast<ImTextureID>(sc.texture.get()->GetNativeHandle()), min, max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), sc.color);
}
void ImGuiManager::DrawSoftwareCursors()
{
// This one's okay to race, worst that happens is we render the wrong number of cursors for a frame.
const u32 pointer_count = InputManager::MAX_POINTER_DEVICES;
for (u32 i = 0; i < pointer_count; i++)
DrawSoftwareCursor(s_software_cursors[i], InputManager::GetPointerAbsolutePosition(i));
for (u32 i = InputManager::MAX_POINTER_DEVICES; i < InputManager::MAX_SOFTWARE_CURSORS; i++)
DrawSoftwareCursor(s_software_cursors[i], s_software_cursors[i].pos);
}
void ImGuiManager::SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color)
{
MTGS::RunOnGSThread([index, image_path = std::move(image_path), image_scale, multiply_color]() {
pxAssert(index < std::size(s_software_cursors));
SoftwareCursor& sc = s_software_cursors[index];
sc.color = multiply_color | 0xFF000000;
if (sc.image_path == image_path && sc.scale == image_scale)
return;
const bool is_hiding_or_showing = (image_path.empty() != sc.image_path.empty());
sc.image_path = std::move(image_path);
sc.scale = image_scale;
if (MTGS::IsOpen())
UpdateSoftwareCursorTexture(index);
// Hide the system cursor when we activate a software cursor.
if (is_hiding_or_showing && index == 0)
Host::RunOnCPUThread(&InputManager::UpdateHostMouseMode);
});
}
bool ImGuiManager::HasSoftwareCursor(u32 index)
{
return (index < s_software_cursors.size() && !s_software_cursors[index].image_path.empty());
}
void ImGuiManager::ClearSoftwareCursor(u32 index)
{
SetSoftwareCursor(index, std::string(), 0.0f, 0);
}
void ImGuiManager::SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y)
{
pxAssert(index >= InputManager::MAX_POINTER_DEVICES);
SoftwareCursor& sc = s_software_cursors[index];
sc.pos.first = pos_x;
sc.pos.second = pos_y;
}
+12
View File
@@ -41,6 +41,10 @@ namespace ImGuiManager
/// Frees all ImGui resources.
void Shutdown(bool clear_state);
/// Returns the size of the display window. Can be safely called from any thread.
float GetWindowWidth();
float GetWindowHeight();
/// Updates internal state when the window is size.
void WindowResized();
@@ -102,6 +106,14 @@ namespace ImGuiManager
/// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation.
bool ProcessGenericInputEvent(GenericInputBinding key, float value);
/// Sets an image and scale for a software cursor. Software cursors can be used for things like crosshairs.
void SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color = 0xFFFFFF);
bool HasSoftwareCursor(u32 index);
void ClearSoftwareCursor(u32 index);
/// Sets the position of a software cursor, used when we have relative coordinates such as controllers.
void SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y);
} // namespace ImGuiManager
namespace Host
+18 -22
View File
@@ -28,12 +28,12 @@
#include "ImGui/ImGuiManager.h"
#include "ImGui/ImGuiOverlays.h"
#include "Input/InputManager.h"
#include "PAD/Host/KeyStatus.h"
#include "PAD/Host/PAD.h"
#include "PerformanceMetrics.h"
#include "Recording/InputRecording.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Pad/PadBase.h"
#include "USB/USB.h"
#include "VMManager.h"
#include "pcsx2/Recording/InputRecording.h"
#include "common/BitUtils.h"
#include "common/StringUtil.h"
@@ -482,10 +482,10 @@ void ImGuiManager::DrawInputsOverlay()
ImDrawList* dl = ImGui::GetBackgroundDrawList();
u32 num_ports = 0;
for (u32 port = 0; port < PAD::NUM_CONTROLLER_PORTS; port++)
for (u32 slot = 0; slot < Pad::NUM_CONTROLLER_PORTS; slot++)
{
const PAD::ControllerType ctype = g_key_status.GetType(port);
if (ctype != PAD::ControllerType::NotConnected)
if (Pad::HasConnectedPad(slot))
num_ports++;
}
@@ -503,29 +503,27 @@ void ImGuiManager::DrawInputsOverlay()
std::string text;
text.reserve(256);
for (u32 port = 0; port < PAD::NUM_CONTROLLER_PORTS; port++)
for (u32 slot = 0; slot < Pad::NUM_CONTROLLER_PORTS; slot++)
{
const PAD::ControllerType ctype = g_key_status.GetType(port);
if (ctype == PAD::ControllerType::NotConnected)
const PadBase* const pad = Pad::GetPad(slot);
const Pad::ControllerType ctype = pad->GetType();
if (ctype == Pad::ControllerType::NotConnected)
continue;
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(ctype);
if (!cinfo)
continue;
text.clear();
fmt::format_to(std::back_inserter(text), "P{} |", port + 1u);
fmt::format_to(std::back_inserter(text), "P{} |", slot + 1u);
for (u32 bind = 0; bind < cinfo->num_bindings; bind++)
const Pad::ControllerInfo& cinfo = pad->GetInfo();
for (u32 bind = 0; bind < static_cast<u32>(cinfo.bindings.size()); bind++)
{
const InputBindingInfo& bi = cinfo->bindings[bind];
const InputBindingInfo& bi = cinfo.bindings[bind];
switch (bi.bind_type)
{
case InputBindingInfo::Type::Axis:
case InputBindingInfo::Type::HalfAxis:
{
// axes are always shown
const float value = static_cast<float>(g_key_status.GetRawPressure(port, bind)) * (1.0f / 255.0f);
const float value = static_cast<float>(pad->GetRawInput(bind)) * (1.0f / 255.0f);
if (value >= (254.0f / 255.0f))
fmt::format_to(std::back_inserter(text), " {}", bi.name);
else if (value > (1.0f / 255.0f))
@@ -536,11 +534,9 @@ void ImGuiManager::DrawInputsOverlay()
case InputBindingInfo::Type::Button:
{
// buttons only shown when active
const float value = static_cast<float>(g_key_status.GetRawPressure(port, bind)) * (1.0f / 255.0f);
if (value == 1.0f)
const float value = static_cast<float>(pad->GetRawInput(bind)) * (1.0f / 255.0f);
if (value >= 0.5f)
fmt::format_to(std::back_inserter(text), " {}", bi.name);
else if (value > 0.0f)
fmt::format_to(std::back_inserter(text), " {}: {:.2f}", bi.name, value);
}
break;
+23 -15
View File
@@ -18,10 +18,11 @@
#include "ImGui/ImGuiManager.h"
#include "Input/InputManager.h"
#include "Input/InputSource.h"
#include "PAD/Host/PAD.h"
#include "SIO/Pad/Pad.h"
#include "USB/USB.h"
#include "VMManager.h"
#include "common/Assertions.h"
#include "common/StringUtil.h"
#include "common/Timer.h"
@@ -631,14 +632,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
if (type.empty() || type == "None")
return;
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(type);
const Pad::ControllerInfo* cinfo = Pad::GetControllerInfo(type);
if (!cinfo)
return;
for (u32 i = 0; i < cinfo->num_bindings; i++)
for (const InputBindingInfo& bi : cinfo->bindings)
{
const InputBindingInfo& bi = cinfo->bindings[i];
switch (bi.bind_type)
{
case InputBindingInfo::Type::Button:
@@ -653,7 +652,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f);
AddBindings(
bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index, sensitivity, deadzone](float value) {
PAD::SetControllerState(pad_index, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
Pad::SetControllerState(pad_index, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
}});
}
}
@@ -666,18 +665,20 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
}
}
for (u32 macro_button_index = 0; macro_button_index < PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_button_index++)
for (u32 macro_button_index = 0; macro_button_index < Pad::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_button_index++)
{
const std::vector<std::string> bindings(si.GetStringList(section.c_str(), fmt::format("Macro{}", macro_button_index + 1).c_str()));
if (!bindings.empty())
{
AddBindings(bindings, InputButtonEventHandler{[pad_index, macro_button_index](bool state) {
PAD::SetMacroButtonState(pad_index, macro_button_index, state);
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("Macro{}Deadzone", macro_button_index + 1).c_str(), 0.0f);
AddBindings(bindings, InputAxisEventHandler{[pad_index, macro_button_index, deadzone](float value) {
const bool state = (value > deadzone);
Pad::SetMacroButtonState(pad_index, macro_button_index, state);
}});
}
}
if (cinfo->vibration_caps != PAD::VibrationCapabilities::NoVibration)
if (cinfo->vibration_caps != Pad::VibrationCapabilities::NoVibration)
{
PadVibrationBinding vib;
vib.pad_index = pad_index;
@@ -685,7 +686,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
bool has_any_bindings = false;
switch (cinfo->vibration_caps)
{
case PAD::VibrationCapabilities::LargeSmallMotors:
case Pad::VibrationCapabilities::LargeSmallMotors:
{
if (const std::string large_binding(si.GetStringValue(section.c_str(), "LargeMotor")); !large_binding.empty())
has_any_bindings |= ParseBindingAndGetSource(large_binding, &vib.motors[0].binding, &vib.motors[0].source);
@@ -694,7 +695,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
}
break;
case PAD::VibrationCapabilities::SingleMotor:
case Pad::VibrationCapabilities::SingleMotor:
{
if (const std::string binding(si.GetStringValue(section.c_str(), "Motor")); !binding.empty())
has_any_bindings |= ParseBindingAndGetSource(binding, &vib.motors[0].binding, &vib.motors[0].source);
@@ -1289,8 +1290,8 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
// If there's an input profile, we load pad bindings from it alone, rather than
// falling back to the base configuration.
for (u32 pad = 0; pad < PAD::NUM_CONTROLLER_PORTS; pad++)
AddPadBindings(binding_si, pad, PAD::GetDefaultPadType(pad));
for (u32 pad = 0; pad < Pad::NUM_CONTROLLER_PORTS; pad++)
AddPadBindings(binding_si, pad, Pad::GetDefaultPadType(pad));
constexpr float ui_ctrl_range = 100.0f;
constexpr float pointer_sensitivity = 0.05f;
@@ -1308,6 +1309,11 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
for (u32 port = 0; port < USB::NUM_PORTS; port++)
AddUSBBindings(binding_si, port);
UpdateHostMouseMode();
}
void InputManager::UpdateHostMouseMode()
{
// Check for relative mode bindings, and enable if there's anything using it.
bool has_relative_mode_bindings = !s_pointer_move_callbacks.empty();
if (!has_relative_mode_bindings)
@@ -1323,7 +1329,9 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
}
}
}
Host::SetRelativeMouseMode(has_relative_mode_bindings);
const bool has_software_cursor = ImGuiManager::HasSoftwareCursor(0);
Host::SetMouseMode(has_relative_mode_bindings, has_relative_mode_bindings || has_software_cursor);
}
// ------------------------------------------------------------------------
+9 -2
View File
@@ -170,6 +170,10 @@ namespace InputManager
static constexpr u32 MAX_POINTER_DEVICES = 1;
static constexpr u32 MAX_POINTER_BUTTONS = 3;
/// Maximum number of software cursors. We allocate an extra two for USB devices with
/// positioning data from the controller instead of a mouse.
static constexpr u32 MAX_SOFTWARE_CURSORS = MAX_POINTER_BUTTONS + 2;
/// Returns a pointer to the external input source class, if present.
InputSource* GetInputSourceInterface(InputSourceType type);
@@ -287,6 +291,9 @@ namespace InputManager
/// Updates relative pointer position. Can call from the UI thread, use when host supports relative coordinate reporting.
void UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input = false);
/// Updates host mouse mode (relative/cursor hiding).
void UpdateHostMouseMode();
/// Called when a new input device is connected.
void OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name);
@@ -305,6 +312,6 @@ namespace Host
/// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier);
/// Enables relative mouse mode in the host.
void SetRelativeMouseMode(bool enabled);
/// Enables relative mouse mode in the host, and/or hides the cursor.
void SetMouseMode(bool relative_mode, bool hide_cursor);
} // namespace Host
+4 -4
View File
@@ -20,7 +20,7 @@
#include "IopCounters.h"
#include "IopHw.h"
#include "IopDma.h"
#include "Sio.h"
#include "SIO/Sio2.h"
#include "Sif.h"
#include "DEV9/DEV9.h"
@@ -232,7 +232,7 @@ void psxDma11(u32 madr, u32 bcr, u32 chcr)
PSXDMA_LOG("*** DMA 11 - SIO2 in *** %lx addr = %lx size = %lx", chcr, madr, bcr);
// Set dmaBlockSize, so SIO2 knows to count based on the DMA block rather than SEND3 length.
// When SEND3 is written, SIO2 will automatically reset this to zero.
sio2.dmaBlockSize = (bcr & 0xffff) * 4;
g_Sio2.dmaBlockSize = (bcr & 0xffff) * 4;
if (chcr != 0x01000201)
{
@@ -244,7 +244,7 @@ void psxDma11(u32 madr, u32 bcr, u32 chcr)
for (j = 0; j < ((bcr & 0xFFFF) * 4); j++)
{
const u8 data = iopMemRead8(madr);
sio2.Write(data);
g_Sio2.Write(data);
madr++;
}
}
@@ -276,7 +276,7 @@ void psxDma12(u32 madr, u32 bcr, u32 chcr)
while (bcr > 0)
{
const u8 data = sio2.Read();
const u8 data = g_Sio2.Read();
iopMemWrite8(madr, data);
bcr--;
madr++;
+2 -3
View File
@@ -16,6 +16,8 @@
#include "PrecompiledHeader.h"
#include "Common.h"
#include "SIO/Sio2.h"
#include "SIO/Sio0.h"
#include "CDVD/CDVD.h"
#include "CDVD/Ps1CD.h"
#include "IopCounters.h"
@@ -23,7 +25,6 @@
#include "IopHw.h"
#include "Mdec.h"
#include "R3000A.h"
#include "Sio.h"
#include "x86/iR5900.h"
// NOTE: Any modifications to read/write fns should also go into their const counterparts
@@ -39,8 +40,6 @@ void psxHwReset() {
cdrReset();
cdvdReset();
psxRcntInit();
sio0.FullReset();
sio2.FullReset();
}
__fi u8 psxHw4Read8(u32 add)
+1 -1
View File
@@ -155,7 +155,7 @@ bool LayeredSettingsInterface::ContainsValue(const char* section, const char* ke
{
if (SettingsInterface* sif = m_layers[layer])
{
if (sif->ContainsValue(key, section))
if (sif->ContainsValue(section, key))
return true;
}
}
-59
View File
@@ -1,59 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
enum gamePadValues
{
PAD_UP, // Directional pad ↑
PAD_RIGHT, // Directional pad →
PAD_DOWN, // Directional pad ↓
PAD_LEFT, // Directional pad ←
PAD_TRIANGLE, // Triangle button ▲
PAD_CIRCLE, // Circle button ●
PAD_CROSS, // Cross button ✖
PAD_SQUARE, // Square button ■
PAD_SELECT, // Select button
PAD_START, // Start button
PAD_L1, // L1 button
PAD_L2, // L2 button
PAD_R1, // R1 button
PAD_R2, // R2 button
PAD_L3, // Left joystick button (L3)
PAD_R3, // Right joystick button (R3)
PAD_ANALOG, // Analog mode toggle
PAD_PRESSURE, // Pressure modifier
PAD_L_UP, // Left joystick (Up) ↑
PAD_L_RIGHT, // Left joystick (Right) →
PAD_L_DOWN, // Left joystick (Down) ↓
PAD_L_LEFT, // Left joystick (Left) ←
PAD_R_UP, // Right joystick (Up) ↑
PAD_R_RIGHT, // Right joystick (Right) →
PAD_R_DOWN, // Right joystick (Down) ↓
PAD_R_LEFT, // Right joystick (Left) ←
MAX_KEYS,
};
static inline bool IsAnalogKey(int index)
{
return ((index >= PAD_L_UP) && (index <= PAD_R_LEFT));
}
static inline bool IsTriggerKey(int index)
{
return (index == PAD_L2 || index == PAD_R2);
}
-217
View File
@@ -1,217 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "PAD/Host/KeyStatus.h"
#include "PAD/Host/Global.h"
#include <array>
#include <cmath>
using namespace PAD;
KeyStatus::KeyStatus()
{
std::memset(&m_analog, 0, sizeof(m_analog));
for (u32 pad = 0; pad < NUM_CONTROLLER_PORTS; pad++)
{
m_axis_scale[pad][0] = 0.0f;
m_axis_scale[pad][1] = 1.0f;
m_pressure_modifier[pad] = 0.5f;
}
Init();
}
void KeyStatus::Init()
{
for (u32 pad = 0; pad < NUM_CONTROLLER_PORTS; pad++)
{
m_button[pad] = 0xFFFFFFFF;
for (u32 index = 0; index < MAX_KEYS; index++)
m_button_pressure[pad][index] = 0;
m_analog[pad].lx = m_analog_released_val;
m_analog[pad].ly = m_analog_released_val;
m_analog[pad].rx = m_analog_released_val;
m_analog[pad].ry = m_analog_released_val;
}
}
void KeyStatus::Set(u32 pad, u32 index, float value)
{
// Since we reordered the buttons for better UI, we need to remap them here.
static constexpr std::array<u8, MAX_KEYS> bitmask_mapping = {{
12, // PAD_UP
13, // PAD_RIGHT
14, // PAD_DOWN
15, // PAD_LEFT
4, // PAD_TRIANGLE
5, // PAD_CIRCLE
6, // PAD_CROSS
7, // PAD_SQUARE
8, // PAD_SELECT
11, // PAD_START
2, // PAD_L1
0, // PAD_L2
3, // PAD_R1
1, // PAD_R2
9, // PAD_L3
10, // PAD_R3
16, // PAD_ANALOG
17, // PAD_PRESSURE
// remainder are analogs and not used here
}};
if (IsAnalogKey(index))
{
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(value * m_axis_scale[pad][1] * 255.0f, 0.0f, 255.0f));
// Left -> -- -> Right
// Value range : FFFF8002 -> 0 -> 7FFE
// Force range : 80 -> 0 -> 7F
// Normal mode : expect value 0 -> 80 -> FF
// Reverse mode: expect value FF -> 7F -> 0
// merge left/right or up/down into rx or ry
#define MERGE(pad, pos, neg) ((m_button_pressure[pad][pos] != 0) ? (127u + ((m_button_pressure[pad][pos] + 1u) / 2u)) : (127u - (m_button_pressure[pad][neg] / 2u)))
if (index <= PAD_L_LEFT)
{
// Left Stick
m_analog[pad].lx = m_analog[pad].invert_lx ? MERGE(pad, PAD_L_LEFT, PAD_L_RIGHT) : MERGE(pad, PAD_L_RIGHT, PAD_L_LEFT);
m_analog[pad].ly = m_analog[pad].invert_ly ? MERGE(pad, PAD_L_UP, PAD_L_DOWN) : MERGE(pad, PAD_L_DOWN, PAD_L_UP);
}
else
{
// Right Stick
m_analog[pad].rx = m_analog[pad].invert_rx ? MERGE(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE(pad, PAD_R_RIGHT, PAD_R_LEFT);
m_analog[pad].ry = m_analog[pad].invert_ry ? MERGE(pad, PAD_R_UP, PAD_R_DOWN) : MERGE(pad, PAD_R_DOWN, PAD_R_UP);
}
#undef MERGE
// Deadzone computation.
const float dz = m_axis_scale[pad][0];
if (dz > 0.0f)
{
#define MERGE_F(pad, pos, neg) ((m_button_pressure[pad][pos] != 0) ? (static_cast<float>(m_button_pressure[pad][pos]) / 255.0f) : (static_cast<float>(m_button_pressure[pad][neg]) / -255.0f))
float pos_x, pos_y;
if (index <= PAD_L_LEFT)
{
pos_x = m_analog[pad].invert_lx ? MERGE_F(pad, PAD_L_LEFT, PAD_L_RIGHT) : MERGE_F(pad, PAD_L_RIGHT, PAD_L_LEFT);
pos_y = m_analog[pad].invert_ly ? MERGE_F(pad, PAD_L_UP, PAD_L_DOWN) : MERGE_F(pad, PAD_L_DOWN, PAD_L_UP);
}
else
{
pos_x = m_analog[pad].invert_rx ? MERGE_F(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE_F(pad, PAD_R_RIGHT, PAD_R_LEFT);
pos_y = m_analog[pad].invert_ry ? MERGE_F(pad, PAD_R_UP, PAD_R_DOWN) : MERGE_F(pad, PAD_R_DOWN, PAD_R_UP);
}
// No point checking if we're at dead center (usually keyboard with no buttons pressed).
if (pos_x != 0.0f || pos_y != 0.0f)
{
// Compute the angle at the given position in the stick's square bounding box.
const float theta = std::atan2(pos_y, pos_x);
// Compute the position that the edge of the circle would be at, given the angle.
const float dz_x = std::cos(theta) * dz;
const float dz_y = std::sin(theta) * dz;
// We're in the deadzone if our position is less than the circle edge.
const bool in_x = (pos_x < 0.0f) ? (pos_x > dz_x) : (pos_x <= dz_x);
const bool in_y = (pos_y < 0.0f) ? (pos_y > dz_y) : (pos_y <= dz_y);
if (in_x && in_y)
{
// In deadzone. Set to 127 (center).
if (index <= PAD_L_LEFT)
m_analog[pad].lx = m_analog[pad].ly = 127;
else
m_analog[pad].rx = m_analog[pad].ry = 127;
}
}
#undef MERGE_F
}
}
else if (IsTriggerKey(index))
{
const float s_value = std::clamp(value * m_trigger_scale[pad][1], 0.0f, 1.0f);
const float dz_value = (m_trigger_scale[pad][0] > 0.0f && s_value < m_trigger_scale[pad][0]) ? 0.0f : s_value;
m_button_pressure[pad][index] = static_cast<u8>(dz_value * 255.0f);
if (dz_value > 0.0f)
m_button[pad] &= ~(1u << bitmask_mapping[index]);
else
m_button[pad] |= (1u << bitmask_mapping[index]);
}
else
{
// Don't affect L2/R2, since they are analog on most pads.
const float pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0) ? m_pressure_modifier[pad] : 1.0f;
const float dz_value = (value < m_button_deadzone[pad]) ? 0.0f : value;
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(dz_value * pmod * 255.0f, 0.0f, 255.0f));
if (dz_value > 0.0f)
m_button[pad] &= ~(1u << bitmask_mapping[index]);
else
m_button[pad] |= (1u << bitmask_mapping[index]);
// Adjust pressure of all other face buttons which are active when pressure modifier is pressed..
if (index == PAD_PRESSURE)
{
const float adjust_pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0) ? m_pressure_modifier[pad] : (1.0f / m_pressure_modifier[pad]);
for (u32 i = 0; i < MAX_KEYS; i++)
{
if (i == index || IsAnalogKey(i) || IsTriggerKey(i))
continue;
// We add 0.5 here so that the round trip between 255->127->255 when applying works as expected.
const float add = (m_button_pressure[pad][i] != 0) ? 0.5f : 0.0f;
m_button_pressure[pad][i] = static_cast<u8>(std::clamp((static_cast<float>(m_button_pressure[pad][i]) + add) * adjust_pmod, 0.0f, 255.0f));
}
}
}
}
u32 KeyStatus::GetButtons(u32 pad)
{
return m_button[pad];
}
u8 KeyStatus::GetPressure(u32 pad, u32 index)
{
switch (index)
{
case PAD_R_LEFT:
case PAD_R_RIGHT:
return m_analog[pad].rx;
case PAD_R_DOWN:
case PAD_R_UP:
return m_analog[pad].ry;
case PAD_L_LEFT:
case PAD_L_RIGHT:
return m_analog[pad].lx;
case PAD_L_DOWN:
case PAD_L_UP:
return m_analog[pad].ly;
default:
return m_button_pressure[pad][index];
}
}
-101
View File
@@ -1,101 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "PAD/Host/PAD.h"
#include <tuple>
namespace PAD
{
enum class ControllerType : u8;
class KeyStatus
{
private:
static constexpr u8 m_analog_released_val = 0x7F;
struct PADAnalog
{
u8 lx, ly;
u8 rx, ry;
u8 invert_lx, invert_ly;
u8 invert_rx, invert_ry;
};
PAD::ControllerType m_type[NUM_CONTROLLER_PORTS] = {};
u32 m_button[NUM_CONTROLLER_PORTS];
u8 m_button_pressure[NUM_CONTROLLER_PORTS][MAX_KEYS];
PADAnalog m_analog[NUM_CONTROLLER_PORTS];
float m_axis_scale[NUM_CONTROLLER_PORTS][2];
float m_trigger_scale[NUM_CONTROLLER_PORTS][2];
float m_vibration_scale[NUM_CONTROLLER_PORTS][2];
float m_pressure_modifier[NUM_CONTROLLER_PORTS];
float m_button_deadzone[NUM_CONTROLLER_PORTS];
public:
KeyStatus();
void Init();
void Set(u32 pad, u32 index, float value);
__fi void SetRawAnalogs(const u32 pad, const std::tuple<u8, u8> left, const std::tuple<u8, u8> right)
{
m_analog[pad].lx = std::get<0>(left);
m_analog[pad].ly = std::get<1>(left);
m_analog[pad].rx = std::get<0>(right);
m_analog[pad].ry = std::get<1>(right);
}
__fi PAD::ControllerType GetType(u32 pad) { return m_type[pad]; }
__fi void SetType(u32 pad, PAD::ControllerType type) { m_type[pad] = type; }
__fi void SetAxisScale(u32 pad, float deadzone, float scale)
{
m_axis_scale[pad][0] = deadzone;
m_axis_scale[pad][1] = scale;
}
__fi void SetTriggerScale(u32 pad, float deadzone, float scale)
{
m_trigger_scale[pad][0] = deadzone;
m_trigger_scale[pad][1] = scale;
}
__fi float GetVibrationScale(u32 pad, u32 motor) const { return m_vibration_scale[pad][motor]; }
__fi void SetVibrationScale(u32 pad, u32 motor, float scale) { m_vibration_scale[pad][motor] = scale; }
__fi float GetPressureModifier(u32 pad) const { return m_pressure_modifier[pad]; }
__fi void SetPressureModifier(u32 pad, float mod) { m_pressure_modifier[pad] = mod; }
__fi void SetButtonDeadzone(u32 pad, float deadzone) { m_button_deadzone[pad] = deadzone; }
__fi void SetAnalogInvertL(u32 pad, bool x, bool y)
{
m_analog[pad].invert_lx = x;
m_analog[pad].invert_ly = y;
}
__fi void SetAnalogInvertR(u32 pad, bool x, bool y)
{
m_analog[pad].invert_rx = x;
m_analog[pad].invert_ry = y;
}
__fi u8 GetRawPressure(u32 pad, u32 index) const { return m_button_pressure[pad][index]; }
__fi std::tuple<u8, u8> GetRawLeftAnalog(u32 pad) const { return {m_analog[pad].lx, m_analog[pad].ly}; }
__fi std::tuple<u8, u8> GetRawRightAnalog(u32 pad) const { return {m_analog[pad].rx, m_analog[pad].ry}; }
u32 GetButtons(u32 pad);
u8 GetPressure(u32 pad, u32 index);
};
} // namespace PAD
extern PAD::KeyStatus g_key_status;
-757
View File
@@ -1,757 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Host.h"
#include "Input/InputManager.h"
#include "PAD/Host/Global.h"
#include "PAD/Host/KeyStatus.h"
#include "PAD/Host/PAD.h"
#include "PAD/Host/StateManagement.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/SettingsInterface.h"
#include "common/StringUtil.h"
#include <array>
const u32 revision = 3;
const u32 build = 0; // increase that with each version
#define PAD_SAVE_STATE_VERSION ((revision << 8) | (build << 0))
PAD::KeyStatus g_key_status;
namespace PAD
{
struct MacroButton
{
std::vector<u32> buttons; ///< Buttons to activate.
float pressure; ///< Pressure to apply when macro is active.
u32 toggle_frequency; ///< Interval at which the buttons will be toggled, if not 0.
u32 toggle_counter; ///< When this counter reaches zero, buttons will be toggled.
bool toggle_state; ///< Current state for turbo.
bool trigger_state; ///< Whether the macro button is active.
};
static std::string GetConfigSection(u32 pad_index);
static void LoadMacroButtonConfig(const SettingsInterface& si, u32 pad, const std::string_view& type, const std::string& section);
static void ApplyMacroButton(u32 pad, const MacroButton& mb);
static void UpdateMacroButtons();
static std::array<std::array<MacroButton, NUM_MACRO_BUTTONS_PER_CONTROLLER>, NUM_CONTROLLER_PORTS> s_macro_buttons;
} // namespace PAD
s32 PADinit()
{
Pad::reset_all();
query.reset();
for (int port = 0; port < 2; port++)
slots[port] = 0;
return 0;
}
void PADshutdown()
{
}
s32 PADopen()
{
g_key_status.Init();
return 0;
}
void PADclose()
{
}
s32 PADsetSlot(u8 port, u8 slot)
{
port--;
slot--;
if (port > 1 || slot > 3)
{
return 0;
}
// Even if no pad there, record the slot, as it is the active slot regardless.
slots[port] = slot;
return 1;
}
s32 PADfreeze(FreezeAction mode, freezeData* data)
{
if (!data)
return -1;
if (mode == FreezeAction::Size)
{
data->size = sizeof(PadFullFreezeData);
}
else if (mode == FreezeAction::Load)
{
PadFullFreezeData* pdata = (PadFullFreezeData*)(data->data);
Pad::stop_vibrate_all();
if (data->size != sizeof(PadFullFreezeData) || pdata->version != PAD_SAVE_STATE_VERSION ||
strncmp(pdata->format, "LinPad", sizeof(pdata->format)))
return 0;
query = pdata->query;
if (pdata->query.slot < 4)
{
query = pdata->query;
}
// Tales of the Abyss - pad fix
// - restore data for both ports
for (int port = 0; port < 2; port++)
{
for (int slot = 0; slot < 4; slot++)
{
u8 mode = pdata->padData[port][slot].mode;
if (mode != MODE_DIGITAL && mode != MODE_ANALOG && mode != MODE_DS2_NATIVE)
{
break;
}
memcpy(&pads[port][slot], &pdata->padData[port][slot], sizeof(PadFreezeData));
}
if (pdata->slot[port] < 4)
slots[port] = pdata->slot[port];
}
}
else if (mode == FreezeAction::Save)
{
if (data->size != sizeof(PadFullFreezeData))
return 0;
PadFullFreezeData* pdata = (PadFullFreezeData*)(data->data);
// Tales of the Abyss - pad fix
// - PCSX2 only saves port0 (save #1), then port1 (save #2)
memset(pdata, 0, data->size);
strncpy(pdata->format, "LinPad", sizeof(pdata->format));
pdata->version = PAD_SAVE_STATE_VERSION;
pdata->query = query;
for (int port = 0; port < 2; port++)
{
for (int slot = 0; slot < 4; slot++)
{
pdata->padData[port][slot] = pads[port][slot];
}
pdata->slot[port] = slots[port];
}
}
else
{
return -1;
}
return 0;
}
u8 PADstartPoll(int _port, int _slot)
{
return pad_start_poll(_port, _slot);
}
u8 PADpoll(u8 value)
{
return pad_poll(value);
}
std::string PAD::GetConfigSection(u32 pad_index)
{
return fmt::format("Pad{}", pad_index + 1);
}
bool PADcomplete()
{
return pad_complete();
}
void PAD::LoadConfig(const SettingsInterface& si)
{
PAD::s_macro_buttons = {};
EmuConfig.MultitapPort0_Enabled = si.GetBoolValue("Pad", "MultitapPort1", false);
EmuConfig.MultitapPort1_Enabled = si.GetBoolValue("Pad", "MultitapPort2", false);
// This is where we would load controller types, if onepad supported them.
for (u32 i = 0; i < NUM_CONTROLLER_PORTS; i++)
{
const std::string section(GetConfigSection(i));
const std::string type(si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(i)));
const ControllerInfo* ci = GetControllerInfo(type);
if (!ci)
{
g_key_status.SetType(i, ControllerType::NotConnected);
continue;
}
g_key_status.SetType(i, ci->type);
const float axis_deadzone = si.GetFloatValue(section.c_str(), "Deadzone", DEFAULT_STICK_DEADZONE);
const float axis_scale = si.GetFloatValue(section.c_str(), "AxisScale", DEFAULT_STICK_SCALE);
const float trigger_deadzone = si.GetFloatValue(section.c_str(), "TriggerDeadzone", DEFAULT_TRIGGER_DEADZONE);
const float trigger_scale = si.GetFloatValue(section.c_str(), "TriggerScale", DEFAULT_TRIGGER_SCALE);
const float button_deadzone = si.GetFloatValue(section.c_str(), "ButtonDeadzone", DEFAULT_BUTTON_DEADZONE);
g_key_status.SetAxisScale(i, axis_deadzone, axis_scale);
g_key_status.SetTriggerScale(i, trigger_deadzone, trigger_scale);
g_key_status.SetButtonDeadzone(i, button_deadzone);
if (ci->vibration_caps != VibrationCapabilities::NoVibration)
{
const float large_motor_scale = si.GetFloatValue(section.c_str(), "LargeMotorScale", DEFAULT_MOTOR_SCALE);
const float small_motor_scale = si.GetFloatValue(section.c_str(), "SmallMotorScale", DEFAULT_MOTOR_SCALE);
g_key_status.SetVibrationScale(i, 0, large_motor_scale);
g_key_status.SetVibrationScale(i, 1, small_motor_scale);
}
const float pressure_modifier = si.GetFloatValue(section.c_str(), "PressureModifier", 1.0f);
g_key_status.SetPressureModifier(i, pressure_modifier);
const int invert_l = si.GetIntValue(section.c_str(), "InvertL", 0);
const int invert_r = si.GetIntValue(section.c_str(), "InvertR", 0);
g_key_status.SetAnalogInvertL(i, (invert_l & 1) != 0, (invert_l & 2) != 0);
g_key_status.SetAnalogInvertR(i, (invert_r & 1) != 0, (invert_r & 2) != 0);
LoadMacroButtonConfig(si, i, type, section);
}
}
const char* PAD::GetDefaultPadType(u32 pad)
{
return (pad == 0) ? "DualShock2" : "None";
}
void PAD::SetDefaultControllerConfig(SettingsInterface& si)
{
si.ClearSection("InputSources");
si.ClearSection("Hotkeys");
si.ClearSection("Pad");
// PCSX2 Controller Settings - Global Settings
for (u32 i = 0; i < static_cast<u32>(InputSourceType::Count); i++)
{
si.SetBoolValue("InputSources",
InputManager::InputSourceToString(static_cast<InputSourceType>(i)),
InputManager::GetInputSourceDefaultEnabled(static_cast<InputSourceType>(i)));
}
#ifdef SDL_BUILD
si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
#endif
si.SetBoolValue("Pad", "MultitapPort1", false);
si.SetBoolValue("Pad", "MultitapPort2", false);
si.SetFloatValue("Pad", "PointerXSpeed", 40.0f);
si.SetFloatValue("Pad", "PointerYSpeed", 40.0f);
si.SetFloatValue("Pad", "PointerXDeadZone", 20.0f);
si.SetFloatValue("Pad", "PointerYDeadZone", 20.0f);
si.SetFloatValue("Pad", "PointerInertia", 10.0f);
// PCSX2 Controller Settings - Default pad types and parameters.
for (u32 i = 0; i < NUM_CONTROLLER_PORTS; i++)
{
const char* type = GetDefaultPadType(i);
const std::string section(GetConfigSection(i));
si.ClearSection(section.c_str());
si.SetStringValue(section.c_str(), "Type", type);
const ControllerInfo* ci = GetControllerInfo(type);
if (ci)
{
for (u32 i = 0; i < ci->num_settings; i++)
{
const SettingInfo& csi = ci->settings[i];
csi.SetDefaultValue(&si, section.c_str(), csi.name);
}
}
}
// PCSX2 Controller Settings - Controller 1 / Controller 2 / ...
// Use the automapper to set this up.
MapController(si, 0, InputManager::GetGenericBindingMapping("Keyboard"));
}
void PAD::SetDefaultHotkeyConfig(SettingsInterface& si)
{
// PCSX2 Controller Settings - Hotkeys
// PCSX2 Controller Settings - Hotkeys - General
si.SetStringValue("Hotkeys", "ToggleFullscreen", "Keyboard/Alt & Keyboard/Return");
// PCSX2 Controller Settings - Hotkeys - Graphics
si.SetStringValue("Hotkeys", "CycleAspectRatio", "Keyboard/F6");
si.SetStringValue("Hotkeys", "CycleInterlaceMode", "Keyboard/F5");
si.SetStringValue("Hotkeys", "CycleMipmapMode", "Keyboard/Insert");
// si.SetStringValue("Hotkeys", "DecreaseUpscaleMultiplier", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "IncreaseUpscaleMultiplier", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ReloadTextureReplacements", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "GSDumpMultiFrame", "Keyboard/Control & Keyboard/Shift & Keyboard/F8");
si.SetStringValue("Hotkeys", "Screenshot", "Keyboard/F8");
si.SetStringValue("Hotkeys", "GSDumpSingleFrame", "Keyboard/Shift & Keyboard/F8");
si.SetStringValue("Hotkeys", "ToggleSoftwareRendering", "Keyboard/F9");
// si.SetStringValue("Hotkeys", "ToggleTextureDumping", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ToggleTextureReplacements", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "ZoomIn", "Keyboard/Control & Keyboard/Plus");
si.SetStringValue("Hotkeys", "ZoomOut", "Keyboard/Control & Keyboard/Minus");
// Missing hotkey for resetting zoom back to 100 with Keyboard/Control & Keyboard/Asterisk
// PCSX2 Controller Settings - Hotkeys - Input Recording
si.SetStringValue("Hotkeys", "InputRecToggleMode", "Keyboard/Shift & Keyboard/R");
// PCSX2 Controller Settings - Hotkeys - Save States
si.SetStringValue("Hotkeys", "LoadStateFromSlot", "Keyboard/F3");
si.SetStringValue("Hotkeys", "SaveStateToSlot", "Keyboard/F1");
si.SetStringValue("Hotkeys", "NextSaveStateSlot", "Keyboard/F2");
si.SetStringValue("Hotkeys", "PreviousSaveStateSlot", "Keyboard/Shift & Keyboard/F2");
// PCSX2 Controller Settings - Hotkeys - System
// si.SetStringValue("Hotkeys", "DecreaseSpeed", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "FrameAdvance", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "IncreaseSpeed", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ResetVM", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ShutdownVM", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "OpenPauseMenu", "Keyboard/Escape");
si.SetStringValue("Hotkeys", "ToggleFrameLimit", "Keyboard/F4");
si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Space");
si.SetStringValue("Hotkeys", "ToggleSlowMotion", "Keyboard/Shift & Keyboard/Backtab");
si.SetStringValue("Hotkeys", "ToggleTurbo", "Keyboard/Tab");
si.SetStringValue("Hotkeys", "HoldTurbo", "Keyboard/Period");
}
void PAD::Update()
{
Pad::rumble_all();
UpdateMacroButtons();
}
static const InputBindingInfo s_dualshock2_binds[] = {
{"Up", TRANSLATE_NOOP("Pad", "D-Pad Up"), InputBindingInfo::Type::Button, PAD_UP, GenericInputBinding::DPadUp},
{"Right", TRANSLATE_NOOP("Pad", "D-Pad Right"), InputBindingInfo::Type::Button, PAD_RIGHT, GenericInputBinding::DPadRight},
{"Down", TRANSLATE_NOOP("Pad", "D-Pad Down"), InputBindingInfo::Type::Button, PAD_DOWN, GenericInputBinding::DPadDown},
{"Left", TRANSLATE_NOOP("Pad", "D-Pad Left"), InputBindingInfo::Type::Button, PAD_LEFT, GenericInputBinding::DPadLeft},
{"Triangle", TRANSLATE_NOOP("Pad", "Triangle"), InputBindingInfo::Type::Button, PAD_TRIANGLE, GenericInputBinding::Triangle},
{"Circle", TRANSLATE_NOOP("Pad", "Circle"), InputBindingInfo::Type::Button, PAD_CIRCLE, GenericInputBinding::Circle},
{"Cross", TRANSLATE_NOOP("Pad", "Cross"), InputBindingInfo::Type::Button, PAD_CROSS, GenericInputBinding::Cross},
{"Square", TRANSLATE_NOOP("Pad", "Square"), InputBindingInfo::Type::Button, PAD_SQUARE, GenericInputBinding::Square},
{"Select", TRANSLATE_NOOP("Pad", "Select"), InputBindingInfo::Type::Button, PAD_SELECT, GenericInputBinding::Select},
{"Start", TRANSLATE_NOOP("Pad", "Start"), InputBindingInfo::Type::Button, PAD_START, GenericInputBinding::Start},
{"L1", TRANSLATE_NOOP("Pad", "L1 (Left Bumper)"), InputBindingInfo::Type::Button, PAD_L1, GenericInputBinding::L1},
{"L2", TRANSLATE_NOOP("Pad", "L2 (Left Trigger)"), InputBindingInfo::Type::HalfAxis, PAD_L2, GenericInputBinding::L2},
{"R1", TRANSLATE_NOOP("Pad", "R1 (Right Bumper)"), InputBindingInfo::Type::Button, PAD_R1, GenericInputBinding::R1},
{"R2", TRANSLATE_NOOP("Pad", "R2 (Right Trigger)"), InputBindingInfo::Type::HalfAxis, PAD_R2, GenericInputBinding::R2},
{"L3", TRANSLATE_NOOP("Pad", "L3 (Left Stick Button)"), InputBindingInfo::Type::Button, PAD_L3, GenericInputBinding::L3},
{"R3", TRANSLATE_NOOP("Pad", "R3 (Right Stick Button)"), InputBindingInfo::Type::Button, PAD_R3, GenericInputBinding::R3},
{"Analog", TRANSLATE_NOOP("Pad", "Analog Toggle"), InputBindingInfo::Type::Button, PAD_ANALOG, GenericInputBinding::System},
{"Pressure", TRANSLATE_NOOP("Pad", "Apply Pressure"), InputBindingInfo::Type::Button, PAD_PRESSURE, GenericInputBinding::Unknown},
{"LUp", TRANSLATE_NOOP("Pad", "Left Stick Up"), InputBindingInfo::Type::HalfAxis, PAD_L_UP, GenericInputBinding::LeftStickUp},
{"LRight", TRANSLATE_NOOP("Pad", "Left Stick Right"), InputBindingInfo::Type::HalfAxis, PAD_L_RIGHT, GenericInputBinding::LeftStickRight},
{"LDown", TRANSLATE_NOOP("Pad", "Left Stick Down"), InputBindingInfo::Type::HalfAxis, PAD_L_DOWN, GenericInputBinding::LeftStickDown},
{"LLeft", TRANSLATE_NOOP("Pad", "Left Stick Left"), InputBindingInfo::Type::HalfAxis, PAD_L_LEFT, GenericInputBinding::LeftStickLeft},
{"RUp", TRANSLATE_NOOP("Pad", "Right Stick Up"), InputBindingInfo::Type::HalfAxis, PAD_R_UP, GenericInputBinding::RightStickUp},
{"RRight", TRANSLATE_NOOP("Pad", "Right Stick Right"), InputBindingInfo::Type::HalfAxis, PAD_R_RIGHT, GenericInputBinding::RightStickRight},
{"RDown", TRANSLATE_NOOP("Pad", "Right Stick Down"), InputBindingInfo::Type::HalfAxis, PAD_R_DOWN, GenericInputBinding::RightStickDown},
{"RLeft", TRANSLATE_NOOP("Pad", "Right Stick Left"), InputBindingInfo::Type::HalfAxis, PAD_R_LEFT, GenericInputBinding::RightStickLeft},
{"LargeMotor", TRANSLATE_NOOP("Pad", "Large (Low Frequency) Motor"), InputBindingInfo::Type::Motor, 0, GenericInputBinding::LargeMotor},
{"SmallMotor", TRANSLATE_NOOP("Pad", "Small (High Frequency) Motor"), InputBindingInfo::Type::Motor, 0, GenericInputBinding::SmallMotor},
};
static const char* s_dualshock2_invert_entries[] = {
TRANSLATE_NOOP("Pad", "Not Inverted"),
TRANSLATE_NOOP("Pad", "Invert Left/Right"),
TRANSLATE_NOOP("Pad", "Invert Up/Down"),
TRANSLATE_NOOP("Pad", "Invert Left/Right + Up/Down"),
nullptr};
static const SettingInfo s_dualshock2_settings[] = {
{SettingInfo::Type::IntegerList, "InvertL", TRANSLATE_NOOP("Pad", "Invert Left Stick"),
TRANSLATE_NOOP("Pad", "Inverts the direction of the left analog stick."), "0", "0", "3", nullptr, nullptr,
s_dualshock2_invert_entries, nullptr, 0.0f},
{SettingInfo::Type::IntegerList, "InvertR", TRANSLATE_NOOP("Pad", "Invert Right Stick"),
TRANSLATE_NOOP("Pad", "Inverts the direction of the right analog stick."), "0", "0", "3", nullptr, nullptr,
s_dualshock2_invert_entries, nullptr, 0.0f},
{SettingInfo::Type::Float, "Deadzone", TRANSLATE_NOOP("Pad", "Analog Deadzone"),
TRANSLATE_NOOP("Pad",
"Sets the analog stick deadzone, i.e. the fraction of the analog stick movement which will be ignored."),
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "AxisScale", TRANSLATE_NOOP("Pad", "Analog Sensitivity"),
TRANSLATE_NOOP("Pad",
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller."),
"1.33", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "TriggerDeadzone", TRANSLATE_NOOP("Pad", "Trigger Deadzone"),
TRANSLATE_NOOP("Pad",
"Sets the deadzone for activating triggers, i.e. the fraction of the trigger press which will be ignored."),
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "TriggerScale", TRANSLATE_NOOP("Pad", "Trigger Sensitivity"),
TRANSLATE_NOOP("Pad", "Sets the trigger scaling factor."), "1.00", "0.01", "2.00", "0.01", "%.0f%%", nullptr,
nullptr, 100.0f},
{SettingInfo::Type::Float, "LargeMotorScale", TRANSLATE_NOOP("Pad", "Large Motor Vibration Scale"),
TRANSLATE_NOOP("Pad", "Increases or decreases the intensity of low frequency vibration sent by the game."),
"1.00", "0.00", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "SmallMotorScale", TRANSLATE_NOOP("Pad", "Small Motor Vibration Scale"),
TRANSLATE_NOOP("Pad", "Increases or decreases the intensity of high frequency vibration sent by the game."),
"1.00", "0.00", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "ButtonDeadzone", TRANSLATE_NOOP("Pad", "Button Deadzone"),
TRANSLATE_NOOP("Pad",
"Sets the deadzone for activating buttons, i.e. the fraction of the button press which will be ignored."),
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "PressureModifier", TRANSLATE_NOOP("Pad", "Modifier Pressure"),
TRANSLATE_NOOP("Pad", "Sets the pressure when the modifier button is held."), "0.50", "0.01", "1.00", "0.01",
"%.0f%%", nullptr, nullptr, 100.0f},
};
static const PAD::ControllerInfo s_controller_info[] = {
{PAD::ControllerType::NotConnected, "None", TRANSLATE_NOOP("Pad", "Not Connected"), nullptr, 0, nullptr, 0,
PAD::VibrationCapabilities::NoVibration},
{PAD::ControllerType::DualShock2, "DualShock2", TRANSLATE_NOOP("Pad", "DualShock 2"), s_dualshock2_binds,
std::size(s_dualshock2_binds), s_dualshock2_settings, std::size(s_dualshock2_settings),
PAD::VibrationCapabilities::LargeSmallMotors},
};
const PAD::ControllerInfo* PAD::GetControllerInfo(ControllerType type)
{
for (const ControllerInfo& info : s_controller_info)
{
if (type == info.type)
return &info;
}
return nullptr;
}
const PAD::ControllerInfo* PAD::GetControllerInfo(const std::string_view& name)
{
for (const ControllerInfo& info : s_controller_info)
{
if (name == info.name)
return &info;
}
return nullptr;
}
std::vector<std::pair<const char*, const char*>> PAD::GetControllerTypeNames()
{
std::vector<std::pair<const char*, const char*>> ret;
for (const ControllerInfo& info : s_controller_info)
ret.emplace_back(info.name, info.display_name);
return ret;
}
std::vector<std::string> PAD::GetControllerBinds(const std::string_view& type)
{
std::vector<std::string> ret;
const ControllerInfo* info = GetControllerInfo(type);
if (info)
{
for (u32 i = 0; i < info->num_bindings; i++)
{
const InputBindingInfo& bi = info->bindings[i];
if (bi.bind_type == InputBindingInfo::Type::Unknown || bi.bind_type == InputBindingInfo::Type::Motor)
continue;
ret.emplace_back(info->bindings[i].name);
}
}
return ret;
}
void PAD::ClearPortBindings(SettingsInterface& si, u32 port)
{
const std::string section(StringUtil::StdStringFromFormat("Pad%u", port + 1));
const std::string type(si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(port)));
const ControllerInfo* info = GetControllerInfo(type);
if (!info)
return;
for (u32 i = 0; i < info->num_bindings; i++)
{
const InputBindingInfo& bi = info->bindings[i];
si.DeleteValue(section.c_str(), bi.name);
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str());
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
}
}
void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
bool copy_pad_config, bool copy_pad_bindings, bool copy_hotkey_bindings)
{
if (copy_pad_config)
{
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort1");
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort2");
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort1");
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort2");
dest_si->CopyFloatValue(src_si, "Pad", "PointerXSpeed");
dest_si->CopyFloatValue(src_si, "Pad", "PointerYSpeed");
dest_si->CopyFloatValue(src_si, "Pad", "PointerXDeadZone");
dest_si->CopyFloatValue(src_si, "Pad", "PointerYDeadZone");
dest_si->CopyFloatValue(src_si, "Pad", "PointerInertia");
for (u32 i = 0; i < static_cast<u32>(InputSourceType::Count); i++)
{
dest_si->CopyBoolValue(src_si, "InputSources",
InputManager::InputSourceToString(static_cast<InputSourceType>(i)));
}
#ifdef SDL_BUILD
dest_si->CopyBoolValue(src_si, "InputSources", "SDLControllerEnhancedMode");
#endif
}
for (u32 port = 0; port < NUM_CONTROLLER_PORTS; port++)
{
const std::string section(fmt::format("Pad{}", port + 1));
const std::string type(src_si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(port)));
if (copy_pad_config)
dest_si->SetStringValue(section.c_str(), "Type", type.c_str());
const ControllerInfo* info = GetControllerInfo(type);
if (!info)
return;
if (copy_pad_bindings)
{
for (u32 i = 0; i < info->num_bindings; i++)
{
const InputBindingInfo& bi = info->bindings[i];
dest_si->CopyStringListValue(src_si, section.c_str(), bi.name);
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Sensitivity", bi.name).c_str());
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
}
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
{
dest_si->CopyStringListValue(src_si, section.c_str(), fmt::format("Macro{}", i + 1).c_str());
dest_si->CopyStringValue(src_si, section.c_str(), fmt::format("Macro{}Binds", i + 1).c_str());
dest_si->CopyUIntValue(src_si, section.c_str(), fmt::format("Macro{}Frequency", i + 1).c_str());
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("Macro{}Pressure", i + 1).c_str());
}
}
if (copy_pad_config)
{
for (u32 i = 0; i < info->num_settings; i++)
{
const SettingInfo& csi = info->settings[i];
csi.CopyValue(dest_si, src_si, section.c_str(), csi.name);
}
}
}
if (copy_hotkey_bindings)
{
std::vector<const HotkeyInfo*> hotkeys(InputManager::GetHotkeyList());
for (const HotkeyInfo* hki : hotkeys)
dest_si->CopyStringListValue(src_si, "Hotkeys", hki->name);
}
}
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
const InputManager::GenericInputBindingMapping& mapping, InputBindingInfo::Type bind_type,
GenericInputBinding generic_name, const char* bind_name)
{
// find the mapping it corresponds to
const std::string* found_mapping = nullptr;
for (const std::pair<GenericInputBinding, std::string>& it : mapping)
{
if (it.first == generic_name)
{
found_mapping = &it.second;
break;
}
}
// Remove previously-set binding scales.
if (bind_type == InputBindingInfo::Type::Button || bind_type == InputBindingInfo::Type::Axis ||
bind_type == InputBindingInfo::Type::HalfAxis)
{
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bind_name).c_str());
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bind_name).c_str());
}
if (found_mapping)
{
Console.WriteLn("(MapController) Map %s/%s to '%s'", section.c_str(), bind_name, found_mapping->c_str());
si.SetStringValue(section.c_str(), bind_name, found_mapping->c_str());
return 1;
}
else
{
si.DeleteValue(section.c_str(), bind_name);
return 0;
}
}
bool PAD::MapController(SettingsInterface& si, u32 controller,
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping)
{
const std::string section(StringUtil::StdStringFromFormat("Pad%u", controller + 1));
const std::string type(si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(controller)));
const ControllerInfo* info = GetControllerInfo(type);
if (!info)
return false;
u32 num_mappings = 0;
for (u32 i = 0; i < info->num_bindings; i++)
{
const InputBindingInfo& bi = info->bindings[i];
if (bi.generic_mapping == GenericInputBinding::Unknown)
continue;
num_mappings += TryMapGenericMapping(si, section, mapping, bi.bind_type, bi.generic_mapping, bi.name);
}
if (info->vibration_caps == VibrationCapabilities::LargeSmallMotors)
{
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "SmallMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "LargeMotor");
}
else if (info->vibration_caps == VibrationCapabilities::SingleMotor)
{
if (TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "Motor") == 0)
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "Motor");
else
num_mappings++;
}
return (num_mappings > 0);
}
void PAD::SetControllerState(u32 controller, u32 bind, float value)
{
if (controller >= NUM_CONTROLLER_PORTS || bind > MAX_KEYS)
return;
g_key_status.Set(controller, bind, value);
}
void PAD::LoadMacroButtonConfig(const SettingsInterface& si, u32 pad, const std::string_view& type, const std::string& section)
{
// lazily initialized
std::vector<std::string> binds;
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
{
std::string binds_string;
if (!si.GetStringValue(section.c_str(), fmt::format("Macro{}Binds", i + 1).c_str(), &binds_string))
continue;
const u32 frequency = si.GetUIntValue(section.c_str(), fmt::format("Macro{}Frequency", i + 1).c_str(), 0u);
if (binds.empty())
binds = GetControllerBinds(type);
const float pressure = si.GetFloatValue(section.c_str(), fmt::format("Macro{}Pressure", i + 1).c_str(), 1.0f);
// convert binds
std::vector<u32> bind_indices;
std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
if (buttons_split.empty())
continue;
for (const std::string_view& button : buttons_split)
{
auto it = std::find(binds.begin(), binds.end(), button);
if (it == binds.end())
{
Console.Error("Invalid bind '%.*s' in macro button %u for pad %u", static_cast<int>(button.size()), button.data(), pad, i);
continue;
}
bind_indices.push_back(static_cast<u32>(std::distance(binds.begin(), it)));
}
if (bind_indices.empty())
continue;
s_macro_buttons[pad][i].buttons = std::move(bind_indices);
s_macro_buttons[pad][i].toggle_frequency = frequency;
s_macro_buttons[pad][i].pressure = pressure;
}
}
void PAD::SetMacroButtonState(u32 pad, u32 index, bool state)
{
if (pad >= NUM_CONTROLLER_PORTS || index >= NUM_MACRO_BUTTONS_PER_CONTROLLER)
return;
MacroButton& mb = s_macro_buttons[pad][index];
if (mb.buttons.empty() || mb.trigger_state == state)
return;
mb.toggle_counter = mb.toggle_frequency;
mb.trigger_state = state;
if (mb.toggle_state != state)
{
mb.toggle_state = state;
ApplyMacroButton(pad, mb);
}
}
std::vector<std::string> PAD::GetInputProfileNames()
{
FileSystem::FindResultsArray results;
FileSystem::FindFiles(EmuFolders::InputProfiles.c_str(), "*.ini",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS,
&results);
std::vector<std::string> ret;
ret.reserve(results.size());
for (FILESYSTEM_FIND_DATA& fd : results)
ret.emplace_back(Path::GetFileTitle(fd.FileName));
return ret;
}
void PAD::ApplyMacroButton(u32 pad, const MacroButton& mb)
{
const float value = mb.toggle_state ? mb.pressure : 0.0f;
for (const u32 btn : mb.buttons)
g_key_status.Set(pad, btn, value);
}
void PAD::UpdateMacroButtons()
{
for (u32 pad = 0; pad < NUM_CONTROLLER_PORTS; pad++)
{
for (u32 index = 0; index < NUM_MACRO_BUTTONS_PER_CONTROLLER; index++)
{
MacroButton& mb = s_macro_buttons[pad][index];
if (!mb.trigger_state || mb.toggle_frequency == 0)
continue;
mb.toggle_counter--;
if (mb.toggle_counter > 0)
continue;
mb.toggle_counter = mb.toggle_frequency;
mb.toggle_state = !mb.toggle_state;
ApplyMacroButton(pad, mb);
}
}
}
-127
View File
@@ -1,127 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "Config.h"
#include "PAD/Host/Global.h"
#include "SaveState.h"
class SettingsInterface;
struct WindowInfo;
enum class GenericInputBinding : u8;
s32 PADinit();
void PADshutdown();
s32 PADopen();
void PADclose();
s32 PADsetSlot(u8 port, u8 slot);
s32 PADfreeze(FreezeAction mode, freezeData* data);
u8 PADstartPoll(int _port, int _slot);
u8 PADpoll(u8 value);
bool PADcomplete();
namespace PAD
{
enum class ControllerType: u8
{
NotConnected,
DualShock2,
Count
};
enum class VibrationCapabilities : u8
{
NoVibration,
LargeSmallMotors,
SingleMotor,
Count
};
struct ControllerInfo
{
ControllerType type;
const char* name;
const char* display_name;
const InputBindingInfo* bindings;
u32 num_bindings;
const SettingInfo* settings;
u32 num_settings;
VibrationCapabilities vibration_caps;
};
/// Total number of pad ports, across both multitaps.
static constexpr u32 NUM_CONTROLLER_PORTS = 8;
/// Number of macro buttons per controller.
static constexpr u32 NUM_MACRO_BUTTONS_PER_CONTROLLER = 16;
/// Default stick deadzone/sensitivity.
static constexpr float DEFAULT_STICK_DEADZONE = 0.0f;
static constexpr float DEFAULT_STICK_SCALE = 1.33f;
static constexpr float DEFAULT_TRIGGER_DEADZONE = 0.0f;
static constexpr float DEFAULT_TRIGGER_SCALE = 1.0f;
static constexpr float DEFAULT_MOTOR_SCALE = 1.0f;
static constexpr float DEFAULT_PRESSURE_MODIFIER = 0.5f;
static constexpr float DEFAULT_BUTTON_DEADZONE = 0.0f;
/// Returns the default type for the specified port.
const char* GetDefaultPadType(u32 pad);
/// Reloads configuration.
void LoadConfig(const SettingsInterface& si);
/// Restores default configuration.
void SetDefaultControllerConfig(SettingsInterface& si);
void SetDefaultHotkeyConfig(SettingsInterface& si);
/// Clears all bindings for a given port.
void ClearPortBindings(SettingsInterface& si, u32 port);
/// Copies pad configuration from one interface (ini) to another.
void CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
bool copy_pad_config = true, bool copy_pad_bindings = true, bool copy_hotkey_bindings = true);
/// Updates vibration and other internal state. Called at the *end* of a frame.
void Update();
/// Returns a list of controller type names. Pair of [name, display name].
std::vector<std::pair<const char*, const char*>> GetControllerTypeNames();
/// Returns the list of binds for the specified controller type.
std::vector<std::string> GetControllerBinds(const std::string_view& type);
/// Returns general information for the specified controller type.
const ControllerInfo* GetControllerInfo(ControllerType type);
const ControllerInfo* GetControllerInfo(const std::string_view& name);
/// Performs automatic controller mapping with the provided list of generic mappings.
bool MapController(SettingsInterface& si, u32 controller,
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping);
/// Sets the specified bind on a controller to the specified pressure (normalized to 0..1).
void SetControllerState(u32 controller, u32 bind, float value);
/// Sets the state of the specified macro button.
void SetMacroButtonState(u32 pad, u32 index, bool state);
/// Returns a list of input profiles available.
std::vector<std::string> GetInputProfileNames();
} // namespace PAD
-523
View File
@@ -1,523 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "PAD/Host/StateManagement.h"
#include "PAD/Host/KeyStatus.h"
#include "PAD/Host/PAD.h"
#include "Input/InputManager.h"
#include "Sio.h"
template <class T>
static bool __fi test_bit(T& value, int bit)
{
return (value & (1 << bit));
}
// Typical packet response on the bus
static const u8 ConfigExit[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const u8 noclue[7] = {0x5A, 0x00, 0x00, 0x02, 0x00, 0x00, 0x5A};
static const u8 setMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const u8 queryModelDS2[7] = {0x5A, 0x03, 0x02, 0x00, 0x02, 0x01, 0x00};
static const u8 queryModelDS1[7] = {0x5A, 0x01, 0x02, 0x00, 0x02, 0x01, 0x00};
static const u8 queryComb[7] = {0x5A, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00};
static const u8 queryMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const u8 setNativeMode[7] = {0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A};
static u8 queryMaskMode[7] = {0x5A, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x5A};
static const u8 queryAct[2][7] = {
{0x5A, 0x00, 0x00, 0x01, 0x02, 0x00, 0x0A},
{0x5A, 0x00, 0x00, 0x01, 0x01, 0x01, 0x14}};
QueryInfo query;
Pad pads[2][4];
int slots[2] = {0, 0};
//////////////////////////////////////////////////////////////////////
// QueryInfo implementation
//////////////////////////////////////////////////////////////////////
void QueryInfo::reset()
{
port = 0;
slot = 0;
lastByte = 1;
currentCommand = 0;
numBytes = 0;
queryDone = 1;
memset(response, 0xF3, sizeof(response));
}
u8 QueryInfo::start_poll(int _port, int _slot)
{
if (_port >= 2)
{
reset();
return 0;
}
port = _port;
slot = _slot;
const u32 ext_port = sioConvertPortAndSlotToPad(port, slot);
if (g_key_status.GetType(ext_port) == PAD::ControllerType::NotConnected)
{
queryDone = 1;
numBytes = 0;
lastByte = 1;
return 0;
}
else
{
queryDone = 0;
numBytes = 2;
lastByte = 0;
return 0xFF;
}
}
//////////////////////////////////////////////////////////////////////
// Pad implementation
//////////////////////////////////////////////////////////////////////
void Pad::set_mode(int _mode)
{
mode = _mode;
}
void Pad::set_vibrate(int motor, u8 val)
{
nextVibrate[motor] = val;
}
void Pad::reset_vibrate()
{
set_vibrate(0, 0);
set_vibrate(1, 0);
memset(vibrate, 0xFF, sizeof(vibrate));
vibrate[0] = 0x5A;
}
void Pad::reset()
{
memset(this, 0, sizeof(PadFreezeData));
set_mode(MODE_DIGITAL);
umask[0] = 0xFF;
umask[1] = 0xFF;
umask[2] = 0x03;
// Sets up vibrate variable.
reset_vibrate();
}
void Pad::rumble(unsigned port)
{
if (nextVibrate[0] == currentVibrate[0] && nextVibrate[1] == currentVibrate[1])
return;
currentVibrate[0] = nextVibrate[0];
currentVibrate[1] = nextVibrate[1];
InputManager::SetPadVibrationIntensity(port,
std::min(static_cast<float>(currentVibrate[0]) * g_key_status.GetVibrationScale(port, 0) * (1.0f / 255.0f), 1.0f),
std::min(static_cast<float>(currentVibrate[1]) * g_key_status.GetVibrationScale(port, 1) * (1.0f / 255.0f), 1.0f)
);
}
void Pad::stop_vibrate_all()
{
#if 0
for (int i=0; i<8; i++) {
SetVibrate(i&1, i>>1, 0, 0);
SetVibrate(i&1, i>>1, 1, 0);
}
#endif
// FIXME equivalent ?
for (int port = 0; port < 2; port++)
for (int slot = 0; slot < 4; slot++)
pads[port][slot].reset_vibrate();
}
void Pad::reset_all()
{
for (int port = 0; port < 2; port++)
for (int slot = 0; slot < 4; slot++)
pads[port][slot].reset();
}
void Pad::rumble_all()
{
for (unsigned port = 0; port < 2; port++)
for (unsigned slot = 0; slot < 4; slot++)
pads[port][slot].rumble(sioConvertPortAndSlotToPad(port, slot));
}
//////////////////////////////////////////////////////////////////////
// Pad implementation
//////////////////////////////////////////////////////////////////////
inline bool IsDualshock2()
{
// FIXME
#if 0
return config.padConfigs[query.port][query.slot].type == Dualshock2Pad ||
(config.padConfigs[query.port][query.slot].type == GuitarPad && config.GH2);
#else
return true;
#endif
}
u8 pad_start_poll(u8 _port, u8 _slot)
{
return query.start_poll(_port, _slot);
}
u8 pad_poll(u8 value)
{
if (query.lastByte + 1 >= query.numBytes)
{
return 0;
}
if (query.lastByte && query.queryDone)
{
return query.response[++query.lastByte];
}
Pad* pad = &pads[query.port][query.slot];
if (query.lastByte == 0)
{
query.lastByte++;
query.currentCommand = value;
switch (value)
{
case CMD_CONFIG_MODE:
if (pad->config)
{
// In config mode. Might not actually be leaving it.
query.set_result(ConfigExit);
return 0xF3;
}
[[fallthrough]]; // fallthrough on purpose (but I don't know why)
case CMD_READ_DATA_AND_VIBRATE:
{
query.response[2] = 0x5A;
#if 0
int i;
Update(query.port, query.slot);
ButtonSum *sum = &pad->sum;
u8 b1 = 0xFF, b2 = 0xFF;
for (i = 0; i<4; i++) {
b1 -= (sum->buttons[i] > 0) << i;
}
for (i = 0; i<8; i++) {
b2 -= (sum->buttons[i+4] > 0) << i;
}
#endif
// FIXME
#if 0
if (config.padConfigs[query.port][query.slot].type == GuitarPad && !config.GH2) {
sum->buttons[15] = 255;
// Not sure about this. Forces wammy to be from 0 to 0x7F.
// if (sum->sticks[2].vert > 0) sum->sticks[2].vert = 0;
}
#endif
#if 0
for (i = 4; i<8; i++) {
b1 -= (sum->buttons[i+8] > 0) << i;
}
#endif
// FIXME
#if 0
//Left, Right and Down are always pressed on Pop'n Music controller.
if (config.padConfigs[query.port][query.slot].type == PopnPad)
b1=b1 & 0x1f;
#endif
const u32 ext_port = sioConvertPortAndSlotToPad(query.port, query.slot);
const u32 buttons = g_key_status.GetButtons(ext_port);
if (!test_bit(buttons, PAD_ANALOG) && !pad->modeLock)
{
switch (pad->mode)
{
case MODE_ANALOG:
case MODE_DS2_NATIVE:
pad->set_mode(MODE_DIGITAL);
break;
case MODE_DIGITAL:
default:
pad->set_mode(MODE_ANALOG);
break;
}
}
query.numBytes = 5;
query.response[3] = (buttons >> 8) & 0xFF;
query.response[4] = (buttons >> 0) & 0xFF;
if (pad->mode != MODE_DIGITAL)
{ // ANALOG || DS2 native
query.numBytes = 9;
query.response[5] = g_key_status.GetPressure(ext_port, PAD_R_RIGHT);
query.response[6] = g_key_status.GetPressure(ext_port, PAD_R_UP);
query.response[7] = g_key_status.GetPressure(ext_port, PAD_L_RIGHT);
query.response[8] = g_key_status.GetPressure(ext_port, PAD_L_UP);
if (pad->mode != MODE_ANALOG)
{ // DS2 native
query.numBytes = 21;
query.response[9] = !test_bit(buttons, 13) ? g_key_status.GetPressure(ext_port, PAD_RIGHT) : 0;
query.response[10] = !test_bit(buttons, 15) ? g_key_status.GetPressure(ext_port, PAD_LEFT) : 0;
query.response[11] = !test_bit(buttons, 12) ? g_key_status.GetPressure(ext_port, PAD_UP) : 0;
query.response[12] = !test_bit(buttons, 14) ? g_key_status.GetPressure(ext_port, PAD_DOWN) : 0;
query.response[13] = !test_bit(buttons, 4) ? g_key_status.GetPressure(ext_port, PAD_TRIANGLE) : 0;
query.response[14] = !test_bit(buttons, 5) ? g_key_status.GetPressure(ext_port, PAD_CIRCLE) : 0;
query.response[15] = !test_bit(buttons, 6) ? g_key_status.GetPressure(ext_port, PAD_CROSS) : 0;
query.response[16] = !test_bit(buttons, 7) ? g_key_status.GetPressure(ext_port, PAD_SQUARE) : 0;
query.response[17] = !test_bit(buttons, 2) ? g_key_status.GetPressure(ext_port, PAD_L1) : 0;
query.response[18] = !test_bit(buttons, 3) ? g_key_status.GetPressure(ext_port, PAD_R1) : 0;
query.response[19] = !test_bit(buttons, 0) ? g_key_status.GetPressure(ext_port, PAD_L2) : 0;
query.response[20] = !test_bit(buttons, 1) ? g_key_status.GetPressure(ext_port, PAD_R2) : 0;
}
}
#if 0
query.response[3] = b1;
query.response[4] = b2;
query.numBytes = 5;
if (pad->mode != MODE_DIGITAL) {
query.response[5] = Cap((sum->sticks[0].horiz+255)/2);
query.response[6] = Cap((sum->sticks[0].vert+255)/2);
query.response[7] = Cap((sum->sticks[1].horiz+255)/2);
query.response[8] = Cap((sum->sticks[1].vert+255)/2);
query.numBytes = 9;
if (pad->mode != MODE_ANALOG) {
// Good idea? No clue.
//query.response[3] &= pad->mask[0];
//query.response[4] &= pad->mask[1];
// No need to cap these, already done int CapSum().
query.response[9] = (unsigned char)sum->buttons[13]; //D-pad right
query.response[10] = (unsigned char)sum->buttons[15]; //D-pad left
query.response[11] = (unsigned char)sum->buttons[12]; //D-pad up
query.response[12] = (unsigned char)sum->buttons[14]; //D-pad down
query.response[13] = (unsigned char) sum->buttons[8];
query.response[14] = (unsigned char) sum->buttons[9];
query.response[15] = (unsigned char) sum->buttons[10];
query.response[16] = (unsigned char) sum->buttons[11];
query.response[17] = (unsigned char) sum->buttons[6];
query.response[18] = (unsigned char) sum->buttons[7];
query.response[19] = (unsigned char) sum->buttons[4];
query.response[20] = (unsigned char) sum->buttons[5];
query.numBytes = 21;
}
}
#endif
}
query.lastByte = 1;
return pad->mode;
case CMD_SET_VREF_PARAM:
query.set_final_result(noclue);
break;
case CMD_QUERY_DS2_ANALOG_MODE:
// Right? Wrong? No clue.
if (pad->mode == MODE_DIGITAL)
{
queryMaskMode[1] = queryMaskMode[2] = queryMaskMode[3] = 0;
queryMaskMode[6] = 0x00;
}
else
{
queryMaskMode[1] = pad->umask[0];
queryMaskMode[2] = pad->umask[1];
queryMaskMode[3] = pad->umask[2];
// Not entirely sure about this.
//queryMaskMode[3] = 0x01 | (pad->mode == MODE_DS2_NATIVE)*2;
queryMaskMode[6] = 0x5A;
}
query.set_final_result(queryMaskMode);
break;
case CMD_SET_MODE_AND_LOCK:
query.set_result(setMode);
pad->reset_vibrate();
break;
case CMD_QUERY_MODEL_AND_MODE:
if (IsDualshock2())
{
query.set_final_result(queryModelDS2);
}
else
{
query.set_final_result(queryModelDS1);
}
// Not digital mode.
query.response[5] = (pad->mode & 0xF) != 1;
break;
case CMD_QUERY_ACT:
query.set_result(queryAct[0]);
break;
case CMD_QUERY_COMB:
query.set_final_result(queryComb);
break;
case CMD_QUERY_MODE:
query.set_result(queryMode);
break;
case CMD_VIBRATION_TOGGLE:
memcpy(query.response + 2, pad->vibrate, 7);
query.numBytes = 9;
//query.set_result(pad->vibrate); // warning copy 7b not 8 (but it is really important?)
pad->reset_vibrate();
break;
case CMD_SET_DS2_NATIVE_MODE:
if (IsDualshock2())
{
query.set_result(setNativeMode);
}
else
{
query.set_final_result(setNativeMode);
}
break;
default:
query.numBytes = 0;
query.queryDone = 1;
break;
}
return 0xF3;
}
else
{
query.lastByte++;
switch (query.currentCommand)
{
case CMD_READ_DATA_AND_VIBRATE:
if (query.lastByte == pad->vibrateI[0])
pad->set_vibrate(1, 255 * (value & 1));
else if (query.lastByte == pad->vibrateI[1])
pad->set_vibrate(0, value);
break;
case CMD_CONFIG_MODE:
if (query.lastByte == 3)
{
query.queryDone = 1;
pad->config = value;
}
break;
case CMD_SET_MODE_AND_LOCK:
if (query.lastByte == 3 && value < 2)
{
pad->set_mode(value ? MODE_ANALOG : MODE_DIGITAL);
}
else if (query.lastByte == 4)
{
if (value == 3)
pad->modeLock = 3;
else
pad->modeLock = 0;
query.queryDone = 1;
}
break;
case CMD_QUERY_ACT:
if (query.lastByte == 3)
{
if (value < 2)
query.set_result(queryAct[value]);
// bunch of 0's
// else query.set_result(setMode);
query.queryDone = 1;
}
break;
case CMD_QUERY_MODE:
if (query.lastByte == 3 && value < 2)
{
query.response[6] = 4 + value * 3;
query.queryDone = 1;
}
// bunch of 0's
//else data = setMode;
break;
case CMD_VIBRATION_TOGGLE:
if (query.lastByte >= 3)
{
if (value == 0)
{
pad->vibrateI[0] = (u8)query.lastByte;
}
else if (value == 1)
{
pad->vibrateI[1] = (u8)query.lastByte;
}
pad->vibrate[query.lastByte - 2] = value;
}
break;
case CMD_SET_DS2_NATIVE_MODE:
if (query.lastByte > 2 && query.lastByte < 6)
{
pad->umask[query.lastByte - 3] = value;
}
pad->set_mode(MODE_DS2_NATIVE);
break;
default:
return 0;
}
return query.response[query.lastByte];
}
}
bool pad_complete()
{
return query.queryDone;
}
-132
View File
@@ -1,132 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2021 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
#include "PAD/Host/Global.h"
#define MODE_DIGITAL 0x41
#define MODE_ANALOG 0x73
#define MODE_DS2_NATIVE 0x79
enum PadCommands
{
CMD_SET_VREF_PARAM = 0x40,
CMD_QUERY_DS2_ANALOG_MODE = 0x41,
CMD_READ_DATA_AND_VIBRATE = 0x42,
CMD_CONFIG_MODE = 0x43,
CMD_SET_MODE_AND_LOCK = 0x44,
CMD_QUERY_MODEL_AND_MODE = 0x45,
CMD_QUERY_ACT = 0x46, // ??
CMD_QUERY_COMB = 0x47, // ??
CMD_QUERY_MODE = 0x4C, // QUERY_MODE ??
CMD_VIBRATION_TOGGLE = 0x4D,
CMD_SET_DS2_NATIVE_MODE = 0x4F // SET_DS2_NATIVE_MODE
};
// The state of the PS2 bus
struct QueryInfo
{
u8 port;
u8 slot;
u8 lastByte;
u8 currentCommand;
u8 numBytes;
u8 queryDone;
u8 response[42];
void reset();
u8 start_poll(int _port, int _slot);
template <size_t S>
void set_result(const u8 (&rsp)[S])
{
memcpy(response + 2, rsp, S);
numBytes = 2 + S;
}
template <size_t S>
void set_final_result(const u8 (&rsp)[S])
{
set_result(rsp);
queryDone = 1;
}
};
// Freeze data, for a single pad. Basically has all pad state that
// a PS2 can set.
struct PadFreezeData
{
// Digital / Analog / DS2 Native
u8 mode;
u8 modeLock;
// In config mode
u8 config;
u8 vibrate[8];
u8 umask[3];
// Vibration indices.
u8 vibrateI[2];
// Last vibration value sent to controller.
// Only used so as not to call vibration
// functions when old and new values are both 0.
u8 currentVibrate[2];
// Next vibrate val to send to controller. If next and current are
// both 0, nothing is sent to the controller. Otherwise, it's sent
// on every update.
u8 nextVibrate[2];
};
class Pad : public PadFreezeData
{
public:
// Lilypad store here the state of PC pad
void rumble(unsigned port);
void set_vibrate(int motor, u8 val);
void reset_vibrate();
void reset();
void set_mode(int mode);
static void reset_all();
static void stop_vibrate_all();
static void rumble_all();
};
// Full state to manage save state
struct PadFullFreezeData
{
char format[8];
u32 version;
// active slot for port
u8 slot[2];
PadFreezeData padData[2][4];
QueryInfo query;
};
extern QueryInfo query;
extern Pad pads[2][4];
extern int slots[2];
extern u8 pad_start_poll(u8 _port, u8 _slot);
extern u8 pad_poll(u8 value);
extern bool pad_complete();
+1 -1
View File
@@ -23,7 +23,7 @@
#include "Config.h"
#include "GS.h"
#include "CDVD/CDVDcommon.h"
#include "MemoryCardFile.h"
#include "SIO/Memcard/MemoryCardFile.h"
#include "USB/USB.h"
#ifdef _WIN32
+2 -2
View File
@@ -18,7 +18,7 @@
#include "R3000A.h"
#include "Common.h"
#include "Sio.h"
#include "SIO/Sio0.h"
#include "Sif.h"
#include "DebugTools/Breakpoints.h"
#include "R5900OpcodeTables.h"
@@ -175,7 +175,7 @@ static __fi void Sio0TestEvent(IopEventId n)
if (psxTestCycle(psxRegs.sCycle[n], psxRegs.eCycle[n]))
{
psxRegs.interrupt &= ~(1 << n);
sio0.Interrupt(Sio0Interrupt::TEST_EVENT);
g_Sio0.Interrupt(Sio0Interrupt::TEST_EVENT);
}
else
{
+54 -52
View File
@@ -17,20 +17,21 @@
#include "DebugTools/Debug.h"
#include "Recording/PadData.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Pad/PadDualshock2.h"
#include "SIO/Sio.h"
#include <fmt/core.h>
#include "PAD/Host/KeyStatus.h"
#include "Sio.h"
PadData::PadData(const int port, const int slot)
{
m_port = port;
m_slot = slot;
m_ext_port = sioConvertPortAndSlotToPad(m_port, m_slot);
PadBase* pad = Pad::GetPad(m_ext_port);
// Get the state of the buttons
// TODO - for the new recording file format, allow informing max number of buttons per frame per controller as well (ie. the analog button)
const u32 buttons = g_key_status.GetButtons(m_ext_port);
const u32 buttons = pad->GetButtons();
// - pressed group one
// - left
// - down
@@ -52,26 +53,26 @@ PadData::PadData(const int port, const int slot)
// - l2
m_compactPressFlagsGroupTwo = (buttons & 0b11111111);
// Get the analog values
m_rightAnalog = g_key_status.GetRawRightAnalog(m_ext_port);
m_leftAnalog = g_key_status.GetRawLeftAnalog(m_ext_port);
m_rightAnalog = pad->GetRawRightAnalog();
m_leftAnalog = pad->GetRawLeftAnalog();
// Get pressure bytes (12 of them)
m_left = {(0b10000000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_LEFT)};
m_down = {(0b01000000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_DOWN)};
m_right = {(0b00100000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_RIGHT)};
m_up = {(0b00010000 & m_compactPressFlagsGroupOne) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_UP)};
m_left = {(0b10000000 & m_compactPressFlagsGroupOne) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_LEFT)};
m_down = {(0b01000000 & m_compactPressFlagsGroupOne) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_DOWN)};
m_right = {(0b00100000 & m_compactPressFlagsGroupOne) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_RIGHT)};
m_up = {(0b00010000 & m_compactPressFlagsGroupOne) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_UP)};
m_start = (0b00001000 & m_compactPressFlagsGroupOne) == 0;
m_r3 = (0b00000100 & m_compactPressFlagsGroupOne) == 0;
m_l3 = (0b00000010 & m_compactPressFlagsGroupOne) == 0;
m_select = (0b00000001 & m_compactPressFlagsGroupOne) == 0;
m_square = {(0b10000000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_SQUARE)};
m_cross = {(0b01000000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_CROSS)};
m_circle = {(0b00100000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_CIRCLE)};
m_triangle = {(0b00010000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_TRIANGLE)};
m_r1 = {(0b00001000 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_R1)};
m_l1 = {(0b00000100 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_L1)};
m_r2 = {(0b00000010 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_R2)};
m_l2 = {(0b00000001 & m_compactPressFlagsGroupTwo) == 0, g_key_status.GetRawPressure(m_ext_port, gamePadValues::PAD_L2)};
m_square = {(0b10000000 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_SQUARE)};
m_cross = {(0b01000000 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_CROSS)};
m_circle = {(0b00100000 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_CIRCLE)};
m_triangle = {(0b00010000 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_TRIANGLE)};
m_r1 = {(0b00001000 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_R1)};
m_l1 = {(0b00000100 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_L1)};
m_r2 = {(0b00000010 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_R2)};
m_l2 = {(0b00000001 & m_compactPressFlagsGroupTwo) == 0, pad->GetRawInput(PadDualshock2::Inputs::PAD_L2)};
}
PadData::PadData(const int port, const int slot, const std::array<u8, 18> data)
@@ -80,53 +81,54 @@ PadData::PadData(const int port, const int slot, const std::array<u8, 18> data)
m_slot = slot;
m_ext_port = sioConvertPortAndSlotToPad(m_port, m_slot);
m_compactPressFlagsGroupOne = data.at(0);
m_compactPressFlagsGroupTwo = data.at(1);
m_compactPressFlagsGroupOne = data[0];
m_compactPressFlagsGroupTwo = data[1];
m_rightAnalog = {data.at(2), data.at(3)};
m_leftAnalog = {data.at(4), data.at(5)};
m_rightAnalog = {data[2], data[3]};
m_leftAnalog = {data[4], data[5]};
m_left = {(0b10000000 & m_compactPressFlagsGroupOne) == 0, data.at(7)};
m_down = {(0b01000000 & m_compactPressFlagsGroupOne) == 0, data.at(9)};
m_right = {(0b00100000 & m_compactPressFlagsGroupOne) == 0, data.at(6)};
m_up = {(0b00010000 & m_compactPressFlagsGroupOne) == 0, data.at(8)};
m_left = {(0b10000000 & m_compactPressFlagsGroupOne) == 0, data[7]};
m_down = {(0b01000000 & m_compactPressFlagsGroupOne) == 0, data[9]};
m_right = {(0b00100000 & m_compactPressFlagsGroupOne) == 0, data[6]};
m_up = {(0b00010000 & m_compactPressFlagsGroupOne) == 0, data[8]};
m_start = (0b00001000 & m_compactPressFlagsGroupOne) == 0;
m_r3 = (0b00000100 & m_compactPressFlagsGroupOne) == 0;
m_l3 = (0b00000010 & m_compactPressFlagsGroupOne) == 0;
m_select = (0b00000001 & m_compactPressFlagsGroupOne) == 0;
m_square = {(0b10000000 & m_compactPressFlagsGroupTwo) == 0, data.at(13)};
m_cross = {(0b01000000 & m_compactPressFlagsGroupTwo) == 0, data.at(12)};
m_circle = {(0b00100000 & m_compactPressFlagsGroupTwo) == 0, data.at(11)};
m_triangle = {(0b00010000 & m_compactPressFlagsGroupTwo) == 0, data.at(10)};
m_r1 = {(0b00001000 & m_compactPressFlagsGroupTwo) == 0, data.at(15)};
m_l1 = {(0b00000100 & m_compactPressFlagsGroupTwo) == 0, data.at(14)};
m_r2 = {(0b00000010 & m_compactPressFlagsGroupTwo) == 0, data.at(17)};
m_l2 = {(0b00000001 & m_compactPressFlagsGroupTwo) == 0, data.at(16)};
m_square = {(0b10000000 & m_compactPressFlagsGroupTwo) == 0, data[13]};
m_cross = {(0b01000000 & m_compactPressFlagsGroupTwo) == 0, data[12]};
m_circle = {(0b00100000 & m_compactPressFlagsGroupTwo) == 0, data[11]};
m_triangle = {(0b00010000 & m_compactPressFlagsGroupTwo) == 0, data[10]};
m_r1 = {(0b00001000 & m_compactPressFlagsGroupTwo) == 0, data[15]};
m_l1 = {(0b00000100 & m_compactPressFlagsGroupTwo) == 0, data[14]};
m_r2 = {(0b00000010 & m_compactPressFlagsGroupTwo) == 0, data[17]};
m_l2 = {(0b00000001 & m_compactPressFlagsGroupTwo) == 0, data[16]};
}
void PadData::OverrideActualController() const
{
g_key_status.SetRawAnalogs(m_ext_port, m_leftAnalog, m_rightAnalog);
PadBase* pad = Pad::GetPad(m_ext_port);
pad->SetRawAnalogs(m_leftAnalog, m_rightAnalog);
g_key_status.Set(m_ext_port, PAD_RIGHT, std::get<1>(m_right));
g_key_status.Set(m_ext_port, PAD_LEFT, std::get<1>(m_left));
g_key_status.Set(m_ext_port, PAD_UP, std::get<1>(m_up));
g_key_status.Set(m_ext_port, PAD_DOWN, std::get<1>(m_down));
g_key_status.Set(m_ext_port, PAD_START, m_start);
g_key_status.Set(m_ext_port, PAD_SELECT, m_select);
g_key_status.Set(m_ext_port, PAD_R3, m_r3);
g_key_status.Set(m_ext_port, PAD_L3, m_l3);
pad->Set(PadDualshock2::Inputs::PAD_RIGHT, std::get<1>(m_right));
pad->Set(PadDualshock2::Inputs::PAD_LEFT, std::get<1>(m_left));
pad->Set(PadDualshock2::Inputs::PAD_UP, std::get<1>(m_up));
pad->Set(PadDualshock2::Inputs::PAD_DOWN, std::get<1>(m_down));
pad->Set(PadDualshock2::Inputs::PAD_START, m_start);
pad->Set(PadDualshock2::Inputs::PAD_SELECT, m_select);
pad->Set(PadDualshock2::Inputs::PAD_R3, m_r3);
pad->Set(PadDualshock2::Inputs::PAD_L3, m_l3);
g_key_status.Set(m_ext_port, PAD_SQUARE, std::get<1>(m_square));
g_key_status.Set(m_ext_port, PAD_CROSS, std::get<1>(m_cross));
g_key_status.Set(m_ext_port, PAD_CIRCLE, std::get<1>(m_circle));
g_key_status.Set(m_ext_port, PAD_TRIANGLE, std::get<1>(m_triangle));
pad->Set(PadDualshock2::Inputs::PAD_SQUARE, std::get<1>(m_square));
pad->Set(PadDualshock2::Inputs::PAD_CROSS, std::get<1>(m_cross));
pad->Set(PadDualshock2::Inputs::PAD_CIRCLE, std::get<1>(m_circle));
pad->Set(PadDualshock2::Inputs::PAD_TRIANGLE, std::get<1>(m_triangle));
g_key_status.Set(m_ext_port, PAD_R1, std::get<1>(m_r1));
g_key_status.Set(m_ext_port, PAD_L1, std::get<1>(m_l1));
g_key_status.Set(m_ext_port, PAD_R2, std::get<1>(m_r2));
g_key_status.Set(m_ext_port, PAD_L2, std::get<1>(m_l2));
pad->Set(PadDualshock2::Inputs::PAD_R1, std::get<1>(m_r1));
pad->Set(PadDualshock2::Inputs::PAD_L1, std::get<1>(m_l1));
pad->Set(PadDualshock2::Inputs::PAD_R2, std::get<1>(m_r2));
pad->Set(PadDualshock2::Inputs::PAD_L2, std::get<1>(m_l2));
}
void addButtonInfoToString(std::string label, std::string& str, std::tuple<bool, u8> buttonInfo)
@@ -173,6 +175,6 @@ void PadData::LogPadData() const
const auto& [right_x, right_y] = m_rightAnalog;
const std::string analogs = fmt::format("Left: [{}, {}] | Right: [{}, {}]", left_x, left_y, right_x, right_y);
const std::string finalLog = fmt::format("[PAD {}:{}:{}]\n\t[Buttons]: {}\n\t[Analogs]: {}\n", m_ext_port, m_port, m_slot, pressedButtons, analogs);
const std::string finalLog = fmt::format("[PAD {}:{}]\n\t[Buttons]: {}\n\t[Analogs]: {}\n", m_port, m_slot, pressedButtons, analogs);
controlLog(finalLog);
}
@@ -14,6 +14,12 @@
*/
#include "PrecompiledHeader.h"
#include "SIO/Memcard/MemoryCardFile.h"
#include "SIO/Memcard/MemoryCardFolder.h"
#include "SIO/Sio.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
@@ -21,10 +27,6 @@
#include <array>
#include <chrono>
#include "MemoryCardFile.h"
#include "MemoryCardFolder.h"
#include "Sio.h"
#include "System.h"
#include "Config.h"
#include "Host.h"
@@ -52,26 +54,26 @@ bool FileMcd_Open = false;
static u32 CalculateECC(u8* buf)
{
const u8 parity_table[256] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,
0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,
1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,
0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,
1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,
0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,
1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,
1,0,1,1,0};
const u8 parity_table[256] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,
1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1,
0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0,
1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1,
0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0,
1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0,
1, 0, 1, 1, 0};
const u8 column_parity_mask[256] = {0,7,22,17,37,34,51,52,52,51,34,37,17,22,
7,0,67,68,85,82,102,97,112,119,119,112,97,102,82,85,68,67,82,85,68,67,119,112,
97,102,102,97,112,119,67,68,85,82,17,22,7,0,52,51,34,37,37,34,51,52,0,7,22,17,
97,102,119,112,68,67,82,85,85,82,67,68,112,119,102,97,34,37,52,51,7,0,17,22,
22,17,0,7,51,52,37,34,51,52,37,34,22,17,0,7,7,0,17,22,34,37,52,51,112,119,102,
97,85,82,67,68,68,67,82,85,97,102,119,112,112,119,102,97,85,82,67,68,68,67,82,
85,97,102,119,112,51,52,37,34,22,17,0,7,7,0,17,22,34,37,52,51,34,37,52,51,7,0,
17,22,22,17,0,7,51,52,37,34,97,102,119,112,68,67,82,85,85,82,67,68,112,119,102,
97,17,22,7,0,52,51,34,37,37,34,51,52,0,7,22,17,82,85,68,67,119,112,97,102,102,
97,112,119,67,68,85,82,67,68,85,82,102,97,112,119,119,112,97,102,82,85,68,67,
0,7,22,17,37,34,51,52,52,51,34,37,17,22,7,0};
const u8 column_parity_mask[256] = {0, 7, 22, 17, 37, 34, 51, 52, 52, 51, 34, 37, 17, 22,
7, 0, 67, 68, 85, 82, 102, 97, 112, 119, 119, 112, 97, 102, 82, 85, 68, 67, 82, 85, 68, 67, 119, 112,
97, 102, 102, 97, 112, 119, 67, 68, 85, 82, 17, 22, 7, 0, 52, 51, 34, 37, 37, 34, 51, 52, 0, 7, 22, 17,
97, 102, 119, 112, 68, 67, 82, 85, 85, 82, 67, 68, 112, 119, 102, 97, 34, 37, 52, 51, 7, 0, 17, 22,
22, 17, 0, 7, 51, 52, 37, 34, 51, 52, 37, 34, 22, 17, 0, 7, 7, 0, 17, 22, 34, 37, 52, 51, 112, 119, 102,
97, 85, 82, 67, 68, 68, 67, 82, 85, 97, 102, 119, 112, 112, 119, 102, 97, 85, 82, 67, 68, 68, 67, 82,
85, 97, 102, 119, 112, 51, 52, 37, 34, 22, 17, 0, 7, 7, 0, 17, 22, 34, 37, 52, 51, 34, 37, 52, 51, 7, 0,
17, 22, 22, 17, 0, 7, 51, 52, 37, 34, 97, 102, 119, 112, 68, 67, 82, 85, 85, 82, 67, 68, 112, 119, 102,
97, 17, 22, 7, 0, 52, 51, 34, 37, 37, 34, 51, 52, 0, 7, 22, 17, 82, 85, 68, 67, 119, 112, 97, 102, 102,
97, 112, 119, 67, 68, 85, 82, 67, 68, 85, 82, 102, 97, 112, 119, 119, 112, 97, 102, 82, 85, 68, 67,
0, 7, 22, 17, 37, 34, 51, 52, 52, 51, 34, 37, 17, 22, 7, 0};
u8 column_parity = 0x77;
u8 line_parity_0 = 0x7F;
@@ -343,14 +345,14 @@ void FileMemoryCard::Open()
// Translation note: detailed description should mention that the memory card will be disabled
// for the duration of this session.
Host::ReportFormattedErrorAsync("Memory Card", "Access denied to memory card: \n\n%s\n\n"
"Another instance of PCSX2 may be using this memory card. Close any other instances of PCSX2, or restart your computer.%s",
"Another instance of PCSX2 may be using this memory card. Close any other instances of PCSX2, or restart your computer.%s",
fname.c_str(),
#ifdef WIN32
"\n\nIf your memory card is in a write-protected folder such as \"Program Files\" or \"Program Files (x86)\", move it to another folder, such as \"Documents\" or \"Desktop\"."
#else
""
#endif
);
);
}
else // Load checksum
{
@@ -445,9 +447,9 @@ s32 FileMemoryCard::IsPresent(uint slot)
void FileMemoryCard::GetSizeInfo(uint slot, McdSizeInfo& outways)
{
outways.SectorSize = 512; // 0x0200
outways.SectorSize = 512; // 0x0200
outways.EraseBlockSizeInSectors = 16; // 0x0010
outways.Xor = 18; // 0x12, XOR 02 00 00 10
outways.Xor = 18; // 0x12, XOR 02 00 00 10
if (pxAssert(m_file[slot]))
outways.McdSizeInSectors = static_cast<u32>(FileSystem::FSize64(m_file[slot])) / (outways.SectorSize + outways.EraseBlockSizeInSectors);
@@ -618,7 +620,7 @@ uint FileMcd_ConvertToSlot(uint port, uint slot)
return port;
if (port == 0)
return slot + 1; // multitap 1
return slot + 4; // multitap 2
return slot + 4; // multitap 2
}
void FileMcd_SetType()
@@ -645,10 +647,10 @@ void FileMcd_SetType()
void FileMcd_EmuOpen()
{
if(FileMcd_Open)
if (FileMcd_Open)
return;
FileMcd_Open = true;
Mcd::impl.Open();
Mcd::implFolder.SetFiltering(EmuConfig.McdFolderAutoManage);
@@ -657,7 +659,7 @@ void FileMcd_EmuOpen()
void FileMcd_EmuClose()
{
if(!FileMcd_Open)
if (!FileMcd_Open)
return;
FileMcd_Open = false;
Mcd::implFolder.Close();
@@ -808,7 +810,7 @@ int FileMcd_ReIndex(uint port, uint slot, const std::string& filter)
return -1;
break;
default:
return -1;
return -1;
break;
}
@@ -1088,4 +1090,4 @@ bool FileMcd_DeleteCard(const std::string_view& name)
}
return true;
}
}
@@ -64,4 +64,4 @@ std::vector<AvailableMcdInfo> FileMcd_GetAvailableCards(bool include_in_use_card
std::optional<AvailableMcdInfo> FileMcd_GetCardInfo(const std::string_view& name);
bool FileMcd_CreateNewCard(const std::string_view& name, MemoryCardType type, MemoryCardFileType file_type);
bool FileMcd_RenameCard(const std::string_view& name, const std::string_view& new_name);
bool FileMcd_DeleteCard(const std::string_view& name);
bool FileMcd_DeleteCard(const std::string_view& name);
@@ -14,10 +14,11 @@
*/
#include "PrecompiledHeader.h"
#include "common/Path.h"
#include "MemoryCardFile.h"
#include "MemoryCardFolder.h"
#include "SIO/Memcard/MemoryCardFile.h"
#include "SIO/Memcard/MemoryCardFolder.h"
#include "common/Path.h"
#include "System.h"
#include "Config.h"
@@ -1914,7 +1915,7 @@ void FolderMemoryCard::DeleteFromIndex(const std::string& filePath, const std::s
if (yaml.has_value() && !yaml.value().empty())
{
ryml::NodeRef index = yaml.value().rootref();
if (index.has_child(c4::csubstr(entry.data(), entry.length())))
{
index.remove_child(c4::csubstr(entry.data(), entry.length()));
@@ -2358,7 +2359,6 @@ s32 FolderMemoryCardAggregator::Save(uint slot, const u8* src, u32 adr, int size
last = std::chrono::system_clock::now();
}
}
return saveResult;
@@ -2391,4 +2391,4 @@ bool FolderMemoryCardAggregator::ReIndex(uint slot, const bool enableFiltering,
SetFiltering(enableFiltering);
m_lastKnownFilter = filter;
return false;
}
}
@@ -572,4 +572,4 @@ public:
u64 GetCRC(uint slot);
void NextFrame(uint slot);
bool ReIndex(uint slot, const bool enableFiltering, const std::string& filter);
};
};
@@ -15,8 +15,11 @@
#include "PrecompiledHeader.h"
#include "MemoryCardProtocol.h"
#include "Sio.h"
#include "SIO/Memcard/MemoryCardProtocol.h"
#include "SIO/Sio.h"
#include "SIO/Sio2.h"
#include "SIO/Sio0.h"
#define MC_LOG_ENABLE 0
#define MC_LOG if (MC_LOG_ENABLE) DevCon
@@ -29,11 +32,11 @@ MemoryCardProtocol g_MemoryCardProtocol;
// If so, return dead air.
bool MemoryCardProtocol::PS1Fail()
{
if (mcd->IsPSX() && sio2.commandLength > 0)
if (mcd->IsPSX() && g_Sio2.commandLength > 0)
{
while (fifoOut.size() < sio2.commandLength)
while (g_Sio2FifoOut.size() < g_Sio2.commandLength)
{
fifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x00);
}
return true;
@@ -46,13 +49,13 @@ bool MemoryCardProtocol::PS1Fail()
// then end with 0x2b and terminator bytes. This function is a shortcut for that.
void MemoryCardProtocol::The2bTerminator(size_t length)
{
while (fifoOut.size() < length - 2)
while (g_Sio2FifoOut.size() < length - 2)
{
fifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x00);
}
fifoOut.push_back(0x2b);
fifoOut.push_back(mcd->term);
g_Sio2FifoOut.push_back(0x2b);
g_Sio2FifoOut.push_back(mcd->term);
}
// After one read or write, the memcard is almost certainly going to be issued a new read or write
@@ -99,16 +102,16 @@ void MemoryCardProtocol::SetSector()
{
MC_LOG.WriteLn("%s", __FUNCTION__);
PS1_FAIL();
const u8 sectorLSB = fifoIn.front();
fifoIn.pop_front();
const u8 sector2nd = fifoIn.front();
fifoIn.pop_front();
const u8 sector3rd = fifoIn.front();
fifoIn.pop_front();
const u8 sectorMSB = fifoIn.front();
fifoIn.pop_front();
const u8 expectedChecksum = fifoIn.front();
fifoIn.pop_front();
const u8 sectorLSB = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 sector2nd = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 sector3rd = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 sectorMSB = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 expectedChecksum = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
u8 computedChecksum = sectorLSB ^ sector2nd ^ sector3rd ^ sectorMSB;
mcd->goodSector = (computedChecksum == expectedChecksum);
@@ -135,89 +138,89 @@ void MemoryCardProtocol::GetSpecs()
//u8 checksum = 0x00;
McdSizeInfo info;
mcd->GetSizeInfo(info);
fifoOut.push_back(0x2b);
g_Sio2FifoOut.push_back(0x2b);
const u8 sectorSizeLSB = (info.SectorSize & 0xff);
//checksum ^= sectorSizeLSB;
fifoOut.push_back(sectorSizeLSB);
g_Sio2FifoOut.push_back(sectorSizeLSB);
const u8 sectorSizeMSB = (info.SectorSize >> 8);
//checksum ^= sectorSizeMSB;
fifoOut.push_back(sectorSizeMSB);
g_Sio2FifoOut.push_back(sectorSizeMSB);
const u8 eraseBlockSizeLSB = (info.EraseBlockSizeInSectors & 0xff);
//checksum ^= eraseBlockSizeLSB;
fifoOut.push_back(eraseBlockSizeLSB);
g_Sio2FifoOut.push_back(eraseBlockSizeLSB);
const u8 eraseBlockSizeMSB = (info.EraseBlockSizeInSectors >> 8);
//checksum ^= eraseBlockSizeMSB;
fifoOut.push_back(eraseBlockSizeMSB);
g_Sio2FifoOut.push_back(eraseBlockSizeMSB);
const u8 sectorCountLSB = (info.McdSizeInSectors & 0xff);
//checksum ^= sectorCountLSB;
fifoOut.push_back(sectorCountLSB);
g_Sio2FifoOut.push_back(sectorCountLSB);
const u8 sectorCount2nd = (info.McdSizeInSectors >> 8);
//checksum ^= sectorCount2nd;
fifoOut.push_back(sectorCount2nd);
g_Sio2FifoOut.push_back(sectorCount2nd);
const u8 sectorCount3rd = (info.McdSizeInSectors >> 16);
//checksum ^= sectorCount3rd;
fifoOut.push_back(sectorCount3rd);
g_Sio2FifoOut.push_back(sectorCount3rd);
const u8 sectorCountMSB = (info.McdSizeInSectors >> 24);
//checksum ^= sectorCountMSB;
fifoOut.push_back(sectorCountMSB);
g_Sio2FifoOut.push_back(sectorCountMSB);
fifoOut.push_back(info.Xor);
fifoOut.push_back(mcd->term);
g_Sio2FifoOut.push_back(info.Xor);
g_Sio2FifoOut.push_back(mcd->term);
}
void MemoryCardProtocol::SetTerminator()
{
MC_LOG.WriteLn("%s", __FUNCTION__);
PS1_FAIL();
const u8 newTerminator = fifoIn.front();
fifoIn.pop_front();
const u8 newTerminator = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 oldTerminator = mcd->term;
mcd->term = newTerminator;
fifoOut.push_back(0x00);
fifoOut.push_back(0x2b);
fifoOut.push_back(oldTerminator);
g_Sio2FifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x2b);
g_Sio2FifoOut.push_back(oldTerminator);
}
void MemoryCardProtocol::GetTerminator()
{
MC_LOG.WriteLn("%s", __FUNCTION__);
PS1_FAIL();
fifoOut.push_back(0x2b);
fifoOut.push_back(mcd->term);
fifoOut.push_back(static_cast<u8>(Terminator::DEFAULT));
g_Sio2FifoOut.push_back(0x2b);
g_Sio2FifoOut.push_back(mcd->term);
g_Sio2FifoOut.push_back(static_cast<u8>(Terminator::DEFAULT));
}
void MemoryCardProtocol::WriteData()
{
MC_LOG.WriteLn("%s", __FUNCTION__);
PS1_FAIL();
fifoOut.push_back(0x00);
fifoOut.push_back(0x2b);
const u8 writeLength = fifoIn.front();
fifoIn.pop_front();
g_Sio2FifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x2b);
const u8 writeLength = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
u8 checksum = 0x00;
std::vector<u8> buf;
for (size_t writeCounter = 0; writeCounter < writeLength; writeCounter++)
{
const u8 writeByte = fifoIn.front();
fifoIn.pop_front();
const u8 writeByte = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
checksum ^= writeByte;
buf.push_back(writeByte);
fifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x00);
}
mcd->Write(buf.data(), buf.size());
fifoOut.push_back(checksum);
fifoOut.push_back(mcd->term);
g_Sio2FifoOut.push_back(checksum);
g_Sio2FifoOut.push_back(mcd->term);
ReadWriteIncrement(writeLength);
}
@@ -226,10 +229,10 @@ void MemoryCardProtocol::ReadData()
{
MC_LOG.WriteLn("%s", __FUNCTION__);
PS1_FAIL();
const u8 readLength = fifoIn.front();
fifoIn.pop_front();
fifoOut.push_back(0x00);
fifoOut.push_back(0x2b);
const u8 readLength = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x2b);
std::vector<u8> buf;
buf.resize(readLength);
mcd->Read(buf.data(), buf.size());
@@ -238,11 +241,11 @@ void MemoryCardProtocol::ReadData()
for (const u8 readByte : buf)
{
checksum ^= readByte;
fifoOut.push_back(readByte);
g_Sio2FifoOut.push_back(readByte);
}
fifoOut.push_back(checksum);
fifoOut.push_back(mcd->term);
g_Sio2FifoOut.push_back(checksum);
g_Sio2FifoOut.push_back(mcd->term);
ReadWriteIncrement(readLength);
}
@@ -294,15 +297,12 @@ u8 MemoryCardProtocol::PS1Read(u8 data)
mcd->Read(ps1McState.buf.data(), ps1McState.buf.size());
[[fallthrough]];
default:
ret = ps1McState.buf.at(ps1McState.currentByte - 10);
ret = ps1McState.buf[ps1McState.currentByte - 10];
ps1McState.checksum ^= ret;
break;
}
if (sendAck)
{
sio0.Acknowledge();
}
g_Sio0.SetAcknowledge(sendAck);
ps1McState.currentByte++;
return ret;
@@ -310,7 +310,7 @@ u8 MemoryCardProtocol::PS1Read(u8 data)
u8 MemoryCardProtocol::PS1State(u8 data)
{
DevCon.Error("%s(%02X) I do not exist, please change that ASAP.", __FUNCTION__, data);
Console.Error("%s(%02X) I do not exist, please change that ASAP.", __FUNCTION__, data);
assert(false);
return 0x00;
}
@@ -372,16 +372,13 @@ u8 MemoryCardProtocol::PS1Write(u8 data)
ps1McState.checksum = ps1McState.sectorAddrMSB ^ ps1McState.sectorAddrLSB;
[[fallthrough]];
default:
ps1McState.buf.at(ps1McState.currentByte - 6) = data;
ps1McState.buf[ps1McState.currentByte - 6] = data;
ps1McState.checksum ^= data;
ret = 0x00;
break;
}
if (sendAck)
{
sio0.Acknowledge();
}
g_Sio0.SetAcknowledge(sendAck);
ps1McState.currentByte++;
return ret;
@@ -390,7 +387,7 @@ u8 MemoryCardProtocol::PS1Write(u8 data)
u8 MemoryCardProtocol::PS1Pocketstation(u8 data)
{
MC_LOG.WriteLn("%s", __FUNCTION__);
sio2.SetRecv1(Recv1::DISCONNECTED);
g_Sio0.SetAcknowledge(false);
return 0x00;
}
@@ -420,8 +417,8 @@ void MemoryCardProtocol::AuthXor()
{
MC_LOG.WriteLn("%s", __FUNCTION__);
PS1_FAIL();
const u8 modeByte = fifoIn.front();
fifoIn.pop_front();
const u8 modeByte = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
switch (modeByte)
{
@@ -435,20 +432,20 @@ void MemoryCardProtocol::AuthXor()
case 0x13:
{
// Long + XOR
fifoOut.push_back(0x00);
fifoOut.push_back(0x2b);
g_Sio2FifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x2b);
u8 xorResult = 0x00;
for (size_t xorCounter = 0; xorCounter < 8; xorCounter++)
{
const u8 toXOR = fifoIn.front();
fifoIn.pop_front();
const u8 toXOR = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
xorResult ^= toXOR;
fifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x00);
}
fifoOut.push_back(xorResult);
fifoOut.push_back(mcd->term);
g_Sio2FifoOut.push_back(xorResult);
g_Sio2FifoOut.push_back(mcd->term);
break;
}
// When encountered, the command length in RECV3 is guaranteed to be 5,
@@ -24,7 +24,7 @@ struct PS1MemoryCardState
u8 sectorAddrLSB = 0;
u8 checksum = 0;
u8 expectedChecksum = 0;
std::array<u8, 128> buf;
std::array<u8, 128> buf = {0};
};
// A global class which contains the behavior of each memory card command.
@@ -15,8 +15,10 @@
#include "PrecompiledHeader.h"
#include "MultitapProtocol.h"
#include "Sio.h"
#include "SIO/Multitap/MultitapProtocol.h"
#include "SIO/Sio2.h"
#include "SIO/SioTypes.h"
#define MT_LOG_ENABLE 0
#define MT_LOG if (MT_LOG_ENABLE) DevCon
@@ -26,30 +28,30 @@ MultitapProtocol g_MultitapProtocol;
void MultitapProtocol::SupportCheck()
{
MT_LOG.WriteLn("%s", __FUNCTION__);
fifoOut.push_back(0x5a);
fifoOut.push_back(0x04);
fifoOut.push_back(0x00);
fifoOut.push_back(0x5a);
g_Sio2FifoOut.push_back(0x5a);
g_Sio2FifoOut.push_back(0x04);
g_Sio2FifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x5a);
}
void MultitapProtocol::Select()
{
MT_LOG.WriteLn("%s", __FUNCTION__);
const u8 newSlot = fifoIn.front();
fifoIn.pop_front();
const u8 newSlot = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const bool isInBounds = (newSlot < SIO::SLOTS);
if (isInBounds)
{
sio2.slot = newSlot;
MT_LOG.WriteLn("Slot changed to %d", sio2.slot);
g_Sio2.slot = newSlot;
MT_LOG.WriteLn("Slot changed to %d", g_Sio2.slot);
}
fifoOut.push_back(0x5a);
fifoOut.push_back(0x00);
fifoOut.push_back(0x00);
fifoOut.push_back(isInBounds ? newSlot : 0xff);
fifoOut.push_back(isInBounds ? 0x5a : 0x66);
g_Sio2FifoOut.push_back(0x5a);
g_Sio2FifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(0x00);
g_Sio2FifoOut.push_back(isInBounds ? newSlot : 0xff);
g_Sio2FifoOut.push_back(isInBounds ? 0x5a : 0x66);
}
MultitapProtocol::MultitapProtocol() = default;
@@ -63,14 +65,14 @@ void MultitapProtocol::FullReset()
{
SoftReset();
sio2.slot = 0;
g_Sio2.slot = 0;
}
void MultitapProtocol::SendToMultitap()
{
const u8 commandByte = fifoIn.front();
fifoIn.pop_front();
fifoOut.push_back(0x80);
const u8 commandByte = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(0x80);
switch (static_cast<MultitapMode>(commandByte))
{
+704
View File
@@ -0,0 +1,704 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Host.h"
#include "Input/InputManager.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Pad/PadDualshock2.h"
#include "SIO/Pad/PadGuitar.h"
#include "SIO/Pad/PadNotConnected.h"
#include "SIO/Sio.h"
#include "IconsFontAwesome5.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/SettingsInterface.h"
#include "common/StringUtil.h"
#include "fmt/format.h"
#include <vector>
namespace Pad
{
struct MacroButton
{
std::vector<u32> buttons; ///< Buttons to activate.
float pressure; ///< Pressure to apply when macro is active.
u32 toggle_frequency; ///< Interval at which the buttons will be toggled, if not 0.
u32 toggle_counter; ///< When this counter reaches zero, buttons will be toggled.
bool toggle_state; ///< Current state for turbo.
bool trigger_state; ///< Whether the macro button is active.
};
static std::unique_ptr<PadBase> CreatePad(u8 unifiedSlot, Pad::ControllerType controllerType);
static PadBase* ChangePadType(u8 unifiedSlot, Pad::ControllerType controllerType);
void LoadMacroButtonConfig(
const SettingsInterface& si, u32 pad, const std::string_view& type, const std::string& section);
static void ApplyMacroButton(u32 controller, const MacroButton& mb);
static std::array<std::array<MacroButton, NUM_MACRO_BUTTONS_PER_CONTROLLER>, NUM_CONTROLLER_PORTS> s_macro_buttons;
static std::array<std::unique_ptr<PadBase>, NUM_CONTROLLER_PORTS> s_controllers;
}
bool Pad::Initialize()
{
return true;
}
void Pad::Shutdown()
{
for (auto& port : s_controllers)
port.reset();
}
const char* Pad::ControllerInfo::GetLocalizedName() const
{
return Host::TranslateToCString("Pad", display_name);
}
void Pad::LoadConfig(const SettingsInterface& si)
{
s_macro_buttons = {};
EmuConfig.MultitapPort0_Enabled = si.GetBoolValue("Pad", "MultitapPort1", false);
EmuConfig.MultitapPort1_Enabled = si.GetBoolValue("Pad", "MultitapPort2", false);
// This is where we would load controller types, if onepad supported them.
for (u32 i = 0; i < Pad::NUM_CONTROLLER_PORTS; i++)
{
const std::string section(GetConfigSection(i));
const std::string type(si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(i)));
const ControllerInfo* ci = GetControllerInfo(type);
PadBase* pad = Pad::GetPad(i);
// If a pad is not yet constructed, at minimum place a NotConnected pad in the slot.
// Do not abort the for loop - If there pad settings, we want those to be applied to the slot.
if (!pad)
{
pad = Pad::ChangePadType(i, Pad::ControllerType::NotConnected);
}
if (!ci)
{
pad = Pad::ChangePadType(i, Pad::ControllerType::NotConnected);
continue;
}
const Pad::ControllerType oldType = pad->GetType();
if (ci->type != oldType)
{
pad = Pad::ChangePadType(i, ci->type);
}
const float axis_deadzone = si.GetFloatValue(section.c_str(), "Deadzone", Pad::DEFAULT_STICK_DEADZONE);
const float axis_scale = si.GetFloatValue(section.c_str(), "AxisScale", Pad::DEFAULT_STICK_SCALE);
const float trigger_deadzone = si.GetFloatValue(section.c_str(), "TriggerDeadzone", Pad::DEFAULT_TRIGGER_DEADZONE);
const float trigger_scale = si.GetFloatValue(section.c_str(), "TriggerScale", Pad::DEFAULT_TRIGGER_SCALE);
const float button_deadzone = si.GetFloatValue(section.c_str(), "ButtonDeadzone", Pad::DEFAULT_BUTTON_DEADZONE);
pad->SetAxisScale(axis_deadzone, axis_scale);
pad->SetTriggerScale(trigger_deadzone, trigger_scale);
pad->SetButtonDeadzone(button_deadzone);
if (ci->vibration_caps != Pad::VibrationCapabilities::NoVibration)
{
const float large_motor_scale = si.GetFloatValue(section.c_str(), "LargeMotorScale", Pad::DEFAULT_MOTOR_SCALE);
const float small_motor_scale = si.GetFloatValue(section.c_str(), "SmallMotorScale", Pad::DEFAULT_MOTOR_SCALE);
pad->SetVibrationScale(0, large_motor_scale);
pad->SetVibrationScale(1, small_motor_scale);
}
const float pressure_modifier = si.GetFloatValue(section.c_str(), "PressureModifier", 1.0f);
pad->SetPressureModifier(pressure_modifier);
const int invert_l = si.GetIntValue(section.c_str(), "InvertL", 0);
const int invert_r = si.GetIntValue(section.c_str(), "InvertR", 0);
pad->SetAnalogInvertL((invert_l & 1) != 0, (invert_l & 2) != 0);
pad->SetAnalogInvertR((invert_r & 1) != 0, (invert_r & 2) != 0);
LoadMacroButtonConfig(si, i, type, section);
}
}
const char* Pad::GetDefaultPadType(u32 pad)
{
return (pad == 0) ? "DualShock2" : "None";
}
void Pad::SetDefaultControllerConfig(SettingsInterface& si)
{
si.ClearSection("InputSources");
si.ClearSection("Hotkeys");
si.ClearSection("Pad");
// PCSX2 Controller Settings - Global Settings
for (u32 i = 0; i < static_cast<u32>(InputSourceType::Count); i++)
{
si.SetBoolValue("InputSources",
InputManager::InputSourceToString(static_cast<InputSourceType>(i)),
InputManager::GetInputSourceDefaultEnabled(static_cast<InputSourceType>(i)));
}
#ifdef SDL_BUILD
si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
#endif
si.SetBoolValue("Pad", "MultitapPort1", false);
si.SetBoolValue("Pad", "MultitapPort2", false);
si.SetFloatValue("Pad", "PointerXScale", 8.0f);
si.SetFloatValue("Pad", "PointerYScale", 8.0f);
// PCSX2 Controller Settings - Default pad types and parameters.
for (u32 i = 0; i < Pad::NUM_CONTROLLER_PORTS; i++)
{
const char* type = GetDefaultPadType(i);
const std::string section(GetConfigSection(i));
si.ClearSection(section.c_str());
si.SetStringValue(section.c_str(), "Type", type);
const ControllerInfo* ci = GetControllerInfo(type);
if (ci)
{
for (const SettingInfo& csi : ci->settings)
{
switch (csi.type)
{
case SettingInfo::Type::Boolean:
si.SetBoolValue(section.c_str(), csi.name, csi.BooleanDefaultValue());
break;
case SettingInfo::Type::Integer:
case SettingInfo::Type::IntegerList:
si.SetIntValue(section.c_str(), csi.name, csi.IntegerDefaultValue());
break;
case SettingInfo::Type::Float:
si.SetFloatValue(section.c_str(), csi.name, csi.FloatDefaultValue());
break;
case SettingInfo::Type::String:
case SettingInfo::Type::StringList:
case SettingInfo::Type::Path:
si.SetStringValue(section.c_str(), csi.name, csi.StringDefaultValue());
break;
default:
break;
}
}
}
}
// PCSX2 Controller Settings - Controller 1 / Controller 2 / ...
// Use the automapper to set this up.
MapController(si, 0, InputManager::GetGenericBindingMapping("Keyboard"));
}
void Pad::SetDefaultHotkeyConfig(SettingsInterface& si)
{
// PCSX2 Controller Settings - Hotkeys
// PCSX2 Controller Settings - Hotkeys - General
si.SetStringValue("Hotkeys", "ToggleFullscreen", "Keyboard/Alt & Keyboard/Return");
// PCSX2 Controller Settings - Hotkeys - Graphics
si.SetStringValue("Hotkeys", "CycleAspectRatio", "Keyboard/F6");
si.SetStringValue("Hotkeys", "CycleInterlaceMode", "Keyboard/F5");
si.SetStringValue("Hotkeys", "CycleMipmapMode", "Keyboard/Insert");
// si.SetStringValue("Hotkeys", "DecreaseUpscaleMultiplier", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "IncreaseUpscaleMultiplier", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ReloadTextureReplacements", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "GSDumpMultiFrame", "Keyboard/Control & Keyboard/Shift & Keyboard/F8");
si.SetStringValue("Hotkeys", "Screenshot", "Keyboard/F8");
si.SetStringValue("Hotkeys", "GSDumpSingleFrame", "Keyboard/Shift & Keyboard/F8");
si.SetStringValue("Hotkeys", "ToggleSoftwareRendering", "Keyboard/F9");
// si.SetStringValue("Hotkeys", "ToggleTextureDumping", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ToggleTextureReplacements", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "ZoomIn", "Keyboard/Control & Keyboard/Plus");
si.SetStringValue("Hotkeys", "ZoomOut", "Keyboard/Control & Keyboard/Minus");
// Missing hotkey for resetting zoom back to 100 with Keyboard/Control & Keyboard/Asterisk
// PCSX2 Controller Settings - Hotkeys - Input Recording
si.SetStringValue("Hotkeys", "InputRecToggleMode", "Keyboard/Shift & Keyboard/R");
// PCSX2 Controller Settings - Hotkeys - Save States
si.SetStringValue("Hotkeys", "LoadStateFromSlot", "Keyboard/F3");
si.SetStringValue("Hotkeys", "SaveStateToSlot", "Keyboard/F1");
si.SetStringValue("Hotkeys", "NextSaveStateSlot", "Keyboard/F2");
si.SetStringValue("Hotkeys", "PreviousSaveStateSlot", "Keyboard/Shift & Keyboard/F2");
// PCSX2 Controller Settings - Hotkeys - System
// si.SetStringValue("Hotkeys", "DecreaseSpeed", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "FrameAdvance", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "IncreaseSpeed", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ResetVM", "Keyboard"); TBD
// si.SetStringValue("Hotkeys", "ShutdownVM", "Keyboard"); TBD
si.SetStringValue("Hotkeys", "OpenPauseMenu", "Keyboard/Escape");
si.SetStringValue("Hotkeys", "ToggleFrameLimit", "Keyboard/F4");
si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Space");
si.SetStringValue("Hotkeys", "ToggleSlowMotion", "Keyboard/Shift & Keyboard/Backtab");
si.SetStringValue("Hotkeys", "ToggleTurbo", "Keyboard/Tab");
si.SetStringValue("Hotkeys", "HoldTurbo", "Keyboard/Period");
}
static const Pad::ControllerInfo* s_controller_info[] = {
&PadNotConnected::ControllerInfo,
&PadDualshock2::ControllerInfo,
&PadGuitar::ControllerInfo,
};
const Pad::ControllerInfo* Pad::GetControllerInfo(Pad::ControllerType type)
{
for (const ControllerInfo* info : s_controller_info)
{
if (type == info->type)
return info;
}
return nullptr;
}
const Pad::ControllerInfo* Pad::GetControllerInfo(const std::string_view& name)
{
for (const ControllerInfo* info : s_controller_info)
{
if (name == info->name)
return info;
}
return nullptr;
}
const char* Pad::GetControllerTypeName(Pad::ControllerType type)
{
// Not localized, because it should never happen.
const ControllerInfo* ci = GetControllerInfo(type);
return ci ? ci->GetLocalizedName() : "UNKNOWN";
}
const std::vector<std::pair<const char*, const char*>> Pad::GetControllerTypeNames()
{
std::vector<std::pair<const char*, const char*>> ret;
for (const ControllerInfo* info : s_controller_info)
ret.emplace_back(info->name, info->GetLocalizedName());
return ret;
}
std::vector<std::string> Pad::GetControllerBinds(const std::string_view& type)
{
std::vector<std::string> ret;
const ControllerInfo* info = GetControllerInfo(type);
if (info)
{
for (const InputBindingInfo& bi : info->bindings)
{
if (bi.bind_type == InputBindingInfo::Type::Unknown || bi.bind_type == InputBindingInfo::Type::Motor)
continue;
ret.emplace_back(bi.name);
}
}
return ret;
}
void Pad::ClearPortBindings(SettingsInterface& si, u32 port)
{
const std::string section(StringUtil::StdStringFromFormat("Pad%u", port + 1));
const std::string type(si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(port)));
const ControllerInfo* info = GetControllerInfo(type);
if (!info)
return;
for (const InputBindingInfo& bi : info->bindings)
si.DeleteValue(section.c_str(), bi.name);
}
void Pad::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
bool copy_pad_config, bool copy_pad_bindings, bool copy_hotkey_bindings)
{
if (copy_pad_config)
{
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort1");
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort2");
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort1");
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort2");
dest_si->CopyFloatValue(src_si, "Pad", "PointerXScale");
dest_si->CopyFloatValue(src_si, "Pad", "PointerYScale");
for (u32 i = 0; i < static_cast<u32>(InputSourceType::Count); i++)
{
dest_si->CopyBoolValue(src_si, "InputSources",
InputManager::InputSourceToString(static_cast<InputSourceType>(i)));
}
#ifdef SDL_BUILD
dest_si->CopyBoolValue(src_si, "InputSources", "SDLControllerEnhancedMode");
#endif
}
for (u32 port = 0; port < Pad::NUM_CONTROLLER_PORTS; port++)
{
const std::string section(fmt::format("Pad{}", port + 1));
const std::string type(src_si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(port)));
if (copy_pad_config)
dest_si->SetStringValue(section.c_str(), "Type", type.c_str());
const ControllerInfo* info = GetControllerInfo(type);
if (!info)
return;
if (copy_pad_bindings)
{
for (const InputBindingInfo& bi : info->bindings)
dest_si->CopyStringListValue(src_si, section.c_str(), bi.name);
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
{
dest_si->CopyStringListValue(src_si, section.c_str(), fmt::format("Macro{}", i + 1).c_str());
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("Macro{}Pressure", i + 1).c_str());
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("Macro{}Deadzone", i + 1).c_str());
dest_si->CopyStringValue(src_si, section.c_str(), fmt::format("Macro{}Binds", i + 1).c_str());
dest_si->CopyUIntValue(src_si, section.c_str(), fmt::format("Macro{}Frequency", i + 1).c_str());
}
}
if (copy_pad_config)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "AxisScale");
if (info->vibration_caps != Pad::VibrationCapabilities::NoVibration)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "LargeMotorScale");
dest_si->CopyFloatValue(src_si, section.c_str(), "SmallMotorScale");
}
for (const SettingInfo& csi : info->settings)
{
switch (csi.type)
{
case SettingInfo::Type::Boolean:
dest_si->CopyBoolValue(src_si, section.c_str(), csi.name);
break;
case SettingInfo::Type::Integer:
case SettingInfo::Type::IntegerList:
dest_si->CopyIntValue(src_si, section.c_str(), csi.name);
break;
case SettingInfo::Type::Float:
dest_si->CopyFloatValue(src_si, section.c_str(), csi.name);
break;
case SettingInfo::Type::String:
case SettingInfo::Type::StringList:
case SettingInfo::Type::Path:
dest_si->CopyStringValue(src_si, section.c_str(), csi.name);
break;
default:
break;
}
}
}
}
if (copy_hotkey_bindings)
{
std::vector<const HotkeyInfo*> hotkeys(InputManager::GetHotkeyList());
for (const HotkeyInfo* hki : hotkeys)
dest_si->CopyStringListValue(src_si, "Hotkeys", hki->name);
}
}
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
const InputManager::GenericInputBindingMapping& mapping, GenericInputBinding generic_name,
const char* bind_name)
{
// find the mapping it corresponds to
const std::string* found_mapping = nullptr;
for (const std::pair<GenericInputBinding, std::string>& it : mapping)
{
if (it.first == generic_name)
{
found_mapping = &it.second;
break;
}
}
if (found_mapping)
{
Console.WriteLn("(MapController) Map %s/%s to '%s'", section.c_str(), bind_name, found_mapping->c_str());
si.SetStringValue(section.c_str(), bind_name, found_mapping->c_str());
return 1;
}
else
{
si.DeleteValue(section.c_str(), bind_name);
return 0;
}
}
bool Pad::MapController(SettingsInterface& si, u32 controller,
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping)
{
const std::string section(StringUtil::StdStringFromFormat("Pad%u", controller + 1));
const std::string type(si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(controller)));
const ControllerInfo* info = GetControllerInfo(type);
if (!info)
return false;
u32 num_mappings = 0;
for (const InputBindingInfo& bi : info->bindings)
{
if (bi.generic_mapping == GenericInputBinding::Unknown)
continue;
num_mappings += TryMapGenericMapping(si, section, mapping, bi.generic_mapping, bi.name);
}
if (info->vibration_caps == Pad::VibrationCapabilities::LargeSmallMotors)
{
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "SmallMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "LargeMotor");
}
else if (info->vibration_caps == Pad::VibrationCapabilities::SingleMotor)
{
if (TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "Motor") == 0)
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "Motor");
else
num_mappings++;
}
return (num_mappings > 0);
}
std::vector<std::string> Pad::GetInputProfileNames()
{
FileSystem::FindResultsArray results;
FileSystem::FindFiles(EmuFolders::InputProfiles.c_str(), "*.ini",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS,
&results);
std::vector<std::string> ret;
ret.reserve(results.size());
for (FILESYSTEM_FIND_DATA& fd : results)
ret.emplace_back(Path::GetFileTitle(fd.FileName));
return ret;
}
std::string Pad::GetConfigSection(u32 pad_index)
{
return fmt::format("Pad{}", pad_index + 1);
}
std::unique_ptr<PadBase> Pad::CreatePad(u8 unifiedSlot, ControllerType controllerType)
{
switch (controllerType)
{
case ControllerType::DualShock2:
return std::make_unique<PadDualshock2>(unifiedSlot);
case ControllerType::Guitar:
return std::make_unique<PadGuitar>(unifiedSlot);
default:
return std::make_unique<PadNotConnected>(unifiedSlot);
}
}
PadBase* Pad::ChangePadType(u8 unifiedSlot, ControllerType controllerType)
{
s_controllers[unifiedSlot] = CreatePad(unifiedSlot, controllerType);
return s_controllers[unifiedSlot].get();
}
bool Pad::HasConnectedPad(u8 unifiedSlot)
{
return (
unifiedSlot < NUM_CONTROLLER_PORTS && s_controllers[unifiedSlot]->GetType() != ControllerType::NotConnected);
}
PadBase* Pad::GetPad(u8 port, u8 slot)
{
const u8 unifiedSlot = sioConvertPortAndSlotToPad(port, slot);
return s_controllers[unifiedSlot].get();
}
PadBase* Pad::GetPad(const u8 unifiedSlot)
{
return s_controllers[unifiedSlot].get();
}
void Pad::SetControllerState(u32 controller, u32 bind, float value)
{
if (controller >= NUM_CONTROLLER_PORTS)
return;
s_controllers[controller]->Set(bind, value);
}
bool Pad::Freeze(StateWrapper& sw)
{
if (sw.IsReading())
{
if (!sw.DoMarker("PAD"))
{
Console.Error("PAD state is invalid! Leaving the current state in place.");
return false;
}
for (u32 unifiedSlot = 0; unifiedSlot < NUM_CONTROLLER_PORTS; unifiedSlot++)
{
ControllerType type;
sw.Do(&type);
if (sw.HasError())
return false;
std::unique_ptr<PadBase> tempPad;
PadBase* pad = GetPad(unifiedSlot);
if (!pad || pad->GetType() != type)
{
const auto& [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot);
Host::AddIconOSDMessage(fmt::format("UnfreezePad{}Changed", unifiedSlot), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("Pad",
"Controller port {}, slot {} has a {} connected, but the save state has a "
"{}.\nLeaving the original controller type connected, but this may cause issues."),
port, slot,
Pad::GetControllerTypeName(pad ? pad->GetType() : Pad::ControllerType::NotConnected),
Pad::GetControllerTypeName(type)));
// Reset the transfer etc state of the pad, at least it has a better chance of surviving.
if (pad)
pad->SoftReset();
// But we still need to pull the data from the state..
tempPad = CreatePad(unifiedSlot, type);
pad = tempPad.get();
}
if (!pad->Freeze(sw))
return false;
}
}
else
{
if (!sw.DoMarker("PAD"))
return false;
for (u32 unifiedSlot = 0; unifiedSlot < NUM_CONTROLLER_PORTS; unifiedSlot++)
{
PadBase* pad = GetPad(unifiedSlot);
ControllerType type = pad->GetType();
sw.Do(&type);
if (sw.HasError() || !pad->Freeze(sw))
return false;
}
}
return !sw.HasError();
}
void Pad::LoadMacroButtonConfig(const SettingsInterface& si, u32 pad, const std::string_view& type, const std::string& section)
{
// lazily initialized
std::vector<std::string> binds;
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
{
std::string binds_string;
if (!si.GetStringValue(section.c_str(), StringUtil::StdStringFromFormat("Macro%uBinds", i + 1).c_str(), &binds_string))
continue;
const u32 frequency = si.GetUIntValue(section.c_str(), StringUtil::StdStringFromFormat("Macro%uFrequency", i + 1).c_str(), 0u);
if (binds.empty())
binds = GetControllerBinds(type);
const float pressure = si.GetFloatValue(section.c_str(), fmt::format("Macro{}Pressure", i + 1).c_str(), 1.0f);
// convert binds
std::vector<u32> bind_indices;
std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
if (buttons_split.empty())
continue;
for (const std::string_view& button : buttons_split)
{
auto it = std::find(binds.begin(), binds.end(), button);
if (it == binds.end())
{
Console.Error("Invalid bind '%.*s' in macro button %u for pad %u", static_cast<int>(button.size()), button.data(), pad, i);
continue;
}
bind_indices.push_back(static_cast<u32>(std::distance(binds.begin(), it)));
}
if (bind_indices.empty())
continue;
MacroButton& macro = s_macro_buttons[pad][i];
macro.buttons = std::move(bind_indices);
macro.toggle_frequency = frequency;
macro.pressure = pressure;
}
}
void Pad::SetMacroButtonState(u32 pad, u32 index, bool state)
{
if (pad >= Pad::NUM_CONTROLLER_PORTS || index >= NUM_MACRO_BUTTONS_PER_CONTROLLER)
return;
MacroButton& mb = s_macro_buttons[pad][index];
if (mb.buttons.empty() || mb.trigger_state == state)
return;
mb.toggle_counter = mb.toggle_frequency;
mb.trigger_state = state;
if (mb.toggle_state != state)
{
mb.toggle_state = state;
ApplyMacroButton(pad, mb);
}
}
void Pad::ApplyMacroButton(u32 controller, const Pad::MacroButton& mb)
{
const float value = mb.toggle_state ? mb.pressure : 0.0f;
PadBase* const pad = Pad::GetPad(controller);
for (const u32 btn : mb.buttons)
pad->Set(btn, value);
}
void Pad::UpdateMacroButtons()
{
for (u32 pad = 0; pad < Pad::NUM_CONTROLLER_PORTS; pad++)
{
for (u32 index = 0; index < NUM_MACRO_BUTTONS_PER_CONTROLLER; index++)
{
Pad::MacroButton& mb = s_macro_buttons[pad][index];
if (!mb.trigger_state || mb.toggle_frequency == 0)
{
continue;
}
mb.toggle_counter--;
if (mb.toggle_counter > 0)
{
continue;
}
mb.toggle_counter = mb.toggle_frequency;
mb.toggle_state = !mb.toggle_state;
ApplyMacroButton(pad, mb);
}
}
}
+83
View File
@@ -0,0 +1,83 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "Config.h"
#include "SIO/Pad/PadTypes.h"
#include <memory>
class PadBase;
enum class GenericInputBinding : u8;
class SettingsInterface;
class StateWrapper;
namespace Pad
{
bool Initialize();
void Shutdown();
// Returns the default type for the specified port.
const char* GetDefaultPadType(u32 pad);
// Reloads configuration.
void LoadConfig(const SettingsInterface& si);
// Restores default configuration.
void SetDefaultControllerConfig(SettingsInterface& si);
void SetDefaultHotkeyConfig(SettingsInterface& si);
// Clears all bindings for a given port.
void ClearPortBindings(SettingsInterface& si, u32 port);
// Copies pad configuration from one interface (ini) to another.
void CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si, bool copy_pad_config = true,
bool copy_pad_bindings = true, bool copy_hotkey_bindings = true);
// Returns a list of controller type names. Pair of [name, display name].
const std::vector<std::pair<const char*, const char*>> GetControllerTypeNames();
// Returns the list of binds for the specified controller type.
std::vector<std::string> GetControllerBinds(const std::string_view& type);
// Returns general information for the specified controller type.
const ControllerInfo* GetControllerInfo(Pad::ControllerType type);
const ControllerInfo* GetControllerInfo(const std::string_view& name);
const char* GetControllerTypeName(Pad::ControllerType type);
// Performs automatic controller mapping with the provided list of generic mappings.
bool MapController(
SettingsInterface& si, u32 controller, const std::vector<std::pair<GenericInputBinding, std::string>>& mapping);
// Returns a list of input profiles available.
std::vector<std::string> GetInputProfileNames();
std::string GetConfigSection(u32 pad_index);
bool HasConnectedPad(u8 unifiedSlot);
PadBase* GetPad(u8 port, u8 slot);
PadBase* GetPad(const u8 unifiedSlot);
// Sets the specified bind on a controller to the specified pressure (normalized to 0..1).
void SetControllerState(u32 controller, u32 bind, float value);
bool Freeze(StateWrapper& sw);
// Sets the state of the specified macro button.
void SetMacroButtonState(u32 pad, u32 index, bool state);
void UpdateMacroButtons();
}; // namespace Pad
+51
View File
@@ -0,0 +1,51 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "SIO/Pad/PadBase.h"
PadBase::PadBase(u8 unifiedSlot)
{
this->unifiedSlot = unifiedSlot;
}
PadBase::~PadBase() = default;
void PadBase::SoftReset()
{
commandBytesReceived = 1;
}
void PadBase::FullReset()
{
this->isInConfig = false;
this->currentMode = Pad::Mode::DIGITAL;
}
bool PadBase::Freeze(StateWrapper& sw)
{
if (!sw.DoMarker("PadBase"))
return false;
// Protected PadBase members
sw.Do(&rawInputs);
sw.Do(&unifiedSlot);
sw.Do(&isInConfig);
sw.Do(&currentMode);
sw.Do(&currentCommand);
sw.Do(&commandBytesReceived);
return !sw.HasError();
}
+65
View File
@@ -0,0 +1,65 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "SIO/Pad/PadTypes.h"
#include "StateWrapper.h"
#include <array>
class PadBase
{
protected:
std::array<u8, 32> rawInputs;
u8 unifiedSlot;
bool isInConfig = false;
Pad::Mode currentMode = Pad::Mode::NOT_SET;
Pad::Command currentCommand = Pad::Command::NOT_SET;
size_t commandBytesReceived = 0;
public: // Public members
PadBase(u8 unifiedSlot);
virtual ~PadBase();
void SoftReset();
void FullReset();
virtual void Init() = 0;
virtual Pad::ControllerType GetType() const = 0;
virtual const Pad::ControllerInfo& GetInfo() const = 0;
virtual void Set(u32 index, float value) = 0;
virtual void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) = 0;
virtual void SetAxisScale(float deadzone, float scale) = 0;
virtual void SetTriggerScale(float deadzone, float scale) = 0;
virtual float GetVibrationScale(u32 motor) const = 0;
virtual void SetVibrationScale(u32 motor, float scale) = 0;
virtual float GetPressureModifier() const = 0;
virtual void SetPressureModifier(float mod) = 0;
virtual void SetButtonDeadzone(float deadzone) = 0;
virtual void SetAnalogInvertL(bool x, bool y) = 0;
virtual void SetAnalogInvertR(bool x, bool y) = 0;
virtual u8 GetRawInput(u32 index) const = 0;
virtual std::tuple<u8, u8> GetRawLeftAnalog() const = 0;
virtual std::tuple<u8, u8> GetRawRightAnalog() const = 0;
virtual u32 GetButtons() const = 0;
virtual u8 GetPressure(u32 index) const = 0;
virtual bool Freeze(StateWrapper& sw);
virtual u8 SendCommandByte(u8 commandByte) = 0;
};
+847
View File
@@ -0,0 +1,847 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "SIO/Pad/PadDualshock2.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Sio.h"
#include "SIO/Sio0.h"
#include "Common.h"
#include "Input/InputManager.h"
#include "Host.h"
static const InputBindingInfo s_bindings[] = {
// clang-format off
{"Up", TRANSLATE_NOOP("Pad", "D-Pad Up"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_UP, GenericInputBinding::DPadUp},
{"Right", TRANSLATE_NOOP("Pad", "D-Pad Right"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_RIGHT, GenericInputBinding::DPadRight},
{"Down", TRANSLATE_NOOP("Pad", "D-Pad Down"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_DOWN, GenericInputBinding::DPadDown},
{"Left", TRANSLATE_NOOP("Pad", "D-Pad Left"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_LEFT, GenericInputBinding::DPadLeft},
{"Triangle", TRANSLATE_NOOP("Pad", "Triangle"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_TRIANGLE, GenericInputBinding::Triangle},
{"Circle", TRANSLATE_NOOP("Pad", "Circle"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_CIRCLE, GenericInputBinding::Circle},
{"Cross", TRANSLATE_NOOP("Pad", "Cross"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_CROSS, GenericInputBinding::Cross},
{"Square", TRANSLATE_NOOP("Pad", "Square"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_SQUARE, GenericInputBinding::Square},
{"Select", TRANSLATE_NOOP("Pad", "Select"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_SELECT, GenericInputBinding::Select},
{"Start", TRANSLATE_NOOP("Pad", "Start"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_START, GenericInputBinding::Start},
{"L1", TRANSLATE_NOOP("Pad", "L1 (Left Bumper)"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_L1, GenericInputBinding::L1},
{"L2", TRANSLATE_NOOP("Pad", "L2 (Left Trigger)"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_L2, GenericInputBinding::L2},
{"R1", TRANSLATE_NOOP("Pad", "R1 (Right Bumper)"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_R1, GenericInputBinding::R1},
{"R2", TRANSLATE_NOOP("Pad", "R2 (Right Trigger)"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_R2, GenericInputBinding::R2},
{"L3", TRANSLATE_NOOP("Pad", "L3 (Left Stick Button)"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_L3, GenericInputBinding::L3},
{"R3", TRANSLATE_NOOP("Pad", "R3 (Right Stick Button)"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_R3, GenericInputBinding::R3},
{"Analog", TRANSLATE_NOOP("Pad", "Analog Toggle"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_ANALOG, GenericInputBinding::System},
{"Pressure", TRANSLATE_NOOP("Pad", "Apply Pressure"), InputBindingInfo::Type::Button, PadDualshock2::Inputs::PAD_PRESSURE, GenericInputBinding::Unknown},
{"LUp", TRANSLATE_NOOP("Pad", "Left Stick Up"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_L_UP, GenericInputBinding::LeftStickUp},
{"LRight", TRANSLATE_NOOP("Pad", "Left Stick Right"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_L_RIGHT, GenericInputBinding::LeftStickRight},
{"LDown", TRANSLATE_NOOP("Pad", "Left Stick Down"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_L_DOWN, GenericInputBinding::LeftStickDown},
{"LLeft", TRANSLATE_NOOP("Pad", "Left Stick Left"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_L_LEFT, GenericInputBinding::LeftStickLeft},
{"RUp", TRANSLATE_NOOP("Pad", "Right Stick Up"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_R_UP, GenericInputBinding::RightStickUp},
{"RRight", TRANSLATE_NOOP("Pad", "Right Stick Right"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_R_RIGHT, GenericInputBinding::RightStickRight},
{"RDown", TRANSLATE_NOOP("Pad", "Right Stick Down"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_R_DOWN, GenericInputBinding::RightStickDown},
{"RLeft", TRANSLATE_NOOP("Pad", "Right Stick Left"), InputBindingInfo::Type::HalfAxis, PadDualshock2::Inputs::PAD_R_LEFT, GenericInputBinding::RightStickLeft},
{"LargeMotor", TRANSLATE_NOOP("Pad", "Large (Low Frequency) Motor"), InputBindingInfo::Type::Motor, 0, GenericInputBinding::LargeMotor},
{"SmallMotor", TRANSLATE_NOOP("Pad", "Small (High Frequency) Motor"), InputBindingInfo::Type::Motor, 0, GenericInputBinding::SmallMotor},
// clang-format on
};
static const char* s_invert_options[] = {TRANSLATE_NOOP("Pad", "Not Inverted"),
TRANSLATE_NOOP("Pad", "Invert Left/Right"), TRANSLATE_NOOP("Pad", "Invert Up/Down"),
TRANSLATE_NOOP("Pad", "Invert Left/Right + Up/Down"), nullptr};
static const SettingInfo s_settings[] = {
{SettingInfo::Type::IntegerList, "InvertL", TRANSLATE_NOOP("Pad", "Invert Left Stick"),
TRANSLATE_NOOP("Pad", "Inverts the direction of the left analog stick."), "0", "0", "3", nullptr, nullptr,
s_invert_options, nullptr, 0.0f},
{SettingInfo::Type::IntegerList, "InvertR", TRANSLATE_NOOP("Pad", "Invert Right Stick"),
TRANSLATE_NOOP("Pad", "Inverts the direction of the right analog stick."), "0", "0", "3", nullptr, nullptr,
s_invert_options, nullptr, 0.0f},
{SettingInfo::Type::Float, "Deadzone", TRANSLATE_NOOP("Pad", "Analog Deadzone"),
TRANSLATE_NOOP(
"Pad", "Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored."),
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "AxisScale", TRANSLATE_NOOP("Pad", "Analog Sensitivity"),
TRANSLATE_NOOP("Pad",
"Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller."),
"1.33", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "LargeMotorScale", TRANSLATE_NOOP("Pad", "Large Motor Vibration Scale"),
TRANSLATE_NOOP("Pad", "Increases or decreases the intensity of low frequency vibration sent by the game."),
"1.00", "0.00", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "SmallMotorScale", TRANSLATE_NOOP("Pad", "Small Motor Vibration Scale"),
TRANSLATE_NOOP("Pad", "Increases or decreases the intensity of high frequency vibration sent by the game."),
"1.00", "0.00", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "ButtonDeadzone", TRANSLATE_NOOP("Pad", "Button/Trigger Deadzone"),
TRANSLATE_NOOP("Pad", "Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger "
"which will be ignored."),
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "PressureModifier", TRANSLATE_NOOP("Pad", "Modifier Pressure"),
TRANSLATE_NOOP("Pad", "Sets the pressure when the modifier button is held."), "0.50", "0.01", "1.00", "0.01",
"%.0f%%", nullptr, nullptr, 100.0f},
};
const Pad::ControllerInfo PadDualshock2::ControllerInfo = {Pad::ControllerType::DualShock2, "DualShock2",
TRANSLATE_NOOP("Pad", "DualShock 2"), s_bindings, s_settings, Pad::VibrationCapabilities::LargeSmallMotors};
u8 PadDualshock2::Mystery(u8 commandByte)
{
switch (commandBytesReceived)
{
case 5:
return 0x02;
case 8:
return 0x5a;
default:
return 0x00;
}
}
u8 PadDualshock2::ButtonQuery(u8 commandByte)
{
switch (commandBytesReceived)
{
case 3:
case 4:
return 0xff;
case 5:
return 0x03;
case 8:
g_Sio0.SetAcknowledge(false);
return 0x5a;
default:
return 0x00;
}
}
u8 PadDualshock2::Poll(u8 commandByte)
{
const u32 buttons = GetButtons();
switch (commandBytesReceived)
{
case 3:
this->vibrationMotors[0] = commandByte;
return (buttons >> 8) & 0xff;
case 4:
this->vibrationMotors[1] = commandByte;
InputManager::SetPadVibrationIntensity(this->unifiedSlot,
std::min(static_cast<float>(this->vibrationMotors[0]) * GetVibrationScale(0) * (1.0f / 255.0f), 1.0f),
std::min(static_cast<float>(this->vibrationMotors[1]) * GetVibrationScale(1) * (1.0f / 255.0f), 1.0f)
);
// PS1 mode: If the controller is still in digital mode, it is time to stop acknowledging.
if (this->currentMode == Pad::Mode::DIGITAL)
{
g_Sio0.SetAcknowledge(false);
}
return buttons & 0xff;
case 5:
return GetPressure(Inputs::PAD_R_RIGHT);
case 6:
return GetPressure(Inputs::PAD_R_UP);
case 7:
return GetPressure(Inputs::PAD_L_RIGHT);
case 8:
// PS1 mode: If the controller reaches this byte, it is in analog mode and has irrefutably reached the last byte.
// There's simply nothing to check, we know it's done and time to stop acknowledgements.
g_Sio0.SetAcknowledge(false);
return GetPressure(Inputs::PAD_L_UP);
case 9:
return IsButtonBitSet(buttons, 13) ? GetPressure(Inputs::PAD_RIGHT) : 0;
case 10:
return IsButtonBitSet(buttons, 15) ? GetPressure(Inputs::PAD_LEFT) : 0;
case 11:
return IsButtonBitSet(buttons, 12) ? GetPressure(Inputs::PAD_UP) : 0;
case 12:
return IsButtonBitSet(buttons, 14) ? GetPressure(Inputs::PAD_DOWN) : 0;
case 13:
return IsButtonBitSet(buttons, 4) ? GetPressure(Inputs::PAD_TRIANGLE) : 0;
case 14:
return IsButtonBitSet(buttons, 5) ? GetPressure(Inputs::PAD_CIRCLE) : 0;
case 15:
return IsButtonBitSet(buttons, 6) ? GetPressure(Inputs::PAD_CROSS) : 0;
case 16:
return IsButtonBitSet(buttons, 7) ? GetPressure(Inputs::PAD_SQUARE) : 0;
case 17:
return IsButtonBitSet(buttons, 2) ? GetPressure(Inputs::PAD_L1) : 0;
case 18:
return IsButtonBitSet(buttons, 3) ? GetPressure(Inputs::PAD_R1) : 0;
case 19:
return IsButtonBitSet(buttons, 0) ? GetPressure(Inputs::PAD_L2) : 0;
case 20:
return IsButtonBitSet(buttons, 1) ? GetPressure(Inputs::PAD_R2) : 0;
}
Console.Warning("%s(%02X) Did not reach a valid return path! Returning zero as a failsafe!", __FUNCTION__, commandByte);
return 0x00;
}
u8 PadDualshock2::Config(u8 commandByte)
{
if (commandBytesReceived == 3)
{
if (commandByte)
{
if (!this->isInConfig)
{
this->isInConfig = true;
}
else
{
Console.Warning("%s(%02X) Unexpected enter while already in config mode", __FUNCTION__, commandByte);
}
}
else
{
if (this->isInConfig)
{
this->isInConfig = false;
const auto [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot);
Console.WriteLn(StringUtil::StdStringFromFormat("[Pad] Game finished pad setup for port %d / slot %d - Analogs: %s - Analog Button: %s - Pressure: %s",
port + 1,
slot + 1,
(this->analogLight ? "On" : "Off"),
(this->analogLocked ? "Locked" : "Usable"),
(this->responseBytes == static_cast<u32>(Pad::ResponseBytes::DUALSHOCK2) ? "On" : "Off")));
}
else
{
Console.Warning("%s(%02X) Unexpected exit while not in config mode", __FUNCTION__, commandByte);
}
}
}
// PS1 mode: Config mode would have been triggered by a prior byte in this command sequence;
// if we are now in config mode, check the current mode and if this is the last byte. If so,
// don't acknowledge.
if (this->isInConfig)
{
if ((this->currentMode == Pad::Mode::DIGITAL && this->commandBytesReceived == 4) || (this->currentMode == Pad::Mode::ANALOG && this->commandBytesReceived == 8))
{
g_Sio0.SetAcknowledge(false);
}
}
return 0x00;
}
// Changes the mode of the controller between digital and analog, and adjusts the analog LED accordingly.
u8 PadDualshock2::ModeSwitch(u8 commandByte)
{
switch (commandBytesReceived)
{
case 3:
this->analogLight = commandByte;
if (this->analogLight)
{
this->currentMode = Pad::Mode::ANALOG;
}
else
{
this->currentMode = Pad::Mode::DIGITAL;
}
break;
case 4:
this->analogLocked = (commandByte == 0x03);
break;
case 8:
g_Sio0.SetAcknowledge(false);
break;
default:
break;
}
return 0x00;
}
u8 PadDualshock2::StatusInfo(u8 commandByte)
{
switch (commandBytesReceived)
{
case 3:
return static_cast<u8>(Pad::PhysicalType::STANDARD);
case 4:
return 0x02;
case 5:
return this->analogLight;
case 6:
return 0x02;
case 7:
return 0x01;
case 8:
g_Sio0.SetAcknowledge(false);
return 0x00;
default:
return 0x00;
}
}
u8 PadDualshock2::Constant1(u8 commandByte)
{
switch (commandBytesReceived)
{
case 3:
commandStage = commandByte != 0;
return 0x00;
case 5:
return 0x01;
case 6:
if (commandStage)
{
return 0x01;
}
else
{
return 0x02;
}
case 7:
if (commandStage)
{
return 0x01;
}
else
{
return 0x00;
}
case 8:
g_Sio0.SetAcknowledge(false);
return (commandStage ? 0x14 : 0x0a);
default:
return 0x00;
}
}
u8 PadDualshock2::Constant2(u8 commandByte)
{
switch (commandBytesReceived)
{
case 5:
return 0x02;
case 8:
g_Sio0.SetAcknowledge(false);
return 0x00;
default:
return 0x00;
}
}
u8 PadDualshock2::Constant3(u8 commandByte)
{
switch (commandBytesReceived)
{
case 3:
commandStage = (commandByte != 0);
return 0x00;
case 6:
if (commandStage)
{
return 0x07;
}
else
{
return 0x04;
}
case 8:
g_Sio0.SetAcknowledge(false);
return 0x00;
default:
return 0x00;
}
}
u8 PadDualshock2::VibrationMap(u8 commandByte)
{
switch (commandBytesReceived)
{
case 3:
return 0x00;
case 4:
return 0x01;
case 8:
g_Sio0.SetAcknowledge(false);
return 0xff;
default:
return 0xff;
}
}
u8 PadDualshock2::ResponseBytes(u8 commandByte)
{
switch (commandBytesReceived)
{
case 3:
this->responseBytes = commandByte;
return 0x00;
case 4:
this->responseBytes |= (commandByte << 8);
return 0x00;
case 5:
this->responseBytes |= (commandByte << 16);
switch (static_cast<Pad::ResponseBytes>(this->responseBytes))
{
case Pad::ResponseBytes::ANALOG:
this->analogLight = true;
this->currentMode = Pad::Mode::ANALOG;
break;
case Pad::ResponseBytes::DUALSHOCK2:
this->analogLight = true;
this->currentMode = Pad::Mode::DUALSHOCK2;
break;
default:
this->analogLight = false;
this->currentMode = Pad::Mode::DIGITAL;
break;
}
return 0x00;
case 8:
return 0x5a;
default:
return 0x00;
}
}
PadDualshock2::PadDualshock2(u8 unifiedSlot)
: PadBase(unifiedSlot)
{
this->currentMode = Pad::Mode::DIGITAL;
Init();
}
PadDualshock2::~PadDualshock2() = default;
void PadDualshock2::Init()
{
this->buttons = 0xffffffff;
this->analogs.lx = Pad::ANALOG_NEUTRAL_POSITION;
this->analogs.ly = Pad::ANALOG_NEUTRAL_POSITION;
this->analogs.rx = Pad::ANALOG_NEUTRAL_POSITION;
this->analogs.ry = Pad::ANALOG_NEUTRAL_POSITION;
this->analogs.lxInvert = 0;
this->analogs.lyInvert = 0;
this->analogs.rxInvert = 0;
this->analogs.ryInvert = 0;
this->analogLight = false;
this->analogLocked = false;
this->analogPressed = false;
this->responseBytes = 0;
for (u8 i = 0; i < this->rawInputs.size(); i++)
{
this->rawInputs[i] = 0;
}
for (u8 i = 0; i < this->pressures.size(); i++)
{
this->pressures[i] = 0;
}
this->axisScale = 1.0f;
this->axisDeadzone = 0.0f;
this->vibrationScale[0] = 0.0f;
this->vibrationScale[1] = 1.0f;
this->pressureModifier = 0.5f;
this->buttonDeadzone = 0.0f;
}
Pad::ControllerType PadDualshock2::GetType() const
{
return Pad::ControllerType::DualShock2;
}
const Pad::ControllerInfo& PadDualshock2::GetInfo() const
{
return ControllerInfo;
}
void PadDualshock2::Set(u32 index, float value)
{
if (index > Inputs::LENGTH)
{
return;
}
// Since we reordered the buttons for better UI, we need to remap them here.
static constexpr std::array<u8, Inputs::LENGTH> bitmaskMapping = {{
12, // PAD_UP
13, // PAD_RIGHT
14, // PAD_DOWN
15, // PAD_LEFT
4, // PAD_TRIANGLE
5, // PAD_CIRCLE
6, // PAD_CROSS
7, // PAD_SQUARE
8, // PAD_SELECT
11, // PAD_START
2, // PAD_L1
0, // PAD_L2
3, // PAD_R1
1, // PAD_R2
9, // PAD_L3
10, // PAD_R3
16, // PAD_ANALOG
17, // PAD_PRESSURE
// remainder are analogs and not used here
}};
if (IsAnalogKey(index))
{
this->rawInputs[index] = static_cast<u8>(std::clamp(value * this->axisScale * 255.0f, 0.0f, 255.0f));
// Left -> -- -> Right
// Value range : FFFF8002 -> 0 -> 7FFE
// Force range : 80 -> 0 -> 7F
// Normal mode : expect value 0 -> 80 -> FF
// Reverse mode: expect value FF -> 7F -> 0
// merge left/right or up/down into rx or ry
#define MERGE(pos, neg) ((this->rawInputs[pos] != 0) ? (127u + ((this->rawInputs[pos] + 1u) / 2u)) : (127u - (this->rawInputs[neg] / 2u)))
if (index <= Inputs::PAD_L_LEFT)
{
// Left Stick
this->analogs.lx = this->analogs.lxInvert ? MERGE(Inputs::PAD_L_LEFT, Inputs::PAD_L_RIGHT) : MERGE(Inputs::PAD_L_RIGHT, Inputs::PAD_L_LEFT);
this->analogs.ly = this->analogs.lyInvert ? MERGE(Inputs::PAD_L_UP, Inputs::PAD_L_DOWN) : MERGE(Inputs::PAD_L_DOWN, Inputs::PAD_L_UP);
}
else
{
// Right Stick
this->analogs.rx = this->analogs.rxInvert ? MERGE(Inputs::PAD_R_LEFT, Inputs::PAD_R_RIGHT) : MERGE(Inputs::PAD_R_RIGHT, Inputs::PAD_R_LEFT);
this->analogs.ry = this->analogs.ryInvert ? MERGE(Inputs::PAD_R_UP, Inputs::PAD_R_DOWN) : MERGE(Inputs::PAD_R_DOWN, Inputs::PAD_R_UP);
}
#undef MERGE
// Deadzone computation.
const float dz = this->axisDeadzone;
if (dz > 0.0f)
{
#define MERGE_F(pos, neg) ((this->rawInputs[pos] != 0) ? (static_cast<float>(this->rawInputs[pos]) / 255.0f) : (static_cast<float>(this->rawInputs[neg]) / -255.0f))
float posX, posY;
if (index <= Inputs::PAD_L_LEFT)
{
posX = this->analogs.lxInvert ? MERGE_F(Inputs::PAD_L_LEFT, Inputs::PAD_L_RIGHT) : MERGE_F(Inputs::PAD_L_RIGHT, Inputs::PAD_L_LEFT);
posY = this->analogs.lyInvert ? MERGE_F(Inputs::PAD_L_UP, Inputs::PAD_L_DOWN) : MERGE_F(Inputs::PAD_L_DOWN, Inputs::PAD_L_UP);
}
else
{
posX = this->analogs.rxInvert ? MERGE_F(Inputs::PAD_R_LEFT, Inputs::PAD_R_RIGHT) : MERGE_F(Inputs::PAD_R_RIGHT, Inputs::PAD_R_LEFT);
posY = this->analogs.ryInvert ? MERGE_F(Inputs::PAD_R_UP, Inputs::PAD_R_DOWN) : MERGE_F(Inputs::PAD_R_DOWN, Inputs::PAD_R_UP);
}
// No point checking if we're at dead center (usually keyboard with no buttons pressed).
if (posX != 0.0f || posY != 0.0f)
{
// Compute the angle at the given position in the stick's square bounding box.
const float theta = std::atan2(posY, posX);
// Compute the position that the edge of the circle would be at, given the angle.
const float dzX = std::cos(theta) * dz;
const float dzY = std::sin(theta) * dz;
// We're in the deadzone if our position is less than the circle edge.
const bool inX = (posX < 0.0f) ? (posX > dzX) : (posX <= dzX);
const bool inY = (posY < 0.0f) ? (posY > dzY) : (posY <= dzY);
if (inX && inY)
{
// In deadzone. Set to 127 (center).
if (index <= Inputs::PAD_L_LEFT)
{
this->analogs.lx = this->analogs.ly = 127;
}
else
{
this->analogs.rx = this->analogs.ry = 127;
}
}
}
#undef MERGE_F
}
}
else if (IsTriggerKey(index))
{
const float s_value = std::clamp(value * this->triggerScale, 0.0f, 1.0f);
const float dz_value = (this->triggerDeadzone > 0.0f && s_value < this->triggerDeadzone) ? 0.0f : s_value;
this->rawInputs[index] = static_cast<u8>(dz_value * 255.0f);
if (dz_value > 0.0f)
this->buttons &= ~(1u << bitmaskMapping[index]);
else
this->buttons |= (1u << bitmaskMapping[index]);
}
else
{
// Don't affect L2/R2, since they are analog on most pads.
const float pMod = ((this->buttons & (1u << Inputs::PAD_PRESSURE)) == 0 && !IsTriggerKey(index)) ? this->pressureModifier : 1.0f;
const float dzValue = (value < this->buttonDeadzone) ? 0.0f : value;
this->rawInputs[index] = static_cast<u8>(std::clamp(dzValue * pMod * 255.0f, 0.0f, 255.0f));
if (dzValue > 0.0f)
{
this->buttons &= ~(1u << bitmaskMapping[index]);
}
else
{
this->buttons |= (1u << bitmaskMapping[index]);
}
// Adjust pressure of all other face buttons which are active when pressure modifier is pressed..
if (index == Inputs::PAD_PRESSURE)
{
const float adjustPMod = ((this->buttons & (1u << Inputs::PAD_PRESSURE)) == 0) ? this->pressureModifier : (1.0f / this->pressureModifier);
for (u32 i = 0; i < Inputs::LENGTH; i++)
{
if (i == index || IsAnalogKey(i) || IsTriggerKey(i))
{
continue;
}
// We add 0.5 here so that the round trip between 255->127->255 when applying works as expected.
const float add = (this->rawInputs[i] != 0) ? 0.5f : 0.0f;
this->rawInputs[i] = static_cast<u8>(std::clamp((static_cast<float>(this->rawInputs[i]) + add) * adjustPMod, 0.0f, 255.0f));
}
}
if (index == Inputs::PAD_ANALOG && !this->analogPressed && value > 0)
{
this->analogPressed = true;
if (!this->analogLocked)
{
this->analogLight = !this->analogLight;
if (this->analogLight)
{
this->currentMode = Pad::Mode::ANALOG;
}
else
{
this->currentMode = Pad::Mode::DIGITAL;
}
const auto [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot);
Host::AddKeyedOSDMessage(fmt::format("PadAnalogButtonChange{}{}", port, slot),
fmt::format(TRANSLATE_FS("Pad", "Analog light is now {} for port {} / slot {}"),
(this->analogLight ? "On" : "Off"),
port + 1,
slot + 1),
Host::OSD_INFO_DURATION);
}
}
else
{
this->analogPressed = false;
}
}
}
void PadDualshock2::SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right)
{
this->analogs.lx = std::get<0>(left);
this->analogs.ly = std::get<1>(left);
this->analogs.rx = std::get<0>(right);
this->analogs.ry = std::get<1>(right);
}
void PadDualshock2::SetAxisScale(float deadzone, float scale)
{
this->axisDeadzone = deadzone;
this->axisScale = scale;
}
void PadDualshock2::SetTriggerScale(float deadzone, float scale)
{
this->triggerDeadzone = deadzone;
this->triggerScale = scale;
}
float PadDualshock2::GetVibrationScale(u32 motor) const
{
return this->vibrationScale[motor];
}
void PadDualshock2::SetVibrationScale(u32 motor, float scale)
{
this->vibrationScale[motor] = scale;
}
float PadDualshock2::GetPressureModifier() const
{
return this->pressureModifier;
}
void PadDualshock2::SetPressureModifier(float mod)
{
this->pressureModifier = mod;
}
void PadDualshock2::SetButtonDeadzone(float deadzone)
{
this->buttonDeadzone = deadzone;
}
void PadDualshock2::SetAnalogInvertL(bool x, bool y)
{
this->analogs.lxInvert = x;
this->analogs.lyInvert = y;
}
void PadDualshock2::SetAnalogInvertR(bool x, bool y)
{
this->analogs.rxInvert = x;
this->analogs.ryInvert = y;
}
u8 PadDualshock2::GetRawInput(u32 index) const
{
return rawInputs[index];
}
std::tuple<u8, u8> PadDualshock2::GetRawLeftAnalog() const
{
return {analogs.lx, analogs.ly};
}
std::tuple<u8, u8> PadDualshock2::GetRawRightAnalog() const
{
return {analogs.rx, analogs.ry};
}
u32 PadDualshock2::GetButtons() const
{
return buttons;
}
u8 PadDualshock2::GetPressure(u32 index) const
{
switch (index)
{
case Inputs::PAD_R_LEFT:
case Inputs::PAD_R_RIGHT:
return this->analogs.rx;
case Inputs::PAD_R_DOWN:
case Inputs::PAD_R_UP:
return this->analogs.ry;
case Inputs::PAD_L_LEFT:
case Inputs::PAD_L_RIGHT:
return this->analogs.lx;
case Inputs::PAD_L_DOWN:
case Inputs::PAD_L_UP:
return this->analogs.ly;
default:
return this->rawInputs[index];
}
}
bool PadDualshock2::Freeze(StateWrapper& sw)
{
if (!PadBase::Freeze(sw) || !sw.DoMarker("PadDualshock2"))
return false;
// Private PadDualshock2 members
sw.Do(&buttons);
sw.DoBytes(&analogs, sizeof(Analogs));
sw.Do(&analogLight);
sw.Do(&analogLocked);
sw.Do(&analogPressed);
sw.Do(&commandStage);
sw.Do(&responseBytes);
sw.Do(&pressures);
sw.Do(&vibrationMotors);
sw.Do(&axisScale);
sw.Do(&axisDeadzone);
sw.Do(&triggerScale);
sw.Do(&triggerDeadzone);
sw.Do(&vibrationScale);
sw.Do(&pressureModifier);
sw.Do(&buttonDeadzone);
return !sw.HasError();
}
u8 PadDualshock2::SendCommandByte(u8 commandByte)
{
u8 ret = 0;
switch (this->commandBytesReceived)
{
case 0:
ret = 0x00;
break;
case 1:
this->currentCommand = static_cast<Pad::Command>(commandByte);
if (this->currentCommand != Pad::Command::POLL && this->currentCommand != Pad::Command::CONFIG && !this->isInConfig)
{
Console.Warning("%s(%02X) Config-only command was sent to a pad outside of config mode!", __FUNCTION__, commandByte);
}
ret = this->isInConfig ? static_cast<u8>(Pad::Mode::CONFIG) : static_cast<u8>(this->currentMode);
break;
case 2:
ret = 0x5a;
break;
default:
switch (this->currentCommand)
{
case Pad::Command::MYSTERY:
ret = Mystery(commandByte);
break;
case Pad::Command::BUTTON_QUERY:
ret = ButtonQuery(commandByte);
break;
case Pad::Command::POLL:
ret = Poll(commandByte);
break;
case Pad::Command::CONFIG:
ret = Config(commandByte);
break;
case Pad::Command::MODE_SWITCH:
ret = ModeSwitch(commandByte);
break;
case Pad::Command::STATUS_INFO:
ret = StatusInfo(commandByte);
break;
case Pad::Command::CONST_1:
ret = Constant1(commandByte);
break;
case Pad::Command::CONST_2:
ret = Constant2(commandByte);
break;
case Pad::Command::CONST_3:
ret = Constant3(commandByte);
break;
case Pad::Command::VIBRATION_MAP:
ret = VibrationMap(commandByte);
break;
case Pad::Command::RESPONSE_BYTES:
ret = ResponseBytes(commandByte);
break;
default:
ret = 0x00;
break;
}
}
this->commandBytesReceived++;
return ret;
}
+151
View File
@@ -0,0 +1,151 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "SIO/Pad/PadBase.h"
#include <array>
static inline bool IsButtonBitSet(u32 value, size_t bit)
{
return !(value & (1 << bit));
}
class PadDualshock2 final : public PadBase
{
public:
enum Inputs
{
PAD_UP, // Directional pad up
PAD_RIGHT, // Directional pad right
PAD_DOWN, // Directional pad down
PAD_LEFT, // Directional pad left
PAD_TRIANGLE, // Triangle button
PAD_CIRCLE, // Circle button
PAD_CROSS, // Cross button
PAD_SQUARE, // Square button
PAD_SELECT, // Select button
PAD_START, // Start button
PAD_L1, // L1 button
PAD_L2, // L2 button
PAD_R1, // R1 button
PAD_R2, // R2 button
PAD_L3, // Left joystick button (L3)
PAD_R3, // Right joystick button (R3)
PAD_ANALOG, // Analog mode toggle
PAD_PRESSURE, // Pressure modifier
PAD_L_UP, // Left joystick (Up)
PAD_L_RIGHT, // Left joystick (Right)
PAD_L_DOWN, // Left joystick (Down)
PAD_L_LEFT, // Left joystick (Left)
PAD_R_UP, // Right joystick (Up)
PAD_R_RIGHT, // Right joystick (Right)
PAD_R_DOWN, // Right joystick (Down)
PAD_R_LEFT, // Right joystick (Left)
LENGTH,
};
static constexpr u32 PRESSURE_BUTTONS = 12;
static constexpr u8 VIBRATION_MOTORS = 2;
private:
struct Analogs
{
u8 lx = 0x7f;
u8 ly = 0x7f;
u8 rx = 0x7f;
u8 ry = 0x7f;
u8 lxInvert = 0x7f;
u8 lyInvert = 0x7f;
u8 rxInvert = 0x7f;
u8 ryInvert = 0x7f;
};
u32 buttons;
Analogs analogs;
bool analogLight = false;
bool analogLocked = false;
// Analog button can be held without changing its state.
// We track here if it is currently held down, to avoid flipping in
// and out of analog mode every frame.
bool analogPressed = false;
bool commandStage = false;
u32 responseBytes;
std::array<u8, PRESSURE_BUTTONS> pressures;
std::array<u8, VIBRATION_MOTORS> vibrationMotors;
float axisScale;
float axisDeadzone;
float triggerScale;
float triggerDeadzone;
std::array<float, 2> vibrationScale;
// When the pressure modifier binding is activated, this is multiplied against
// all values in pressures, to artificially reduce pressures and give players
// a way to simulate pressure sensitive controls.
float pressureModifier;
float buttonDeadzone;
u8 Mystery(u8 commandByte);
u8 ButtonQuery(u8 commandByte);
u8 Poll(u8 commandByte);
u8 Config(u8 commandByte);
u8 ModeSwitch(u8 commandByte);
u8 StatusInfo(u8 commandByte);
u8 Constant1(u8 commandByte);
u8 Constant2(u8 commandByte);
u8 Constant3(u8 commandByte);
u8 VibrationMap(u8 commandByte);
u8 ResponseBytes(u8 commandByte);
public:
PadDualshock2(u8 unifiedSlot);
~PadDualshock2() override;
static inline bool IsAnalogKey(int index)
{
return ((index >= Inputs::PAD_L_UP) && (index <= Inputs::PAD_R_LEFT));
}
static inline bool IsTriggerKey(int index)
{
return (index == Inputs::PAD_L2 || index == Inputs::PAD_R2);
}
void Init() override;
Pad::ControllerType GetType() const override;
const Pad::ControllerInfo& GetInfo() const override;
void Set(u32 index, float value) override;
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetAxisScale(float deadzone, float scale) override;
void SetTriggerScale(float deadzone, float scale) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
void SetPressureModifier(float mod) override;
void SetButtonDeadzone(float deadzone) override;
void SetAnalogInvertL(bool x, bool y) override;
void SetAnalogInvertR(bool x, bool y) override;
u8 GetRawInput(u32 index) const override;
std::tuple<u8, u8> GetRawLeftAnalog() const override;
std::tuple<u8, u8> GetRawRightAnalog() const override;
u32 GetButtons() const override;
u8 GetPressure(u32 index) const override;
bool Freeze(StateWrapper& sw) override;
u8 SendCommandByte(u8 commandByte) override;
static const Pad::ControllerInfo ControllerInfo;
};
+484
View File
@@ -0,0 +1,484 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "SIO/Pad/PadGuitar.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Sio.h"
#include "Host.h"
// The generic input bindings on this might seem bizarre, but they are intended to match what DS2 buttons
// would do what actions, if you played Guitar Hero on a PS2 with a DS2 instead of a controller.
static const InputBindingInfo s_bindings[] = {
// clang-format off
{"Up", TRANSLATE_NOOP("Pad", "Strum Up"), InputBindingInfo::Type::Button, PadGuitar::Inputs::STRUM_UP, GenericInputBinding::DPadUp},
{"Down", TRANSLATE_NOOP("Pad", "Strum Down"), InputBindingInfo::Type::Button, PadGuitar::Inputs::STRUM_DOWN, GenericInputBinding::DPadDown},
{"Select", TRANSLATE_NOOP("Pad", "Select"), InputBindingInfo::Type::Button, PadGuitar::Inputs::SELECT, GenericInputBinding::Select},
{"Start", TRANSLATE_NOOP("Pad", "Start"), InputBindingInfo::Type::Button, PadGuitar::Inputs::START, GenericInputBinding::Start},
{"Green", TRANSLATE_NOOP("Pad", "Green Fret"), InputBindingInfo::Type::Button, PadGuitar::Inputs::GREEN, GenericInputBinding::R2},
{"Red", TRANSLATE_NOOP("Pad", "Red Fret"), InputBindingInfo::Type::Button, PadGuitar::Inputs::RED, GenericInputBinding::Circle},
{"Yellow", TRANSLATE_NOOP("Pad", "Yellow Fret"), InputBindingInfo::Type::Button, PadGuitar::Inputs::YELLOW, GenericInputBinding::Triangle},
{"Blue", TRANSLATE_NOOP("Pad", "Blue Fret"), InputBindingInfo::Type::Button, PadGuitar::Inputs::BLUE, GenericInputBinding::Cross},
{"Orange", TRANSLATE_NOOP("Pad", "Orange Fret"), InputBindingInfo::Type::Button, PadGuitar::Inputs::ORANGE, GenericInputBinding::Square},
{"Whammy", TRANSLATE_NOOP("Pad", "Whammy Bar"), InputBindingInfo::Type::HalfAxis, PadGuitar::Inputs::WHAMMY, GenericInputBinding::LeftStickUp},
{"Tilt", TRANSLATE_NOOP("Pad", "Tilt Up"), InputBindingInfo::Type::Button, PadGuitar::Inputs::TILT, GenericInputBinding::L2},
// clang-format on
};
static const SettingInfo s_settings[] = {
{SettingInfo::Type::Float, "Deadzone", TRANSLATE_NOOP("Pad", "Whammy Bar Deadzone"),
TRANSLATE_NOOP("Pad", "Sets the whammy bar deadzone. Inputs below this value will not be sent to the PS2."),
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "AxisScale", TRANSLATE_NOOP("Pad", "Whammy Bar Sensitivity"),
TRANSLATE_NOOP("Pad", "Sets the whammy bar axis scaling factor."), "1.0", "0.01", "2.00", "0.01", "%.0f%%",
nullptr, nullptr, 100.0f},
};
const Pad::ControllerInfo PadGuitar::ControllerInfo = {Pad::ControllerType::Guitar, "Guitar",
TRANSLATE_NOOP("Pad", "Guitar"), s_bindings, s_settings, Pad::VibrationCapabilities::NoVibration};
u8 PadGuitar::Mystery(u8 commandByte)
{
switch (this->commandBytesReceived)
{
case 5:
return 0x02;
case 8:
return 0x5a;
default:
return 0x00;
}
}
u8 PadGuitar::ButtonQuery(u8 commandByte)
{
switch (this->commandBytesReceived)
{
case 3:
case 4:
return 0xff;
case 5:
return 0x03;
case 8:
return 0x5a;
default:
return 0x00;
}
}
u8 PadGuitar::Poll(u8 commandByte)
{
const u32 buttons = GetButtons();
switch (this->commandBytesReceived)
{
case 3:
return (buttons >> 8) & 0x7f;
case 4:
return buttons & 0xff;
case 5:
return 0x7f;
case 6:
return 0x7f;
case 7:
return 0x7f;
case 8:
return GetPressure(Inputs::WHAMMY);
}
Console.Warning("%s(%02X) Did not reach a valid return path! Returning zero as a failsafe!", __FUNCTION__, commandByte);
return 0x00;
}
u8 PadGuitar::Config(u8 commandByte)
{
if (this->commandBytesReceived == 3)
{
if (commandByte)
{
if (!this->isInConfig)
{
this->isInConfig = true;
}
else
{
Console.Warning("%s(%02X) Unexpected enter while already in config mode", __FUNCTION__, commandByte);
}
}
else
{
if (this->isInConfig)
{
this->isInConfig = false;
const auto [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot);
Console.WriteLn("[Pad] Game finished pad setup for port %d / slot %d - Analogs: %s - Analog Button: %s - Pressure: Not available on guitars",
port + 1,
slot + 1,
(this->analogLight ? "On" : "Off"),
(this->analogLocked ? "Locked" : "Usable"));
}
else
{
Console.Warning("%s(%02X) Unexpected exit while not in config mode", __FUNCTION__, commandByte);
}
}
}
return 0x00;
}
// Changes the mode of the controller between digital and analog, and adjusts the analog LED accordingly.
u8 PadGuitar::ModeSwitch(u8 commandByte)
{
switch (this->commandBytesReceived)
{
case 3:
this->analogLight = commandByte;
if (this->analogLight)
{
this->currentMode = Pad::Mode::ANALOG;
}
else
{
this->currentMode = Pad::Mode::DIGITAL;
}
break;
case 4:
this->analogLocked = (commandByte == 0x03);
break;
default:
break;
}
return 0x00;
}
u8 PadGuitar::StatusInfo(u8 commandByte)
{
switch (this->commandBytesReceived)
{
case 3:
return static_cast<u8>(Pad::PhysicalType::GUITAR);
case 4:
return 0x02;
case 5:
return this->analogLight;
case 6:
return 0x02;
case 7:
return 0x01;
default:
return 0x00;
}
}
u8 PadGuitar::Constant1(u8 commandByte)
{
switch (this->commandBytesReceived)
{
case 3:
commandStage = (commandByte != 0);
return 0x00;
case 5:
return 0x01;
case 6:
return (!commandStage ? 0x02 : 0x01);
case 7:
return (!commandStage ? 0x00 : 0x01);
case 8:
return (commandStage ? 0x0a : 0x14);
default:
return 0x00;
}
}
u8 PadGuitar::Constant2(u8 commandByte)
{
switch (this->commandBytesReceived)
{
case 5:
return 0x02;
case 7:
return 0x01;
default:
return 0x00;
}
}
u8 PadGuitar::Constant3(u8 commandByte)
{
switch (this->commandBytesReceived)
{
case 3:
commandStage = (commandByte != 0);
return 0x00;
case 6:
return (!commandStage ? 0x04 : 0x07);
default:
return 0x00;
}
}
u8 PadGuitar::VibrationMap(u8 commandByte)
{
return 0xff;
}
PadGuitar::PadGuitar(u8 unifiedSlot)
: PadBase(unifiedSlot)
{
this->currentMode = Pad::Mode::DIGITAL;
Init();
}
PadGuitar::~PadGuitar() = default;
void PadGuitar::Init()
{
this->buttons = 0xffffffff;
this->whammy = Pad::ANALOG_NEUTRAL_POSITION;
this->analogLight = false;
this->analogLocked = false;
this->whammyAxisScale = 1.0f;
this->whammyDeadzone = 0.0f;
}
Pad::ControllerType PadGuitar::GetType() const
{
return Pad::ControllerType::Guitar;
}
const Pad::ControllerInfo& PadGuitar::GetInfo() const
{
return ControllerInfo;
}
void PadGuitar::Set(u32 index, float value)
{
if (index > Inputs::LENGTH)
{
return;
}
// The whammy bar is a special kind of weird in that rather than resting at 0 and going to 255,
// they chose to rest it at 127 like a normal analog, but then also make its full press 0, as if
// it were the negative Y component of a normal analog. Fun!
if (index == Inputs::WHAMMY)
{
this->whammy = static_cast<u8>(std::clamp(127 - (value * this->whammyAxisScale) * 255.0f, 0.0f, 127.0f));
if (this->whammyDeadzone > 0.0f)
{
// Whammy has a range of 0x7f to 0x00, since it is only half of an axis with no ability to go the
// other direction. So whatever we get in, we basically need to cut half of that off in order to
// figure out where our deadzone truly lives. I think.
const float whammyF = (static_cast<float>(this->whammy - 127.0f) / 127.0f);
if (whammyF != 0.0f && whammyF <= this->whammyDeadzone)
{
this->whammy = 0x7f;
}
}
}
else
{
// Don't affect L2/R2, since they are analog on most pads.
const float dzValue = (value < this->buttonDeadzone) ? 0.0f : value;
this->rawInputs[index] = static_cast<u8>(std::clamp(dzValue * 255.0f, 0.0f, 255.0f));
// Since we reordered the buttons for better UI, we need to remap them here.
static constexpr std::array<u8, Inputs::LENGTH> bitmaskMapping = {{
12, // STRUM_UP
14, // STRUM_DOWN
8, // SELECT
11, // START
1, // GREEN
5, // RED
4, // YELLOW
6, // BLUE
7, // ORANGE
0 // TILT
}};
if (dzValue > 0.0f)
{
this->buttons &= ~(1u << bitmaskMapping[index]);
}
else
{
this->buttons |= (1u << bitmaskMapping[index]);
}
}
}
void PadGuitar::SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right)
{
}
void PadGuitar::SetAxisScale(float deadzone, float scale)
{
this->whammyDeadzone = deadzone;
this->whammyAxisScale = scale;
}
void PadGuitar::SetTriggerScale(float deadzone, float scale)
{
}
float PadGuitar::GetVibrationScale(u32 motor) const
{
return 0;
}
void PadGuitar::SetVibrationScale(u32 motor, float scale)
{
}
float PadGuitar::GetPressureModifier() const
{
return 0;
}
void PadGuitar::SetPressureModifier(float mod)
{
}
void PadGuitar::SetButtonDeadzone(float deadzone)
{
this->buttonDeadzone = deadzone;
}
void PadGuitar::SetAnalogInvertL(bool x, bool y)
{
}
void PadGuitar::SetAnalogInvertR(bool x, bool y)
{
}
u8 PadGuitar::GetRawInput(u32 index) const
{
return rawInputs[index];
}
std::tuple<u8, u8> PadGuitar::GetRawLeftAnalog() const
{
return std::tuple<u8, u8>{0x7f, 0x7f};
}
std::tuple<u8, u8> PadGuitar::GetRawRightAnalog() const
{
return std::tuple<u8, u8>{0x7f, 0x7f};
}
u32 PadGuitar::GetButtons() const
{
return buttons;
}
u8 PadGuitar::GetPressure(u32 index) const
{
if (index == Inputs::WHAMMY)
return whammy;
return 0;
}
bool PadGuitar::Freeze(StateWrapper& sw)
{
if (!PadBase::Freeze(sw) || !sw.DoMarker("PadGuitar"))
return false;
// Private PadGuitar members
sw.Do(&buttons);
sw.Do(&whammy);
sw.Do(&analogLight);
sw.Do(&analogLocked);
sw.Do(&commandStage);
sw.Do(&whammyAxisScale);
sw.Do(&whammyDeadzone);
sw.Do(&buttonDeadzone);
return !sw.HasError();
}
u8 PadGuitar::SendCommandByte(u8 commandByte)
{
u8 ret = 0;
switch (this->commandBytesReceived)
{
case 0:
ret = 0x00;
break;
case 1:
this->currentCommand = static_cast<Pad::Command>(commandByte);
if (this->currentCommand != Pad::Command::POLL && this->currentCommand != Pad::Command::CONFIG && !this->isInConfig)
{
Console.Warning("%s(%02X) Config-only command was sent to a pad outside of config mode!", __FUNCTION__, commandByte);
}
ret = this->isInConfig ? static_cast<u8>(Pad::Mode::CONFIG) : static_cast<u8>(this->currentMode);
break;
case 2:
ret = 0x5a;
break;
default:
switch (this->currentCommand)
{
case Pad::Command::MYSTERY:
ret = Mystery(commandByte);
break;
case Pad::Command::BUTTON_QUERY:
ret = ButtonQuery(commandByte);
break;
case Pad::Command::POLL:
ret = Poll(commandByte);
break;
case Pad::Command::CONFIG:
ret = Config(commandByte);
break;
case Pad::Command::MODE_SWITCH:
ret = ModeSwitch(commandByte);
break;
case Pad::Command::STATUS_INFO:
ret = StatusInfo(commandByte);
break;
case Pad::Command::CONST_1:
ret = Constant1(commandByte);
break;
case Pad::Command::CONST_2:
ret = Constant2(commandByte);
break;
case Pad::Command::CONST_3:
ret = Constant3(commandByte);
break;
case Pad::Command::VIBRATION_MAP:
ret = VibrationMap(commandByte);
break;
default:
ret = 0x00;
break;
}
}
this->commandBytesReceived++;
return ret;
}
+92
View File
@@ -0,0 +1,92 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "SIO/Pad/PadBase.h"
class PadGuitar final : public PadBase
{
public:
enum Inputs
{
STRUM_UP, // Strum bar
STRUM_DOWN, // Strum bar down
SELECT, // Select button
START, // Start button
GREEN, // Green fret
RED, // Red fret
YELLOW, // Yellow fret
BLUE, // Blue fret
ORANGE, // Orange fret
WHAMMY, // Whammy bar axis
TILT, // Tilt sensor
LENGTH,
};
private:
u32 buttons;
u8 whammy;
// Technically guitars do not have an analog light, but they still use the same ModeSwitch command
// as a DS2, and are told to "turn on their light".
bool analogLight = false;
// Guitars are also instructed to "lock" their "analog light", despite not having one.
bool analogLocked = false;
bool commandStage = false;
float whammyAxisScale; // Guitars only have 1 axis on the whammy bar.
float whammyDeadzone;
float buttonDeadzone; // Button deadzone is still a good idea, in case a host analog stick is bound to a guitar button
u8 Mystery(u8 commandByte);
u8 ButtonQuery(u8 commandByte);
u8 Poll(u8 commandByte);
u8 Config(u8 commandByte);
u8 ModeSwitch(u8 commandByte);
u8 StatusInfo(u8 commandByte);
u8 Constant1(u8 commandByte);
u8 Constant2(u8 commandByte);
u8 Constant3(u8 commandByte);
u8 VibrationMap(u8 commandByte);
public:
PadGuitar(u8 unifiedSlot);
~PadGuitar() override;
void Init() override;
Pad::ControllerType GetType() const override;
const Pad::ControllerInfo& GetInfo() const override;
void Set(u32 index, float value) override;
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetAxisScale(float deadzone, float scale) override;
void SetTriggerScale(float deadzone, float scale) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
void SetPressureModifier(float mod) override;
void SetButtonDeadzone(float deadzone) override;
void SetAnalogInvertL(bool x, bool y) override;
void SetAnalogInvertR(bool x, bool y) override;
u8 GetRawInput(u32 index) const override;
std::tuple<u8, u8> GetRawLeftAnalog() const override;
std::tuple<u8, u8> GetRawRightAnalog() const override;
u32 GetButtons() const override;
u8 GetPressure(u32 index) const override;
bool Freeze(StateWrapper& sw) override;
u8 SendCommandByte(u8 commandByte) override;
static const Pad::ControllerInfo ControllerInfo;
};
+131
View File
@@ -0,0 +1,131 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "SIO/Pad/PadNotConnected.h"
#include "Host.h"
const Pad::ControllerInfo PadNotConnected::ControllerInfo = {Pad::ControllerType::NotConnected, "None",
TRANSLATE_NOOP("Pad", "Not Connected"), {}, {}, Pad::VibrationCapabilities::NoVibration };
PadNotConnected::PadNotConnected(u8 unifiedSlot)
: PadBase(unifiedSlot)
{
}
PadNotConnected::~PadNotConnected() = default;
void PadNotConnected::Init()
{
}
Pad::ControllerType PadNotConnected::GetType() const
{
return Pad::ControllerType::NotConnected;
}
const Pad::ControllerInfo& PadNotConnected::GetInfo() const
{
return ControllerInfo;
}
void PadNotConnected::Set(u32 index, float value)
{
}
void PadNotConnected::SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right)
{
}
void PadNotConnected::SetAxisScale(float deadzone, float scale)
{
}
void PadNotConnected::SetTriggerScale(float deadzone, float scale)
{
}
float PadNotConnected::GetVibrationScale(u32 motor) const
{
return 0;
}
void PadNotConnected::SetVibrationScale(u32 motor, float scale)
{
}
float PadNotConnected::GetPressureModifier() const
{
return 0;
}
void PadNotConnected::SetPressureModifier(float mod)
{
}
void PadNotConnected::SetButtonDeadzone(float deadzone)
{
}
void PadNotConnected::SetAnalogInvertL(bool x, bool y)
{
}
void PadNotConnected::SetAnalogInvertR(bool x, bool y)
{
}
u8 PadNotConnected::GetRawInput(u32 index) const
{
return 0;
}
std::tuple<u8, u8> PadNotConnected::GetRawLeftAnalog() const
{
return std::tuple<u8, u8>{0, 0};
}
std::tuple<u8, u8> PadNotConnected::GetRawRightAnalog() const
{
return std::tuple<u8, u8>{0, 0};
}
u32 PadNotConnected::GetButtons() const
{
return 0;
}
u8 PadNotConnected::GetPressure(u32 index) const
{
return 0;
}
u8 PadNotConnected::SendCommandByte(u8 commandByte)
{
return 0xff;
}
+49
View File
@@ -0,0 +1,49 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "SIO/Pad/PadBase.h"
class PadNotConnected final : public PadBase
{
public:
PadNotConnected(u8 unifiedSlot);
~PadNotConnected() override;
void Init() override;
Pad::ControllerType GetType() const override;
const Pad::ControllerInfo& GetInfo() const override;
void Set(u32 index, float value) override;
void SetRawAnalogs(const std::tuple<u8, u8> left, const std::tuple<u8, u8> right) override;
void SetAxisScale(float deadzone, float scale) override;
void SetTriggerScale(float deadzone, float scale) override;
float GetVibrationScale(u32 motor) const override;
void SetVibrationScale(u32 motor, float scale) override;
float GetPressureModifier() const override;
void SetPressureModifier(float mod) override;
void SetButtonDeadzone(float deadzone) override;
void SetAnalogInvertL(bool x, bool y) override;
void SetAnalogInvertR(bool x, bool y) override;
u8 GetRawInput(u32 index) const override;
std::tuple<u8, u8> GetRawLeftAnalog() const override;
std::tuple<u8, u8> GetRawRightAnalog() const override;
u32 GetButtons() const override;
u8 GetPressure(u32 index) const override;
u8 SendCommandByte(u8 commandByte) override;
static const Pad::ControllerInfo ControllerInfo;
};
+116
View File
@@ -0,0 +1,116 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "Config.h"
#include <span>
namespace Pad
{
enum class Command : u8
{
NOT_SET = 0x00,
MYSTERY = 0x40,
BUTTON_QUERY = 0x41,
POLL = 0x42,
CONFIG = 0x43,
MODE_SWITCH = 0x44,
STATUS_INFO = 0x45,
CONST_1 = 0x46,
CONST_2 = 0x47,
CONST_3 = 0x4c,
VIBRATION_MAP = 0x4d,
RESPONSE_BYTES = 0x4f
};
enum class Mode : u8
{
NOT_SET = 0x00,
PS1_MOUSE = 0x12,
NEGCON = 0x23,
PS1_KONAMI_LIGHTGUN = 0x31,
DIGITAL = 0x41,
PS1_FLIGHT_STICK = 0x53,
PS1_NAMCO_LIGHTGUN = 0x63,
ANALOG = 0x73,
DUALSHOCK2 = 0x79,
PS1_MULTITAP = 0x80,
PS1_JOGCON = 0xe3,
CONFIG = 0xf3,
DISCONNECTED = 0xff
};
enum class PhysicalType : u8
{
NOT_SET = 0x00,
GUITAR = 0x01,
STANDARD = 0x03
};
enum class ResponseBytes : u32
{
DIGITAL = 0x00000000,
ANALOG = 0x0000003f,
DUALSHOCK2 = 0x0003ffff
};
static constexpr u8 ANALOG_NEUTRAL_POSITION = 0x7f;
enum class ControllerType : u8
{
NotConnected,
DualShock2,
Guitar,
Count
};
enum class VibrationCapabilities : u8
{
NoVibration,
LargeSmallMotors,
SingleMotor,
Count
};
struct ControllerInfo
{
ControllerType type;
const char* name;
const char* display_name;
std::span<const InputBindingInfo> bindings;
std::span<const SettingInfo> settings;
VibrationCapabilities vibration_caps;
// Returns localized controller type name.
const char* GetLocalizedName() const;
};
// Total number of pad ports, across both multitaps.
static constexpr u32 NUM_CONTROLLER_PORTS = 8;
// Default stick deadzone/sensitivity.
static constexpr float DEFAULT_STICK_DEADZONE = 0.0f;
static constexpr float DEFAULT_STICK_SCALE = 1.33f;
static constexpr float DEFAULT_TRIGGER_DEADZONE = 0.0f;
static constexpr float DEFAULT_TRIGGER_SCALE = 1.0f;
static constexpr float DEFAULT_MOTOR_SCALE = 1.0f;
static constexpr float DEFAULT_PRESSURE_MODIFIER = 0.5f;
static constexpr float DEFAULT_BUTTON_DEADZONE = 0.0f;
// Number of macro buttons per controller.
static constexpr u32 NUM_MACRO_BUTTONS_PER_CONTROLLER = 16;
} // namespace Pad
+136
View File
@@ -0,0 +1,136 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "SIO/Sio.h"
#include "SIO/SioTypes.h"
#include "SIO/Memcard/MemoryCardProtocol.h"
#include "Host.h"
#include "IconsFontAwesome5.h"
_mcd mcds[2][4];
_mcd *mcd;
void sioNextFrame() {
for ( uint port = 0; port < 2; ++port ) {
for ( uint slot = 0; slot < 4; ++slot ) {
mcds[port][slot].NextFrame();
}
}
}
void sioSetGameSerial( const std::string& serial ) {
for ( uint port = 0; port < 2; ++port ) {
for ( uint slot = 0; slot < 4; ++slot ) {
if ( mcds[port][slot].ReIndex( serial ) ) {
AutoEject::Set( port, slot );
}
}
}
}
std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index)
{
if (index > 4) // [5,6,7]
return std::make_tuple(1, index - 4); // 2B,2C,2D
else if (index > 1) // [2,3,4]
return std::make_tuple(0, index - 1); // 1B,1C,1D
else // [0,1]
return std::make_tuple(index, 0); // 1A,2A
}
u32 sioConvertPortAndSlotToPad(u32 port, u32 slot)
{
if (slot == 0)
return port;
else if (port == 0) // slot=[0,1]
return slot + 1; // 2,3,4
else
return slot + 4; // 5,6,7
}
bool sioPadIsMultitapSlot(u32 index)
{
return (index >= 2);
}
bool sioPortAndSlotIsMultitap(u32 port, u32 slot)
{
return (slot != 0);
}
void AutoEject::CountDownTicks()
{
bool reinserted = false;
for (size_t port = 0; port < SIO::PORTS; port++)
{
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
{
if (mcds[port][slot].autoEjectTicks > 0)
{
if (--mcds[port][slot].autoEjectTicks == 0)
reinserted |= EmuConfig.Mcd[sioConvertPortAndSlotToPad(port, slot)].Enabled;
}
}
}
if (reinserted)
{
Host::AddIconOSDMessage("AutoEjectAllSet", ICON_FA_SD_CARD,
TRANSLATE_SV("MemoryCard", "Memory Cards reinserted."), Host::OSD_INFO_DURATION);
}
}
void AutoEject::Set(size_t port, size_t slot)
{
if (EmuConfig.McdEnableEjection && mcds[port][slot].autoEjectTicks == 0)
{
mcds[port][slot].autoEjectTicks = 1; // 1 second is enough.
mcds[port][slot].term = 0x55; // Reset terminator to default (0x55), forces the PS2 to recheck the memcard.
}
}
void AutoEject::Clear(size_t port, size_t slot)
{
mcds[port][slot].autoEjectTicks = 0;
}
void AutoEject::SetAll()
{
Host::AddIconOSDMessage("AutoEjectAllSet", ICON_FA_SD_CARD,
TRANSLATE_SV("MemoryCard", "Force ejecting all Memory Cards. Reinserting in 1 second."), Host::OSD_INFO_DURATION);
for (size_t port = 0; port < SIO::PORTS; port++)
{
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
{
AutoEject::Set(port, slot);
}
}
}
void AutoEject::ClearAll()
{
for (size_t port = 0; port < SIO::PORTS; port++)
{
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
{
AutoEject::Clear(port, slot);
}
}
}
+131
View File
@@ -0,0 +1,131 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
// Huge thanks to PSI for his work reversing the PS2, his documentation on SIO2 pretty much saved
// this entire implementation. https://psi-rockin.github.io/ps2tek/#sio2registers
#pragma once
#include "SIO/Memcard/MemoryCardFile.h"
struct _mcd
{
u8 currentCommand;
u8 term; // terminator value;
bool goodSector; // xor sector check
u8 msb;
u8 lsb;
u32 sectorAddr; // read/write sector address
u32 transferAddr; // Transfer address
std::vector<u8> buf; // Buffer for reading and writing
u8 FLAG; // for PSX;
u8 port; // port
u8 slot; // and slot for this memcard
size_t autoEjectTicks;
void GetSizeInfo(McdSizeInfo &info)
{
FileMcd_GetSizeInfo(port, slot, &info);
}
bool IsPSX()
{
return FileMcd_IsPSX(port, slot);
}
void EraseBlock()
{
//DevCon.WriteLn("Memcard Erase (sectorAddr = %08X)", sectorAddr);
FileMcd_EraseBlock(port, slot, transferAddr);
}
// Read from memorycard to dest
void Read(u8 *dest, int size)
{
//DevCon.WriteLn("Memcard Read (sectorAddr = %08X)", sectorAddr);
FileMcd_Read(port, slot, dest, transferAddr, size);
}
// Write to memorycard from src
void Write(u8 *src, int size)
{
//DevCon.WriteLn("Memcard Write (sectorAddr = %08X)", sectorAddr);
FileMcd_Save(port, slot, src,transferAddr, size);
}
bool IsPresent()
{
return FileMcd_IsPresent(port, slot);
}
u8 DoXor()
{
u8 ret = msb ^ lsb;
for (const u8 byte : buf)
{
ret ^= byte;
}
return ret;
}
u64 GetChecksum()
{
return FileMcd_GetCRC(port, slot);
}
void NextFrame() {
FileMcd_NextFrame( port, slot );
}
bool ReIndex(const std::string& filter) {
return FileMcd_ReIndex(port, slot, filter);
}
};
extern _mcd mcds[2][4];
extern _mcd *mcd;
extern void sioNextFrame();
/// Converts a global pad index to a multitap port and slot.
extern std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index);
/// Convert the PS2's port/slot addressing to a single value.
/// Physical ports 0 and 1 still correspond to unified slots 0 and 1.
/// The remaining unified slots are for multitapped slots.
/// Port 0's three multitap slots then occupy unified slots 2, 3 and 4.
/// Port 1's three multitap slots then occupy unified slots 5, 6 and 7.
extern u32 sioConvertPortAndSlotToPad(u32 port, u32 slot);
/// Returns true if the given pad index is a multitap slot.
extern bool sioPadIsMultitapSlot(u32 index);
extern bool sioPortAndSlotIsMultitap(u32 port, u32 slot);
extern void sioSetGameSerial(const std::string& serial);
namespace AutoEject
{
extern void CountDownTicks();
extern void Set(size_t port, size_t slot);
extern void Clear(size_t port, size_t slot);
extern void SetAll();
extern void ClearAll();
} // namespace AutoEject
+339
View File
@@ -0,0 +1,339 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Common.h"
#include "IopDma.h"
#include "IopHw.h"
#include "R3000A.h"
#include "SIO/Memcard/MemoryCardProtocol.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Pad/PadBase.h"
#include "SIO/Sio.h"
#include "SIO/Sio0.h"
#include "StateWrapper.h"
#define SIO0LOG_ENABLE 0
#define Sio0Log if (SIO0LOG_ENABLE) DevCon
Sio0 g_Sio0;
void Sio0::ClearStatAcknowledge()
{
stat &= ~(SIO0_STAT::ACK);
}
Sio0::Sio0() = default;
Sio0::~Sio0() = default;
bool Sio0::Initialize()
{
SoftReset();
port = 0;
slot = 0;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 4; j++)
{
mcds[i][j].term = 0x55;
mcds[i][j].port = i;
mcds[i][j].slot = j;
mcds[i][j].FLAG = 0x08;
mcds[i][j].autoEjectTicks = 0;
}
}
mcd = &mcds[0][0];
return true;
}
bool Sio0::Shutdown()
{
return true;
}
void Sio0::SoftReset()
{
padStarted = false;
sioMode = SioMode::NOT_SET;
sioCommand = 0;
sioStage = SioStage::IDLE;
g_MemoryCardProtocol.ResetPS1State();
}
// Simulates the ACK line on the bus. Peripherals are expected to send an ACK signal
// over this line to tell the PS1 "keep sending me things I'm not done yet". The PS1
// then uses this after it receives the peripheral's response to decide what to do.
void Sio0::SetAcknowledge(bool ack)
{
if (ack)
{
stat |= SIO0_STAT::ACK;
}
else
{
stat &= ~(SIO0_STAT::ACK);
}
}
void Sio0::Interrupt(Sio0Interrupt sio0Interrupt)
{
switch (sio0Interrupt)
{
case Sio0Interrupt::TEST_EVENT:
iopIntcIrq(7);
break;
case Sio0Interrupt::STAT_READ:
ClearStatAcknowledge();
break;
case Sio0Interrupt::TX_DATA_WRITE:
break;
default:
Console.Error("%s(%d) Invalid parameter", __FUNCTION__, sio0Interrupt);
assert(false);
break;
}
if (!(psxRegs.interrupt & (1 << IopEvt_SIO)))
{
PSX_INT(IopEvt_SIO, PSXCLK / 250000); // PSXCLK/250000);
}
}
u8 Sio0::GetTxData()
{
Sio0Log.WriteLn("%s()\tSIO0 TX_DATA Read\t(%02X)", __FUNCTION__, txData);
return txData;
}
u8 Sio0::GetRxData()
{
Sio0Log.WriteLn("%s()\tSIO0 RX_DATA Read\t(%02X)", __FUNCTION__, rxData);
stat |= (SIO0_STAT::TX_READY | SIO0_STAT::TX_EMPTY);
stat &= ~(SIO0_STAT::RX_FIFO_NOT_EMPTY);
return rxData;
}
u32 Sio0::GetStat()
{
Sio0Log.WriteLn("%s()\tSIO0 STAT Read\t(%08X)", __FUNCTION__, stat);
const u32 ret = stat;
Interrupt(Sio0Interrupt::STAT_READ);
return ret;
}
u16 Sio0::GetMode()
{
Sio0Log.WriteLn("%s()\tSIO0 MODE Read\t(%08X)", __FUNCTION__, mode);
return mode;
}
u16 Sio0::GetCtrl()
{
Sio0Log.WriteLn("%s()\tSIO0 CTRL Read\t(%08X)", __FUNCTION__, ctrl);
return ctrl;
}
u16 Sio0::GetBaud()
{
Sio0Log.WriteLn("%s()\tSIO0 BAUD Read\t(%08X)", __FUNCTION__, baud);
return baud;
}
void Sio0::SetTxData(u8 cmd)
{
Sio0Log.WriteLn("%s()\tSIO0 TX_DATA Write\t(%02X)", __FUNCTION__, cmd);
stat |= SIO0_STAT::TX_READY | SIO0_STAT::TX_EMPTY;
stat |= (SIO0_STAT::RX_FIFO_NOT_EMPTY);
if (!(ctrl & SIO0_CTRL::TX_ENABLE))
{
Console.Warning("%s(%02X) CTRL in illegal state, exiting instantly", __FUNCTION__, cmd);
return;
}
txData = cmd;
u8 data = 0;
PadBase* currentPad = nullptr;
switch (sioMode)
{
case SioMode::NOT_SET:
sioMode = cmd;
currentPad = Pad::GetPad(port, slot);
currentPad->SoftReset();
mcd = &mcds[port][slot];
SetAcknowledge(true);
break;
case SioMode::PAD:
currentPad = Pad::GetPad(port, slot);
pxAssertMsg(currentPad != nullptr, "Got nullptr when looking up pad");
// Set ACK in advance of sending the command to the pad.
// The pad will, if the command is done, set ACK to false.
SetAcknowledge(true);
data = currentPad->SendCommandByte(cmd);
SetRxData(data);
break;
case SioMode::MEMCARD:
if (this->sioCommand == MemcardCommand::NOT_SET)
{
if (IsMemcardCommand(cmd) && mcd->IsPresent() && mcd->IsPSX())
{
this->sioCommand = cmd;
SetAcknowledge(true);
SetRxData(this->flag);
}
else
{
SetAcknowledge(false);
SetRxData(0x00);
}
}
else
{
SetRxData(Memcard(cmd));
}
break;
default:
SetRxData(0xff);
SetAcknowledge(false);
break;
}
// If the peripheral did not ACK, the command is done. Time for a soft reset.
if (!(this->stat & SIO0_STAT::ACK))
{
this->SoftReset();
}
Interrupt(Sio0Interrupt::TX_DATA_WRITE);
}
void Sio0::SetRxData(u8 value)
{
Sio0Log.WriteLn("%s()\tSIO0 RX_DATA Write\t(%02X)", __FUNCTION__, value);
rxData = value;
}
void Sio0::SetStat(u32 value)
{
Sio0Log.Error("%s()\tSIO0 STAT Write\t(%08X)", __FUNCTION__, value);
}
void Sio0::SetMode(u16 value)
{
Sio0Log.WriteLn("%s()\tSIO0 MODE Write\t(%04X)", __FUNCTION__, value);
mode = value;
}
void Sio0::SetCtrl(u16 value)
{
Sio0Log.WriteLn("%s()\tSIO0 CTRL Write\t(%04X)", __FUNCTION__, value);
ctrl = value;
port = (ctrl & SIO0_CTRL::PORT) > 0;
// CTRL appears to be set to 0 between every "transaction".
// Not documented anywhere, but we'll use this to "reset"
// the SIO0 state, particularly during the annoying probes
// to memcards that occur when a game boots.
if (ctrl == 0)
{
g_MemoryCardProtocol.ResetPS1State();
SoftReset();
}
// If CTRL acknowledge, reset STAT bits 3 and 9
if (ctrl & SIO0_CTRL::ACK)
{
stat &= ~(SIO0_STAT::IRQ | SIO0_STAT::RX_PARITY_ERROR);
}
if (ctrl & SIO0_CTRL::RESET)
{
stat = 0;
ctrl = 0;
mode = 0;
SoftReset();
}
}
void Sio0::SetBaud(u16 value)
{
Sio0Log.WriteLn("%s()\tSIO0 BAUD Write\t(%04X)", __FUNCTION__, value);
baud = value;
}
bool Sio0::IsPadCommand(u8 command)
{
return command >= static_cast<u8>(Pad::Command::MYSTERY) && command <= static_cast<u8>(Pad::Command::RESPONSE_BYTES);
}
bool Sio0::IsMemcardCommand(u8 command)
{
return command == MemcardCommand::PS1_READ || command == MemcardCommand::PS1_STATE || command == MemcardCommand::PS1_WRITE;
}
bool Sio0::IsPocketstationCommand(u8 command)
{
return command == MemcardCommand::PS1_POCKETSTATION;
}
u8 Sio0::Memcard(u8 value)
{
switch (sioCommand)
{
case MemcardCommand::PS1_READ:
return g_MemoryCardProtocol.PS1Read(value);
case MemcardCommand::PS1_STATE:
return g_MemoryCardProtocol.PS1State(value);
case MemcardCommand::PS1_WRITE:
return g_MemoryCardProtocol.PS1Write(value);
case MemcardCommand::PS1_POCKETSTATION:
return g_MemoryCardProtocol.PS1Pocketstation(value);
default:
Console.Error("%s(%02X) Unhandled memcard command (%02X)", __FUNCTION__, value, sioCommand);
SoftReset();
break;
}
return 0xff;
}
bool Sio0::DoState(StateWrapper& sw)
{
if (!sw.DoMarker("Sio0"))
return false;
sw.Do(&txData);
sw.Do(&rxData);
sw.Do(&stat);
sw.Do(&mode);
sw.Do(&ctrl);
sw.Do(&baud);
sw.Do(&flag);
sw.Do(&sioStage);
sw.Do(&sioMode);
sw.Do(&sioCommand);
sw.Do(&padStarted);
sw.Do(&rxDataSet);
sw.Do(&port);
sw.Do(&slot);
return sw.IsGood();
}
+79
View File
@@ -0,0 +1,79 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "SIO/SioTypes.h"
class StateWrapper;
class Sio0
{
private:
u32 txData; // 0x1f801040
u32 rxData; // 0x1f801040
u32 stat; // 0x1f801044
u16 mode; // 0x1f801048
u16 ctrl; // 0x1f80104a
u16 baud; // 0x1f80104e
void ClearStatAcknowledge();
public:
u8 flag = 0;
SioStage sioStage = SioStage::IDLE;
u8 sioMode = SioMode::NOT_SET;
u8 sioCommand = 0;
bool padStarted = false;
bool rxDataSet = false;
u8 port = 0;
u8 slot = 0;
Sio0();
~Sio0();
bool Initialize();
bool Shutdown();
void SoftReset();
bool DoState(StateWrapper& sw);
void SetAcknowledge(bool ack);
void Interrupt(Sio0Interrupt sio0Interrupt);
u8 GetTxData();
u8 GetRxData();
u32 GetStat();
u16 GetMode();
u16 GetCtrl();
u16 GetBaud();
void SetTxData(u8 value);
void SetRxData(u8 value);
void SetStat(u32 value);
void SetMode(u16 value);
void SetCtrl(u16 value);
void SetBaud(u16 value);
bool IsPadCommand(u8 command);
bool IsMemcardCommand(u8 command);
bool IsPocketstationCommand(u8 command);
u8 Memcard(u8 value);
};
extern Sio0 g_Sio0;
+513
View File
@@ -0,0 +1,513 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Common.h"
#include "Host.h"
#include "IopDma.h"
#include "Recording/InputRecording.h"
#include "SIO/Memcard/MemoryCardProtocol.h"
#include "SIO/Multitap/MultitapProtocol.h"
#include "SIO/Pad/Pad.h"
#include "SIO/Pad/PadBase.h"
#include "SIO/Sio.h"
#include "SIO/Sio2.h"
#include "SIO/SioTypes.h"
#include "StateWrapper.h"
#define SIO2LOG_ENABLE 0
#define Sio2Log if (SIO2LOG_ENABLE) DevCon
std::deque<u8> g_Sio2FifoIn;
std::deque<u8> g_Sio2FifoOut;
Sio2 g_Sio2;
Sio2::Sio2() = default;
Sio2::~Sio2() = default;
bool Sio2::Initialize()
{
this->SoftReset();
for (size_t i = 0; i < send3.size(); i++)
{
send3[i] = 0;
}
for (size_t i = 0; i < send1.size(); i++)
{
send1[i] = 0;
send2[i] = 0;
}
dataIn = 0;
dataOut = 0;
SetCtrl(Sio2Ctrl::SIO2MAN_RESET);
SetRecv1(Recv1::DISCONNECTED);
recv2 = Recv2::DEFAULT;
recv3 = Recv3::DEFAULT;
unknown1 = 0;
unknown2 = 0;
iStat = 0;
port = 0;
slot = 0;
while (!g_Sio2FifoOut.empty())
{
g_Sio2FifoOut.pop_front();
}
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 4; j++)
{
mcds[i][j].term = 0x55;
mcds[i][j].port = i;
mcds[i][j].slot = j;
mcds[i][j].FLAG = 0x08;
mcds[i][j].autoEjectTicks = 0;
}
}
mcd = &mcds[0][0];
return true;
}
bool Sio2::Shutdown()
{
return true;
}
void Sio2::SoftReset()
{
send3Read = false;
send3Position = 0;
commandLength = 0;
processedLength = 0;
// Clear dmaBlockSize, in case the next SIO2 command is not sent over DMA11.
dmaBlockSize = 0;
send3Complete = false;
// Anything in g_Sio2FifoIn which was not necessary to consume should be cleared out prior to the next SIO2 cycle.
while (!g_Sio2FifoIn.empty())
{
g_Sio2FifoIn.pop_front();
}
}
void Sio2::Interrupt()
{
iopIntcIrq(17);
}
void Sio2::SetCtrl(u32 value)
{
this->ctrl = value;
if (this->ctrl & Sio2Ctrl::START_TRANSFER)
{
Interrupt();
}
}
void Sio2::SetSend3(size_t position, u32 value)
{
this->send3[position] = value;
if (position == 0)
{
SoftReset();
}
}
void Sio2::SetRecv1(u32 value)
{
this->recv1 = value;
}
void Sio2::Pad()
{
// Send PAD our current port, and get back whatever it says the first response byte should be.
PadBase* pad = Pad::GetPad(port, slot);
// RECV1 is set once per DMA; if any device is present at all, it should be set to connected.
// For now, we will always report connected for pads.
SetRecv1(Recv1::CONNECTED);
g_Sio2FifoOut.push_back(0xff);
// Then for every byte in g_Sio2FifoIn, pass to PAD and see what it kicks back to us.
pad->SoftReset();
while (!g_Sio2FifoIn.empty())
{
const u8 commandByte = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 responseByte = pad->SendCommandByte(commandByte);
g_Sio2FifoOut.push_back(responseByte);
}
}
void Sio2::Multitap()
{
g_Sio2FifoOut.push_back(0x00);
const bool multitapEnabled = (port == 0 && EmuConfig.MultitapPort0_Enabled) || (port == 1 && EmuConfig.MultitapPort1_Enabled);
SetRecv1(multitapEnabled ? Recv1::CONNECTED : Recv1::DISCONNECTED);
if (multitapEnabled)
{
g_MultitapProtocol.SendToMultitap();
}
else
{
while (g_Sio2FifoOut.size() < commandLength)
{
g_Sio2FifoOut.push_back(0x00);
}
}
}
void Sio2::Infrared()
{
SetRecv1(Recv1::DISCONNECTED);
g_Sio2FifoIn.pop_front();
const u8 responseByte = 0xff;
while (g_Sio2FifoOut.size() < commandLength)
{
g_Sio2FifoOut.push_back(responseByte);
}
}
void Sio2::Memcard()
{
mcd = &mcds[port][slot];
// Check if auto ejection is active. If so, set RECV1 to DISCONNECTED,
// and zero out the fifo to simulate dead air over the wire.
if (mcd->autoEjectTicks)
{
SetRecv1(Recv1::DISCONNECTED);
g_Sio2FifoOut.push_back(0x00); // Because Sio2::Write pops the first g_Sio2FifoIn member
while (!g_Sio2FifoIn.empty())
{
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(0x00);
}
mcd->autoEjectTicks--;
if (mcd->autoEjectTicks == 0)
{
Host::AddKeyedOSDMessage(fmt::format("AutoEjectSlotClear{}{}", port, slot),
fmt::format(TRANSLATE_FS("MemoryCard", "Memory card in port {} / slot {} reinserted"),
port + 1,
slot + 1),
Host::OSD_INFO_DURATION);
}
return;
}
SetRecv1(mcd->IsPresent() ? Recv1::CONNECTED : Recv1::DISCONNECTED);
const u8 commandByte = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 responseByte = mcd->IsPresent() ? 0x00 : 0xff;
g_Sio2FifoOut.push_back(responseByte);
// Technically, the FLAG byte is only for PS1 memcards. However,
// since this response byte is still a dud on PS2 memcards, we can
// basically just cheat and always make this our second response byte for memcards.
g_Sio2FifoOut.push_back(mcd->FLAG);
u8 ps1Input = 0;
u8 ps1Output = 0;
switch (commandByte)
{
case MemcardCommand::PROBE:
g_MemoryCardProtocol.Probe();
break;
case MemcardCommand::UNKNOWN_WRITE_DELETE_END:
g_MemoryCardProtocol.UnknownWriteDeleteEnd();
break;
case MemcardCommand::SET_ERASE_SECTOR:
case MemcardCommand::SET_WRITE_SECTOR:
case MemcardCommand::SET_READ_SECTOR:
g_MemoryCardProtocol.SetSector();
break;
case MemcardCommand::GET_SPECS:
g_MemoryCardProtocol.GetSpecs();
break;
case MemcardCommand::SET_TERMINATOR:
g_MemoryCardProtocol.SetTerminator();
break;
case MemcardCommand::GET_TERMINATOR:
g_MemoryCardProtocol.GetTerminator();
break;
case MemcardCommand::WRITE_DATA:
g_MemoryCardProtocol.WriteData();
break;
case MemcardCommand::READ_DATA:
g_MemoryCardProtocol.ReadData();
break;
case MemcardCommand::PS1_READ:
g_MemoryCardProtocol.ResetPS1State();
while (!g_Sio2FifoIn.empty())
{
ps1Input = g_Sio2FifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1Read(ps1Input);
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::PS1_STATE:
g_MemoryCardProtocol.ResetPS1State();
while (!g_Sio2FifoIn.empty())
{
ps1Input = g_Sio2FifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1State(ps1Input);
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::PS1_WRITE:
g_MemoryCardProtocol.ResetPS1State();
while (!g_Sio2FifoIn.empty())
{
ps1Input = g_Sio2FifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1Write(ps1Input);
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::PS1_POCKETSTATION:
g_MemoryCardProtocol.ResetPS1State();
while (!g_Sio2FifoIn.empty())
{
ps1Input = g_Sio2FifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1Pocketstation(ps1Input);
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::READ_WRITE_END:
g_MemoryCardProtocol.ReadWriteEnd();
break;
case MemcardCommand::ERASE_BLOCK:
g_MemoryCardProtocol.EraseBlock();
break;
case MemcardCommand::UNKNOWN_BOOT:
g_MemoryCardProtocol.UnknownBoot();
break;
case MemcardCommand::AUTH_XOR:
g_MemoryCardProtocol.AuthXor();
break;
case MemcardCommand::AUTH_F3:
g_MemoryCardProtocol.AuthF3();
break;
case MemcardCommand::AUTH_F7:
g_MemoryCardProtocol.AuthF7();
break;
default:
Console.Warning("%s() Unhandled memcard command %02X, things are about to break!", __FUNCTION__, commandByte);
break;
}
}
void Sio2::Write(u8 data)
{
Sio2Log.WriteLn("%s(%02X) SIO2 DATA Write", __FUNCTION__, data);
if (!send3Read)
{
// No more SEND3 positions to access, but the game is still sending us SIO2 writes. Lets ignore them.
if (send3Position > send3.size())
{
Console.Warning("%s(%02X) Received data after exhausting all SEND3 values!", __FUNCTION__, data);
return;
}
const u32 currentSend3 = send3[send3Position];
port = currentSend3 & Send3::PORT;
commandLength = (currentSend3 >> 8) & Send3::COMMAND_LENGTH_MASK;
send3Read = true;
// The freshly read SEND3 position had a length of 0, so we are done handling SIO2 commands until
// the next SEND3 writes.
if (commandLength == 0)
{
send3Complete = true;
}
// If the prior command did not need to fully pop g_Sio2FifoIn, do so now,
// so that the next command isn't trying to read the last command's leftovers.
while (!g_Sio2FifoIn.empty())
{
g_Sio2FifoIn.pop_front();
}
}
if (send3Complete)
{
return;
}
g_Sio2FifoIn.push_back(data);
// We have received as many command bytes as we expect, and...
//
// ... These were from direct writes into IOP memory (DMA block size is zero when direct writes occur)
// ... These were from SIO2 DMA (DMA block size is non-zero when SIO2 DMA occurs)
if ((g_Sio2FifoIn.size() == g_Sio2.commandLength && g_Sio2.dmaBlockSize == 0) || g_Sio2FifoIn.size() == g_Sio2.dmaBlockSize)
{
// Go ahead and prep so the next write triggers a load of the new SEND3 value.
g_Sio2.send3Read = false;
g_Sio2.send3Position++;
// Check the SIO mode
const u8 sioMode = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
switch (sioMode)
{
case SioMode::PAD:
this->Pad();
break;
case SioMode::MULTITAP:
this->Multitap();
break;
case SioMode::INFRARED:
this->Infrared();
break;
case SioMode::MEMCARD:
this->Memcard();
break;
default:
Console.Error("%s(%02X) Unhandled SIO mode %02X", __FUNCTION__, data, sioMode);
g_Sio2FifoOut.push_back(0x00);
SetRecv1(Recv1::DISCONNECTED);
break;
}
// If command was sent over SIO2 DMA, align g_Sio2FifoOut to the block size
if (g_Sio2.dmaBlockSize > 0)
{
const size_t dmaDiff = g_Sio2FifoOut.size() % g_Sio2.dmaBlockSize;
if (dmaDiff > 0)
{
const size_t padding = g_Sio2.dmaBlockSize - dmaDiff;
for (size_t i = 0; i < padding; i++)
{
g_Sio2FifoOut.push_back(0x00);
}
}
}
}
}
u8 Sio2::Read()
{
u8 ret = 0x00;
if (!g_Sio2FifoOut.empty())
{
ret = g_Sio2FifoOut.front();
g_Sio2FifoOut.pop_front();
}
else
{
Console.Warning("%s() g_Sio2FifoOut underflow! Returning 0x00.", __FUNCTION__);
}
Sio2Log.WriteLn("%s() SIO2 DATA Read (%02X)", __FUNCTION__, ret);
return ret;
}
bool Sio2::DoState(StateWrapper& sw)
{
if (!sw.DoMarker("Sio2"))
return false;
sw.Do(&send3);
sw.Do(&send1);
sw.Do(&send2);
sw.Do(&dataIn);
sw.Do(&dataOut);
sw.Do(&ctrl);
sw.Do(&recv1);
sw.Do(&recv2);
sw.Do(&recv3);
sw.Do(&unknown1);
sw.Do(&unknown2);
sw.Do(&iStat);
sw.Do(&port);
sw.Do(&slot);
sw.Do(&send3Read);
sw.Do(&send3Position);
sw.Do(&commandLength);
sw.Do(&processedLength);
sw.Do(&dmaBlockSize);
sw.Do(&send3Complete);
sw.Do(&g_Sio2FifoIn);
sw.Do(&g_Sio2FifoOut);
// CRCs for memory cards.
// If the memory card hasn't changed when loading state, we can safely skip ejecting it.
u64 mcdCrcs[SIO::PORTS][SIO::SLOTS];
if (sw.IsWriting())
{
for (u32 port = 0; port < SIO::PORTS; port++)
{
for (u32 slot = 0; slot < SIO::SLOTS; slot++)
mcdCrcs[port][slot] = mcds[port][slot].GetChecksum();
}
}
sw.DoBytes(mcdCrcs, sizeof(mcdCrcs));
if (sw.IsReading())
{
bool ejected = false;
for (u32 port = 0; port < SIO::PORTS && !ejected; port++)
{
for (u32 slot = 0; slot < SIO::SLOTS; slot++)
{
if (mcdCrcs[port][slot] != mcds[port][slot].GetChecksum())
{
AutoEject::SetAll();
ejected = true;
break;
}
}
}
}
return sw.IsGood();
}
+84
View File
@@ -0,0 +1,84 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <array>
class StateWrapper;
class Sio2
{
public:
std::array<u32, 16> send3; // 0x1f808200 - 0x1f80823f
// SEND1 and SEND2 are an unusual bunch. It's not entirely clear just from
// documentation but these registers almost seem like they are the same thing;
// when bit 2 is set, SEND2 is being read/written. When bit 2 isn't set, it is
// SEND1. Their use is not really known, either.
std::array<u32, 4> send1; // 0x1f808240 - 0x1f80825f
std::array<u32, 4> send2; // 0x1f808240 - 0x1f80825f
u32 dataIn; // 0x1f808260
u32 dataOut; // 0x1f808264
u32 ctrl; // 0x1f808268
u32 recv1; // 0x1f80826c
u32 recv2; // 0x1f808270
u32 recv3; // 0x1f808274
u32 unknown1; // 0x1f808278
u32 unknown2; // 0x1f80827c
u32 iStat; // 0x1f808280
u8 port = 0;
u8 slot = 0;
// The current working index of SEND3. The SEND3 register is a 16 position
// array of command descriptors. Each descriptor describes the port the command
// is targeting, as well as the length of the command in bytes.
bool send3Read = false;
size_t send3Position = 0;
size_t commandLength = 0;
size_t processedLength = 0;
// Tracks the size of a single block of DMA11/DMA12 data. psxDma11 will set this prior
// to doing writes, and Sio2::SetSend3 will clear this to ensure a non-DMA write into SIO2
// does not accidentally use dmaBlockSize.
size_t dmaBlockSize = 0;
bool send3Complete = false;
Sio2();
~Sio2();
bool Initialize();
bool Shutdown();
void SoftReset();
bool DoState(StateWrapper& sw);
void Interrupt();
void SetCtrl(u32 value);
void SetSend3(size_t position, u32 value);
void SetRecv1(u32 value);
void Pad();
void Multitap();
void Infrared();
void Memcard();
void Write(u8 data);
u8 Read();
};
extern std::deque<u8> g_Sio2FifoIn;
extern std::deque<u8> g_Sio2FifoOut;
extern Sio2 g_Sio2;
+3 -1
View File
@@ -1,5 +1,5 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
@@ -31,6 +31,7 @@ namespace SioMode
static constexpr u8 MEMCARD = 0x81;
} // namespace SioMode
/*
namespace PadCommand
{
static constexpr u8 UNK_0 = 0x40;
@@ -50,6 +51,7 @@ namespace PadCommand
static constexpr u8 UNK_E = 0x4e;
static constexpr u8 ANALOG = 0x4f;
} // namespace PadCommand
*/
namespace MemcardCommand
{
+41 -6
View File
@@ -28,9 +28,11 @@
#include "Host.h"
#include "MTGS.h"
#include "MTVU.h"
#include "PAD/Host/PAD.h"
#include "SIO/Pad/Pad.h"
#include "Patch.h"
#include "R3000A.h"
#include "SIO/Sio0.h"
#include "SIO/Sio2.h"
#include "SPU2/spu2.h"
#include "SaveState.h"
#include "StateWrapper.h"
@@ -237,8 +239,42 @@ bool SaveStateBase::FreezeInternals()
FreezeMem(iopMem->Sif, sizeof(iopMem->Sif)); // iop's sif memory (not really needed, but oh well)
okay = okay && psxRcntFreeze();
okay = okay && sioFreeze();
okay = okay && sio2Freeze();
// TODO: move all the others over to StateWrapper too...
if (!okay)
return false;
{
// This is horrible. We need to move the rest over...
std::optional<StateWrapper::VectorMemoryStream> save_stream;
std::optional<StateWrapper::ReadOnlyMemoryStream> load_stream;
if (IsSaving())
save_stream.emplace();
else
load_stream.emplace(&m_memory[m_idx], static_cast<int>(m_memory.size()) - m_idx);
StateWrapper sw(IsSaving() ? static_cast<StateWrapper::IStream*>(&save_stream.value()) :
static_cast<StateWrapper::IStream*>(&load_stream.value()),
IsSaving() ? StateWrapper::Mode::Write : StateWrapper::Mode::Read, g_SaveVersion);
okay = okay && g_Sio0.DoState(sw);
okay = okay && g_Sio2.DoState(sw);
if (!okay || !sw.IsGood())
return false;
if (IsSaving())
{
FreezeMem(const_cast<u8*>(save_stream->GetBuffer().data()), save_stream->GetPosition());
}
else
{
const int new_idx = m_idx + static_cast<int>(load_stream->GetPosition());
if (static_cast<size_t>(new_idx) >= m_memory.size())
return false;
m_idx = new_idx;
}
}
okay = okay && cdrFreeze();
okay = okay && cdvdFreeze();
@@ -315,7 +351,6 @@ static int SysState_MTGSFreeze(FreezeAction mode, freezeData* fP)
}
static constexpr SysState_Component SPU2_{ "SPU2", SPU2freeze };
static constexpr SysState_Component PAD_{ "PAD", PADfreeze };
static constexpr SysState_Component GS{ "GS", SysState_MTGSFreeze };
static bool SysState_ComponentFreezeIn(zip_file_t* zf, SysState_Component comp)
@@ -600,8 +635,8 @@ public:
~SavestateEntry_PAD() override = default;
const char* GetFilename() const override { return "PAD.bin"; }
bool FreezeIn(zip_file_t* zf) const override { return SysState_ComponentFreezeIn(zf, PAD_); }
bool FreezeOut(SaveStateBase& writer) const override { return SysState_ComponentFreezeOut(writer, PAD_); }
bool FreezeIn(zip_file_t* zf) const override { return SysState_ComponentFreezeInNew(zf, "PAD", &Pad::Freeze); }
bool FreezeOut(SaveStateBase& writer) const override { return SysState_ComponentFreezeOutNew(writer, "PAD", 16 * 1024, &Pad::Freeze); }
bool IsRequired() const override { return true; }
};
+1 -3
View File
@@ -37,7 +37,7 @@ enum class FreezeAction
// [SAVEVERSION+]
// This informs the auto updater that the users savestates will be invalidated.
static const u32 g_SaveVersion = (0x9A37 << 16) | 0x0000;
static const u32 g_SaveVersion = (0x9A3B << 16) | 0x0000;
// the freezing data between submodules and core
@@ -221,8 +221,6 @@ protected:
bool cdrFreeze();
bool cdvdFreeze();
bool psxRcntFreeze();
bool sio2Freeze();
bool deci2Freeze();
// Save or load PCSX2's global frame counter (g_FrameCount) along with each savestate
-970
View File
@@ -1,970 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "R3000A.h"
#include "IopHw.h"
#include "IopDma.h"
#include "Common.h"
#include "Sio.h"
#include "MemoryCardProtocol.h"
#include "MultitapProtocol.h"
#include "Config.h"
#include "Host.h"
#include "PAD/Host/PAD.h"
#include "common/Timer.h"
#include "Recording/InputRecording.h"
#include "IconsFontAwesome5.h"
#define SIO0LOG_ENABLE 0
#define SIO2LOG_ENABLE 0
#define Sio0Log if (SIO0LOG_ENABLE) DevCon
#define Sio2Log if (SIO2LOG_ENABLE) DevCon
std::deque<u8> fifoIn;
std::deque<u8> fifoOut;
Sio0 sio0;
Sio2 sio2;
_mcd mcds[2][4];
_mcd *mcd;
// ============================================================================
// SIO0
// ============================================================================
void Sio0::ClearStatAcknowledge()
{
stat &= ~(SIO0_STAT::ACK);
}
Sio0::Sio0()
{
this->FullReset();
}
Sio0::~Sio0() = default;
void Sio0::SoftReset()
{
padStarted = false;
sioMode = SioMode::NOT_SET;
sioCommand = 0;
sioStage = SioStage::IDLE;
}
void Sio0::FullReset()
{
SoftReset();
port = 0;
slot = 0;
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 4; j++)
{
mcds[i][j].term = 0x55;
mcds[i][j].port = i;
mcds[i][j].slot = j;
mcds[i][j].FLAG = 0x08;
mcds[i][j].autoEjectTicks = 0;
}
}
mcd = &mcds[0][0];
}
// Simulates the ACK line on the bus. Peripherals are expected to send an ACK signal
// over this line to tell the PS1 "keep sending me things I'm not done yet". The PS1
// then uses this after it receives the peripheral's response to decide what to do.
void Sio0::Acknowledge()
{
stat |= SIO0_STAT::ACK;
}
void Sio0::Interrupt(Sio0Interrupt sio0Interrupt)
{
switch (sio0Interrupt)
{
case Sio0Interrupt::TEST_EVENT:
iopIntcIrq(7);
break;
case Sio0Interrupt::STAT_READ:
ClearStatAcknowledge();
break;
case Sio0Interrupt::TX_DATA_WRITE:
break;
default:
Console.Error("%s(%d) Invalid parameter", __FUNCTION__, sio0Interrupt);
assert(false);
break;
}
if (!(psxRegs.interrupt & (1 << IopEvt_SIO)))
{
PSX_INT(IopEvt_SIO, PSXCLK / 250000); // PSXCLK/250000);
}
}
u8 Sio0::GetTxData()
{
Sio0Log.WriteLn("%s() SIO0 TX_DATA Read (%02X)", __FUNCTION__, txData);
return txData;
}
u8 Sio0::GetRxData()
{
Sio0Log.WriteLn("%s() SIO0 RX_DATA Read (%02X)", __FUNCTION__, rxData);
stat |= (SIO0_STAT::TX_READY | SIO0_STAT::TX_EMPTY);
stat &= ~(SIO0_STAT::RX_FIFO_NOT_EMPTY);
return rxData;
}
u32 Sio0::GetStat()
{
Sio0Log.WriteLn("%s() SIO0 STAT Read (%08X)", __FUNCTION__, stat);
const u32 ret = stat;
Interrupt(Sio0Interrupt::STAT_READ);
return ret;
}
u16 Sio0::GetMode()
{
Sio0Log.WriteLn("%s() SIO0 MODE Read (%08X)", __FUNCTION__, mode);
return mode;
}
u16 Sio0::GetCtrl()
{
Sio0Log.WriteLn("%s() SIO0 CTRL Read (%08X)", __FUNCTION__, ctrl);
return ctrl;
}
u16 Sio0::GetBaud()
{
Sio0Log.WriteLn("%s() SIO0 BAUD Read (%08X)", __FUNCTION__, baud);
return baud;
}
void Sio0::SetTxData(u8 value)
{
Sio0Log.WriteLn("%s(%02X) SIO0 TX_DATA Write", __FUNCTION__, value);
stat |= SIO0_STAT::TX_READY | SIO0_STAT::TX_EMPTY;
stat |= (SIO0_STAT::RX_FIFO_NOT_EMPTY);
if (!(ctrl & SIO0_CTRL::TX_ENABLE))
{
Console.Warning("%s(%02X) CTRL in illegal state, exiting instantly", __FUNCTION__, value);
return;
}
txData = value;
u8 res = 0;
switch (sioStage)
{
case SioStage::IDLE:
sioMode = value;
stat |= SIO0_STAT::TX_READY;
switch (sioMode)
{
case SioMode::PAD:
res = PADstartPoll(port, slot);
if (res)
{
Acknowledge();
}
break;
case SioMode::MEMCARD:
mcd = &mcds[port][slot];
// Check if auto ejection is active. If so, set RECV1 to DISCONNECTED,
// and zero out the fifo to simulate dead air over the wire.
if (mcd->autoEjectTicks)
{
SetRxData(0x00);
return;
}
// If memcard is missing, not PS1, or auto ejected, do not let SIO0 stage advance,
// reply with dead air and no ACK.
if (!mcd->IsPresent() || !mcd->IsPSX())
{
SetRxData(0x00);
return;
}
Acknowledge();
break;
}
SetRxData(res);
sioStage = SioStage::WAITING_COMMAND;
break;
case SioStage::WAITING_COMMAND:
stat &= ~(SIO0_STAT::TX_READY);
if (IsPadCommand(value))
{
res = PADpoll(value);
SetRxData(res);
if (!PADcomplete())
{
Acknowledge();
}
sioStage = SioStage::WORKING;
}
else if (IsMemcardCommand(value))
{
SetRxData(flag);
Acknowledge();
sioCommand = value;
sioStage = SioStage::WORKING;
}
else if (IsPocketstationCommand(value))
{
// Set the line low, no acknowledge.
SetRxData(0x00);
sioStage = SioStage::IDLE;
}
else
{
Console.Error("%s(%02X) Bad SIO command", __FUNCTION__, value);
SetRxData(0xff);
SoftReset();
}
break;
case SioStage::WORKING:
switch (sioMode)
{
case SioMode::PAD:
res = PADpoll(value);
SetRxData(res);
if (!PADcomplete())
{
Acknowledge();
}
break;
case SioMode::MEMCARD:
SetRxData(Memcard(value));
break;
default:
Console.Error("%s(%02X) Unhandled SioMode: %02X", __FUNCTION__, value, sioMode);
SetRxData(0xff);
SoftReset();
break;
}
break;
default:
Console.Error("%s(%02X) Unhandled SioStage: %02X", __FUNCTION__, value, static_cast<u8>(sioStage));
SetRxData(0xff);
SoftReset();
break;
}
Interrupt(Sio0Interrupt::TX_DATA_WRITE);
}
void Sio0::SetRxData(u8 value)
{
Sio0Log.WriteLn("%s(%02X) SIO0 RX_DATA Write", __FUNCTION__, value);
rxData = value;
}
void Sio0::SetStat(u32 value)
{
Sio0Log.Error("%s(%08X) SIO0 STAT Write", __FUNCTION__, value);
}
void Sio0::SetMode(u16 value)
{
Sio0Log.WriteLn("%s(%04X) SIO0 MODE Write", __FUNCTION__, value);
mode = value;
}
void Sio0::SetCtrl(u16 value)
{
Sio0Log.WriteLn("%s(%04X) SIO0 CTRL Write", __FUNCTION__, value);
ctrl = value;
port = (ctrl & SIO0_CTRL::PORT) > 0;
// CTRL appears to be set to 0 between every "transaction".
// Not documented anywhere, but we'll use this to "reset"
// the SIO0 state, particularly during the annoying probes
// to memcards that occur when a game boots.
if (ctrl == 0)
{
g_MemoryCardProtocol.ResetPS1State();
SoftReset();
}
// If CTRL acknowledge, reset STAT bits 3 and 9
if (ctrl & SIO0_CTRL::ACK)
{
stat &= ~(SIO0_STAT::IRQ | SIO0_STAT::RX_PARITY_ERROR);
}
if (ctrl & SIO0_CTRL::RESET)
{
stat = 0;
ctrl = 0;
mode = 0;
SoftReset();
}
}
void Sio0::SetBaud(u16 value)
{
Sio0Log.WriteLn("%s(%04X) SIO0 BAUD Write", __FUNCTION__, value);
baud = value;
}
bool Sio0::IsPadCommand(u8 command)
{
return command >= PadCommand::UNK_0 && command <= PadCommand::ANALOG;
}
bool Sio0::IsMemcardCommand(u8 command)
{
return command == MemcardCommand::PS1_READ || command == MemcardCommand::PS1_STATE || command == MemcardCommand::PS1_WRITE;
}
bool Sio0::IsPocketstationCommand(u8 command)
{
return command == MemcardCommand::PS1_POCKETSTATION;
}
u8 Sio0::Pad(u8 value)
{
if (PADcomplete())
{
padStarted = false;
}
else if (!padStarted)
{
padStarted = true;
PADstartPoll(port, slot);
Acknowledge();
}
return PADpoll(value);
}
u8 Sio0::Memcard(u8 value)
{
switch (sioCommand)
{
case MemcardCommand::PS1_READ:
return g_MemoryCardProtocol.PS1Read(value);
case MemcardCommand::PS1_STATE:
return g_MemoryCardProtocol.PS1State(value);
case MemcardCommand::PS1_WRITE:
return g_MemoryCardProtocol.PS1Write(value);
case MemcardCommand::PS1_POCKETSTATION:
return g_MemoryCardProtocol.PS1Pocketstation(value);
default:
Console.Error("%s(%02X) Unhandled memcard command (%02X)", __FUNCTION__, value, sioCommand);
SoftReset();
break;
}
return 0xff;
}
// ============================================================================
// SIO2
// ============================================================================
Sio2::Sio2()
{
this->FullReset();
}
Sio2::~Sio2() = default;
void Sio2::SoftReset()
{
send3Read = false;
send3Position = 0;
commandLength = 0;
processedLength = 0;
// Clear dmaBlockSize, in case the next SIO2 command is not sent over DMA11.
dmaBlockSize = 0;
send3Complete = false;
// Anything in fifoIn which was not necessary to consume should be cleared out prior to the next SIO2 cycle.
while (!fifoIn.empty())
{
fifoIn.pop_front();
}
}
void Sio2::FullReset()
{
this->SoftReset();
for (size_t i = 0; i < send3.size(); i++)
{
send3.at(i) = 0;
}
for (size_t i = 0; i < send1.size(); i++)
{
send1.at(i) = 0;
send2.at(i) = 0;
}
dataIn = 0;
dataOut = 0;
SetCtrl(Sio2Ctrl::SIO2MAN_RESET);
SetRecv1(Recv1::DISCONNECTED);
recv2 = Recv2::DEFAULT;
recv3 = Recv3::DEFAULT;
unknown1 = 0;
unknown2 = 0;
iStat = 0;
port = 0;
slot = 0;
while (!fifoOut.empty())
{
fifoOut.pop_front();
}
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 4; j++)
{
mcds[i][j].term = 0x55;
mcds[i][j].port = i;
mcds[i][j].slot = j;
mcds[i][j].FLAG = 0x08;
mcds[i][j].autoEjectTicks = 0;
}
}
mcd = &mcds[0][0];
}
void Sio2::Interrupt()
{
iopIntcIrq(17);
}
void Sio2::SetCtrl(u32 value)
{
this->ctrl = value;
if (this->ctrl & Sio2Ctrl::START_TRANSFER)
{
Interrupt();
}
}
void Sio2::SetSend3(size_t position, u32 value)
{
this->send3.at(position) = value;
if (position == 0)
{
SoftReset();
}
}
void Sio2::SetRecv1(u32 value)
{
this->recv1 = value;
}
void Sio2::Pad()
{
// Send PAD our current port, and get back whatever it says the first response byte should be.
const u8 firstResponseByte = PADstartPoll(port, slot);
fifoOut.push_back(firstResponseByte);
// Some games will refuse to read ALL pads, if RECV1 is not set to the CONNECTED value when ANY pad is polled,
// REGARDLESS of whether that pad is truly connected or not.
SetRecv1(Recv1::CONNECTED);
// Then for every byte in fifoIn, pass to PAD and see what it kicks back to us.
while (!fifoIn.empty())
{
const u8 commandByte = fifoIn.front();
fifoIn.pop_front();
const u8 responseByte = PADpoll(commandByte);
fifoOut.push_back(responseByte);
}
}
void Sio2::Multitap()
{
fifoOut.push_back(0x00);
const bool multitapEnabled = (port == 0 && EmuConfig.MultitapPort0_Enabled) || (port == 1 && EmuConfig.MultitapPort1_Enabled);
SetRecv1(multitapEnabled ? Recv1::CONNECTED : Recv1::DISCONNECTED);
if (multitapEnabled)
{
g_MultitapProtocol.SendToMultitap();
}
else
{
while (fifoOut.size() < commandLength)
{
fifoOut.push_back(0x00);
}
}
}
void Sio2::Infrared()
{
SetRecv1(Recv1::DISCONNECTED);
fifoIn.pop_front();
const u8 responseByte = 0xff;
while (fifoOut.size() < commandLength)
{
fifoOut.push_back(responseByte);
}
}
void Sio2::Memcard()
{
mcd = &mcds[port][slot];
// Check if auto ejection is active. If so, set RECV1 to DISCONNECTED,
// and zero out the fifo to simulate dead air over the wire.
if (mcd->autoEjectTicks)
{
SetRecv1(Recv1::DISCONNECTED);
fifoOut.push_back(0x00); // Because Sio2::Write pops the first fifoIn member
while (!fifoIn.empty())
{
fifoIn.pop_front();
fifoOut.push_back(0x00);
}
return;
}
SetRecv1(mcd->IsPresent() ? Recv1::CONNECTED : Recv1::DISCONNECTED);
const u8 commandByte = fifoIn.front();
fifoIn.pop_front();
const u8 responseByte = mcd->IsPresent() ? 0x00 : 0xff;
fifoOut.push_back(responseByte);
// Technically, the FLAG byte is only for PS1 memcards. However,
// since this response byte is still a dud on PS2 memcards, we can
// basically just cheat and always make this our second response byte for memcards.
fifoOut.push_back(mcd->FLAG);
u8 ps1Input = 0;
u8 ps1Output = 0;
switch (commandByte)
{
case MemcardCommand::PROBE:
g_MemoryCardProtocol.Probe();
break;
case MemcardCommand::UNKNOWN_WRITE_DELETE_END:
g_MemoryCardProtocol.UnknownWriteDeleteEnd();
break;
case MemcardCommand::SET_ERASE_SECTOR:
case MemcardCommand::SET_WRITE_SECTOR:
case MemcardCommand::SET_READ_SECTOR:
g_MemoryCardProtocol.SetSector();
break;
case MemcardCommand::GET_SPECS:
g_MemoryCardProtocol.GetSpecs();
break;
case MemcardCommand::SET_TERMINATOR:
g_MemoryCardProtocol.SetTerminator();
break;
case MemcardCommand::GET_TERMINATOR:
g_MemoryCardProtocol.GetTerminator();
break;
case MemcardCommand::WRITE_DATA:
g_MemoryCardProtocol.WriteData();
break;
case MemcardCommand::READ_DATA:
g_MemoryCardProtocol.ReadData();
break;
case MemcardCommand::PS1_READ:
g_MemoryCardProtocol.ResetPS1State();
while (!fifoIn.empty())
{
ps1Input = fifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1Read(ps1Input);
fifoIn.pop_front();
fifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::PS1_STATE:
g_MemoryCardProtocol.ResetPS1State();
while (!fifoIn.empty())
{
ps1Input = fifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1State(ps1Input);
fifoIn.pop_front();
fifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::PS1_WRITE:
g_MemoryCardProtocol.ResetPS1State();
while (!fifoIn.empty())
{
ps1Input = fifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1Write(ps1Input);
fifoIn.pop_front();
fifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::PS1_POCKETSTATION:
g_MemoryCardProtocol.ResetPS1State();
while (!fifoIn.empty())
{
ps1Input = fifoIn.front();
ps1Output = g_MemoryCardProtocol.PS1Pocketstation(ps1Input);
fifoIn.pop_front();
fifoOut.push_back(ps1Output);
}
break;
case MemcardCommand::READ_WRITE_END:
g_MemoryCardProtocol.ReadWriteEnd();
break;
case MemcardCommand::ERASE_BLOCK:
g_MemoryCardProtocol.EraseBlock();
break;
case MemcardCommand::UNKNOWN_BOOT:
g_MemoryCardProtocol.UnknownBoot();
break;
case MemcardCommand::AUTH_XOR:
g_MemoryCardProtocol.AuthXor();
break;
case MemcardCommand::AUTH_F3:
g_MemoryCardProtocol.AuthF3();
break;
case MemcardCommand::AUTH_F7:
g_MemoryCardProtocol.AuthF7();
break;
default:
Console.Warning("%s() Unhandled memcard command %02X, things are about to break!", __FUNCTION__, commandByte);
break;
}
}
void Sio2::Write(u8 data)
{
Sio2Log.WriteLn("%s(%02X) SIO2 DATA Write", __FUNCTION__, data);
if (!send3Read)
{
// No more SEND3 positions to access, but the game is still sending us SIO2 writes. Lets ignore them.
if (send3Position > send3.size())
{
Console.Warning("%s(%02X) Received data after exhausting all SEND3 values!", __FUNCTION__, data);
return;
}
const u32 currentSend3 = send3.at(send3Position);
port = currentSend3 & Send3::PORT;
commandLength = (currentSend3 >> 8) & Send3::COMMAND_LENGTH_MASK;
send3Read = true;
// The freshly read SEND3 position had a length of 0, so we are done handling SIO2 commands until
// the next SEND3 writes.
if (commandLength == 0)
{
send3Complete = true;
}
// If the prior command did not need to fully pop fifoIn, do so now,
// so that the next command isn't trying to read the last command's leftovers.
while (!fifoIn.empty())
{
fifoIn.pop_front();
}
}
if (send3Complete)
{
return;
}
fifoIn.push_back(data);
// We have received as many command bytes as we expect, and...
//
// ... These were from direct writes into IOP memory (DMA block size is zero when direct writes occur)
// ... These were from SIO2 DMA (DMA block size is non-zero when SIO2 DMA occurs)
if ((fifoIn.size() == sio2.commandLength && sio2.dmaBlockSize == 0) || fifoIn.size() == sio2.dmaBlockSize)
{
// Go ahead and prep so the next write triggers a load of the new SEND3 value.
sio2.send3Read = false;
sio2.send3Position++;
// Check the SIO mode
const u8 sioMode = fifoIn.front();
fifoIn.pop_front();
switch (sioMode)
{
case SioMode::PAD:
this->Pad();
break;
case SioMode::MULTITAP:
this->Multitap();
break;
case SioMode::INFRARED:
this->Infrared();
break;
case SioMode::MEMCARD:
this->Memcard();
break;
default:
Console.Error("%s(%02X) Unhandled SIO mode %02X", __FUNCTION__, data, sioMode);
fifoOut.push_back(0x00);
SetRecv1(Recv1::DISCONNECTED);
break;
}
// If command was sent over SIO2 DMA, align fifoOut to the block size
if (sio2.dmaBlockSize > 0)
{
const size_t dmaDiff = fifoOut.size() % sio2.dmaBlockSize;
if (dmaDiff > 0)
{
const size_t padding = sio2.dmaBlockSize - dmaDiff;
for (size_t i = 0; i < padding; i++)
{
fifoOut.push_back(0x00);
}
}
}
}
}
u8 Sio2::Read()
{
u8 ret = 0x00;
if (!fifoOut.empty())
{
ret = fifoOut.front();
fifoOut.pop_front();
}
else
{
Console.Warning("%s() fifoOut underflow! Returning 0x00.", __FUNCTION__);
}
Sio2Log.WriteLn("%s() SIO2 DATA Read (%02X)", __FUNCTION__, ret);
return ret;
}
void sioNextFrame() {
for ( uint port = 0; port < 2; ++port ) {
for ( uint slot = 0; slot < 4; ++slot ) {
mcds[port][slot].NextFrame();
}
}
}
void sioSetGameSerial( const std::string& serial ) {
for ( uint port = 0; port < 2; ++port ) {
for ( uint slot = 0; slot < 4; ++slot ) {
if ( int index = mcds[port][slot].ReIndex( serial ) >= 0 ) {
Console.WriteLn("Ejecting Memory Card %u (port %u slot %u) due to source change. Reinsert in 1 second.", index, port, slot);
AutoEject::Set( port, slot );
}
}
}
}
bool SaveStateBase::sio2Freeze()
{
if (!FreezeTag("sio2"))
return false;
Freeze(sio2);
FreezeDeque(fifoIn);
FreezeDeque(fifoOut);
if (!IsOkay())
return false;
// CRCs for memory cards.
// If the memory card hasn't changed when loading state, we can safely skip ejecting it.
u64 mcdCrcs[SIO::PORTS][SIO::SLOTS];
if (IsSaving())
{
for (u32 port = 0; port < SIO::PORTS; port++)
{
for (u32 slot = 0; slot < SIO::SLOTS; slot++)
mcdCrcs[port][slot] = mcds[port][slot].GetChecksum();
}
}
Freeze(mcdCrcs);
if (!IsOkay())
return false;
if (IsLoading())
{
bool ejected = false;
for (u32 port = 0; port < SIO::PORTS && !ejected; port++)
{
for (u32 slot = 0; slot < SIO::SLOTS; slot++)
{
if (mcdCrcs[port][slot] != mcds[port][slot].GetChecksum())
{
AutoEject::SetAll();
ejected = true;
break;
}
}
}
}
return true;
}
bool SaveStateBase::sioFreeze()
{
if (!FreezeTag("sio0"))
return false;
Freeze(sio0);
return IsOkay();
}
std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index)
{
if (index > 4) // [5,6,7]
return std::make_tuple(1, index - 4); // 2B,2C,2D
else if (index > 1) // [2,3,4]
return std::make_tuple(0, index - 1); // 1B,1C,1D
else // [0,1]
return std::make_tuple(index, 0); // 1A,2A
}
u32 sioConvertPortAndSlotToPad(u32 port, u32 slot)
{
if (slot == 0)
return port;
else if (port == 0) // slot=[0,1]
return slot + 1; // 2,3,4
else
return slot + 4; // 5,6,7
}
bool sioPadIsMultitapSlot(u32 index)
{
return (index >= 2);
}
bool sioPortAndSlotIsMultitap(u32 port, u32 slot)
{
return (slot != 0);
}
void AutoEject::CountDownTicks()
{
bool reinserted = false;
for (size_t port = 0; port < SIO::PORTS; port++)
{
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
{
if (mcds[port][slot].autoEjectTicks > 0)
{
if (--mcds[port][slot].autoEjectTicks == 0)
reinserted |= EmuConfig.Mcd[sioConvertPortAndSlotToPad(port, slot)].Enabled;
}
}
}
if (reinserted)
{
Host::AddIconOSDMessage("AutoEjectAllSet", ICON_FA_SD_CARD,
TRANSLATE_SV("MemoryCard", "Memory Cards reinserted."), Host::OSD_INFO_DURATION);
}
}
void AutoEject::Set(size_t port, size_t slot)
{
if (EmuConfig.McdEnableEjection && mcds[port][slot].autoEjectTicks == 0)
{
mcds[port][slot].autoEjectTicks = 1; // 1 second is enough.
mcds[port][slot].term = 0x55; // Reset terminator to default (0x55), forces the PS2 to recheck the memcard.
}
}
void AutoEject::Clear(size_t port, size_t slot)
{
mcds[port][slot].autoEjectTicks = 0;
}
void AutoEject::SetAll()
{
Host::AddIconOSDMessage("AutoEjectAllSet", ICON_FA_SD_CARD,
TRANSLATE_SV("MemoryCard", "Force ejecting all Memory Cards. Reinserting in 1 second."), Host::OSD_INFO_DURATION);
for (size_t port = 0; port < SIO::PORTS; port++)
{
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
{
AutoEject::Set(port, slot);
}
}
}
void AutoEject::ClearAll()
{
for (size_t port = 0; port < SIO::PORTS; port++)
{
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
{
AutoEject::Clear(port, slot);
}
}
}
-251
View File
@@ -1,251 +0,0 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
// Huge thanks to PSI for his work reversing the PS2, his documentation on SIO2 pretty much saved
// this entire implementation. https://psi-rockin.github.io/ps2tek/#sio2registers
#pragma once
#include "SioTypes.h"
#include "MemoryCardFile.h"
#include <array>
#include <deque>
struct _mcd
{
u8 currentCommand;
u8 term; // terminator value;
bool goodSector; // xor sector check
u8 msb;
u8 lsb;
u32 sectorAddr; // read/write sector address
u32 transferAddr; // Transfer address
std::vector<u8> buf; // Buffer for reading and writing
u8 FLAG; // for PSX;
u8 port; // port
u8 slot; // and slot for this memcard
size_t autoEjectTicks;
void GetSizeInfo(McdSizeInfo &info)
{
FileMcd_GetSizeInfo(port, slot, &info);
}
bool IsPSX()
{
return FileMcd_IsPSX(port, slot);
}
void EraseBlock()
{
//DevCon.WriteLn("Memcard Erase (sectorAddr = %08X)", sectorAddr);
FileMcd_EraseBlock(port, slot, transferAddr);
}
// Read from memorycard to dest
void Read(u8 *dest, int size)
{
//DevCon.WriteLn("Memcard Read (sectorAddr = %08X)", sectorAddr);
FileMcd_Read(port, slot, dest, transferAddr, size);
}
// Write to memorycard from src
void Write(u8 *src, int size)
{
//DevCon.WriteLn("Memcard Write (sectorAddr = %08X)", sectorAddr);
FileMcd_Save(port, slot, src,transferAddr, size);
}
bool IsPresent()
{
return FileMcd_IsPresent(port, slot);
}
u8 DoXor()
{
u8 ret = msb ^ lsb;
for (const u8 byte : buf)
{
ret ^= byte;
}
return ret;
}
u64 GetChecksum()
{
return FileMcd_GetCRC(port, slot);
}
void NextFrame() {
FileMcd_NextFrame( port, slot );
}
int ReIndex(const std::string& filter) {
return FileMcd_ReIndex(port, slot, filter);
}
};
class Sio0
{
private:
u32 txData; // 0x1f801040
u32 rxData; // 0x1f801040
u32 stat; // 0x1f801044
u16 mode; // 0x1f801048
u16 ctrl; // 0x1f80104a
u16 baud; // 0x1f80104e
void ClearStatAcknowledge();
public:
u8 flag = 0;
SioStage sioStage = SioStage::IDLE;
u8 sioMode = SioMode::NOT_SET;
u8 sioCommand = 0;
bool padStarted = false;
bool rxDataSet = false;
u8 port = 0;
u8 slot = 0;
Sio0();
~Sio0();
void SoftReset();
void FullReset();
void Acknowledge();
void Interrupt(Sio0Interrupt sio0Interrupt);
u8 GetTxData();
u8 GetRxData();
u32 GetStat();
u16 GetMode();
u16 GetCtrl();
u16 GetBaud();
void SetTxData(u8 value);
void SetRxData(u8 value);
void SetStat(u32 value);
void SetMode(u16 value);
void SetCtrl(u16 value);
void SetBaud(u16 value);
bool IsPadCommand(u8 command);
bool IsMemcardCommand(u8 command);
bool IsPocketstationCommand(u8 command);
u8 Pad(u8 value);
u8 Memcard(u8 value);
};
class Sio2
{
private:
void UpdateInputRecording(u8& dataIn, u8& dataOut);
public:
std::array<u32, 16> send3; // 0x1f808200 - 0x1f80823f
// SEND1 and SEND2 are an unusual bunch. It's not entirely clear just from
// documentation but these registers almost seem like they are the same thing;
// when bit 2 is set, SEND2 is being read/written. When bit 2 isn't set, it is
// SEND1. Their use is not really known, either.
std::array<u32, 4> send1; // 0x1f808240 - 0x1f80825f
std::array<u32, 4> send2; // 0x1f808240 - 0x1f80825f
u32 dataIn; // 0x1f808260
u32 dataOut; // 0x1f808264
u32 ctrl; // 0x1f808268
u32 recv1; // 0x1f80826c
u32 recv2; // 0x1f808270
u32 recv3; // 0x1f808274
u32 unknown1; // 0x1f808278
u32 unknown2; // 0x1f80827c
u32 iStat; // 0x1f808280
u8 port = 0;
u8 slot = 0;
// The current working index of SEND3. The SEND3 register is a 16 position
// array of command descriptors. Each descriptor describes the port the command
// is targeting, as well as the length of the command in bytes.
bool send3Read = false;
size_t send3Position = 0;
size_t commandLength = 0;
size_t processedLength = 0;
// Tracks the size of a single block of DMA11/DMA12 data. psxDma11 will set this prior
// to doing writes, and Sio2::SetSend3 will clear this to ensure a non-DMA write into SIO2
// does not accidentally use dmaBlockSize.
size_t dmaBlockSize = 0;
bool send3Complete = false;
Sio2();
~Sio2();
void SoftReset();
void FullReset();
void Interrupt();
void SetCtrl(u32 value);
void SetSend3(size_t position, u32 value);
void SetRecv1(u32 value);
void Pad();
void Multitap();
void Infrared();
void Memcard();
void Write(u8 data);
u8 Read();
};
extern std::deque<u8> fifoIn;
extern std::deque<u8> fifoOut;
extern Sio0 sio0;
extern Sio2 sio2;
extern _mcd mcds[2][4];
extern _mcd *mcd;
extern void sioNextFrame();
/// Converts a global pad index to a multitap port and slot.
extern std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index);
/// Converts a multitap port and slot to a global pad index.
extern u32 sioConvertPortAndSlotToPad(u32 port, u32 slot);
/// Returns true if the given pad index is a multitap slot.
extern bool sioPadIsMultitapSlot(u32 index);
extern bool sioPortAndSlotIsMultitap(u32 port, u32 slot);
extern void sioSetGameSerial(const std::string& serial);
namespace AutoEject
{
extern void CountDownTicks();
extern void Set(size_t port, size_t slot);
extern void Clear(size_t port, size_t slot);
extern void SetAll();
extern void ClearAll();
}

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