mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-08-06 21:37:33 +00:00
Compare commits
23 Commits
Canary-1.3
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
5613d3f35d | ||
|
54d4d184f4 | ||
|
d22756f1bd | ||
|
324f18aa5f | ||
|
31870707cf | ||
|
fd6648e30a | ||
|
bc6be4e088 | ||
|
64a6494d90 | ||
|
ddb8afa6f4 | ||
|
c2f4118b1f | ||
|
47aa2c1513 | ||
|
f3a2f59683 | ||
|
51bcb9e128 | ||
|
dce5f0eb55 | ||
|
f2eb3749f9 | ||
|
45b2e613cf | ||
|
932c480325 | ||
|
0e24435414 | ||
|
a5cf0482b4 | ||
|
14e794af84 | ||
|
29a02f4787 | ||
|
e2f9d84b64 | ||
|
0cc94fdf37 |
78
.github/workflows/canary.yml
vendored
78
.github/workflows/canary.yml
vendored
@ -106,45 +106,47 @@ jobs:
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||
shell: bash
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
# If anyone wants to look into why appimagetool randomly errors with exit code 8, that would be cool
|
||||
|
||||
sudo apt install -y zsync desktop-file-utils appstream
|
||||
|
||||
mkdir -p tools
|
||||
export PATH="$PATH:$(readlink -f tools)"
|
||||
|
||||
# Setup appimagetool
|
||||
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x tools/appimagetool
|
||||
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||
ARCH_NAME=x64
|
||||
export ARCH=x86_64
|
||||
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||
ARCH_NAME=arm64
|
||||
export ARCH=aarch64
|
||||
else
|
||||
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export UFLAG="gh-releases-zsync|${{ secrets.RC_OWNER }}${{ secrets.RC_CANARY_NAME }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||
shell: bash
|
||||
# - name: Build AppImage (Linux)
|
||||
# if: matrix.platform.os == 'ubuntu-latest'
|
||||
# run: |
|
||||
# BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
# PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
#
|
||||
# sudo apt install -y zsync desktop-file-utils appstream
|
||||
#
|
||||
# mkdir -p tools
|
||||
# export PATH="$PATH:$(readlink -f tools)"
|
||||
#
|
||||
# # Setup appimagetool
|
||||
# wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
# chmod +x tools/appimagetool
|
||||
# chmod +x distribution/linux/appimage/build-appimage.sh
|
||||
#
|
||||
# # Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||
# if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||
# ARCH_NAME=x64
|
||||
# export ARCH=x86_64
|
||||
# elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||
# ARCH_NAME=arm64
|
||||
# export ARCH=aarch64
|
||||
# else
|
||||
# echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||
# exit 1
|
||||
# fi
|
||||
#
|
||||
# export UFLAG="gh-releases-zsync|${{ secrets.RC_OWNER }}${{ secrets.RC_CANARY_NAME }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
# BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
#
|
||||
# pushd publish_appimage
|
||||
# mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
# mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
# popd
|
||||
#
|
||||
# gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
# gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||
# shell: bash
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
|
78
.github/workflows/release.yml
vendored
78
.github/workflows/release.yml
vendored
@ -97,45 +97,47 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build AppImage (Linux)
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
# If anyone wants to look into why appimagetool randomly errors with exit code 8, that would be cool
|
||||
|
||||
sudo apt install -y zsync desktop-file-utils appstream
|
||||
|
||||
mkdir -p tools
|
||||
export PATH="$PATH:$(readlink -f tools)"
|
||||
|
||||
# Setup appimagetool
|
||||
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x tools/appimagetool
|
||||
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||
ARCH_NAME=x64
|
||||
export ARCH=x86_64
|
||||
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||
ARCH_NAME=arm64
|
||||
export ARCH=aarch64
|
||||
else
|
||||
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
popd
|
||||
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||
shell: bash
|
||||
# - name: Build AppImage (Linux)
|
||||
# if: matrix.platform.os == 'ubuntu-latest'
|
||||
# run: |
|
||||
# BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
# PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
#
|
||||
# sudo apt install -y zsync desktop-file-utils appstream
|
||||
#
|
||||
# mkdir -p tools
|
||||
# export PATH="$PATH:$(readlink -f tools)"
|
||||
#
|
||||
# # Setup appimagetool
|
||||
# wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
# chmod +x tools/appimagetool
|
||||
# chmod +x distribution/linux/appimage/build-appimage.sh
|
||||
#
|
||||
# # Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||
# if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||
# ARCH_NAME=x64
|
||||
# export ARCH=x86_64
|
||||
# elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||
# ARCH_NAME=arm64
|
||||
# export ARCH=aarch64
|
||||
# else
|
||||
# echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||
# exit 1
|
||||
# fi
|
||||
#
|
||||
# export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
# BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
#
|
||||
# pushd publish_appimage
|
||||
# mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
# mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
# popd
|
||||
#
|
||||
# gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||
# gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||
# shell: bash
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
|
@ -40,7 +40,7 @@
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.20.0" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.116" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.29" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.29" />
|
||||
|
@ -7,8 +7,8 @@
|
||||
|
||||
# Ryujinx
|
||||
|
||||
[](https://git.ryujinx.app/ryubing/ryujinx/-/releases)
|
||||
[](https://git.ryujinx.app/ryubing/canary/-/releases)
|
||||
[](https://update.ryujinx.app/latest/stable)
|
||||
[](https://update.ryujinx.app/latest/canary)
|
||||
<br>
|
||||
<a href="https://discord.gg/PEuzjrFXUA">
|
||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||
|
3151
assets/locales.json
3151
assets/locales.json
File diff suppressed because it is too large
Load Diff
@ -188,6 +188,8 @@
|
||||
01003DD00BFEE000,"Airheart - Tales of broken Wings",,playable,2021-02-26 15:20:27
|
||||
01007F100DE52000,"Akane",nvdec,playable,2022-07-21 00:12:18
|
||||
01009A800F0C8000,"Akash: Path of the Five",gpu;nvdec,ingame,2020-12-14 22:33:12
|
||||
01009E8012976000,"AKIBA'S TRIP: Hellbound & Debriefed",,playable,2025-07-30 23:22:47
|
||||
0100D74019A0E000,"AKIBA'S TRIP: Undead & Undressed Director's Cut",,playable,2025-07-31 13:58:42
|
||||
010053100B0EA000,"Akihabara - Feel the Rhythm Remixed",,playable,2021-02-22 14:39:35
|
||||
0100D4C00EE0C000,"Akuarium",slow,playable,2020-12-12 23:43:36
|
||||
010026E00FEBE000,"Akuto: Showdown",,playable,2020-08-04 19:43:27
|
||||
@ -1095,6 +1097,7 @@
|
||||
0100F9600E746000,"ESP Ra.De. Psi",audio;slow,ingame,2024-03-07 15:05:08
|
||||
010073000FE18000,"Esports powerful pro yakyuu 2020",gpu;crash;Needs More Attention,ingame,2024-04-29 05:34:14
|
||||
01004F9012FD8000,"Estranged: The Departure",nvdec;UE4,playable,2022-10-24 10:37:58
|
||||
010018f01e0a0000,"Eternights",,playable,2025-07-30 12:10:24
|
||||
0100CB900B498000,"Eternum Ex",,playable,2021-01-13 20:28:32
|
||||
010092501EB2C000,"Europa (Demo)",gpu;crash;UE4,ingame,2024-04-23 10:47:12
|
||||
01007BE0160D6000,"EVE ghost enemies",gpu,ingame,2023-01-14 03:13:30
|
||||
@ -1240,6 +1243,8 @@
|
||||
010003F00BD48000,"Friday the 13th: Killer Puzzle",,playable,2021-01-28 01:33:38
|
||||
010092A00C4B6000,"Friday the 13th: The Game Ultimate Slasher Edition",nvdec;online-broken;UE4,playable,2022-09-06 17:33:27
|
||||
0100F200178F4000,"FRONT MISSION 1st: Remake",,playable,2023-06-09 07:44:24
|
||||
0100c4e018a24000,"FRONT MISSION 2: Remake",,playable,2025-07-30 12:11:23
|
||||
01007E6019872000,"FRONT MISSION 3: Remake",,playable,2025-07-30 12:12:02
|
||||
0100861012474000,"Frontline Zed",,playable,2020-10-03 12:55:59
|
||||
0100B5300B49A000,"Frost",,playable,2022-07-27 12:00:36
|
||||
010038A007AA4000,"FruitFall Crush",,playable,2020-10-20 11:33:33
|
||||
@ -1518,6 +1523,7 @@
|
||||
010095C016C14000,"Iridium",,playable,2022-08-05 23:19:53
|
||||
0100AD300B786000,"Iris School of Wizardry -Vinculum Hearts-",,playable,2022-12-05 13:11:15
|
||||
0100945012168000,"Iris.Fall",nvdec,playable,2022-10-18 13:40:22
|
||||
010059801B736000,"IronFall: Invasion",,playable,2025-07-30 11:42:30
|
||||
01005270118D6000,"Iron Wings",slow,ingame,2022-08-07 08:32:57
|
||||
01004DB003E6A000,"IRONCAST",,playable,2021-01-13 13:54:29
|
||||
0100E5700CD56000,"Irony Curtain: From Matryoshka with Love",,playable,2021-06-04 20:12:37
|
||||
@ -2257,6 +2263,7 @@
|
||||
010086F0064CE000,"Poi: Explorer Edition",nvdec,playable,2021-01-21 19:32:00
|
||||
0100EB6012FD2000,"Poison Control",,playable,2021-05-16 14:01:54
|
||||
010072400E04A000,"Pokémon Café ReMix",,playable,2021-08-17 20:00:04
|
||||
010008c01e742000,"Pokémon Friends",crash;services,menus,2025-07-24 13:32:00
|
||||
01003D200BAA2000,"Pokémon Mystery Dungeon™: Rescue Team DX",mac-bug,playable,2024-01-21 00:16:32
|
||||
01008DB008C2C000,"Pokémon Shield + Pokémon Shield Expansion Pass",deadlock;crash;online-broken;ldn-works;LAN,ingame,2024-08-12 07:20:22
|
||||
0100ABF008968000,"Pokémon Sword + Pokémon Sword Expansion Pass",deadlock;crash;online-broken;ldn-works;LAN,ingame,2024-08-26 15:40:37
|
||||
@ -2305,6 +2312,7 @@
|
||||
010077B00BDD8000,"Professional Farmer: Nintendo Switch™ Edition",slow,playable,2020-12-16 13:38:19
|
||||
010018300C83A000,"Professor Lupo and his Horrible Pets",,playable,2020-06-12 00:08:45
|
||||
0100D1F0132F6000,"Professor Lupo: Ocean",,playable,2021-04-14 16:33:33
|
||||
0100c3a017834000,"Prodeus",,playable,2025-07-30 12:07:52
|
||||
0100BBD00976C000,"Project Highrise: Architect's Edition",,playable,2022-08-10 17:19:12
|
||||
0100ACE00DAB6000,"Project Nimbus: Complete Edition",nvdec;UE4;vulkan-backend-bug,playable,2022-08-10 17:35:43
|
||||
01002980140F6000,"Project TRIANGLE STRATEGY™ Debut Demo",UE4;demo,playable,2022-10-24 21:40:27
|
||||
@ -2436,6 +2444,7 @@
|
||||
0100E9C010EA8000,"Rise of Insanity",,playable,2020-08-30 15:42:14
|
||||
01006BA00E652000,"Rise: Race The Future",,playable,2021-02-27 13:29:06
|
||||
010020C012F48000,"Rising Hell",,playable,2022-10-31 13:54:02
|
||||
0100D1801A0F4000,"Risk of Rain Returns",,playable,2025-06-28 04:24:04
|
||||
010076D00E4BA000,"Risk of Rain 2",online-broken,playable,2024-03-04 17:01:05
|
||||
0100E8300A67A000,"RISK® Global Domination",nvdec;online-broken,playable,2022-08-01 18:53:28
|
||||
010042500FABA000,"Ritual: Crown of Horns",,playable,2021-01-26 16:01:47
|
||||
@ -2766,7 +2775,7 @@
|
||||
0100E6B0115FC000,"Star99",online,menus,2021-11-26 14:18:51
|
||||
01002100137BA000,"Stardash",,playable,2021-01-21 16:31:19
|
||||
0100E65002BB8000,"Stardew Valley",online-broken;ldn-untested,playable,2024-02-14 03:11:19
|
||||
01002CC003FE6000,"Starlink: Battle for Atlas™ Digital Edition",services-horizon;crash;Needs Update,nothing,2024-05-05 17:25:11
|
||||
01002CC003FE6000,"Starlink: Battle for Atlas™ Digital Edition",,playable,2025-07-30 12:09:37
|
||||
010098E010FDA000,"Starlit Adventures Golden Stars",,playable,2020-11-21 12:14:43
|
||||
01001BB00AC26000,"STARSHIP AVENGER Operation: Take Back Earth",,playable,2021-01-12 15:52:55
|
||||
010000700A572000,"State of Anarchy: Master of Mayhem",nvdec,playable,2021-01-12 19:00:05
|
||||
@ -2974,6 +2983,7 @@
|
||||
0100C2E0129A6000,"The Executioner",nvdec,playable,2021-01-23 00:31:28
|
||||
01006050114D4000,"The Experiment: Escape Room",gpu,ingame,2022-09-30 13:20:35
|
||||
0100B5900DFB2000,"The Eyes of Ara",,playable,2022-09-16 14:44:06
|
||||
0100BA5013E52000,"The Falconeer: Warrior Edition",,playable,2025-07-30 12:04:50
|
||||
01002DD00AF9E000,"The Fall",gpu,ingame,2020-05-31 23:31:16
|
||||
01003E5002320000,"The Fall Part 2: Unbound",,playable,2021-11-06 02:18:08
|
||||
0100CDC00789E000,"The Final Station",nvdec,playable,2022-08-22 15:54:39
|
||||
@ -3196,6 +3206,7 @@
|
||||
010000400F582000,"TT Isle of Man Ride on the Edge 2",gpu;nvdec;online-broken,ingame,2022-09-30 22:13:05
|
||||
0100752011628000,"TTV2",,playable,2020-11-27 13:21:36
|
||||
0100AFE00452E000,"Tumblestone",,playable,2021-01-07 17:49:20
|
||||
0100D1A01D7BA000,"Turbo Overkill",,playable,2025-07-30 12:08:57
|
||||
010085500D5F6000,"Turok",gpu,ingame,2021-06-04 13:16:24
|
||||
0100CDC00D8D6000,"Turok 2: Seeds of Evil",gpu;vulkan,ingame,2022-09-12 17:50:05
|
||||
010004B0130C8000,"Turrican Flashback",audout,playable,2021-08-30 10:07:56
|
||||
@ -3219,6 +3230,8 @@
|
||||
0100592005164000,"UNBOX: Newbie's Adventure",UE4,playable,2022-08-29 13:12:56
|
||||
01002D900C5E4000,"Uncanny Valley",nvdec,playable,2021-06-04 13:28:45
|
||||
010076F011F54000,"Undead & Beyond",nvdec,playable,2022-10-04 09:11:18
|
||||
01009B700D0B8000,"Undead Horde",,playable,2025-07-30 12:05:05
|
||||
0100FC301A878000,"Undead Horde 2: Necropolis",,playable,2025-07-30 12:06:07
|
||||
01008F3013E4E000,"Under Leaves",,playable,2021-05-22 18:13:58
|
||||
010080B00AD66000,"Undertale",,playable,2022-08-31 17:31:46
|
||||
01008F80049C6000,"Unepic",,playable,2024-01-15 17:03:00
|
||||
|
|
@ -5,7 +5,7 @@
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
|
||||
<!--<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />-->
|
||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
|
||||
<add key="Ryujinx.UpdateClient" value="https://git.ryujinx.app/api/v4/projects/71/packages/nuget/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
@ -18,8 +18,8 @@
|
||||
<package pattern="Ryujinx.UpdateClient" />
|
||||
<package pattern="Ryujinx.Systems.Update.Common" />
|
||||
</packageSource>
|
||||
<!--<packageSource key="LibHacAlpha">
|
||||
<packageSource key="LibHacAlpha">
|
||||
<package pattern="Ryujinx.LibHac" />
|
||||
</packageSource>-->
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
|
@ -143,6 +143,12 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void EmitCall(ArmEmitterContext context, ulong immediate)
|
||||
{
|
||||
if (context.IsSingleStep)
|
||||
{
|
||||
context.Return(Const(immediate));
|
||||
return;
|
||||
}
|
||||
|
||||
bool isRecursive = immediate == context.EntryAddress;
|
||||
|
||||
if (isRecursive)
|
||||
@ -157,12 +163,24 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
public static void EmitVirtualCall(ArmEmitterContext context, Operand target)
|
||||
{
|
||||
EmitTableBranch(context, target, isJump: false);
|
||||
if (context.IsSingleStep)
|
||||
{
|
||||
if (target.Type == OperandType.I32)
|
||||
{
|
||||
target = context.ZeroExtend32(OperandType.I64, target);
|
||||
}
|
||||
|
||||
context.Return(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitTableBranch(context, target, isJump: false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn)
|
||||
{
|
||||
if (isReturn)
|
||||
if (isReturn || context.IsSingleStep)
|
||||
{
|
||||
if (target.Type == OperandType.I32)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using ExecutionContext = ARMeilleure.State.ExecutionContext;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
@ -200,7 +201,11 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
ExecutionContext context = GetContext();
|
||||
|
||||
context.CheckInterrupt();
|
||||
// If debugging, we'll handle interrupts outside
|
||||
if (!Optimizations.EnableDebugging)
|
||||
{
|
||||
context.CheckInterrupt();
|
||||
}
|
||||
|
||||
Statistics.ResumeTimer();
|
||||
|
||||
|
@ -12,6 +12,7 @@ namespace ARMeilleure
|
||||
|
||||
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||
public static bool EnableDebugging { get; set; } = false;
|
||||
|
||||
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
||||
public static bool UseArm64AesIfAvailable { get; set; } = true;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using ARMeilleure.Memory;
|
||||
using System.Threading;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
@ -10,7 +11,7 @@ namespace ARMeilleure.State
|
||||
|
||||
internal nint NativeContextPtr => _nativeContext.BasePtr;
|
||||
|
||||
private bool _interrupted;
|
||||
internal bool Interrupted { get; private set; }
|
||||
|
||||
private readonly ICounter _counter;
|
||||
|
||||
@ -65,6 +66,8 @@ namespace ARMeilleure.State
|
||||
|
||||
public bool IsAarch32 { get; set; }
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
internal ExecutionMode ExecutionMode
|
||||
{
|
||||
get
|
||||
@ -90,14 +93,19 @@ namespace ARMeilleure.State
|
||||
|
||||
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
||||
private readonly ExceptionCallback _breakCallback;
|
||||
private readonly ExceptionCallbackNoArgs _stepCallback;
|
||||
private readonly ExceptionCallback _supervisorCallback;
|
||||
private readonly ExceptionCallback _undefinedCallback;
|
||||
|
||||
internal int ShouldStep;
|
||||
public ulong DebugPc { get; set; }
|
||||
|
||||
public ExecutionContext(
|
||||
IJitMemoryAllocator allocator,
|
||||
ICounter counter,
|
||||
ExceptionCallbackNoArgs interruptCallback = null,
|
||||
ExceptionCallback breakCallback = null,
|
||||
ExceptionCallbackNoArgs stepCallback = null,
|
||||
ExceptionCallback supervisorCallback = null,
|
||||
ExceptionCallback undefinedCallback = null)
|
||||
{
|
||||
@ -105,6 +113,7 @@ namespace ARMeilleure.State
|
||||
_counter = counter;
|
||||
_interruptCallback = interruptCallback;
|
||||
_breakCallback = breakCallback;
|
||||
_stepCallback = stepCallback;
|
||||
_supervisorCallback = supervisorCallback;
|
||||
_undefinedCallback = undefinedCallback;
|
||||
|
||||
@ -127,9 +136,9 @@ namespace ARMeilleure.State
|
||||
|
||||
internal void CheckInterrupt()
|
||||
{
|
||||
if (_interrupted)
|
||||
if (Interrupted)
|
||||
{
|
||||
_interrupted = false;
|
||||
Interrupted = false;
|
||||
|
||||
_interruptCallback?.Invoke(this);
|
||||
}
|
||||
@ -139,16 +148,37 @@ namespace ARMeilleure.State
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
_interrupted = true;
|
||||
Interrupted = true;
|
||||
}
|
||||
|
||||
public void StepHandler()
|
||||
{
|
||||
_stepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
public void RequestDebugStep()
|
||||
{
|
||||
Interlocked.Exchange(ref ShouldStep, 1);
|
||||
RequestInterrupt();
|
||||
}
|
||||
|
||||
internal void OnBreak(ulong address, int imm)
|
||||
{
|
||||
if (Optimizations.EnableDebugging)
|
||||
{
|
||||
DebugPc = Pc;
|
||||
}
|
||||
|
||||
_breakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
internal void OnSupervisorCall(ulong address, int imm)
|
||||
{
|
||||
if (Optimizations.EnableDebugging)
|
||||
{
|
||||
DebugPc = Pc;
|
||||
}
|
||||
|
||||
_supervisorCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,12 @@ namespace ARMeilleure.State
|
||||
public ulong ExclusiveValueHigh;
|
||||
public int Running;
|
||||
public long Tpidr2El0;
|
||||
|
||||
/// <summary>
|
||||
/// Precise PC value used for debugging.
|
||||
/// This will only be set when Optimizations.EnableDebugging is true.
|
||||
/// </summary>
|
||||
public ulong DebugPrecisePc;
|
||||
}
|
||||
|
||||
private static NativeCtxStorage _dummyStorage = new();
|
||||
@ -39,6 +45,11 @@ namespace ARMeilleure.State
|
||||
|
||||
public ulong GetPc()
|
||||
{
|
||||
if (Optimizations.EnableDebugging)
|
||||
{
|
||||
return GetStorage().DebugPrecisePc;
|
||||
}
|
||||
|
||||
// TODO: More precise tracking of PC value.
|
||||
return GetStorage().DispatchAddress;
|
||||
}
|
||||
@ -268,6 +279,11 @@ namespace ARMeilleure.State
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
|
||||
}
|
||||
|
||||
public static int GetDebugPrecisePcOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc);
|
||||
}
|
||||
|
||||
private static int StorageOffset<T>(ref NativeCtxStorage storage, ref T target)
|
||||
{
|
||||
return (int)Unsafe.ByteOffset(ref Unsafe.As<NativeCtxStorage, T>(ref storage), ref target);
|
||||
|
@ -52,6 +52,7 @@ namespace ARMeilleure.Translation
|
||||
public bool HighCq { get; }
|
||||
public bool HasPtc { get; }
|
||||
public Aarch32Mode Mode { get; }
|
||||
public bool IsSingleStep { get; }
|
||||
|
||||
private int _ifThenBlockStateIndex = 0;
|
||||
private Condition[] _ifThenBlockState = [];
|
||||
@ -66,7 +67,8 @@ namespace ARMeilleure.Translation
|
||||
ulong entryAddress,
|
||||
bool highCq,
|
||||
bool hasPtc,
|
||||
Aarch32Mode mode)
|
||||
Aarch32Mode mode,
|
||||
bool isSingleStep)
|
||||
{
|
||||
Memory = memory;
|
||||
CountTable = countTable;
|
||||
@ -76,6 +78,7 @@ namespace ARMeilleure.Translation
|
||||
HighCq = highCq;
|
||||
HasPtc = hasPtc;
|
||||
Mode = mode;
|
||||
IsSingleStep = isSingleStep;
|
||||
|
||||
_labels = new Dictionary<ulong, Operand>();
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 7008; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@ -303,6 +303,13 @@ namespace ARMeilleure.Translation.PTC
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outerHeader.DebuggerMode != Optimizations.EnableDebugging)
|
||||
{
|
||||
InvalidateCompressedStream(compressedStream);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
nint intPtr = nint.Zero;
|
||||
|
||||
try
|
||||
@ -479,6 +486,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
MemoryManagerMode = GetMemoryManagerMode(),
|
||||
OSPlatform = GetOSPlatform(),
|
||||
Architecture = (uint)RuntimeInformation.ProcessArchitecture,
|
||||
DebuggerMode = Optimizations.EnableDebugging,
|
||||
|
||||
UncompressedStreamSize =
|
||||
(long)Unsafe.SizeOf<InnerHeader>() +
|
||||
@ -1068,7 +1076,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
return osPlatform;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 87*/)]
|
||||
private struct OuterHeader
|
||||
{
|
||||
public ulong Magic;
|
||||
@ -1080,6 +1088,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
public byte MemoryManagerMode;
|
||||
public uint OSPlatform;
|
||||
public uint Architecture;
|
||||
public bool DebuggerMode;
|
||||
|
||||
public long UncompressedStreamSize;
|
||||
|
||||
|
@ -119,7 +119,25 @@ namespace ARMeilleure.Translation
|
||||
|
||||
NativeInterface.RegisterThread(context, Memory, this);
|
||||
|
||||
if (Optimizations.UseUnmanagedDispatchLoop)
|
||||
if (Optimizations.EnableDebugging)
|
||||
{
|
||||
context.DebugPc = address;
|
||||
do
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
|
||||
{
|
||||
context.DebugPc = Step(context, context.DebugPc);
|
||||
context.StepHandler();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.DebugPc = ExecuteSingle(context, context.DebugPc);
|
||||
}
|
||||
context.CheckInterrupt();
|
||||
}
|
||||
while (context.Running && context.DebugPc != 0);
|
||||
}
|
||||
else if (Optimizations.UseUnmanagedDispatchLoop)
|
||||
{
|
||||
Stubs.DispatchLoop(context.NativeContextPtr, address);
|
||||
}
|
||||
@ -175,7 +193,7 @@ namespace ARMeilleure.Translation
|
||||
return nextAddr;
|
||||
}
|
||||
|
||||
public ulong Step(State.ExecutionContext context, ulong address)
|
||||
private ulong Step(State.ExecutionContext context, ulong address)
|
||||
{
|
||||
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
|
||||
|
||||
@ -186,6 +204,8 @@ namespace ARMeilleure.Translation
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
|
||||
{
|
||||
if (!Functions.TryGetValue(address, out TranslatedFunction func))
|
||||
@ -229,7 +249,8 @@ namespace ARMeilleure.Translation
|
||||
address,
|
||||
highCq,
|
||||
_ptc.State != PtcState.Disabled,
|
||||
mode: Aarch32Mode.User);
|
||||
mode: Aarch32Mode.User,
|
||||
isSingleStep: singleStep);
|
||||
|
||||
Logger.StartPass(PassName.Decoding);
|
||||
|
||||
@ -367,9 +388,13 @@ namespace ARMeilleure.Translation
|
||||
|
||||
if (block.Exit)
|
||||
{
|
||||
// Left option here as it may be useful if we need to return to managed rather than tail call in
|
||||
// future. (eg. for debug)
|
||||
bool useReturns = false;
|
||||
// Return to managed rather than tail call.
|
||||
bool useReturns = Optimizations.EnableDebugging;
|
||||
|
||||
if (Optimizations.EnableDebugging)
|
||||
{
|
||||
EmitDebugPrecisePcUpdate(context, block.Address);
|
||||
}
|
||||
|
||||
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
|
||||
}
|
||||
@ -393,6 +418,11 @@ namespace ARMeilleure.Translation
|
||||
}
|
||||
}
|
||||
|
||||
if (Optimizations.EnableDebugging)
|
||||
{
|
||||
EmitDebugPrecisePcUpdate(context, opCode.Address);
|
||||
}
|
||||
|
||||
Operand lblPredicateSkip = default;
|
||||
|
||||
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
|
||||
@ -489,6 +519,14 @@ namespace ARMeilleure.Translation
|
||||
context.MarkLabel(lblExit);
|
||||
}
|
||||
|
||||
internal static void EmitDebugPrecisePcUpdate(EmitterContext context, ulong address)
|
||||
{
|
||||
long debugPrecisePcOffs = NativeContext.GetDebugPrecisePcOffset();
|
||||
|
||||
Operand debugPrecisePcAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(debugPrecisePcOffs));
|
||||
context.Store(debugPrecisePcAddr, Const(address));
|
||||
}
|
||||
|
||||
public void InvalidateJitCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
ulong[] overlapAddresses = [];
|
||||
|
@ -33,7 +33,7 @@ namespace Ryujinx.BuildValidationTasks
|
||||
LocalesJson json;
|
||||
|
||||
if (isGitRunner && data.Contains("\r\n"))
|
||||
throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, build locally to fix...");
|
||||
throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
|
||||
|
||||
try
|
||||
{
|
||||
@ -86,7 +86,7 @@ namespace Ryujinx.BuildValidationTasks
|
||||
}
|
||||
|
||||
if (isGitRunner && encounteredIssue)
|
||||
throw new JsonException("1 or more locales are invalid!");
|
||||
throw new JsonException("1 or more locales are invalid! Rebuild locally to fix...");
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(json, _jsonOptions);
|
||||
|
||||
@ -102,6 +102,7 @@ namespace Ryujinx.BuildValidationTasks
|
||||
|
||||
struct LocalesJson
|
||||
{
|
||||
public Dictionary<string, string> Info { get; set; }
|
||||
public List<string> Languages { get; set; }
|
||||
public List<LocalesEntry> Locales { get; set; }
|
||||
}
|
||||
|
@ -11,7 +11,17 @@
|
||||
Command="dotnet Ryujinx.BuildValidationTasks.dll "$(ProjectDir)..\..\\""
|
||||
ConsoleToMsBuild="true"
|
||||
Condition="'$(RuntimeIdentifier)' == ''"
|
||||
/>
|
||||
IgnoreExitCode="true">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
|
||||
<Output TaskParameter="ExitCode" PropertyName="BuildExitCode"/>
|
||||
</Exec>
|
||||
|
||||
<PropertyGroup Condition=" '$(OutputOfExec.IndexOf(Unhandled exception))' != '-1'">
|
||||
<ErrorOutput>$(OutputOfExec.Substring($(OutputOfExec.IndexOf("Unhandled exception"))))</ErrorOutput>
|
||||
<ErrorOutput>$(ErrorOutput.Substring(0, $(ErrorOutput.IndexOf(';'))))</ErrorOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<Error Text="$(ErrorOutput)" Condition=" '$(BuildExitCode)' != '0'"/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
@ -14,12 +14,13 @@ namespace Ryujinx.Common.Collections
|
||||
/// Adds a new node into the tree.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to be added</param>
|
||||
/// <param name="parent">Node to be added under</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||
public void Add(T node)
|
||||
public void Add(T node, T parent = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
Insert(node);
|
||||
Insert(node, parent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -76,9 +77,11 @@ namespace Ryujinx.Common.Collections
|
||||
/// Inserts a new node into the tree.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to be inserted</param>
|
||||
private void Insert(T node)
|
||||
/// <param name="parent">Node to be inserted under</param>
|
||||
private void Insert(T node, T parent = null)
|
||||
{
|
||||
T newNode = BSTInsert(node);
|
||||
T newNode = parent != null ? InsertWithParent(node, parent) : BSTInsert(node);
|
||||
|
||||
RestoreBalanceAfterInsertion(newNode);
|
||||
}
|
||||
|
||||
@ -122,10 +125,78 @@ namespace Ryujinx.Common.Collections
|
||||
else if (newNode.CompareTo(parent) < 0)
|
||||
{
|
||||
parent.Left = newNode;
|
||||
|
||||
newNode.Successor = parent;
|
||||
|
||||
if (parent.Predecessor != null)
|
||||
{
|
||||
newNode.Predecessor = parent.Predecessor;
|
||||
parent.Predecessor = newNode;
|
||||
newNode.Predecessor.Successor = newNode;
|
||||
}
|
||||
|
||||
parent.Predecessor = newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.Right = newNode;
|
||||
|
||||
newNode.Predecessor = parent;
|
||||
|
||||
if (parent.Successor != null)
|
||||
{
|
||||
newNode.Successor = parent.Successor;
|
||||
newNode.Successor.Predecessor = newNode;
|
||||
}
|
||||
|
||||
parent.Successor = newNode;
|
||||
}
|
||||
Count++;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insertion Mechanism for a Binary Search Tree (BST).
|
||||
/// <br></br>
|
||||
/// Inserts a new node directly under a parent node
|
||||
/// where all children in the left subtree are less than <paramref name="newNode"/>,
|
||||
/// and all children in the right subtree are greater than <paramref name="newNode"/>.
|
||||
/// </summary>
|
||||
/// <param name="newNode">Node to be inserted</param>
|
||||
/// <param name="parent">Node to be inserted under</param>
|
||||
/// <returns>The inserted Node</returns>
|
||||
private T InsertWithParent(T newNode, T parent)
|
||||
{
|
||||
newNode.Parent = parent;
|
||||
|
||||
if (newNode.CompareTo(parent) < 0)
|
||||
{
|
||||
parent.Left = newNode;
|
||||
|
||||
newNode.Successor = parent;
|
||||
|
||||
if (parent.Predecessor != null)
|
||||
{
|
||||
newNode.Predecessor = parent.Predecessor;
|
||||
parent.Predecessor = newNode;
|
||||
newNode.Predecessor.Successor = newNode;
|
||||
}
|
||||
|
||||
parent.Predecessor = newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.Right = newNode;
|
||||
|
||||
newNode.Predecessor = parent;
|
||||
|
||||
if (parent.Successor != null)
|
||||
{
|
||||
newNode.Successor = parent.Successor;
|
||||
newNode.Successor.Predecessor = newNode;
|
||||
}
|
||||
|
||||
parent.Successor = newNode;
|
||||
}
|
||||
|
||||
Count++;
|
||||
@ -159,7 +230,7 @@ namespace Ryujinx.Common.Collections
|
||||
}
|
||||
else
|
||||
{
|
||||
T element = Minimum(RightOf(nodeToDelete));
|
||||
T element = nodeToDelete.Successor;
|
||||
|
||||
child = RightOf(element);
|
||||
parent = ParentOf(element);
|
||||
@ -187,6 +258,9 @@ namespace Ryujinx.Common.Collections
|
||||
element.Left = old.Left;
|
||||
element.Right = old.Right;
|
||||
element.Parent = old.Parent;
|
||||
element.Predecessor = old.Predecessor;
|
||||
if (element.Predecessor != null)
|
||||
element.Predecessor.Successor = element;
|
||||
|
||||
if (ParentOf(old) == null)
|
||||
{
|
||||
@ -242,6 +316,11 @@ namespace Ryujinx.Common.Collections
|
||||
RestoreBalanceAfterRemoval(child);
|
||||
}
|
||||
|
||||
if (old.Successor != null)
|
||||
old.Successor.Predecessor = old.Predecessor;
|
||||
if (old.Predecessor != null)
|
||||
old.Predecessor.Successor = old.Successor;
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,7 @@ namespace Ryujinx.Common.Collections
|
||||
public T Left;
|
||||
public T Right;
|
||||
public T Parent;
|
||||
|
||||
public T Predecessor => IntrusiveRedBlackTreeImpl<T>.PredecessorOf((T)this);
|
||||
public T Successor => IntrusiveRedBlackTreeImpl<T>.SuccessorOf((T)this);
|
||||
public T Predecessor;
|
||||
public T Successor;
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ namespace Ryujinx.Common.Collections
|
||||
Node<TKey, TValue> node = GetNode(key);
|
||||
if (node != null)
|
||||
{
|
||||
Node<TKey, TValue> successor = SuccessorOf(node);
|
||||
Node<TKey, TValue> successor = node.Successor;
|
||||
|
||||
return successor != null ? successor.Key : default;
|
||||
}
|
||||
@ -127,7 +127,7 @@ namespace Ryujinx.Common.Collections
|
||||
Node<TKey, TValue> node = GetNode(key);
|
||||
if (node != null)
|
||||
{
|
||||
Node<TKey, TValue> predecessor = PredecessorOf(node);
|
||||
Node<TKey, TValue> predecessor = node.Predecessor;
|
||||
|
||||
return predecessor != null ? predecessor.Key : default;
|
||||
}
|
||||
@ -136,11 +136,10 @@ namespace Ryujinx.Common.Collections
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all the nodes in the dictionary as key/value pairs into <paramref name="list"/>.
|
||||
/// Adds all the nodes in the dictionary as key/value pairs into a list.
|
||||
/// <br></br>
|
||||
/// The key/value pairs will be added in Level Order.
|
||||
/// </summary>
|
||||
/// <param name="list">List to add the tree pairs into</param>
|
||||
public List<KeyValuePair<TKey, TValue>> AsLevelOrderList()
|
||||
{
|
||||
List<KeyValuePair<TKey, TValue>> list = [];
|
||||
@ -170,7 +169,7 @@ namespace Ryujinx.Common.Collections
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
|
||||
/// Adds all the nodes in the dictionary into a list.
|
||||
/// </summary>
|
||||
/// <returns>A list of all KeyValuePairs sorted by Key Order</returns>
|
||||
public List<KeyValuePair<TKey, TValue>> AsList()
|
||||
@ -284,7 +283,7 @@ namespace Ryujinx.Common.Collections
|
||||
}
|
||||
|
||||
Node<TKey, TValue> newNode = new(key, value, parent);
|
||||
if (newNode.Parent == null)
|
||||
if (parent == null)
|
||||
{
|
||||
Root = newNode;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging
|
||||
Cpu,
|
||||
Emulation,
|
||||
FFmpeg,
|
||||
GdbStub,
|
||||
Font,
|
||||
Gpu,
|
||||
Hid,
|
||||
|
@ -93,6 +93,7 @@ namespace Ryujinx.Common
|
||||
//The Pokémon Franchise
|
||||
"0100f4300bf2c000", // New Pokémon Snap
|
||||
"0100000011d90000", // Pokémon Brilliant Diamond
|
||||
"010008c01e742000", // Pokémon Friends
|
||||
"01001f5010dfa000", // Pokémon Legends: Arceus
|
||||
"01003d200baa2000", // Pokémon Mystery Dungeon - Rescue Team DX
|
||||
"0100a3d008c5c000", // Pokémon Scarlet
|
||||
|
10
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
Normal file
10
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.Cpu.AppleHv.Arm
|
||||
{
|
||||
enum ExceptionLevel : uint
|
||||
{
|
||||
PstateMask = 0xfffffff0,
|
||||
EL1h = 0b0101,
|
||||
El1t = 0b0100,
|
||||
EL0 = 0b0000,
|
||||
}
|
||||
}
|
@ -11,7 +11,18 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
class HvExecutionContext : IExecutionContext
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ulong Pc => _impl.ElrEl1;
|
||||
public ulong Pc
|
||||
{
|
||||
get
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
return _impl.ElrEl1;
|
||||
}
|
||||
return _impl.Pc;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long TpidrEl0
|
||||
@ -48,6 +59,9 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
set => _impl.Fpsr = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAarch32
|
||||
{
|
||||
@ -67,6 +81,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private readonly ICounter _counter;
|
||||
private readonly IHvExecutionContext _shadowContext;
|
||||
private IHvExecutionContext _impl;
|
||||
private int _shouldStep;
|
||||
|
||||
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||
|
||||
@ -103,6 +118,11 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void StepHandler()
|
||||
{
|
||||
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void SupervisorCallHandler(ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
@ -127,6 +147,30 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestDebugStep()
|
||||
{
|
||||
Interlocked.Exchange(ref _shouldStep, 1);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong DebugPc
|
||||
{
|
||||
get => Pc;
|
||||
set
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
_impl.ElrEl1 = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_impl.Pc = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopRunning()
|
||||
{
|
||||
@ -142,6 +186,22 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
|
||||
while (Running)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||
spsr |= (1 << 21);
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pstate |= (1 << 21);
|
||||
}
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
|
||||
}
|
||||
|
||||
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
|
||||
|
||||
HvExitReason reason = vcpu.ExitInfo->Reason;
|
||||
@ -209,6 +269,20 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
SupervisorCallHandler(elr - 4UL, id);
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
case ExceptionClass.SoftwareStepLowerEl:
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||
spsr &= ~((ulong)(1 << 21));
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError();
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0);
|
||||
ReturnToPool(vcpu);
|
||||
StepHandler();
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
case ExceptionClass.BrkAarch64:
|
||||
ReturnToPool(vcpu);
|
||||
BreakHandler(elr, (ushort)esr);
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unhandled guest exception {ec}.");
|
||||
}
|
||||
@ -219,10 +293,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
// TODO: Invalidate only the range that was modified?
|
||||
return HvAddressSpace.KernelRegionTlbiEretAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HvAddressSpace.KernelRegionEretAddress;
|
||||
}
|
||||
return HvAddressSpace.KernelRegionEretAddress;
|
||||
}
|
||||
|
||||
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
|
||||
|
@ -18,6 +18,8 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
|
||||
public bool IsAarch32 { get; set; }
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
private readonly ulong[] _x;
|
||||
private readonly V128[] _v;
|
||||
|
||||
@ -46,5 +48,14 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
_v[index] = value;
|
||||
}
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
}
|
||||
|
||||
public bool GetAndClearInterruptRequested()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ using ARMeilleure.State;
|
||||
using Ryujinx.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
@ -13,6 +14,8 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private static readonly SetSimdFpReg _setSimdFpReg;
|
||||
private static readonly nint _setSimdFpRegNativePtr;
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
static HvExecutionContextVcpu()
|
||||
{
|
||||
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
|
||||
@ -135,6 +138,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
}
|
||||
|
||||
private readonly ulong _vcpu;
|
||||
private int _interruptRequested;
|
||||
|
||||
public HvExecutionContextVcpu(ulong vcpu)
|
||||
{
|
||||
@ -180,8 +184,16 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
ulong vcpu = _vcpu;
|
||||
HvApi.hv_vcpus_exit(ref vcpu, 1);
|
||||
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
|
||||
{
|
||||
ulong vcpu = _vcpu;
|
||||
HvApi.hv_vcpus_exit(ref vcpu, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetAndClearInterruptRequested()
|
||||
{
|
||||
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
|
||||
uint Fpcr { get; set; }
|
||||
uint Fpsr { get; set; }
|
||||
ulong ThreadUid { get; set; }
|
||||
|
||||
ulong GetX(int index);
|
||||
void SetX(int index, ulong value);
|
||||
@ -39,5 +40,8 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
SetV(i, context.GetV(i));
|
||||
}
|
||||
}
|
||||
|
||||
void RequestInterrupt();
|
||||
bool GetAndClearInterruptRequested();
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ namespace Ryujinx.Cpu
|
||||
/// </summary>
|
||||
public readonly ExceptionCallback BreakCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CPU software interrupts caused by single-stepping.
|
||||
/// </summary>
|
||||
public readonly ExceptionCallbackNoArgs StepCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CPU software interrupts caused by the Arm SVC instruction.
|
||||
/// </summary>
|
||||
@ -47,16 +52,19 @@ namespace Ryujinx.Cpu
|
||||
/// </remarks>
|
||||
/// <param name="interruptCallback">Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/></param>
|
||||
/// <param name="breakCallback">Handler for CPU software interrupts caused by the Arm BRK instruction</param>
|
||||
/// <param name="stepCallback">Handler for CPU software interrupts caused by single-stepping</param>
|
||||
/// <param name="supervisorCallback">Handler for CPU software interrupts caused by the Arm SVC instruction</param>
|
||||
/// <param name="undefinedCallback">Handler for CPU software interrupts caused by any undefined Arm instruction</param>
|
||||
public ExceptionCallbacks(
|
||||
ExceptionCallbackNoArgs interruptCallback = null,
|
||||
ExceptionCallback breakCallback = null,
|
||||
ExceptionCallbackNoArgs stepCallback = null,
|
||||
ExceptionCallback supervisorCallback = null,
|
||||
ExceptionCallback undefinedCallback = null)
|
||||
{
|
||||
InterruptCallback = interruptCallback;
|
||||
BreakCallback = breakCallback;
|
||||
StepCallback = stepCallback;
|
||||
SupervisorCallback = supervisorCallback;
|
||||
UndefinedCallback = undefinedCallback;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using ARMeilleure.State;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
@ -46,6 +47,11 @@ namespace Ryujinx.Cpu
|
||||
/// </summary>
|
||||
bool IsAarch32 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Thread UID.
|
||||
/// </summary>
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the CPU is still running code.
|
||||
/// </summary>
|
||||
@ -108,5 +114,23 @@ namespace Ryujinx.Cpu
|
||||
/// If you only need to pause the thread temporarily, use <see cref="RequestInterrupt"/> instead.
|
||||
/// </remarks>
|
||||
void StopRunning();
|
||||
|
||||
/// <summary>
|
||||
/// Requests the thread to stop running temporarily and call <see cref="ExceptionCallbacks.InterruptCallback"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The thread might not pause immediately.
|
||||
/// One must not assume that guest code is no longer being executed by the thread after calling this function.
|
||||
/// After single stepping, the thread should call call <see cref="ExceptionCallbacks.StepCallback"/>.
|
||||
/// </remarks>
|
||||
void RequestDebugStep();
|
||||
|
||||
/// <summary>
|
||||
/// Current Program Counter (for debugging).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// PC register for the debugger. Must not be accessed while the thread isn't stopped for debugging.
|
||||
/// </remarks>
|
||||
ulong DebugPc { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using ExecutionContext = ARMeilleure.State.ExecutionContext;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
@ -53,6 +54,13 @@ namespace Ryujinx.Cpu.Jit
|
||||
set => _impl.IsAarch32 = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ThreadUid
|
||||
{
|
||||
get => _impl.ThreadUid;
|
||||
set => _impl.ThreadUid = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Running => _impl.Running;
|
||||
|
||||
@ -65,6 +73,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
counter,
|
||||
InterruptHandler,
|
||||
BreakHandler,
|
||||
StepHandler,
|
||||
SupervisorCallHandler,
|
||||
UndefinedHandler);
|
||||
|
||||
@ -93,6 +102,11 @@ namespace Ryujinx.Cpu.Jit
|
||||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void StepHandler(ExecutionContext context)
|
||||
{
|
||||
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
@ -109,6 +123,16 @@ namespace Ryujinx.Cpu.Jit
|
||||
_impl.RequestInterrupt();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestDebugStep() => _impl.RequestDebugStep();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong DebugPc
|
||||
{
|
||||
get => _impl.DebugPc;
|
||||
set => _impl.DebugPc = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopRunning()
|
||||
{
|
||||
|
@ -1,6 +1,8 @@
|
||||
using ARMeilleure;
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.State
|
||||
{
|
||||
@ -52,6 +54,8 @@ namespace Ryujinx.Cpu.LightningJit.State
|
||||
|
||||
public bool IsAarch32 { get; set; }
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
internal ExecutionMode ExecutionMode
|
||||
{
|
||||
get
|
||||
@ -77,15 +81,20 @@ namespace Ryujinx.Cpu.LightningJit.State
|
||||
|
||||
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
||||
private readonly ExceptionCallback _breakCallback;
|
||||
private readonly ExceptionCallbackNoArgs _stepCallback;
|
||||
private readonly ExceptionCallback _supervisorCallback;
|
||||
private readonly ExceptionCallback _undefinedCallback;
|
||||
|
||||
internal int ShouldStep;
|
||||
public ulong DebugPc { get; set; }
|
||||
|
||||
public ExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
_nativeContext = new NativeContext(allocator);
|
||||
_counter = counter;
|
||||
_interruptCallback = exceptionCallbacks.InterruptCallback;
|
||||
_breakCallback = exceptionCallbacks.BreakCallback;
|
||||
_stepCallback = exceptionCallbacks.StepCallback;
|
||||
_supervisorCallback = exceptionCallbacks.SupervisorCallback;
|
||||
_undefinedCallback = exceptionCallbacks.UndefinedCallback;
|
||||
|
||||
@ -117,6 +126,17 @@ namespace Ryujinx.Cpu.LightningJit.State
|
||||
_interrupted = true;
|
||||
}
|
||||
|
||||
public void StepHandler()
|
||||
{
|
||||
_stepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
public void RequestDebugStep()
|
||||
{
|
||||
Interlocked.Exchange(ref ShouldStep, 1);
|
||||
RequestInterrupt();
|
||||
}
|
||||
|
||||
internal void OnBreak(ulong address, int imm)
|
||||
{
|
||||
_breakCallback?.Invoke(this, address, imm);
|
||||
|
@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
||||
/// </summary>
|
||||
class Buffer : IRange, ISyncActionHandler, IDisposable
|
||||
class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable
|
||||
{
|
||||
private const ulong GranularBufferThreshold = 4096;
|
||||
|
||||
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Size of the buffer in bytes.
|
||||
/// </summary>
|
||||
public ulong Size { get; }
|
||||
public ulong Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// End address of the buffer in guest memory.
|
||||
@ -60,13 +60,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <remarks>
|
||||
/// This is null until at least one modification occurs.
|
||||
/// </remarks>
|
||||
private BufferModifiedRangeList _modifiedRanges = null;
|
||||
private BufferModifiedRangeList _modifiedRanges;
|
||||
|
||||
/// <summary>
|
||||
/// A structure that is used to flush buffer data back to a host mapped buffer for cached readback.
|
||||
/// Only used if the buffer data is explicitly owned by device local memory.
|
||||
/// </summary>
|
||||
private BufferPreFlush _preFlush = null;
|
||||
private BufferPreFlush _preFlush;
|
||||
|
||||
/// <summary>
|
||||
/// Usage tracking state that determines what type of backing the buffer should use.
|
||||
@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong size,
|
||||
BufferStage stage,
|
||||
bool sparseCompatible,
|
||||
IEnumerable<Buffer> baseBuffers = null)
|
||||
List<Buffer> baseBuffers)
|
||||
{
|
||||
_context = context;
|
||||
_physicalMemory = physicalMemory;
|
||||
@ -126,21 +126,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_useGranular = size > GranularBufferThreshold;
|
||||
|
||||
IEnumerable<IRegionHandle> baseHandles = null;
|
||||
List<IRegionHandle> baseHandles = null;
|
||||
|
||||
if (baseBuffers != null)
|
||||
if (baseBuffers.Count != 0)
|
||||
{
|
||||
baseHandles = baseBuffers.SelectMany(buffer =>
|
||||
baseHandles = new List<IRegionHandle>();
|
||||
foreach (Buffer buffer in baseBuffers)
|
||||
{
|
||||
if (buffer._useGranular)
|
||||
{
|
||||
return buffer._memoryTrackingGranular.GetHandles();
|
||||
baseHandles.AddRange((buffer._memoryTrackingGranular.GetHandles()));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Enumerable.Repeat(buffer._memoryTracking, 1);
|
||||
baseHandles.Add(buffer._memoryTracking);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (_useGranular)
|
||||
@ -171,9 +172,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_memoryTracking.RegisterPreciseAction(PreciseAction);
|
||||
}
|
||||
|
||||
_externalFlushDelegate = new RegionSignal(ExternalFlush);
|
||||
_loadDelegate = new Action<ulong, ulong>(LoadRegion);
|
||||
_modifiedDelegate = new Action<ulong, ulong>(RegionModified);
|
||||
_externalFlushDelegate = ExternalFlush;
|
||||
_loadDelegate = LoadRegion;
|
||||
_modifiedDelegate = RegionModified;
|
||||
|
||||
_virtualDependenciesLock = new ReaderWriterLockSlim();
|
||||
}
|
||||
@ -247,6 +248,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return Address < address + size && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given range is fully contained in the buffer.
|
||||
/// </summary>
|
||||
@ -435,7 +441,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="from">The buffer to inherit from</param>
|
||||
public void InheritModifiedRanges(Buffer from)
|
||||
{
|
||||
if (from._modifiedRanges != null && from._modifiedRanges.HasRanges)
|
||||
if (from._modifiedRanges is { HasRanges: true })
|
||||
{
|
||||
if (from._syncActionRegistered && !_syncActionRegistered)
|
||||
{
|
||||
@ -443,7 +449,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
|
||||
void registerRangeAction(ulong address, ulong size)
|
||||
void RegisterRangeAction(ulong address, ulong size)
|
||||
{
|
||||
if (_useGranular)
|
||||
{
|
||||
@ -457,7 +463,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
EnsureRangeList();
|
||||
|
||||
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
|
||||
_modifiedRanges.InheritRanges(from._modifiedRanges, RegisterRangeAction);
|
||||
}
|
||||
|
||||
if (from._dirtyStart != ulong.MaxValue)
|
||||
@ -499,14 +505,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
// Cut off the start.
|
||||
|
||||
if (end < _dirtyEnd)
|
||||
{
|
||||
_dirtyStart = end;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dirtyStart = ulong.MaxValue;
|
||||
}
|
||||
_dirtyStart = end < _dirtyEnd ? end : ulong.MaxValue;
|
||||
}
|
||||
else if (end >= _dirtyEnd)
|
||||
{
|
||||
|
@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="parent">Parent buffer</param>
|
||||
/// <param name="stage">Initial buffer stage</param>
|
||||
/// <param name="baseBuffers">Buffers to inherit state from</param>
|
||||
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, IEnumerable<Buffer> baseBuffers = null)
|
||||
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, List<Buffer> baseBuffers)
|
||||
{
|
||||
_size = (int)parent.Size;
|
||||
_systemMemoryType = context.Capabilities.MemoryType;
|
||||
@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
BufferStage storageFlags = stage & BufferStage.StorageMask;
|
||||
|
||||
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers == null)
|
||||
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers.Count == 0)
|
||||
{
|
||||
_desiredType = BufferBackingType.DeviceMemory;
|
||||
}
|
||||
@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// TODO: Might be nice to force atomic access to be device local for any stage.
|
||||
}
|
||||
|
||||
if (baseBuffers != null)
|
||||
if (baseBuffers.Count != 0)
|
||||
{
|
||||
foreach (Buffer buffer in baseBuffers)
|
||||
{
|
||||
|
@ -2,7 +2,6 @@ using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
@ -39,11 +38,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Only modified from the GPU thread. Must lock for add/remove.
|
||||
/// Must lock for any access from other threads.
|
||||
/// </remarks>
|
||||
private readonly RangeList<Buffer> _buffers;
|
||||
private readonly NonOverlappingRangeList<Buffer> _buffers;
|
||||
private readonly MultiRangeList<MultiRangeBuffer> _multiRangeBuffers;
|
||||
|
||||
private Buffer[] _bufferOverlaps;
|
||||
|
||||
private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache;
|
||||
private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache;
|
||||
private bool _pruneCaches;
|
||||
@ -64,8 +61,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_buffers = [];
|
||||
_multiRangeBuffers = [];
|
||||
|
||||
_bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity];
|
||||
|
||||
_dirtyCache = new Dictionary<ulong, BufferCacheEntry>();
|
||||
|
||||
// There are a lot more entries on the modified cache, so it is separate from the one for ForceDirty.
|
||||
@ -79,24 +74,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="e">Event arguments</param>
|
||||
public void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
|
||||
{
|
||||
Buffer[] overlaps = new Buffer[10];
|
||||
int overlapCount;
|
||||
|
||||
MultiRange range = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||
|
||||
for (int index = 0; index < range.Count; index++)
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(index);
|
||||
|
||||
lock (_buffers)
|
||||
_buffers.Lock.EnterReadLock();
|
||||
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(subRange.Address, subRange.Size);
|
||||
|
||||
RangeItem<Buffer> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
overlapCount = _buffers.FindOverlaps(subRange.Address, subRange.Size, ref overlaps);
|
||||
current.Value.Unmapped(subRange.Address, subRange.Size);
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
for (int i = 0; i < overlapCount; i++)
|
||||
{
|
||||
overlaps[i].Unmapped(subRange.Address, subRange.Size);
|
||||
}
|
||||
_buffers.Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +131,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <returns>Physical ranges of the buffer, after address translation</returns>
|
||||
public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
|
||||
{
|
||||
if (gpuVa == 0)
|
||||
if (gpuVa == 0 || size == 0)
|
||||
{
|
||||
return new MultiRange(MemoryManager.PteUnmapped, size);
|
||||
}
|
||||
@ -336,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
|
||||
ulong alignedSize = alignedEndAddress - alignedAddress;
|
||||
|
||||
Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
|
||||
Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value;
|
||||
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
|
||||
|
||||
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
|
||||
@ -403,7 +397,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
||||
{
|
||||
Buffer buffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size);
|
||||
Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
|
||||
|
||||
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
|
||||
physicalBuffers.Add(buffer);
|
||||
@ -495,10 +489,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="stage">The type of usage that created the buffer</param>
|
||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
|
||||
{
|
||||
Buffer[] overlaps = _bufferOverlaps;
|
||||
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(address, size);
|
||||
|
||||
if (overlapsCount != 0)
|
||||
if (first is not null)
|
||||
{
|
||||
// The buffer already exists. We can just return the existing buffer
|
||||
// if the buffer we need is fully contained inside the overlapping buffer.
|
||||
@ -507,9 +501,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// old buffer(s) to the new buffer.
|
||||
|
||||
ulong endAddress = address + size;
|
||||
Buffer overlap0 = overlaps[0];
|
||||
|
||||
if (overlap0.Address > address || overlap0.EndAddress < endAddress)
|
||||
if (first.Address > address || first.EndAddress < endAddress)
|
||||
{
|
||||
bool anySparseCompatible = false;
|
||||
|
||||
@ -522,53 +515,52 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// sequential memory.
|
||||
// Allowing for 2 pages (rather than just one) is necessary to catch cases where the
|
||||
// range crosses a page, and after alignment, ends having a size of 2 pages.
|
||||
if (overlapsCount == 1 &&
|
||||
address >= overlap0.Address &&
|
||||
endAddress - overlap0.EndAddress <= BufferAlignmentSize * 2)
|
||||
if (first == last &&
|
||||
address >= first.Address &&
|
||||
endAddress - first.EndAddress <= BufferAlignmentSize * 2)
|
||||
{
|
||||
// Try to grow the buffer by 1.5x of its current size.
|
||||
// This improves performance in the cases where the buffer is resized often by small amounts.
|
||||
ulong existingSize = overlap0.Size;
|
||||
ulong existingSize = first.Value.Size;
|
||||
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
|
||||
|
||||
size = Math.Max(size, growthSize);
|
||||
endAddress = address + size;
|
||||
|
||||
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
(first, last) = _buffers.FindOverlaps(address, size);
|
||||
}
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
address = Math.Min(address, first.Address);
|
||||
endAddress = Math.Max(endAddress, last.EndAddress);
|
||||
|
||||
List<Buffer> overlaps = [];
|
||||
|
||||
RangeItem<Buffer> current = first;
|
||||
while (current != last.Next)
|
||||
{
|
||||
Buffer buffer = overlaps[index];
|
||||
anySparseCompatible |= current.Value.SparseCompatible;
|
||||
overlaps.Add(current.Value);
|
||||
_buffers.Remove(current.Value);
|
||||
|
||||
anySparseCompatible |= buffer.SparseCompatible;
|
||||
|
||||
address = Math.Min(address, buffer.Address);
|
||||
endAddress = Math.Max(endAddress, buffer.EndAddress);
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
_buffers.Remove(buffer);
|
||||
}
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount);
|
||||
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps);
|
||||
|
||||
_buffers.Add(newBuffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No overlap, just create a new buffer.
|
||||
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false);
|
||||
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
_buffers.Add(buffer);
|
||||
}
|
||||
_buffers.Add(buffer);
|
||||
}
|
||||
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -582,72 +574,68 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="alignment">Alignment of the start address of the buffer</param>
|
||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
|
||||
{
|
||||
Buffer[] overlaps = _bufferOverlaps;
|
||||
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
|
||||
|
||||
if (overlapsCount != 0)
|
||||
_buffers.Lock.EnterWriteLock();
|
||||
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(address, size);
|
||||
|
||||
if (first is not null)
|
||||
{
|
||||
// If the buffer already exists, make sure if covers the entire range,
|
||||
// and make sure it is properly aligned, otherwise sparse mapping may fail.
|
||||
|
||||
ulong endAddress = address + size;
|
||||
Buffer overlap0 = overlaps[0];
|
||||
|
||||
if (overlap0.Address > address ||
|
||||
overlap0.EndAddress < endAddress ||
|
||||
(overlap0.Address & (alignment - 1)) != 0 ||
|
||||
(!overlap0.SparseCompatible && sparseAligned))
|
||||
if (first.Address > address ||
|
||||
first.EndAddress < endAddress ||
|
||||
(first.Address & (alignment - 1)) != 0 ||
|
||||
(!first.Value.SparseCompatible && sparseAligned))
|
||||
{
|
||||
// We need to make sure the new buffer is properly aligned.
|
||||
// However, after the range is aligned, it is possible that it
|
||||
// overlaps more buffers, so try again after each extension
|
||||
// and ensure we cover all overlaps.
|
||||
|
||||
int oldOverlapsCount;
|
||||
RangeItem<Buffer> oldFirst;
|
||||
endAddress = Math.Max(endAddress, last.EndAddress);
|
||||
|
||||
do
|
||||
{
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Buffer buffer = overlaps[index];
|
||||
|
||||
address = Math.Min(address, buffer.Address);
|
||||
endAddress = Math.Max(endAddress, buffer.EndAddress);
|
||||
}
|
||||
address = Math.Min(address, first.Address);
|
||||
|
||||
address &= ~(alignment - 1);
|
||||
|
||||
oldOverlapsCount = overlapsCount;
|
||||
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, endAddress - address, ref overlaps);
|
||||
}
|
||||
while (oldOverlapsCount != overlapsCount);
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
_buffers.Remove(overlaps[index]);
|
||||
}
|
||||
oldFirst = first;
|
||||
(first, last) = _buffers.FindOverlaps(address, endAddress - address);
|
||||
}
|
||||
while (oldFirst != first);
|
||||
|
||||
ulong newSize = endAddress - address;
|
||||
|
||||
CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount);
|
||||
List<Buffer> overlaps = [];
|
||||
|
||||
RangeItem<Buffer> current = first;
|
||||
while (current != last.Next)
|
||||
{
|
||||
overlaps.Add(current.Value);
|
||||
_buffers.Remove(current.Value);
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps);
|
||||
|
||||
_buffers.Add(newBuffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No overlap, just create a new buffer.
|
||||
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned);
|
||||
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
_buffers.Add(buffer);
|
||||
}
|
||||
_buffers.Add(buffer);
|
||||
}
|
||||
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
_buffers.Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -660,17 +648,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="stage">The type of usage that created the buffer</param>
|
||||
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
||||
/// <param name="overlaps">Buffers overlapping the range</param>
|
||||
/// <param name="overlapsCount">Total of overlaps</param>
|
||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
|
||||
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, List<Buffer> overlaps)
|
||||
{
|
||||
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps.Take(overlapsCount));
|
||||
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
|
||||
|
||||
lock (_buffers)
|
||||
{
|
||||
_buffers.Add(newBuffer);
|
||||
}
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
for (int index = 0; index < overlaps.Count; index++)
|
||||
{
|
||||
Buffer buffer = overlaps[index];
|
||||
|
||||
@ -688,6 +670,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
NotifyBuffersModified?.Invoke();
|
||||
|
||||
RecreateMultiRangeBuffers(address, size);
|
||||
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -718,17 +702,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the temporary buffer used for range list intersection results, if it has grown too much.
|
||||
/// </summary>
|
||||
private void ShrinkOverlapsBufferIfNeeded()
|
||||
{
|
||||
if (_bufferOverlaps.Length > OverlapsBufferMaxCapacity)
|
||||
{
|
||||
Array.Resize(ref _bufferOverlaps, OverlapsBufferMaxCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy a buffer data from a given address to another.
|
||||
/// </summary>
|
||||
@ -909,7 +882,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
MemoryRange subRange = range.GetSubRange(i);
|
||||
|
||||
Buffer subBuffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size);
|
||||
Buffer subBuffer = _buffers.FindOverlapFast(subRange.Address, subRange.Size).Value;
|
||||
|
||||
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
|
||||
|
||||
@ -957,7 +930,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (size != 0)
|
||||
{
|
||||
buffer = _buffers.FindFirstOverlap(address, size);
|
||||
buffer = _buffers.FindOverlapFast(address, size).Value;
|
||||
|
||||
buffer.CopyFromDependantVirtualBuffers();
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
@ -969,7 +942,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = _buffers.FindFirstOverlap(address, 1);
|
||||
buffer = _buffers.FindOverlapFast(address, 1).Value;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
@ -1007,7 +980,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
if (size != 0)
|
||||
{
|
||||
Buffer buffer = _buffers.FindFirstOverlap(address, size);
|
||||
Buffer buffer = _buffers.FindOverlapFast(address, size).Value;
|
||||
|
||||
if (copyBackVirtual)
|
||||
{
|
||||
|
@ -258,7 +258,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
RecordStorageAlignment(_cpStorageBuffers, index, gpuVa);
|
||||
|
||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
|
||||
|
||||
@ -282,7 +282,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
RecordStorageAlignment(buffers, index, gpuVa);
|
||||
|
||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
||||
|
||||
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
|
||||
|
||||
@ -761,7 +761,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (!bounds.IsUnmapped)
|
||||
{
|
||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
bool isWrite = (bounds.Flags & BufferUsageFlags.Write) == BufferUsageFlags.Write;
|
||||
BufferRange range = isStorage
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Range, bufferStage);
|
||||
@ -798,7 +798,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
if (!bounds.IsUnmapped)
|
||||
{
|
||||
bool isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||
bool isWrite = (bounds.Flags & BufferUsageFlags.Write) == BufferUsageFlags.Write;
|
||||
BufferRange range = isStorage
|
||||
? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
||||
: bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
||||
@ -817,7 +817,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Bind respective buffer bindings on the host API.
|
||||
/// </summary>
|
||||
/// <param name="ranges">Host buffers to bind, with their offsets and sizes</param>
|
||||
/// <param name="first">First binding point</param>
|
||||
/// <param name="count">Number of bindings</param>
|
||||
/// <param name="isStorage">Indicates if the buffers are storage or uniform buffers</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@ -866,7 +865,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="texture">Buffer texture</param>
|
||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||
/// <param name="bindingInfo">Binding info for the buffer texture</param>
|
||||
/// <param name="format">Format of the buffer texture</param>
|
||||
/// <param name="isImage">Whether the binding is for an image or a sampler</param>
|
||||
public void SetBufferTextureStorage(
|
||||
ShaderStage stage,
|
||||
@ -889,7 +887,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||
/// <param name="bindingInfo">Binding info for the buffer texture</param>
|
||||
/// <param name="index">Index of the binding on the array</param>
|
||||
/// <param name="format">Format of the buffer texture</param>
|
||||
public void SetBufferTextureStorage(
|
||||
ShaderStage stage,
|
||||
ITextureArray array,
|
||||
@ -912,7 +909,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||
/// <param name="bindingInfo">Binding info for the buffer texture</param>
|
||||
/// <param name="index">Index of the binding on the array</param>
|
||||
/// <param name="format">Format of the buffer texture</param>
|
||||
public void SetBufferTextureStorage(
|
||||
ShaderStage stage,
|
||||
IImageArray array,
|
||||
|
@ -1,25 +1,24 @@
|
||||
using Ryujinx.Common.Pools;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// A range within a buffer that has been modified by the GPU.
|
||||
/// </summary>
|
||||
class BufferModifiedRange : IRange
|
||||
class BufferModifiedRange : INonOverlappingRange
|
||||
{
|
||||
/// <summary>
|
||||
/// Start address of the range in guest memory.
|
||||
/// </summary>
|
||||
public ulong Address { get; }
|
||||
public ulong Address { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the range in bytes.
|
||||
/// </summary>
|
||||
public ulong Size { get; }
|
||||
public ulong Size { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// End address of the range in guest memory.
|
||||
@ -61,14 +60,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A structure used to track GPU modified ranges within a buffer.
|
||||
/// </summary>
|
||||
class BufferModifiedRangeList : RangeList<BufferModifiedRange>
|
||||
class BufferModifiedRangeList : NonOverlappingRangeList<BufferModifiedRange>
|
||||
{
|
||||
private const int BackingInitialSize = 8;
|
||||
private new const int BackingInitialSize = 8;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly Buffer _parent;
|
||||
@ -77,8 +81,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private BufferMigration _source;
|
||||
private BufferModifiedRangeList _migrationTarget;
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the modified range list has any entries or not.
|
||||
/// </summary>
|
||||
@ -86,10 +88,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return Count > 0;
|
||||
}
|
||||
Lock.EnterReadLock();
|
||||
bool result = Count > 0;
|
||||
Lock.ExitReadLock();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,33 +116,41 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="action">Action to perform for each remaining sub-range of the input range</param>
|
||||
public void ExcludeModifiedRegions(ulong address, ulong size, Action<ulong, ulong> action)
|
||||
{
|
||||
lock (_lock)
|
||||
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
|
||||
bool lockOwner = Lock.IsReadLockHeld;
|
||||
if (!lockOwner)
|
||||
{
|
||||
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
|
||||
ref BufferModifiedRange[] overlaps = ref ThreadStaticArray<BufferModifiedRange>.Get();
|
||||
Lock.EnterReadLock();
|
||||
}
|
||||
|
||||
int count = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
RangeItem<BufferModifiedRange> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
BufferModifiedRange overlap = current.Value;
|
||||
|
||||
if (overlap.Address > address)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
|
||||
if (overlap.Address > address)
|
||||
{
|
||||
// The start of the remaining region is uncovered by this overlap. Call the action for it.
|
||||
action(address, overlap.Address - address);
|
||||
}
|
||||
|
||||
// Remaining region is after this overlap.
|
||||
size -= overlap.EndAddress - address;
|
||||
address = overlap.EndAddress;
|
||||
// The start of the remaining region is uncovered by this overlap. Call the action for it.
|
||||
action(address, overlap.Address - address);
|
||||
}
|
||||
|
||||
if ((long)size > 0)
|
||||
{
|
||||
// If there is any region left after removing the overlaps, signal it.
|
||||
action(address, size);
|
||||
}
|
||||
// Remaining region is after this overlap.
|
||||
size -= overlap.EndAddress - address;
|
||||
address = overlap.EndAddress;
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
if (!lockOwner)
|
||||
{
|
||||
Lock.ExitReadLock();
|
||||
}
|
||||
|
||||
if ((long)size > 0)
|
||||
{
|
||||
// If there is any region left after removing the overlaps, signal it.
|
||||
action(address, size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,51 +162,101 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size of the modified region in bytes</param>
|
||||
public void SignalModified(ulong address, ulong size)
|
||||
{
|
||||
// Must lock, as this can affect flushes from the background thread.
|
||||
lock (_lock)
|
||||
// We may overlap with some existing modified regions. They must be cut into by the new entry.
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
||||
|
||||
ulong endAddress = address + size;
|
||||
ulong syncNumber = _context.SyncNumber;
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
// We may overlap with some existing modified regions. They must be cut into by the new entry.
|
||||
ref BufferModifiedRange[] overlaps = ref ThreadStaticArray<BufferModifiedRange>.Get();
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
int count = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
BufferModifiedRange buffPost = null;
|
||||
bool extendsPost = false;
|
||||
bool extendsPre = false;
|
||||
|
||||
ulong endAddress = address + size;
|
||||
ulong syncNumber = _context.SyncNumber;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
if (first == last)
|
||||
{
|
||||
if (first.Address == address && first.EndAddress == endAddress)
|
||||
{
|
||||
// The overlaps must be removed or split.
|
||||
first.Value.SyncNumber = syncNumber;
|
||||
first.Value.Parent = this;
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
if (first.Address < address)
|
||||
{
|
||||
first.Value.Size = address - first.Address;
|
||||
|
||||
if (overlap.Address == address && overlap.Size == size)
|
||||
extendsPre = true;
|
||||
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
// Region already exists. Just update the existing sync number.
|
||||
overlap.SyncNumber = syncNumber;
|
||||
overlap.Parent = this;
|
||||
|
||||
return;
|
||||
buffPost = new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
extendsPost = true;
|
||||
}
|
||||
|
||||
Remove(overlap);
|
||||
|
||||
if (overlap.Address < address && overlap.EndAddress > address)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
// A split item must be created behind this overlap.
|
||||
|
||||
Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent));
|
||||
first.Value.Size = first.EndAddress - endAddress;
|
||||
first.Value.Address = endAddress;
|
||||
}
|
||||
|
||||
if (overlap.Address < endAddress && overlap.EndAddress > endAddress)
|
||||
else
|
||||
{
|
||||
// A split item must be created after this overlap.
|
||||
|
||||
Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent));
|
||||
Remove(first.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (extendsPre && extendsPost)
|
||||
{
|
||||
Add(buffPost);
|
||||
}
|
||||
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
Lock.ExitWriteLock();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
BufferModifiedRange buffPre = null;
|
||||
|
||||
if (first.Address < address)
|
||||
{
|
||||
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
extendsPre = true;
|
||||
}
|
||||
|
||||
if (last.EndAddress > endAddress)
|
||||
{
|
||||
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
|
||||
last.Value.SyncNumber, last.Value.Parent);
|
||||
extendsPost = true;
|
||||
}
|
||||
|
||||
RemoveRange(first, last);
|
||||
|
||||
if (extendsPre)
|
||||
{
|
||||
Add(buffPre);
|
||||
}
|
||||
|
||||
if (extendsPost)
|
||||
{
|
||||
Add(buffPost);
|
||||
}
|
||||
|
||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -208,25 +268,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
int count = 0;
|
||||
Lock.EnterReadLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
||||
|
||||
ref BufferModifiedRange[] overlaps = ref ThreadStaticArray<BufferModifiedRange>.Get();
|
||||
|
||||
// Range list must be consistent for this operation.
|
||||
lock (_lock)
|
||||
RangeItem<BufferModifiedRange> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
count = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
BufferModifiedRange overlap = current.Value;
|
||||
|
||||
if (overlap.SyncNumber == syncNumber)
|
||||
{
|
||||
rangeAction(overlap.Address, overlap.Size);
|
||||
}
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
Lock.ExitReadLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -237,19 +295,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||
public void GetRanges(ulong address, ulong size, Action<ulong, ulong> rangeAction)
|
||||
{
|
||||
int count = 0;
|
||||
List<RangeItem<BufferModifiedRange>> overlaps = [];
|
||||
|
||||
ref BufferModifiedRange[] overlaps = ref ThreadStaticArray<BufferModifiedRange>.Get();
|
||||
// We use the non-span method here because keeping the lock will cause a deadlock.
|
||||
Lock.EnterReadLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
||||
|
||||
// Range list must be consistent for this operation.
|
||||
lock (_lock)
|
||||
RangeItem<BufferModifiedRange> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
count = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
overlaps.Add(current);
|
||||
current = current.Next;
|
||||
}
|
||||
Lock.ExitReadLock();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
for (int i = 0; i < overlaps.Count; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
rangeAction(overlap.Address, overlap.Size);
|
||||
}
|
||||
}
|
||||
@ -262,11 +324,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <returns>True if a range exists in the specified region, false otherwise</returns>
|
||||
public bool HasRange(ulong address, ulong size)
|
||||
{
|
||||
// Range list must be consistent for this operation.
|
||||
lock (_lock)
|
||||
{
|
||||
return FindOverlapsNonOverlapping(address, size, ref ThreadStaticArray<BufferModifiedRange>.Get()) > 0;
|
||||
}
|
||||
Lock.EnterReadLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> _) = FindOverlaps(address, size);
|
||||
bool result = first is not null;
|
||||
Lock.ExitReadLock();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -298,38 +360,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="address">The start address of the flush range</param>
|
||||
/// <param name="endAddress">The end address of the flush range</param>
|
||||
private void RemoveRangesAndFlush(
|
||||
BufferModifiedRange[] overlaps,
|
||||
RangeItem<BufferModifiedRange>[] overlaps,
|
||||
int rangeCount,
|
||||
long highestDiff,
|
||||
ulong currentSync,
|
||||
ulong address,
|
||||
ulong endAddress)
|
||||
{
|
||||
lock (_lock)
|
||||
if (_migrationTarget == null)
|
||||
{
|
||||
if (_migrationTarget == null)
|
||||
ulong waitSync = currentSync + (ulong)highestDiff;
|
||||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
ulong waitSync = currentSync + (ulong)highestDiff;
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
if (diff <= highestDiff)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
ulong clampAddress = Math.Max(address, overlap.Address);
|
||||
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
Lock.EnterWriteLock();
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
Lock.ExitWriteLock();
|
||||
|
||||
if (diff <= highestDiff)
|
||||
{
|
||||
ulong clampAddress = Math.Max(address, overlap.Address);
|
||||
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
||||
|
||||
ClearPart(overlap, clampAddress, clampEnd);
|
||||
|
||||
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
|
||||
}
|
||||
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine.
|
||||
@ -355,28 +416,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
int rangeCount = 0;
|
||||
|
||||
ref BufferModifiedRange[] overlaps = ref ThreadStaticArray<BufferModifiedRange>.Get();
|
||||
List<RangeItem<BufferModifiedRange>> overlaps = [];
|
||||
|
||||
// Range list must be consistent for this operation
|
||||
lock (_lock)
|
||||
Lock.EnterReadLock();
|
||||
if (_migrationTarget != null)
|
||||
{
|
||||
if (_migrationTarget != null)
|
||||
rangeCount = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
||||
|
||||
RangeItem<BufferModifiedRange> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
rangeCount = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
rangeCount++;
|
||||
overlaps.Add(current);
|
||||
current = current.Next;
|
||||
}
|
||||
}
|
||||
Lock.ExitReadLock();
|
||||
|
||||
if (rangeCount == -1)
|
||||
{
|
||||
_migrationTarget.WaitForAndFlushRanges(address, size);
|
||||
_migrationTarget!.WaitForAndFlushRanges(address, size);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (rangeCount == 0)
|
||||
|
||||
if (rangeCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -388,7 +458,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
{
|
||||
BufferModifiedRange overlap = overlaps[i];
|
||||
BufferModifiedRange overlap = overlaps[i].Value;
|
||||
|
||||
long diff = (long)(overlap.SyncNumber - currentSync);
|
||||
|
||||
@ -406,7 +476,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
// Wait for the syncpoint.
|
||||
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
|
||||
|
||||
RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
|
||||
RemoveRangesAndFlush(overlaps.ToArray(), rangeCount, highestDiff, currentSync, address, endAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -419,42 +489,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="registerRangeAction">The action to call for each modified range</param>
|
||||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
|
||||
{
|
||||
BufferModifiedRange[] inheritRanges;
|
||||
ranges.Lock.EnterReadLock();
|
||||
BufferModifiedRange[] inheritRanges = ranges.ToArray();
|
||||
ranges.Lock.ExitReadLock();
|
||||
|
||||
lock (ranges._lock)
|
||||
// Copy over the migration from the previous range list
|
||||
|
||||
BufferMigration oldMigration = ranges._source;
|
||||
|
||||
BufferMigrationSpan span = new(ranges._parent, ranges._flushAction, oldMigration);
|
||||
ranges._parent.IncrementReferenceCount();
|
||||
|
||||
if (_source == null)
|
||||
{
|
||||
inheritRanges = ranges.ToArray();
|
||||
// Create a new migration.
|
||||
_source = new BufferMigration([span], this, _context.SyncNumber);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
// Copy over the migration from the previous range list
|
||||
|
||||
BufferMigration oldMigration = ranges._source;
|
||||
|
||||
BufferMigrationSpan span = new(ranges._parent, ranges._flushAction, oldMigration);
|
||||
ranges._parent.IncrementReferenceCount();
|
||||
|
||||
if (_source == null)
|
||||
{
|
||||
// Create a new migration.
|
||||
_source = new BufferMigration([span], this, _context.SyncNumber);
|
||||
|
||||
_context.RegisterBufferMigration(_source);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extend the migration
|
||||
_source.AddSpanToEnd(span);
|
||||
}
|
||||
|
||||
ranges._migrationTarget = this;
|
||||
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
{
|
||||
Add(range);
|
||||
}
|
||||
}
|
||||
_context.RegisterBufferMigration(_source);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extend the migration
|
||||
_source.AddSpanToEnd(span);
|
||||
}
|
||||
|
||||
ranges._migrationTarget = this;
|
||||
|
||||
Lock.EnterWriteLock();
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
{
|
||||
Add(range);
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
|
||||
ulong currentSync = _context.SyncNumber;
|
||||
foreach (BufferModifiedRange range in inheritRanges)
|
||||
@ -473,18 +540,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
public void SelfMigration()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
BufferMigrationSpan span = new(_parent, _parent.GetSnapshotDisposeAction(), _parent.GetSnapshotFlushAction(), _source);
|
||||
BufferMigration migration = new([span], this, _context.SyncNumber);
|
||||
BufferMigrationSpan span = new(_parent, _parent.GetSnapshotDisposeAction(),
|
||||
_parent.GetSnapshotFlushAction(), _source);
|
||||
BufferMigration migration = new([span], this, _context.SyncNumber);
|
||||
|
||||
// Migration target is used to redirect flush actions to the latest range list,
|
||||
// so we don't need to set it here. (this range list is still the latest)
|
||||
// Migration target is used to redirect flush actions to the latest range list,
|
||||
// so we don't need to set it here. (this range list is still the latest)
|
||||
|
||||
_context.RegisterBufferMigration(migration);
|
||||
_context.RegisterBufferMigration(migration);
|
||||
|
||||
_source = migration;
|
||||
}
|
||||
Lock.EnterWriteLock();
|
||||
_source = migration;
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -493,13 +560,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="migration">The migration to remove</param>
|
||||
public void RemoveMigration(BufferMigration migration)
|
||||
{
|
||||
lock (_lock)
|
||||
Lock.EnterWriteLock();
|
||||
if (_source == migration)
|
||||
{
|
||||
if (_source == migration)
|
||||
{
|
||||
_source = null;
|
||||
}
|
||||
_source = null;
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress)
|
||||
@ -526,33 +593,85 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <param name="size">Size to clear</param>
|
||||
public void Clear(ulong address, ulong size)
|
||||
{
|
||||
lock (_lock)
|
||||
ulong endAddress = address + size;
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
||||
|
||||
if (first is null)
|
||||
{
|
||||
// This function can be called from any thread, so it cannot use the arrays for background or foreground.
|
||||
BufferModifiedRange[] toClear = new BufferModifiedRange[1];
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
int rangeCount = FindOverlapsNonOverlapping(address, size, ref toClear);
|
||||
BufferModifiedRange buffPost = null;
|
||||
bool extendsPost = false;
|
||||
bool extendsPre = false;
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
for (int i = 0; i < rangeCount; i++)
|
||||
if (first == last)
|
||||
{
|
||||
if (first.Address < address)
|
||||
{
|
||||
BufferModifiedRange overlap = toClear[i];
|
||||
first.Value.Size = address - first.Address;
|
||||
extendsPre = true;
|
||||
|
||||
ClearPart(overlap, address, endAddress);
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
buffPost = new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
extendsPost = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.EndAddress > endAddress)
|
||||
{
|
||||
first.Value.Size = first.EndAddress - endAddress;
|
||||
first.Value.Address = endAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(first.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all modified ranges.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Count = 0;
|
||||
if (extendsPre && extendsPost)
|
||||
{
|
||||
Add(buffPost);
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
BufferModifiedRange buffPre = null;
|
||||
|
||||
if (first.Address < address)
|
||||
{
|
||||
buffPre = new BufferModifiedRange(first.Address, address - first.Address,
|
||||
first.Value.SyncNumber, first.Value.Parent);
|
||||
extendsPre = true;
|
||||
}
|
||||
|
||||
if (last.EndAddress > endAddress)
|
||||
{
|
||||
buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress,
|
||||
last.Value.SyncNumber, last.Value.Parent);
|
||||
extendsPost = true;
|
||||
}
|
||||
|
||||
RemoveRange(first, last);
|
||||
|
||||
if (extendsPre)
|
||||
{
|
||||
Add(buffPre);
|
||||
}
|
||||
|
||||
if (extendsPost)
|
||||
{
|
||||
Add(buffPost);
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
@ -7,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Pipeline stages that can modify buffer data, as well as flags indicating storage usage.
|
||||
/// Must match ShaderStage for the shader stages, though anything after that can be in any order.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum BufferStage : byte
|
||||
{
|
||||
Compute,
|
||||
|
@ -691,10 +691,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
_pageTable[l0] = new ulong[PtLvl1Size];
|
||||
|
||||
for (ulong index = 0; index < PtLvl1Size; index++)
|
||||
{
|
||||
_pageTable[l0][index] = PteUnmapped;
|
||||
}
|
||||
Array.Fill(_pageTable[l0], PteUnmapped);
|
||||
}
|
||||
|
||||
_pageTable[l0][l1] = pte;
|
||||
|
@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Represents a GPU virtual memory range.
|
||||
/// </summary>
|
||||
private readonly struct VirtualRange : IRange
|
||||
private class VirtualRange : INonOverlappingRange
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU virtual address where the range starts.
|
||||
@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Size of the range in bytes.
|
||||
/// </summary>
|
||||
public ulong Size { get; }
|
||||
public ulong Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU virtual address where the range ends.
|
||||
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <summary>
|
||||
/// Physical regions where the GPU virtual region is mapped.
|
||||
/// </summary>
|
||||
public MultiRange Range { get; }
|
||||
public MultiRange Range { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new virtual memory range.
|
||||
@ -60,10 +60,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
}
|
||||
|
||||
public INonOverlappingRange Split(ulong splitAddress)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly RangeList<VirtualRange> _virtualRanges;
|
||||
private VirtualRange[] _virtualRangeOverlaps;
|
||||
private readonly NonOverlappingRangeList<VirtualRange> _virtualRanges;
|
||||
private readonly ConcurrentQueue<VirtualRange> _deferredUnmaps;
|
||||
private int _hasDeferredUnmaps;
|
||||
|
||||
@ -75,7 +79,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_virtualRanges = [];
|
||||
_virtualRangeOverlaps = new VirtualRange[BufferCache.OverlapsBufferInitialCapacity];
|
||||
_deferredUnmaps = new ConcurrentQueue<VirtualRange>();
|
||||
}
|
||||
|
||||
@ -106,19 +109,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// <returns>True if the range already existed, false if a new one was created and added</returns>
|
||||
public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range)
|
||||
{
|
||||
VirtualRange[] overlaps = _virtualRangeOverlaps;
|
||||
int overlapsCount;
|
||||
|
||||
if (Interlocked.Exchange(ref _hasDeferredUnmaps, 0) != 0)
|
||||
{
|
||||
while (_deferredUnmaps.TryDequeue(out VirtualRange unmappedRange))
|
||||
{
|
||||
overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(unmappedRange.Address, unmappedRange.Size, ref overlaps);
|
||||
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
_virtualRanges.Remove(overlaps[index]);
|
||||
}
|
||||
_virtualRanges.RemoveRange(unmappedRange.Address, unmappedRange.Size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,27 +121,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
ulong originalVa = gpuVa;
|
||||
|
||||
overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(gpuVa, size, ref overlaps);
|
||||
_virtualRanges.Lock.EnterWriteLock();
|
||||
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlaps(gpuVa, size);
|
||||
|
||||
if (overlapsCount != 0)
|
||||
if (first is not null)
|
||||
{
|
||||
// The virtual range already exists. We just need to check if our range fits inside
|
||||
// the existing one, and if not, we must extend the existing one.
|
||||
|
||||
ulong endAddress = gpuVa + size;
|
||||
VirtualRange overlap0 = overlaps[0];
|
||||
|
||||
if (overlap0.Address > gpuVa || overlap0.EndAddress < endAddress)
|
||||
if (first.Address > gpuVa || first.EndAddress < endAddress)
|
||||
{
|
||||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
VirtualRange virtualRange = overlaps[index];
|
||||
gpuVa = Math.Min(gpuVa, first.Address);
|
||||
endAddress = Math.Max(endAddress, last.EndAddress);
|
||||
|
||||
gpuVa = Math.Min(gpuVa, virtualRange.Address);
|
||||
endAddress = Math.Max(endAddress, virtualRange.EndAddress);
|
||||
|
||||
_virtualRanges.Remove(virtualRange);
|
||||
}
|
||||
_virtualRanges.RemoveRange(first, last);
|
||||
|
||||
ulong newSize = endAddress - gpuVa;
|
||||
MultiRange newRange = _memoryManager.GetPhysicalRegions(gpuVa, newSize);
|
||||
@ -157,8 +147,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
else
|
||||
{
|
||||
found = overlap0.Range.Count == 1 || IsSparseAligned(overlap0.Range);
|
||||
range = overlap0.Range.Slice(gpuVa - overlap0.Address, size);
|
||||
found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range);
|
||||
range = first.Value.Range.Slice(gpuVa - first.Address, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -170,8 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
_virtualRanges.Add(virtualRange);
|
||||
}
|
||||
|
||||
ShrinkOverlapsBufferIfNeeded();
|
||||
_virtualRanges.Lock.ExitWriteLock();
|
||||
|
||||
// If the range is not properly aligned for sparse mapping,
|
||||
// let's just force it to a single range.
|
||||
@ -221,16 +210,5 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the temporary buffer used for range list intersection results, if it has grown too much.
|
||||
/// </summary>
|
||||
private void ShrinkOverlapsBufferIfNeeded()
|
||||
{
|
||||
if (_virtualRangeOverlaps.Length > BufferCache.OverlapsBufferMaxCapacity)
|
||||
{
|
||||
Array.Resize(ref _virtualRangeOverlaps, BufferCache.OverlapsBufferMaxCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
203
src/Ryujinx.HLE/Debugger/BreakpointManager.cs
Normal file
203
src/Ryujinx.HLE/Debugger/BreakpointManager.cs
Normal file
@ -0,0 +1,203 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Memory;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
internal class Breakpoint
|
||||
{
|
||||
public byte[] OriginalData { get; }
|
||||
|
||||
public bool IsStep { get; }
|
||||
|
||||
public Breakpoint(byte[] originalData, bool isStep)
|
||||
{
|
||||
OriginalData = originalData;
|
||||
IsStep = isStep;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages software breakpoints for the debugger.
|
||||
/// </summary>
|
||||
public class BreakpointManager
|
||||
{
|
||||
private readonly Debugger _debugger;
|
||||
private readonly ConcurrentDictionary<ulong, Breakpoint> _breakpoints = new();
|
||||
|
||||
private static readonly byte[] _aarch64BreakInstruction = { 0x00, 0x00, 0x20, 0xD4 }; // BRK #0
|
||||
private static readonly byte[] _aarch32BreakInstruction = { 0xFE, 0xDE, 0xFF, 0xE7 }; // TRAP
|
||||
private static readonly byte[] _aarch32ThumbBreakInstruction = { 0x80, 0xB6 };
|
||||
|
||||
public BreakpointManager(Debugger debugger)
|
||||
{
|
||||
_debugger = debugger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a software breakpoint at a specified address.
|
||||
/// </summary>
|
||||
/// <param name="address">The memory address to set the breakpoint at.</param>
|
||||
/// <param name="length">The length of the instruction to replace.</param>
|
||||
/// <param name="isStep">Indicates if this is a single-step breakpoint.</param>
|
||||
/// <returns>True if the breakpoint was set successfully; otherwise, false.</returns>
|
||||
public bool SetBreakPoint(ulong address, ulong length, bool isStep = false)
|
||||
{
|
||||
if (_breakpoints.ContainsKey(address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] breakInstruction = GetBreakInstruction(length);
|
||||
if (breakInstruction == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.GdbStub, $"Unsupported instruction length for breakpoint: {length}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var originalInstruction = new byte[length];
|
||||
if (!ReadMemory(address, originalInstruction))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.GdbStub, $"Failed to read memory at 0x{address:X16} to set breakpoint.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WriteMemory(address, breakInstruction))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.GdbStub, $"Failed to write breakpoint at 0x{address:X16}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var breakpoint = new Breakpoint(originalInstruction, isStep);
|
||||
if (_breakpoints.TryAdd(address, breakpoint))
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint set at 0x{address:X16}");
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.Error?.Print(LogClass.GdbStub, $"Failed to add breakpoint at 0x{address:X16}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a software breakpoint at a specified address.
|
||||
/// </summary>
|
||||
/// <param name="address">The memory address of the breakpoint to clear.</param>
|
||||
/// <param name="length">The length of the instruction (unused).</param>
|
||||
/// <returns>True if the breakpoint was cleared successfully; otherwise, false.</returns>
|
||||
public bool ClearBreakPoint(ulong address, ulong length)
|
||||
{
|
||||
if (_breakpoints.TryGetValue(address, out Breakpoint breakpoint))
|
||||
{
|
||||
if (!WriteMemory(address, breakpoint.OriginalData))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{address:X16} to clear breakpoint.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_breakpoints.TryRemove(address, out _);
|
||||
Logger.Debug?.Print(LogClass.GdbStub, $"Breakpoint cleared at 0x{address:X16}");
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.GdbStub, $"No breakpoint found at address 0x{address:X16}");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all currently set software breakpoints.
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
foreach (var bp in _breakpoints)
|
||||
{
|
||||
if (!WriteMemory(bp.Key, bp.Value.OriginalData))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.GdbStub, $"Failed to restore original instruction at 0x{bp.Key:X16} while clearing all breakpoints.");
|
||||
}
|
||||
|
||||
}
|
||||
_breakpoints.Clear();
|
||||
Logger.Debug?.Print(LogClass.GdbStub, "All breakpoints cleared.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all currently set single-step software breakpoints.
|
||||
/// </summary>
|
||||
public void ClearAllStepBreakpoints()
|
||||
{
|
||||
var stepBreakpoints = _breakpoints.Where(p => p.Value.IsStep).ToList();
|
||||
|
||||
if (stepBreakpoints.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var bp in stepBreakpoints)
|
||||
{
|
||||
if (_breakpoints.TryRemove(bp.Key, out Breakpoint removedBreakpoint))
|
||||
{
|
||||
WriteMemory(bp.Key, removedBreakpoint.OriginalData);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Debug?.Print(LogClass.GdbStub, "All step breakpoints cleared.");
|
||||
}
|
||||
|
||||
|
||||
private byte[] GetBreakInstruction(ulong length)
|
||||
{
|
||||
if (_debugger.IsProcessAarch32)
|
||||
{
|
||||
if (length == 2)
|
||||
{
|
||||
return _aarch32ThumbBreakInstruction;
|
||||
}
|
||||
|
||||
if (length == 4)
|
||||
{
|
||||
return _aarch32BreakInstruction;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (length == 4)
|
||||
{
|
||||
return _aarch64BreakInstruction;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool ReadMemory(ulong address, byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
_debugger.DebugProcess.CpuMemory.Read(address, data);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool WriteMemory(ulong address, byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
_debugger.DebugProcess.CpuMemory.Write(address, data);
|
||||
_debugger.DebugProcess.InvalidateCacheRegion(address, (ulong)data.Length);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
src/Ryujinx.HLE/Debugger/DebugState.cs
Normal file
9
src/Ryujinx.HLE/Debugger/DebugState.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
public enum DebugState
|
||||
{
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped,
|
||||
}
|
||||
}
|
1327
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
1327
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
File diff suppressed because it is too large
Load Diff
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
enum GdbSignal
|
||||
{
|
||||
Zero = 0,
|
||||
Int = 2,
|
||||
Quit = 3,
|
||||
Trap = 5,
|
||||
Abort = 6,
|
||||
Alarm = 14,
|
||||
IO = 23,
|
||||
XCPU = 24,
|
||||
Unknown = 143
|
||||
}
|
||||
}
|
93
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
Normal file
93
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
Normal file
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.aarch64.core">
|
||||
<reg name="x0" bitsize="64"/>
|
||||
<reg name="x1" bitsize="64"/>
|
||||
<reg name="x2" bitsize="64"/>
|
||||
<reg name="x3" bitsize="64"/>
|
||||
<reg name="x4" bitsize="64"/>
|
||||
<reg name="x5" bitsize="64"/>
|
||||
<reg name="x6" bitsize="64"/>
|
||||
<reg name="x7" bitsize="64"/>
|
||||
<reg name="x8" bitsize="64"/>
|
||||
<reg name="x9" bitsize="64"/>
|
||||
<reg name="x10" bitsize="64"/>
|
||||
<reg name="x11" bitsize="64"/>
|
||||
<reg name="x12" bitsize="64"/>
|
||||
<reg name="x13" bitsize="64"/>
|
||||
<reg name="x14" bitsize="64"/>
|
||||
<reg name="x15" bitsize="64"/>
|
||||
<reg name="x16" bitsize="64"/>
|
||||
<reg name="x17" bitsize="64"/>
|
||||
<reg name="x18" bitsize="64"/>
|
||||
<reg name="x19" bitsize="64"/>
|
||||
<reg name="x20" bitsize="64"/>
|
||||
<reg name="x21" bitsize="64"/>
|
||||
<reg name="x22" bitsize="64"/>
|
||||
<reg name="x23" bitsize="64"/>
|
||||
<reg name="x24" bitsize="64"/>
|
||||
<reg name="x25" bitsize="64"/>
|
||||
<reg name="x26" bitsize="64"/>
|
||||
<reg name="x27" bitsize="64"/>
|
||||
<reg name="x28" bitsize="64"/>
|
||||
<reg name="x29" bitsize="64"/>
|
||||
<reg name="x30" bitsize="64"/>
|
||||
<reg name="sp" bitsize="64" type="data_ptr"/>
|
||||
|
||||
<reg name="pc" bitsize="64" type="code_ptr"/>
|
||||
|
||||
<flags id="cpsr_flags" size="4">
|
||||
<!-- Stack Pointer. -->
|
||||
<field name="SP" start="0" end="0"/>
|
||||
|
||||
<!-- Exception Level. -->
|
||||
<field name="EL" start="2" end="3"/>
|
||||
<!-- Execution state. -->
|
||||
<field name="nRW" start="4" end="4"/>
|
||||
|
||||
<!-- FIQ interrupt mask. -->
|
||||
<field name="F" start="6" end="6"/>
|
||||
<!-- IRQ interrupt mask. -->
|
||||
<field name="I" start="7" end="7"/>
|
||||
<!-- SError interrupt mask. -->
|
||||
<field name="A" start="8" end="8"/>
|
||||
<!-- Debug exception mask. -->
|
||||
<field name="D" start="9" end="9"/>
|
||||
|
||||
<!-- ARMv8.5-A: Branch Target Identification BTYPE. -->
|
||||
<field name="BTYPE" start="10" end="11"/>
|
||||
|
||||
<!-- ARMv8.0-A: Speculative Store Bypass. -->
|
||||
<field name="SSBS" start="12" end="12"/>
|
||||
|
||||
<!-- Illegal Execution state. -->
|
||||
<field name="IL" start="20" end="20"/>
|
||||
<!-- Software Step. -->
|
||||
<field name="SS" start="21" end="21"/>
|
||||
<!-- ARMv8.1-A: Privileged Access Never. -->
|
||||
<field name="PAN" start="22" end="22"/>
|
||||
<!-- ARMv8.2-A: User Access Override. -->
|
||||
<field name="UAO" start="23" end="23"/>
|
||||
<!-- ARMv8.4-A: Data Independent Timing. -->
|
||||
<field name="DIT" start="24" end="24"/>
|
||||
<!-- ARMv8.5-A: Tag Check Override. -->
|
||||
<field name="TCO" start="25" end="25"/>
|
||||
|
||||
<!-- Overflow Condition flag. -->
|
||||
<field name="V" start="28" end="28"/>
|
||||
<!-- Carry Condition flag. -->
|
||||
<field name="C" start="29" end="29"/>
|
||||
<!-- Zero Condition flag. -->
|
||||
<field name="Z" start="30" end="30"/>
|
||||
<!-- Negative Condition flag. -->
|
||||
<field name="N" start="31" end="31"/>
|
||||
</flags>
|
||||
<reg name="cpsr" bitsize="32" type="cpsr_flags"/>
|
||||
|
||||
</feature>
|
159
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
Normal file
159
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
Normal file
@ -0,0 +1,159 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.aarch64.fpu">
|
||||
<vector id="v2d" type="ieee_double" count="2"/>
|
||||
<vector id="v2u" type="uint64" count="2"/>
|
||||
<vector id="v2i" type="int64" count="2"/>
|
||||
<vector id="v4f" type="ieee_single" count="4"/>
|
||||
<vector id="v4u" type="uint32" count="4"/>
|
||||
<vector id="v4i" type="int32" count="4"/>
|
||||
<vector id="v8f" type="ieee_half" count="8"/>
|
||||
<vector id="v8u" type="uint16" count="8"/>
|
||||
<vector id="v8i" type="int16" count="8"/>
|
||||
<vector id="v8bf16" type="bfloat16" count="8"/>
|
||||
<vector id="v16u" type="uint8" count="16"/>
|
||||
<vector id="v16i" type="int8" count="16"/>
|
||||
<vector id="v1u" type="uint128" count="1"/>
|
||||
<vector id="v1i" type="int128" count="1"/>
|
||||
<union id="vnd">
|
||||
<field name="f" type="v2d"/>
|
||||
<field name="u" type="v2u"/>
|
||||
<field name="s" type="v2i"/>
|
||||
</union>
|
||||
<union id="vns">
|
||||
<field name="f" type="v4f"/>
|
||||
<field name="u" type="v4u"/>
|
||||
<field name="s" type="v4i"/>
|
||||
</union>
|
||||
<union id="vnh">
|
||||
<field name="bf" type="v8bf16"/>
|
||||
<field name="f" type="v8f"/>
|
||||
<field name="u" type="v8u"/>
|
||||
<field name="s" type="v8i"/>
|
||||
</union>
|
||||
<union id="vnb">
|
||||
<field name="u" type="v16u"/>
|
||||
<field name="s" type="v16i"/>
|
||||
</union>
|
||||
<union id="vnq">
|
||||
<field name="u" type="v1u"/>
|
||||
<field name="s" type="v1i"/>
|
||||
</union>
|
||||
<union id="aarch64v">
|
||||
<field name="d" type="vnd"/>
|
||||
<field name="s" type="vns"/>
|
||||
<field name="h" type="vnh"/>
|
||||
<field name="b" type="vnb"/>
|
||||
<field name="q" type="vnq"/>
|
||||
</union>
|
||||
<reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
|
||||
<reg name="v1" bitsize="128" type="aarch64v" />
|
||||
<reg name="v2" bitsize="128" type="aarch64v" />
|
||||
<reg name="v3" bitsize="128" type="aarch64v" />
|
||||
<reg name="v4" bitsize="128" type="aarch64v" />
|
||||
<reg name="v5" bitsize="128" type="aarch64v" />
|
||||
<reg name="v6" bitsize="128" type="aarch64v" />
|
||||
<reg name="v7" bitsize="128" type="aarch64v" />
|
||||
<reg name="v8" bitsize="128" type="aarch64v" />
|
||||
<reg name="v9" bitsize="128" type="aarch64v" />
|
||||
<reg name="v10" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v11" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v12" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v13" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v14" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v15" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v16" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v17" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v18" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v19" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v20" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v21" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v22" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v23" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v24" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v25" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v26" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v27" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v28" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v29" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v30" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v31" bitsize="128" type="aarch64v"/>
|
||||
|
||||
<flags id="fpsr_flags" size="4">
|
||||
<!-- Invalid Operation cumulative floating-point exception bit. -->
|
||||
<field name="IOC" start="0" end="0"/>
|
||||
<!-- Divide by Zero cumulative floating-point exception bit. -->
|
||||
<field name="DZC" start="1" end="1"/>
|
||||
<!-- Overflow cumulative floating-point exception bit. -->
|
||||
<field name="OFC" start="2" end="2"/>
|
||||
<!-- Underflow cumulative floating-point exception bit. -->
|
||||
<field name="UFC" start="3" end="3"/>
|
||||
<!-- Inexact cumulative floating-point exception bit.. -->
|
||||
<field name="IXC" start="4" end="4"/>
|
||||
<!-- Input Denormal cumulative floating-point exception bit. -->
|
||||
<field name="IDC" start="7" end="7"/>
|
||||
<!-- Cumulative saturation bit, Advanced SIMD only. -->
|
||||
<field name="QC" start="27" end="27"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented: Overflow condition flag for AArch32
|
||||
floating-point comparison operations. -->
|
||||
<field name="V" start="28" end="28"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented:
|
||||
Carry condition flag for AArch32 floating-point comparison operations.
|
||||
-->
|
||||
<field name="C" start="29" end="29"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented:
|
||||
Zero condition flag for AArch32 floating-point comparison operations.
|
||||
-->
|
||||
<field name="Z" start="30" end="30"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented:
|
||||
Negative condition flag for AArch32 floating-point comparison
|
||||
operations. -->
|
||||
<field name="N" start="31" end="31"/>
|
||||
</flags>
|
||||
<reg name="fpsr" bitsize="32" type="fpsr_flags"/>
|
||||
|
||||
<flags id="fpcr_flags" size="4">
|
||||
<!-- Flush Inputs to Zero (part of Armv8.7). -->
|
||||
<field name="FIZ" start="0" end="0"/>
|
||||
<!-- Alternate Handling (part of Armv8.7). -->
|
||||
<field name="AH" start="1" end="1"/>
|
||||
<!-- Controls how the output elements other than the lowest element of the
|
||||
vector are determined for Advanced SIMD scalar instructions (part of
|
||||
Armv8.7). -->
|
||||
<field name="NEP" start="2" end="2"/>
|
||||
<!-- Invalid Operation floating-point exception trap enable. -->
|
||||
<field name="IOE" start="8" end="8"/>
|
||||
<!-- Divide by Zero floating-point exception trap enable. -->
|
||||
<field name="DZE" start="9" end="9"/>
|
||||
<!-- Overflow floating-point exception trap enable. -->
|
||||
<field name="OFE" start="10" end="10"/>
|
||||
<!-- Underflow floating-point exception trap enable. -->
|
||||
<field name="UFE" start="11" end="11"/>
|
||||
<!-- Inexact floating-point exception trap enable. -->
|
||||
<field name="IXE" start="12" end="12"/>
|
||||
<!-- Input Denormal floating-point exception trap enable. -->
|
||||
<field name="IDE" start="15" end="15"/>
|
||||
<!-- Flush-to-zero mode control bit on half-precision data-processing
|
||||
instructions. -->
|
||||
<field name="FZ16" start="19" end="19"/>
|
||||
<!-- Rounding Mode control field. -->
|
||||
<field name="RMode" start="22" end="23"/>
|
||||
<!-- Flush-to-zero mode control bit. -->
|
||||
<field name="FZ" start="24" end="24"/>
|
||||
<!-- Default NaN mode control bit. -->
|
||||
<field name="DN" start="25" end="25"/>
|
||||
<!-- Alternative half-precision control bit. -->
|
||||
<field name="AHP" start="26" end="26"/>
|
||||
</flags>
|
||||
<reg name="fpcr" bitsize="32" type="fpcr_flags"/>
|
||||
</feature>
|
27
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
Normal file
27
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.arm.core">
|
||||
<reg name="r0" bitsize="32"/>
|
||||
<reg name="r1" bitsize="32"/>
|
||||
<reg name="r2" bitsize="32"/>
|
||||
<reg name="r3" bitsize="32"/>
|
||||
<reg name="r4" bitsize="32"/>
|
||||
<reg name="r5" bitsize="32"/>
|
||||
<reg name="r6" bitsize="32"/>
|
||||
<reg name="r7" bitsize="32"/>
|
||||
<reg name="r8" bitsize="32"/>
|
||||
<reg name="r9" bitsize="32"/>
|
||||
<reg name="r10" bitsize="32"/>
|
||||
<reg name="r11" bitsize="32"/>
|
||||
<reg name="r12" bitsize="32"/>
|
||||
<reg name="sp" bitsize="32" type="data_ptr"/>
|
||||
<reg name="lr" bitsize="32"/>
|
||||
<reg name="pc" bitsize="32" type="code_ptr"/>
|
||||
<reg name="cpsr" bitsize="32" />
|
||||
</feature>
|
86
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
Normal file
86
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
Normal file
@ -0,0 +1,86 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.arm.vfp">
|
||||
<vector id="neon_uint8x8" type="uint8" count="8"/>
|
||||
<vector id="neon_uint16x4" type="uint16" count="4"/>
|
||||
<vector id="neon_uint32x2" type="uint32" count="2"/>
|
||||
<vector id="neon_float32x2" type="ieee_single" count="2"/>
|
||||
<union id="neon_d">
|
||||
<field name="u8" type="neon_uint8x8"/>
|
||||
<field name="u16" type="neon_uint16x4"/>
|
||||
<field name="u32" type="neon_uint32x2"/>
|
||||
<field name="u64" type="uint64"/>
|
||||
<field name="f32" type="neon_float32x2"/>
|
||||
<field name="f64" type="ieee_double"/>
|
||||
</union>
|
||||
<vector id="neon_uint8x16" type="uint8" count="16"/>
|
||||
<vector id="neon_uint16x8" type="uint16" count="8"/>
|
||||
<vector id="neon_uint32x4" type="uint32" count="4"/>
|
||||
<vector id="neon_uint64x2" type="uint64" count="2"/>
|
||||
<vector id="neon_float32x4" type="ieee_single" count="4"/>
|
||||
<vector id="neon_float64x2" type="ieee_double" count="2"/>
|
||||
<union id="neon_q">
|
||||
<field name="u8" type="neon_uint8x16"/>
|
||||
<field name="u16" type="neon_uint16x8"/>
|
||||
<field name="u32" type="neon_uint32x4"/>
|
||||
<field name="u64" type="neon_uint64x2"/>
|
||||
<field name="f32" type="neon_float32x4"/>
|
||||
<field name="f64" type="neon_float64x2"/>
|
||||
</union>
|
||||
<reg name="d0" bitsize="64" type="neon_d"/>
|
||||
<reg name="d1" bitsize="64" type="neon_d"/>
|
||||
<reg name="d2" bitsize="64" type="neon_d"/>
|
||||
<reg name="d3" bitsize="64" type="neon_d"/>
|
||||
<reg name="d4" bitsize="64" type="neon_d"/>
|
||||
<reg name="d5" bitsize="64" type="neon_d"/>
|
||||
<reg name="d6" bitsize="64" type="neon_d"/>
|
||||
<reg name="d7" bitsize="64" type="neon_d"/>
|
||||
<reg name="d8" bitsize="64" type="neon_d"/>
|
||||
<reg name="d9" bitsize="64" type="neon_d"/>
|
||||
<reg name="d10" bitsize="64" type="neon_d"/>
|
||||
<reg name="d11" bitsize="64" type="neon_d"/>
|
||||
<reg name="d12" bitsize="64" type="neon_d"/>
|
||||
<reg name="d13" bitsize="64" type="neon_d"/>
|
||||
<reg name="d14" bitsize="64" type="neon_d"/>
|
||||
<reg name="d15" bitsize="64" type="neon_d"/>
|
||||
<reg name="d16" bitsize="64" type="neon_d"/>
|
||||
<reg name="d17" bitsize="64" type="neon_d"/>
|
||||
<reg name="d18" bitsize="64" type="neon_d"/>
|
||||
<reg name="d19" bitsize="64" type="neon_d"/>
|
||||
<reg name="d20" bitsize="64" type="neon_d"/>
|
||||
<reg name="d21" bitsize="64" type="neon_d"/>
|
||||
<reg name="d22" bitsize="64" type="neon_d"/>
|
||||
<reg name="d23" bitsize="64" type="neon_d"/>
|
||||
<reg name="d24" bitsize="64" type="neon_d"/>
|
||||
<reg name="d25" bitsize="64" type="neon_d"/>
|
||||
<reg name="d26" bitsize="64" type="neon_d"/>
|
||||
<reg name="d27" bitsize="64" type="neon_d"/>
|
||||
<reg name="d28" bitsize="64" type="neon_d"/>
|
||||
<reg name="d29" bitsize="64" type="neon_d"/>
|
||||
<reg name="d30" bitsize="64" type="neon_d"/>
|
||||
<reg name="d31" bitsize="64" type="neon_d"/>
|
||||
|
||||
<reg name="q0" bitsize="128" type="neon_q"/>
|
||||
<reg name="q1" bitsize="128" type="neon_q"/>
|
||||
<reg name="q2" bitsize="128" type="neon_q"/>
|
||||
<reg name="q3" bitsize="128" type="neon_q"/>
|
||||
<reg name="q4" bitsize="128" type="neon_q"/>
|
||||
<reg name="q5" bitsize="128" type="neon_q"/>
|
||||
<reg name="q6" bitsize="128" type="neon_q"/>
|
||||
<reg name="q7" bitsize="128" type="neon_q"/>
|
||||
<reg name="q8" bitsize="128" type="neon_q"/>
|
||||
<reg name="q9" bitsize="128" type="neon_q"/>
|
||||
<reg name="q10" bitsize="128" type="neon_q"/>
|
||||
<reg name="q11" bitsize="128" type="neon_q"/>
|
||||
<reg name="q12" bitsize="128" type="neon_q"/>
|
||||
<reg name="q13" bitsize="128" type="neon_q"/>
|
||||
<reg name="q14" bitsize="128" type="neon_q"/>
|
||||
<reg name="q15" bitsize="128" type="neon_q"/>
|
||||
|
||||
<reg name="fpscr" bitsize="32" type="int" group="float"/>
|
||||
</feature>
|
14
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
Normal file
14
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target>
|
||||
<architecture>arm</architecture>
|
||||
<xi:include href="arm-core.xml"/>
|
||||
<xi:include href="arm-neon.xml"/>
|
||||
</target>
|
14
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
Normal file
14
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target>
|
||||
<architecture>aarch64</architecture>
|
||||
<xi:include href="aarch64-core.xml"/>
|
||||
<xi:include href="aarch64-fpu.xml"/>
|
||||
</target>
|
21
src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
Normal file
21
src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Memory;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
internal interface IDebuggableProcess
|
||||
{
|
||||
void DebugStop();
|
||||
void DebugContinue();
|
||||
void DebugContinue(KThread thread);
|
||||
bool DebugStep(KThread thread);
|
||||
KThread GetThread(ulong threadUid);
|
||||
DebugState GetDebugState();
|
||||
bool IsThreadPaused(KThread thread);
|
||||
ulong[] GetThreadUids();
|
||||
public void DebugInterruptHandler(IExecutionContext ctx);
|
||||
IVirtualMemoryManager CpuMemory { get; }
|
||||
void InvalidateCacheRegion(ulong address, ulong size);
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct BreakInMessage : IMessage
|
||||
{
|
||||
}
|
||||
}
|
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct CommandMessage : IMessage
|
||||
{
|
||||
public string Command;
|
||||
|
||||
public CommandMessage(string cmd)
|
||||
{
|
||||
Command = cmd;
|
||||
}
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
interface IMessage
|
||||
{
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct KillMessage : IMessage
|
||||
{
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct SendNackMessage : IMessage
|
||||
{
|
||||
}
|
||||
}
|
18
src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
Normal file
18
src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
public class ThreadBreakMessage : IMessage
|
||||
{
|
||||
public IExecutionContext Context { get; }
|
||||
public ulong Address { get; }
|
||||
public int Opcode { get; }
|
||||
|
||||
public ThreadBreakMessage(IExecutionContext context, ulong address, int opcode)
|
||||
{
|
||||
Context = context;
|
||||
Address = address;
|
||||
Opcode = opcode;
|
||||
}
|
||||
}
|
||||
}
|
28
src/Ryujinx.HLE/Debugger/RegisterInformation.cs
Normal file
28
src/Ryujinx.HLE/Debugger/RegisterInformation.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
class RegisterInformation
|
||||
{
|
||||
public static readonly Dictionary<string, string> Features = new()
|
||||
{
|
||||
{ "target64.xml", GetEmbeddedResourceContent("target64.xml") },
|
||||
{ "target32.xml", GetEmbeddedResourceContent("target32.xml") },
|
||||
{ "aarch64-core.xml", GetEmbeddedResourceContent("aarch64-core.xml") },
|
||||
{ "aarch64-fpu.xml", GetEmbeddedResourceContent("aarch64-fpu.xml") },
|
||||
{ "arm-core.xml", GetEmbeddedResourceContent("arm-core.xml") },
|
||||
{ "arm-neon.xml", GetEmbeddedResourceContent("arm-neon.xml") },
|
||||
};
|
||||
|
||||
private static string GetEmbeddedResourceContent(string resourceName)
|
||||
{
|
||||
Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.GdbXml." + resourceName);
|
||||
StreamReader reader = new StreamReader(stream);
|
||||
string result = reader.ReadToEnd();
|
||||
reader.Dispose();
|
||||
stream.Dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
109
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
109
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
class StringStream
|
||||
{
|
||||
private readonly string Data;
|
||||
private int Position;
|
||||
|
||||
public StringStream(string s)
|
||||
{
|
||||
Data = s;
|
||||
}
|
||||
|
||||
public char ReadChar()
|
||||
{
|
||||
return Data[Position++];
|
||||
}
|
||||
|
||||
public string ReadUntil(char needle)
|
||||
{
|
||||
int needlePos = Data.IndexOf(needle, Position);
|
||||
|
||||
if (needlePos == -1)
|
||||
{
|
||||
needlePos = Data.Length;
|
||||
}
|
||||
|
||||
string result = Data.Substring(Position, needlePos - Position);
|
||||
Position = needlePos + 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ReadLength(int len)
|
||||
{
|
||||
string result = Data.Substring(Position, len);
|
||||
Position += len;
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ReadRemaining()
|
||||
{
|
||||
string result = Data.Substring(Position);
|
||||
Position = Data.Length;
|
||||
return result;
|
||||
}
|
||||
|
||||
public ulong ReadRemainingAsHex()
|
||||
{
|
||||
return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public ulong ReadUntilAsHex(char needle)
|
||||
{
|
||||
return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public ulong ReadLengthAsHex(int len)
|
||||
{
|
||||
return ulong.Parse(ReadLength(len), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public ulong ReadLengthAsLEHex(int len)
|
||||
{
|
||||
Debug.Assert(len % 2 == 0);
|
||||
|
||||
ulong result = 0;
|
||||
int pos = 0;
|
||||
while (pos < len)
|
||||
{
|
||||
result += ReadLengthAsHex(2) << (4 * pos);
|
||||
pos += 2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public ulong? ReadRemainingAsThreadUid()
|
||||
{
|
||||
string s = ReadRemaining();
|
||||
return s == "-1" ? null : ulong.Parse(s, NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public bool ConsumePrefix(string prefix)
|
||||
{
|
||||
if (Data.Substring(Position).StartsWith(prefix))
|
||||
{
|
||||
Position += prefix.Length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ConsumeRemaining(string match)
|
||||
{
|
||||
if (Data.Substring(Position) == match)
|
||||
{
|
||||
Position += match.Length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return Position >= Data.Length;
|
||||
}
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ namespace Ryujinx.HLE.HOS
|
||||
mode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe)
|
||||
ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && !context.Device.Configuration.EnableGdbStub
|
||||
? new LightningJitEngine(_tickSource)
|
||||
: new JitEngine(_tickSource);
|
||||
|
||||
|
@ -5,6 +5,7 @@ using LibHac.Fs.Shim;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Debugger;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
@ -500,5 +501,21 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
IsPaused = pause;
|
||||
}
|
||||
|
||||
internal IDebuggableProcess DebugGetApplicationProcessDebugInterface()
|
||||
{
|
||||
lock (KernelContext.Processes)
|
||||
{
|
||||
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
|
||||
}
|
||||
}
|
||||
|
||||
internal KProcess DebugGetApplicationProcess()
|
||||
{
|
||||
lock (KernelContext.Processes)
|
||||
{
|
||||
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,13 +89,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
if (baseAddress > currBaseAddr)
|
||||
{
|
||||
KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress);
|
||||
_blockTree.Add(newBlock);
|
||||
if (currBlock.Left == null)
|
||||
_blockTree.Add(newBlock, currBlock);
|
||||
else
|
||||
_blockTree.Add(newBlock, currBlock.Predecessor);
|
||||
}
|
||||
|
||||
if (endAddr < currEndAddr)
|
||||
{
|
||||
KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr);
|
||||
_blockTree.Add(newBlock);
|
||||
if (currBlock.Left == null)
|
||||
_blockTree.Add(newBlock, currBlock);
|
||||
else
|
||||
_blockTree.Add(newBlock, currBlock.Predecessor);
|
||||
currBlock = newBlock;
|
||||
}
|
||||
|
||||
@ -143,13 +149,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
if (baseAddress > currBaseAddr)
|
||||
{
|
||||
KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress);
|
||||
_blockTree.Add(newBlock);
|
||||
if (currBlock.Left == null)
|
||||
_blockTree.Add(newBlock, currBlock);
|
||||
else
|
||||
_blockTree.Add(newBlock, currBlock.Predecessor);
|
||||
}
|
||||
|
||||
if (endAddr < currEndAddr)
|
||||
{
|
||||
KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr);
|
||||
_blockTree.Add(newBlock);
|
||||
if (currBlock.Left == null)
|
||||
_blockTree.Add(newBlock, currBlock);
|
||||
else
|
||||
_blockTree.Add(newBlock, currBlock.Predecessor);
|
||||
currBlock = newBlock;
|
||||
}
|
||||
|
||||
@ -199,13 +211,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
if (baseAddress > currBaseAddr)
|
||||
{
|
||||
KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress);
|
||||
_blockTree.Add(newBlock);
|
||||
if (currBlock.Left == null)
|
||||
_blockTree.Add(newBlock, currBlock);
|
||||
else
|
||||
_blockTree.Add(newBlock, currBlock.Predecessor);
|
||||
}
|
||||
|
||||
if (endAddr < currEndAddr)
|
||||
{
|
||||
KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr);
|
||||
_blockTree.Add(newBlock);
|
||||
if (currBlock.Left == null)
|
||||
_blockTree.Add(newBlock, currBlock);
|
||||
else
|
||||
_blockTree.Add(newBlock, currBlock.Predecessor);
|
||||
currBlock = newBlock;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.Loaders.Elf;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -17,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
private readonly KProcess _owner;
|
||||
|
||||
private class Image
|
||||
public class Image
|
||||
{
|
||||
public ulong BaseAddress { get; }
|
||||
public ulong Size { get; }
|
||||
@ -54,6 +55,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
|
||||
|
||||
string ThreadName = thread.GetThreadName();
|
||||
|
||||
if (!String.IsNullOrEmpty(ThreadName))
|
||||
{
|
||||
trace.AppendLine($"Thread ID: {thread.ThreadUid} ({ThreadName})");
|
||||
} else {
|
||||
trace.AppendLine($"Thread ID: {thread.ThreadUid}");
|
||||
}
|
||||
|
||||
void AppendTrace(ulong address)
|
||||
{
|
||||
if (AnalyzePointer(out PointerInfo info, address, thread))
|
||||
@ -283,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetGuessedNsoNameFromIndex(int index)
|
||||
public string GetGuessedNsoNameFromIndex(int index)
|
||||
{
|
||||
if ((uint)index > 11)
|
||||
{
|
||||
@ -316,6 +326,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
}
|
||||
}
|
||||
|
||||
public List<Image> GetLoadedImages()
|
||||
{
|
||||
EnsureLoaded();
|
||||
|
||||
lock (_images)
|
||||
{
|
||||
return [.. _images];
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureLoaded()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Debugger;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
@ -11,6 +12,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
|
||||
using ExceptionCallbackNoArgs = Ryujinx.Cpu.ExceptionCallbackNoArgs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
@ -89,6 +92,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
|
||||
|
||||
public HleProcessDebugger Debugger { get; private set; }
|
||||
public IDebuggableProcess DebugInterface { get; private set; }
|
||||
protected int debugState = (int)DebugState.Running;
|
||||
|
||||
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
|
||||
{
|
||||
@ -110,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
_threads = [];
|
||||
|
||||
Debugger = new HleProcessDebugger(this);
|
||||
DebugInterface = new DebuggerInterface(this);
|
||||
}
|
||||
|
||||
public Result InitializeKip(
|
||||
@ -679,6 +685,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
SetState(newState);
|
||||
|
||||
if (KernelContext.Device.Configuration.DebuggerSuspendOnStart && IsApplication)
|
||||
{
|
||||
mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
debugState = (int)DebugState.Stopped;
|
||||
Logger.Notice.Print(LogClass.Kernel, $"Application is suspended on start for debugging.");
|
||||
}
|
||||
|
||||
result = mainThread.Start();
|
||||
|
||||
if (result != Result.Success)
|
||||
@ -727,9 +740,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
public IExecutionContext CreateExecutionContext()
|
||||
{
|
||||
ExceptionCallback breakCallback = null;
|
||||
ExceptionCallbackNoArgs stepCallback = null;
|
||||
|
||||
if (KernelContext.Device.Configuration.EnableGdbStub && KernelContext.Device.Debugger != null)
|
||||
{
|
||||
breakCallback = KernelContext.Device.Debugger.BreakHandler;
|
||||
stepCallback = KernelContext.Device.Debugger.StepHandler;
|
||||
}
|
||||
|
||||
return Context?.CreateExecutionContext(new ExceptionCallbacks(
|
||||
InterruptHandler,
|
||||
null,
|
||||
breakCallback,
|
||||
stepCallback,
|
||||
KernelContext.SyscallHandler.SvcCall,
|
||||
UndefinedInstructionHandler));
|
||||
}
|
||||
@ -1174,5 +1197,186 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
return Capabilities.IsSvcPermitted(svcId);
|
||||
}
|
||||
|
||||
private class DebuggerInterface : IDebuggableProcess
|
||||
{
|
||||
private Barrier StepBarrier;
|
||||
private readonly KProcess _parent;
|
||||
private readonly KernelContext _kernelContext;
|
||||
private KThread steppingThread;
|
||||
|
||||
public DebuggerInterface(KProcess p)
|
||||
{
|
||||
_parent = p;
|
||||
_kernelContext = p.KernelContext;
|
||||
StepBarrier = new(2);
|
||||
}
|
||||
|
||||
public void DebugStop()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Stopping,
|
||||
(int)DebugState.Running) != (int)DebugState.Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
thread.Context.RequestInterrupt();
|
||||
if (!thread.DebugHalt.WaitOne(TimeSpan.FromMilliseconds(50)))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_parent.debugState = (int)DebugState.Stopped;
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
public void DebugContinue()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Running,
|
||||
(int)DebugState.Stopped) != (int)DebugState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
}
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
public void DebugContinue(KThread target)
|
||||
{
|
||||
Interlocked.Exchange(ref _parent.debugState, (int)DebugState.Running);
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
target.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
public bool DebugStep(KThread target)
|
||||
{
|
||||
if (!IsThreadPaused(target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
steppingThread = target;
|
||||
bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration;
|
||||
target.Context.RequestDebugStep();
|
||||
if (waiting)
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
|
||||
bool stepTimedOut = false;
|
||||
if (!StepBarrier.SignalAndWait(TimeSpan.FromMilliseconds(2000)))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Kernel, $"Failed to step thread {target.ThreadUid} in time.");
|
||||
stepTimedOut = true;
|
||||
}
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
steppingThread = null;
|
||||
if (waiting)
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
|
||||
if (stepTimedOut)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StepBarrier.SignalAndWait();
|
||||
return true;
|
||||
}
|
||||
|
||||
public DebugState GetDebugState()
|
||||
{
|
||||
return (DebugState)_parent.debugState;
|
||||
}
|
||||
|
||||
public bool IsThreadPaused(KThread target)
|
||||
{
|
||||
return (target.SchedFlags & ThreadSchedState.ThreadPauseFlag) != 0;
|
||||
}
|
||||
|
||||
public ulong[] GetThreadUids()
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
|
||||
return threads.Select(x => x.ThreadUid).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public KThread GetThread(ulong threadUid)
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
|
||||
return threads.FirstOrDefault(x => x.ThreadUid == threadUid);
|
||||
}
|
||||
}
|
||||
|
||||
public void DebugInterruptHandler(IExecutionContext ctx)
|
||||
{
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
bool stepping = steppingThread != null;
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
if (stepping)
|
||||
{
|
||||
StepBarrier.SignalAndWait();
|
||||
StepBarrier.SignalAndWait();
|
||||
}
|
||||
_parent.InterruptHandler(ctx);
|
||||
}
|
||||
|
||||
public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } }
|
||||
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
_parent.Context.InvalidateCacheRegion(address, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Cpu;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
@ -17,10 +18,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
public bool IsAarch32 { get => false; set { } }
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
public bool Running { get; private set; } = true;
|
||||
|
||||
private readonly ulong[] _x = new ulong[32];
|
||||
|
||||
public ulong DebugPc { get; set; }
|
||||
|
||||
public ulong GetX(int index) => _x[index];
|
||||
public void SetX(int index, ulong value) => _x[index] = value;
|
||||
|
||||
@ -31,6 +36,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
}
|
||||
|
||||
public void RequestDebugStep()
|
||||
{
|
||||
}
|
||||
|
||||
public void StopRunning()
|
||||
{
|
||||
Running = false;
|
||||
|
@ -301,6 +301,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
currentThread.SchedulerWaitEvent.Reset();
|
||||
currentThread.ThreadContext.Unlock();
|
||||
currentThread.DebugHalt.Set();
|
||||
|
||||
// Wake all the threads that might be waiting until this thread context is unlocked.
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
|
@ -1,12 +1,15 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Debugger;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
@ -16,6 +19,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
private const int TlsUserDisableCountOffset = 0x100;
|
||||
private const int TlsUserInterruptFlagOffset = 0x102;
|
||||
|
||||
// Tls -> ThreadType
|
||||
private const int TlsThreadTypeOffsetAArch64 = 0x1F8;
|
||||
private const int TlsThreadTypeOffsetAArch32 = 0x1FC;
|
||||
|
||||
// Tls -> ThreadType -> Version
|
||||
private const int TlsThreadTypeVersionOffsetAArch64 = 0x46;
|
||||
private const int TlsThreadTypeVersionOffsetAArch32 = 0x26;
|
||||
|
||||
// Tls -> ThreadType (Version 0) -> ThreadNamePointer
|
||||
private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64 = 0x1A8;
|
||||
private const int TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32 = 0xE8;
|
||||
|
||||
// Tls -> ThreadType (Version 1) -> ThreadNamePointer
|
||||
private const int TlsThreadTypeThreadNamePointerOffsetAArch64 = 0x1A0;
|
||||
private const int TlsThreadTypeThreadNamePointerOffsetAArch32 = 0xE4;
|
||||
|
||||
|
||||
public const int MaxWaitSyncObjects = 64;
|
||||
|
||||
private ManualResetEvent _schedulerWaitEvent;
|
||||
@ -114,6 +134,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
private readonly Lock _activityOperationLock = new();
|
||||
|
||||
internal readonly ManualResetEvent DebugHalt = new(false);
|
||||
|
||||
public KThread(KernelContext context) : base(context)
|
||||
{
|
||||
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
|
||||
@ -202,8 +224,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
}
|
||||
|
||||
Context.TpidrroEl0 = (long)_tlsAddress;
|
||||
Context.DebugPc = _entrypoint;
|
||||
|
||||
ThreadUid = KernelContext.NewThreadUid();
|
||||
Context.ThreadUid = ThreadUid;
|
||||
|
||||
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
|
||||
|
||||
@ -307,7 +331,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
KernelContext.CriticalSection.Enter();
|
||||
|
||||
if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this)
|
||||
{
|
||||
Owner.UnpinThread(this);
|
||||
}
|
||||
@ -362,7 +388,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
ThreadSchedState state = PrepareForTermination();
|
||||
|
||||
if (state != ThreadSchedState.TerminationPending)
|
||||
if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending)
|
||||
{
|
||||
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
|
||||
}
|
||||
@ -1248,6 +1274,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
private void ThreadStart()
|
||||
{
|
||||
_schedulerWaitEvent.WaitOne();
|
||||
DebugHalt.Reset();
|
||||
KernelStatic.SetKernelContext(KernelContext, this);
|
||||
|
||||
if (_customThreadStart != null)
|
||||
@ -1431,5 +1458,84 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
|
||||
}
|
||||
|
||||
public string GetThreadName()
|
||||
{
|
||||
try
|
||||
{
|
||||
ulong threadNamePtr = 0;
|
||||
if (Context.IsAarch32)
|
||||
{
|
||||
uint threadTypePtr32 = Owner.CpuMemory.Read<uint>(_tlsAddress + TlsThreadTypeOffsetAArch32);
|
||||
if (threadTypePtr32 == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
ushort version = Owner.CpuMemory.Read<ushort>(threadTypePtr32 + TlsThreadTypeVersionOffsetAArch32);
|
||||
switch (version)
|
||||
{
|
||||
case 0x0000:
|
||||
case 0xFFFF:
|
||||
threadNamePtr = Owner.CpuMemory.Read<uint>(threadTypePtr32 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch32);
|
||||
break;
|
||||
case 0x0001:
|
||||
threadNamePtr = Owner.CpuMemory.Read<uint>(threadTypePtr32 + TlsThreadTypeThreadNamePointerOffsetAArch32);
|
||||
break;
|
||||
default:
|
||||
Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong threadTypePtr64 = Owner.CpuMemory.Read<ulong>(_tlsAddress + TlsThreadTypeOffsetAArch64);
|
||||
if (threadTypePtr64 == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
ushort version = Owner.CpuMemory.Read<ushort>(threadTypePtr64 + TlsThreadTypeVersionOffsetAArch64);
|
||||
switch (version)
|
||||
{
|
||||
case 0x0000:
|
||||
case 0xFFFF:
|
||||
threadNamePtr = Owner.CpuMemory.Read<ulong>(threadTypePtr64 + TlsThreadTypeVersion0ThreadNamePointerOffsetAArch64);
|
||||
break;
|
||||
case 0x0001:
|
||||
threadNamePtr = Owner.CpuMemory.Read<ulong>(threadTypePtr64 + TlsThreadTypeThreadNamePointerOffsetAArch64);
|
||||
break;
|
||||
default:
|
||||
Logger.Warning?.Print(LogClass.Kernel, $"Unknown ThreadType struct version: {version}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (threadNamePtr == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
List<byte> nameBytes = new();
|
||||
for (int i = 0; i < 0x20; i++)
|
||||
{
|
||||
byte b = Owner.CpuMemory.Read<byte>(threadNamePtr + (ulong)i);
|
||||
if (b == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
nameBytes.Add(b);
|
||||
}
|
||||
return Encoding.UTF8.GetString(nameBytes.ToArray());
|
||||
|
||||
} catch (InvalidMemoryRegionException)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Kernel, "Failed to get thread name.");
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
Logger.Error?.Print(LogClass.Kernel, $"Error getting thread name: {e.Message}");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,6 +194,21 @@ namespace Ryujinx.HLE
|
||||
/// </summary>
|
||||
public Action RefreshInputConfig { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables gdbstub to allow for debugging of the guest .
|
||||
/// </summary>
|
||||
public bool EnableGdbStub { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A TCP port to use to expose a gdbstub for a debugger to connect to.
|
||||
/// </summary>
|
||||
public ushort GdbStubPort { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Suspend execution when starting an application
|
||||
/// </summary>
|
||||
public bool DebuggerSuspendOnStart { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The desired hacky workarounds.
|
||||
/// </summary>
|
||||
@ -222,6 +237,9 @@ namespace Ryujinx.HLE
|
||||
bool multiplayerDisableP2p,
|
||||
string multiplayerLdnPassphrase,
|
||||
string multiplayerLdnServer,
|
||||
bool enableGdbStub,
|
||||
ushort gdbStubPort,
|
||||
bool debuggerSuspendOnStart,
|
||||
int customVSyncInterval,
|
||||
EnabledDirtyHack[] dirtyHacks = null)
|
||||
{
|
||||
@ -248,6 +266,9 @@ namespace Ryujinx.HLE
|
||||
MultiplayerDisableP2p = multiplayerDisableP2p;
|
||||
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
|
||||
MultiplayerLdnServer = multiplayerLdnServer;
|
||||
EnableGdbStub = enableGdbStub;
|
||||
GdbStubPort = gdbStubPort;
|
||||
DebuggerSuspendOnStart = debuggerSuspendOnStart;
|
||||
Hacks = dirtyHacks ?? [];
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Debugger\GdbXml\aarch64-core.xml" />
|
||||
<None Remove="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||
<None Remove="Debugger\GdbXml\arm-core.xml" />
|
||||
<None Remove="Debugger\GdbXml\arm-neon.xml" />
|
||||
<None Remove="Debugger\GdbXml\target64.xml" />
|
||||
<None Remove="Debugger\GdbXml\target32.xml" />
|
||||
<None Remove="Homebrew.npdm" />
|
||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||
@ -42,6 +48,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Debugger\GdbXml\aarch64-core.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\arm-core.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\arm-neon.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\target64.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\target32.xml" />
|
||||
<EmbeddedResource Include="Homebrew.npdm" />
|
||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||
|
@ -14,6 +14,7 @@ using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.HLE.UI;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE
|
||||
{
|
||||
@ -41,6 +42,7 @@ namespace Ryujinx.HLE
|
||||
public Hid Hid { get; }
|
||||
public TamperMachine TamperMachine { get; }
|
||||
public IHostUIHandler UIHandler { get; }
|
||||
public Debugger.Debugger Debugger { get; }
|
||||
|
||||
public int CpuCoresCount = 4; // Switch has a quad-core Tegra X1 SoC
|
||||
|
||||
@ -72,6 +74,7 @@ namespace Ryujinx.HLE
|
||||
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
|
||||
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
|
||||
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
|
||||
Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, Configuration.GdbStubPort) : null;
|
||||
System = new HOS.Horizon(this);
|
||||
Statistics = new PerformanceStatistics(this);
|
||||
Hid = new Hid(this, System.HidStorage);
|
||||
@ -173,6 +176,7 @@ namespace Ryujinx.HLE
|
||||
AudioDeviceDriver.Dispose();
|
||||
FileSystem.Dispose();
|
||||
Memory.Dispose();
|
||||
Debugger?.Dispose();
|
||||
|
||||
TitleIDs.CurrentApplication.Value = null;
|
||||
Shared = null;
|
||||
|
@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// <summary>
|
||||
/// Range of memory that can be split in two.
|
||||
/// </summary>
|
||||
interface INonOverlappingRange : IRange
|
||||
public interface INonOverlappingRange : IRange
|
||||
{
|
||||
/// <summary>
|
||||
/// Split this region into two, around the specified address.
|
||||
|
@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
@ -7,8 +10,284 @@ namespace Ryujinx.Memory.Range
|
||||
/// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the range.</typeparam>
|
||||
class NonOverlappingRangeList<T> : RangeList<T> where T : INonOverlappingRange
|
||||
public class NonOverlappingRangeList<T> : RangeListBase<T> where T : INonOverlappingRange
|
||||
{
|
||||
private readonly Dictionary<ulong, RangeItem<T>> _quickAccess = new(AddressEqualityComparer.Comparer);
|
||||
private readonly Dictionary<ulong, RangeItem<T>> _fastQuickAccess = new(AddressEqualityComparer.Comparer);
|
||||
|
||||
public readonly ReaderWriterLockSlim Lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new non-overlapping range list.
|
||||
/// </summary>
|
||||
public NonOverlappingRangeList() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new non-overlapping range list.
|
||||
/// </summary>
|
||||
/// <param name="backingInitialSize">The initial size of the backing array</param>
|
||||
public NonOverlappingRangeList(int backingInitialSize) : base(backingInitialSize) { }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the list.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be added</param>
|
||||
public override void Add(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
RangeItem<T> rangeItem = new(item);
|
||||
|
||||
Insert(index, rangeItem);
|
||||
|
||||
_quickAccess.Add(item.Address, rangeItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected override bool Update(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0 && Items[index].Value.Equals(item))
|
||||
{
|
||||
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Items[index - 1].Next = rangeItem;
|
||||
}
|
||||
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = rangeItem;
|
||||
}
|
||||
|
||||
foreach (ulong addr in Items[index].QuickAccessAddresses)
|
||||
{
|
||||
_quickAccess.Remove(addr);
|
||||
_fastQuickAccess.Remove(addr);
|
||||
}
|
||||
|
||||
Items[index] = rangeItem;
|
||||
|
||||
_quickAccess[item.Address] = rangeItem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Insert(int index, RangeItem<T> item)
|
||||
{
|
||||
Debug.Assert(item.Address != item.EndAddress);
|
||||
|
||||
if (Count + 1 > Items.Length)
|
||||
{
|
||||
Array.Resize(ref Items, Items.Length + BackingGrowthSize);
|
||||
}
|
||||
|
||||
if (index >= Count)
|
||||
{
|
||||
if (index == Count)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
item.Previous = Items[index - 1];
|
||||
Items[index - 1].Next = item;
|
||||
}
|
||||
Items[index] = item;
|
||||
Count++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(Items, index, Items, index + 1, Count - index);
|
||||
|
||||
Items[index] = item;
|
||||
if (index != 0)
|
||||
{
|
||||
item.Previous = Items[index - 1];
|
||||
Items[index - 1].Next = item;
|
||||
}
|
||||
|
||||
item.Next = Items[index + 1];
|
||||
Items[index + 1].Previous = item;
|
||||
|
||||
Count++;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void RemoveAt(int index)
|
||||
{
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = index > 0 ? Items[index - 1] : null;
|
||||
}
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Items[index - 1].Next = index < Count - 1 ? Items[index + 1] : null;
|
||||
}
|
||||
|
||||
if (index < --Count)
|
||||
{
|
||||
Array.Copy(Items, index + 1, Items, index, Count - index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the list.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be removed</param>
|
||||
/// <returns>True if the item was removed, or false if it was not found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Remove(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0 && Items[index].Value.Equals(item))
|
||||
{
|
||||
_quickAccess.Remove(item.Address);
|
||||
|
||||
foreach (ulong addr in Items[index].QuickAccessAddresses)
|
||||
{
|
||||
_quickAccess.Remove(addr);
|
||||
_fastQuickAccess.Remove(addr);
|
||||
}
|
||||
|
||||
RemoveAt(index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a range of items from the item list
|
||||
/// </summary>
|
||||
/// <param name="startItem">The first item in the range of items to be removed</param>
|
||||
/// <param name="endItem">The last item in the range of items to be removed</param>
|
||||
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem)
|
||||
{
|
||||
if (startItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (startItem == endItem)
|
||||
{
|
||||
Remove(startItem.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
int startIndex = BinarySearch(startItem.Address);
|
||||
int endIndex = BinarySearch(endItem.Address);
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
{
|
||||
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
|
||||
}
|
||||
|
||||
if (startIndex > 0)
|
||||
{
|
||||
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
|
||||
}
|
||||
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
{
|
||||
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
|
||||
}
|
||||
|
||||
Count -= endIndex - startIndex + 1;
|
||||
|
||||
while (startItem != endItem.Next)
|
||||
{
|
||||
_quickAccess.Remove(startItem.Address);
|
||||
foreach (ulong addr in startItem.QuickAccessAddresses)
|
||||
{
|
||||
_quickAccess.Remove(addr);
|
||||
_fastQuickAccess.Remove(addr);
|
||||
}
|
||||
startItem = startItem.Next;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a range of items from the item list
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size of the range</param>
|
||||
public void RemoveRange(ulong address, ulong size)
|
||||
{
|
||||
int startIndex = BinarySearchLeftEdge(address, address + size);
|
||||
|
||||
if (startIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RangeItem<T> startItem = Items[startIndex];
|
||||
|
||||
int endIndex = startIndex;
|
||||
|
||||
while (startItem is not null && startItem.Address < address + size)
|
||||
{
|
||||
_quickAccess.Remove(startItem.Address);
|
||||
foreach (ulong addr in startItem.QuickAccessAddresses)
|
||||
{
|
||||
_quickAccess.Remove(addr);
|
||||
_fastQuickAccess.Remove(addr);
|
||||
}
|
||||
startItem = startItem.Next;
|
||||
endIndex++;
|
||||
}
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
{
|
||||
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
|
||||
}
|
||||
|
||||
if (startIndex > 0)
|
||||
{
|
||||
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
|
||||
}
|
||||
|
||||
|
||||
if (endIndex < Count - 1)
|
||||
{
|
||||
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
|
||||
}
|
||||
|
||||
Count -= endIndex - startIndex + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all ranges.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Clear()
|
||||
{
|
||||
Lock.EnterWriteLock();
|
||||
Count = 0;
|
||||
_quickAccess.Clear();
|
||||
_fastQuickAccess.Clear();
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a list of regions that cover the desired (address, size) range.
|
||||
/// If this range starts or ends in the middle of an existing region, it is split and only the relevant part is added.
|
||||
@ -19,17 +298,18 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="address">Start address of the search region</param>
|
||||
/// <param name="size">Size of the search region</param>
|
||||
/// <param name="factory">Factory for creating new ranges</param>
|
||||
public void GetOrAddRegions(List<T> list, ulong address, ulong size, Func<ulong, ulong, T> factory)
|
||||
public void GetOrAddRegions(out List<T> list, ulong address, ulong size, Func<ulong, ulong, T> factory)
|
||||
{
|
||||
// (regarding the specific case this generalized function is used for)
|
||||
// A new region may be split into multiple parts if multiple virtual regions have mapped to it.
|
||||
// For instance, while a virtual mapping could cover 0-2 in physical space, the space 0-1 may have already been reserved...
|
||||
// So we need to return both the split 0-1 and 1-2 ranges.
|
||||
|
||||
T[] results = new T[1];
|
||||
int count = FindOverlapsNonOverlapping(address, size, ref results);
|
||||
Lock.EnterWriteLock();
|
||||
(RangeItem<T> first, RangeItem<T> last) = FindOverlaps(address, size);
|
||||
list = new List<T>();
|
||||
|
||||
if (count == 0)
|
||||
if (first is null)
|
||||
{
|
||||
// The region is fully unmapped. Create and add it to the range list.
|
||||
T region = factory(address, size);
|
||||
@ -41,13 +321,15 @@ namespace Ryujinx.Memory.Range
|
||||
ulong lastAddress = address;
|
||||
ulong endAddress = address + size;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
RangeItem<T> current = first;
|
||||
while (last is not null && current is not null && current.Address < endAddress)
|
||||
{
|
||||
T region = results[i];
|
||||
if (count == 1 && region.Address == address && region.Size == size)
|
||||
T region = current.Value;
|
||||
if (first == last && region.Address == address && region.Size == size)
|
||||
{
|
||||
// Exact match, no splitting required.
|
||||
list.Add(region);
|
||||
Lock.ExitWriteLock();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -75,6 +357,7 @@ namespace Ryujinx.Memory.Range
|
||||
|
||||
list.Add(region);
|
||||
lastAddress = region.EndAddress;
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
if (lastAddress < endAddress)
|
||||
@ -85,6 +368,8 @@ namespace Ryujinx.Memory.Range
|
||||
Add(fillRegion);
|
||||
}
|
||||
}
|
||||
|
||||
Lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -95,6 +380,7 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="region">The region to split</param>
|
||||
/// <param name="splitAddress">The address to split with</param>
|
||||
/// <returns>The new region (high part)</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private T Split(T region, ulong splitAddress)
|
||||
{
|
||||
T newRegion = (T)region.Split(splitAddress);
|
||||
@ -102,5 +388,113 @@ namespace Ryujinx.Memory.Range
|
||||
Add(newRegion);
|
||||
return newRegion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an item on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The leftmost overlapping item, or null if none is found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlap(ulong address, ulong size)
|
||||
{
|
||||
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap))
|
||||
{
|
||||
return overlap;
|
||||
}
|
||||
|
||||
int index = BinarySearchLeftEdge(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Items[index].Address < address)
|
||||
{
|
||||
_quickAccess.TryAdd(address, Items[index]);
|
||||
Items[index].QuickAccessAddresses.Add(address);
|
||||
}
|
||||
|
||||
return Items[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an item on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The overlapping item, or null if none is found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
|
||||
{
|
||||
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap) || _fastQuickAccess.TryGetValue(address, out overlap))
|
||||
{
|
||||
return overlap;
|
||||
}
|
||||
|
||||
int index = BinarySearch(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Items[index].Address < address)
|
||||
{
|
||||
_quickAccess.TryAdd(address, Items[index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fastQuickAccess.TryAdd(address, Items[index]);
|
||||
}
|
||||
|
||||
Items[index].QuickAccessAddresses.Add(address);
|
||||
|
||||
return Items[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The first and last overlapping items, or null if none are found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public (RangeItem<T>, RangeItem<T>) FindOverlaps(ulong address, ulong size)
|
||||
{
|
||||
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap))
|
||||
{
|
||||
if (overlap.Next is null || overlap.Next.Address >= address + size)
|
||||
{
|
||||
return (overlap, overlap);
|
||||
}
|
||||
|
||||
return (overlap, Items[BinarySearchRightEdge(address, address + size)]);
|
||||
}
|
||||
|
||||
(int index, int endIndex) = BinarySearchEdges(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
if (Items[index].Address < address)
|
||||
{
|
||||
_quickAccess.TryAdd(address, Items[index]);
|
||||
Items[index].QuickAccessAddresses.Add(address);
|
||||
}
|
||||
|
||||
return (Items[index], Items[endIndex - 1]);
|
||||
}
|
||||
|
||||
public override IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
yield return Items[i].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +1,91 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
public class RangeItem<TValue>(TValue value) where TValue : IRange
|
||||
{
|
||||
public RangeItem<TValue> Next;
|
||||
public RangeItem<TValue> Previous;
|
||||
|
||||
public readonly ulong Address = value.Address;
|
||||
public readonly ulong EndAddress = value.Address + value.Size;
|
||||
|
||||
public readonly TValue Value = value;
|
||||
|
||||
public readonly List<ulong> QuickAccessAddresses = [];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
}
|
||||
|
||||
class AddressEqualityComparer : IEqualityComparer<ulong>
|
||||
{
|
||||
public bool Equals(ulong u1, ulong u2)
|
||||
{
|
||||
return u1 == u2;
|
||||
}
|
||||
|
||||
public int GetHashCode(ulong value) => (int)(value >> 5);
|
||||
|
||||
public static readonly AddressEqualityComparer Comparer = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of an Overlaps Finder function. WARNING: if the result is from the optimized
|
||||
/// Overlaps Finder, the StartIndex will be -1 even when the result isn't empty
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// startIndex is inclusive.
|
||||
/// endIndex is exclusive.
|
||||
/// </remarks>
|
||||
public readonly struct OverlapResult<T> where T : IRange
|
||||
{
|
||||
public readonly int StartIndex = -1;
|
||||
public readonly int EndIndex = -1;
|
||||
public readonly RangeItem<T> QuickResult;
|
||||
public int Count => EndIndex - StartIndex;
|
||||
|
||||
public OverlapResult(int startIndex, int endIndex, RangeItem<T> quickResult = null)
|
||||
{
|
||||
this.StartIndex = startIndex;
|
||||
this.EndIndex = endIndex;
|
||||
this.QuickResult = quickResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorted list of ranges that supports binary search.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the range.</typeparam>
|
||||
public class RangeList<T> : IEnumerable<T> where T : IRange
|
||||
public class RangeList<T> : RangeListBase<T> where T : IRange
|
||||
{
|
||||
private readonly struct RangeItem<TValue> where TValue : IRange
|
||||
{
|
||||
public readonly ulong Address;
|
||||
public readonly ulong EndAddress;
|
||||
public readonly ReaderWriterLockSlim Lock = new();
|
||||
|
||||
public readonly TValue Value;
|
||||
private readonly Dictionary<ulong, RangeItem<T>> _quickAccess = new(AddressEqualityComparer.Comparer);
|
||||
|
||||
public RangeItem(TValue value)
|
||||
{
|
||||
Value = value;
|
||||
|
||||
Address = value.Address;
|
||||
EndAddress = value.Address + value.Size;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool OverlapsWith(ulong address, ulong endAddress)
|
||||
{
|
||||
return Address < endAddress && address < EndAddress;
|
||||
}
|
||||
}
|
||||
|
||||
private const int BackingInitialSize = 1024;
|
||||
private const int ArrayGrowthSize = 32;
|
||||
|
||||
private RangeItem<T>[] _items;
|
||||
private readonly int _backingGrowthSize;
|
||||
|
||||
public int Count { get; protected set; }
|
||||
/// <summary>
|
||||
/// Creates a new range list.
|
||||
/// </summary>
|
||||
public RangeList() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new range list.
|
||||
/// </summary>
|
||||
/// <param name="backingInitialSize">The initial size of the backing array</param>
|
||||
public RangeList(int backingInitialSize = BackingInitialSize)
|
||||
{
|
||||
_backingGrowthSize = backingInitialSize;
|
||||
_items = new RangeItem<T>[backingInitialSize];
|
||||
}
|
||||
public RangeList(int backingInitialSize) : base(backingInitialSize) { }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the list.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be added</param>
|
||||
public void Add(T item)
|
||||
public override void Add(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
@ -72,27 +102,27 @@ namespace Ryujinx.Memory.Range
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
public bool Update(T item)
|
||||
protected override bool Update(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index > 0 && _items[index - 1].Address == item.Address)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
while (index < Count)
|
||||
{
|
||||
if (_items[index].Value.Equals(item))
|
||||
if (Items[index].Value.Equals(item))
|
||||
{
|
||||
_items[index] = new RangeItem<T>(item);
|
||||
foreach (ulong address in Items[index].QuickAccessAddresses)
|
||||
{
|
||||
_quickAccess.Remove(address);
|
||||
}
|
||||
|
||||
Items[index] = new RangeItem<T>(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_items[index].Address > item.Address)
|
||||
if (Items[index].Address > item.Address)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -107,23 +137,42 @@ namespace Ryujinx.Memory.Range
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Insert(int index, RangeItem<T> item)
|
||||
{
|
||||
if (Count + 1 > _items.Length)
|
||||
Debug.Assert(item.Address != item.EndAddress);
|
||||
|
||||
Debug.Assert(item.Address % 32 == 0);
|
||||
|
||||
if (Count + 1 > Items.Length)
|
||||
{
|
||||
Array.Resize(ref _items, _items.Length + _backingGrowthSize);
|
||||
Array.Resize(ref Items, Items.Length + BackingGrowthSize);
|
||||
}
|
||||
|
||||
if (index >= Count)
|
||||
{
|
||||
if (index == Count)
|
||||
{
|
||||
_items[Count++] = item;
|
||||
if (index != 0)
|
||||
{
|
||||
item.Previous = Items[index - 1];
|
||||
Items[index - 1].Next = item;
|
||||
}
|
||||
Items[index] = item;
|
||||
Count++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(_items, index, _items, index + 1, Count - index);
|
||||
Array.Copy(Items, index, Items, index + 1, Count - index);
|
||||
|
||||
Items[index] = item;
|
||||
if (index != 0)
|
||||
{
|
||||
item.Previous = Items[index - 1];
|
||||
Items[index - 1].Next = item;
|
||||
}
|
||||
|
||||
item.Next = Items[index + 1];
|
||||
Items[index + 1].Previous = item;
|
||||
|
||||
_items[index] = item;
|
||||
Count++;
|
||||
}
|
||||
}
|
||||
@ -131,9 +180,71 @@ namespace Ryujinx.Memory.Range
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void RemoveAt(int index)
|
||||
{
|
||||
foreach (ulong address in Items[index].QuickAccessAddresses)
|
||||
{
|
||||
_quickAccess.Remove(address);
|
||||
}
|
||||
|
||||
if (index < Count - 1)
|
||||
{
|
||||
Items[index + 1].Previous = index > 0 ? Items[index - 1] : null;
|
||||
}
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
Items[index - 1].Next = index < Count - 1 ? Items[index + 1] : null;
|
||||
}
|
||||
|
||||
if (index < --Count)
|
||||
{
|
||||
Array.Copy(_items, index + 1, _items, index, Count - index);
|
||||
Array.Copy(Items, index + 1, Items, index, Count - index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a range of items from the item list
|
||||
/// </summary>
|
||||
/// <param name="startItem">The first item in the range of items to be removed</param>
|
||||
/// <param name="endItem">The last item in the range of items to be removed</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem)
|
||||
{
|
||||
if (endItem.Next is not null)
|
||||
{
|
||||
endItem.Next.Previous = startItem.Previous;
|
||||
}
|
||||
|
||||
if (startItem.Previous is not null)
|
||||
{
|
||||
startItem.Previous.Next = endItem.Next;
|
||||
}
|
||||
|
||||
RangeItem<T> current = startItem;
|
||||
while (current != endItem.Next)
|
||||
{
|
||||
foreach (ulong address in current.QuickAccessAddresses)
|
||||
{
|
||||
_quickAccess.Remove(address);
|
||||
}
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
RangeItem<T>[] array = [];
|
||||
OverlapResult<T> overlapResult = FindOverlaps(startItem.Address, endItem.EndAddress, ref array);
|
||||
|
||||
if (overlapResult.EndIndex < Count)
|
||||
{
|
||||
Array.Copy(Items, overlapResult.EndIndex, Items, overlapResult.StartIndex, Count - overlapResult.EndIndex);
|
||||
Count -= overlapResult.Count;
|
||||
}
|
||||
else if (overlapResult.EndIndex == Count)
|
||||
{
|
||||
Count = overlapResult.StartIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,27 +253,22 @@ namespace Ryujinx.Memory.Range
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be removed</param>
|
||||
/// <returns>True if the item was removed, or false if it was not found</returns>
|
||||
public bool Remove(T item)
|
||||
public override bool Remove(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index > 0 && _items[index - 1].Address == item.Address)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
while (index < Count)
|
||||
{
|
||||
if (_items[index].Value.Equals(item))
|
||||
if (Items[index].Value.Equals(item))
|
||||
{
|
||||
RemoveAt(index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_items[index].Address > item.Address)
|
||||
if (Items[index].Address > item.Address)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -175,84 +281,60 @@ namespace Ryujinx.Memory.Range
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated</param>
|
||||
public void UpdateEndAddress(T item)
|
||||
{
|
||||
int index = BinarySearch(item.Address);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index > 0 && _items[index - 1].Address == item.Address)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
while (index < Count)
|
||||
{
|
||||
if (_items[index].Value.Equals(item))
|
||||
{
|
||||
_items[index] = new RangeItem<T>(item);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_items[index].Address > item.Address)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first item on the list overlapping in memory with the specified item.
|
||||
/// Gets an item on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Despite the name, this has no ordering guarantees of the returned item.
|
||||
/// It only ensures that the item returned overlaps the specified item.
|
||||
/// </remarks>
|
||||
/// <param name="item">Item to check for overlaps</param>
|
||||
/// <returns>The overlapping item, or the default value for the type if none found</returns>
|
||||
public T FindFirstOverlap(T item)
|
||||
{
|
||||
return FindFirstOverlap(item.Address, item.Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first item on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Despite the name, this has no ordering guarantees of the returned item.
|
||||
/// This has no ordering guarantees of the returned item.
|
||||
/// It only ensures that the item returned overlaps the specified memory range.
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The overlapping item, or the default value for the type if none found</returns>
|
||||
public T FindFirstOverlap(ulong address, ulong size)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlap(ulong address, ulong size)
|
||||
{
|
||||
int index = BinarySearchLeftEdge(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Items[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an item on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This has no ordering guarantees of the returned item.
|
||||
/// It only ensures that the item returned overlaps the specified memory range.
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>The overlapping item, or the default value for the type if none found</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
|
||||
{
|
||||
if (_quickAccess.TryGetValue(address, out RangeItem<T> quickResult))
|
||||
{
|
||||
return quickResult;
|
||||
}
|
||||
|
||||
int index = BinarySearch(address, address + size);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return default;
|
||||
return null;
|
||||
}
|
||||
|
||||
return _items[index].Value;
|
||||
}
|
||||
if (Items[index].OverlapsWith(address, address + 1))
|
||||
{
|
||||
_quickAccess.Add(address, Items[index]);
|
||||
Items[index].QuickAccessAddresses.Add(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items overlapping with the specified item in memory.
|
||||
/// </summary>
|
||||
/// <param name="item">Item to check for overlaps</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of overlapping items found</returns>
|
||||
public int FindOverlaps(T item, ref T[] output)
|
||||
{
|
||||
return FindOverlaps(item.Address, item.Size, ref output);
|
||||
return Items[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -261,222 +343,66 @@ namespace Ryujinx.Memory.Range
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of overlapping items found</returns>
|
||||
public int FindOverlaps(ulong address, ulong size, ref T[] output)
|
||||
/// <returns>Range information of overlapping items found</returns>
|
||||
private OverlapResult<T> FindOverlaps(ulong address, ulong size, ref RangeItem<T>[] output)
|
||||
{
|
||||
int outputIndex = 0;
|
||||
int outputCount = 0;
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
int startIndex = BinarySearch(address, endAddress);
|
||||
if (startIndex < 0)
|
||||
startIndex = ~startIndex;
|
||||
int endIndex = -1;
|
||||
|
||||
for (int i = startIndex; i < Count; i++)
|
||||
{
|
||||
ref RangeItem<T> item = ref _items[i];
|
||||
ref RangeItem<T> item = ref Items[i];
|
||||
|
||||
if (item.Address >= endAddress)
|
||||
{
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (item.OverlapsWith(address, endAddress))
|
||||
{
|
||||
if (outputIndex == output.Length)
|
||||
{
|
||||
Array.Resize(ref output, outputIndex + ArrayGrowthSize);
|
||||
}
|
||||
|
||||
output[outputIndex++] = item.Value;
|
||||
outputCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return outputIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items overlapping with the specified item in memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method only returns correct results if none of the items on the list overlaps with
|
||||
/// each other. If that is not the case, this method should not be used.
|
||||
/// This method is faster than the regular method to find all overlaps.
|
||||
/// </remarks>
|
||||
/// <param name="item">Item to check for overlaps</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of overlapping items found</returns>
|
||||
public int FindOverlapsNonOverlapping(T item, ref T[] output)
|
||||
{
|
||||
return FindOverlapsNonOverlapping(item.Address, item.Size, ref output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list overlapping the specified memory range.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method only returns correct results if none of the items on the list overlaps with
|
||||
/// each other. If that is not the case, this method should not be used.
|
||||
/// This method is faster than the regular method to find all overlaps.
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of overlapping items found</returns>
|
||||
public int FindOverlapsNonOverlapping(ulong address, ulong size, ref T[] output)
|
||||
{
|
||||
// This is a bit faster than FindOverlaps, but only works
|
||||
// when none of the items on the list overlaps with each other.
|
||||
int outputIndex = 0;
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
int index = BinarySearch(address, endAddress);
|
||||
|
||||
if (index >= 0)
|
||||
if (endIndex == -1 && outputCount > 0)
|
||||
{
|
||||
while (index > 0 && _items[index - 1].OverlapsWith(address, endAddress))
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (outputIndex == output.Length)
|
||||
{
|
||||
Array.Resize(ref output, outputIndex + ArrayGrowthSize);
|
||||
}
|
||||
|
||||
output[outputIndex++] = _items[index++].Value;
|
||||
}
|
||||
while (index < Count && _items[index].OverlapsWith(address, endAddress));
|
||||
endIndex = Count;
|
||||
}
|
||||
|
||||
return outputIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items on the list with the specified memory address.
|
||||
/// </summary>
|
||||
/// <param name="address">Address to find</param>
|
||||
/// <param name="output">Output array where matches will be written. It is automatically resized to fit the results</param>
|
||||
/// <returns>The number of matches found</returns>
|
||||
public int FindOverlaps(ulong address, ref T[] output)
|
||||
{
|
||||
int index = BinarySearch(address);
|
||||
|
||||
int outputIndex = 0;
|
||||
|
||||
if (index >= 0)
|
||||
if (outputCount > 0 && outputCount == endIndex - startIndex)
|
||||
{
|
||||
while (index > 0 && _items[index - 1].Address == address)
|
||||
{
|
||||
index--;
|
||||
}
|
||||
Array.Resize(ref output, outputCount);
|
||||
Array.Copy(Items, endIndex - outputCount, output, 0, outputCount);
|
||||
|
||||
while (index < Count)
|
||||
{
|
||||
ref RangeItem<T> overlap = ref _items[index++];
|
||||
|
||||
if (overlap.Address != address)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (outputIndex == output.Length)
|
||||
{
|
||||
Array.Resize(ref output, outputIndex + ArrayGrowthSize);
|
||||
}
|
||||
|
||||
output[outputIndex++] = overlap.Value;
|
||||
}
|
||||
return new OverlapResult<T>(startIndex, endIndex);
|
||||
}
|
||||
|
||||
return outputIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search on the internal list of items.
|
||||
/// </summary>
|
||||
/// <param name="address">Address to find</param>
|
||||
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
|
||||
private int BinarySearch(ulong address)
|
||||
{
|
||||
int left = 0;
|
||||
int right = Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
else if (outputCount > 0)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref _items[middle];
|
||||
|
||||
if (item.Address == address)
|
||||
Array.Resize(ref output, outputCount);
|
||||
int arrIndex = 0;
|
||||
for (int i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
return middle;
|
||||
output[arrIndex++] = Items[i];
|
||||
}
|
||||
|
||||
if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
return new OverlapResult<T>(endIndex - outputCount, endIndex);
|
||||
}
|
||||
|
||||
return ~left;
|
||||
return new OverlapResult<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search for items overlapping a given memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
|
||||
private int BinarySearch(ulong address, ulong endAddress)
|
||||
{
|
||||
int left = 0;
|
||||
int right = Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref _items[middle];
|
||||
|
||||
if (item.OverlapsWith(address, endAddress))
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
|
||||
if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
public override IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
yield return _items[i].Value;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
yield return _items[i].Value;
|
||||
yield return Items[i].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
359
src/Ryujinx.Memory/Range/RangeListBase.cs
Normal file
359
src/Ryujinx.Memory/Range/RangeListBase.cs
Normal file
@ -0,0 +1,359 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Memory.Range
|
||||
{
|
||||
public abstract class RangeListBase<T> : IEnumerable<T> where T : IRange
|
||||
{
|
||||
protected const int BackingInitialSize = 1024;
|
||||
|
||||
protected RangeItem<T>[] Items;
|
||||
protected readonly int BackingGrowthSize;
|
||||
|
||||
public int Count { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new range list.
|
||||
/// </summary>
|
||||
/// <param name="backingInitialSize">The initial size of the backing array</param>
|
||||
protected RangeListBase(int backingInitialSize = BackingInitialSize)
|
||||
{
|
||||
BackingGrowthSize = backingInitialSize;
|
||||
Items = new RangeItem<T>[backingInitialSize];
|
||||
}
|
||||
|
||||
public abstract void Add(T item);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an item's end address on the list. Address must be the same.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated</param>
|
||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||
protected abstract bool Update(T item);
|
||||
|
||||
public abstract bool Remove(T item);
|
||||
|
||||
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem);
|
||||
|
||||
public abstract RangeItem<T> FindOverlap(ulong address, ulong size);
|
||||
|
||||
public abstract RangeItem<T> FindOverlapFast(ulong address, ulong size);
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search on the internal list of items.
|
||||
/// </summary>
|
||||
/// <param name="address">Address to find</param>
|
||||
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected int BinarySearch(ulong address)
|
||||
{
|
||||
int left = 0;
|
||||
int right = Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
|
||||
if (item.Address == address)
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
|
||||
if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search for items overlapping a given memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected int BinarySearch(ulong address, ulong endAddress)
|
||||
{
|
||||
int left = 0;
|
||||
int right = Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
|
||||
if (item.OverlapsWith(address, endAddress))
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
|
||||
if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search for items overlapping a given memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected int BinarySearchLeftEdge(ulong address, ulong endAddress)
|
||||
{
|
||||
if (Count == 0)
|
||||
return ~0;
|
||||
|
||||
int left = 0;
|
||||
int right = Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
if (match)
|
||||
return middle;
|
||||
else if (address < item.Address)
|
||||
return ~(right);
|
||||
else
|
||||
return ~(right + 1);
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
right = middle;
|
||||
}
|
||||
else if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search for items overlapping a given memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>List index of the item, or complement index of nearest item with lower value on the list</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected int BinarySearchRightEdge(ulong address, ulong endAddress)
|
||||
{
|
||||
if (Count == 0)
|
||||
return ~0;
|
||||
|
||||
int left = 0;
|
||||
int right = Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = right - (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
if (match)
|
||||
return middle;
|
||||
else if (endAddress > item.EndAddress)
|
||||
return ~(left + 1);
|
||||
else
|
||||
return ~(left);
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
left = middle;
|
||||
}
|
||||
else if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search for items overlapping a given memory range.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="endAddress">End address of the range</param>
|
||||
/// <returns>Range information (inclusive, exclusive) of items that overlaps, or complement index of nearest item with lower value on the list</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected (int, int) BinarySearchEdges(ulong address, ulong endAddress)
|
||||
{
|
||||
if (Count == 0)
|
||||
return (~0, ~0);
|
||||
|
||||
if (Count == 1)
|
||||
{
|
||||
ref RangeItem<T> item = ref Items[0];
|
||||
|
||||
if (item.OverlapsWith(address, endAddress))
|
||||
{
|
||||
return (0, 1);
|
||||
}
|
||||
|
||||
if (address < item.Address)
|
||||
{
|
||||
return (~0, ~0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (~1, ~1);
|
||||
}
|
||||
}
|
||||
|
||||
int left = 0;
|
||||
int right = Count - 1;
|
||||
|
||||
int leftEdge = -1;
|
||||
int rightEdgeMatch = -1;
|
||||
int rightEdgeNoMatch = -1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
if (match)
|
||||
{
|
||||
leftEdge = middle;
|
||||
break;
|
||||
}
|
||||
else if (address < item.Address)
|
||||
{
|
||||
return (~right, ~right);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (~(right + 1), ~(right + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
right = middle;
|
||||
if (rightEdgeMatch == -1)
|
||||
rightEdgeMatch = middle;
|
||||
}
|
||||
else if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
rightEdgeNoMatch = middle;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (left > right)
|
||||
{
|
||||
return (~left, ~left);
|
||||
}
|
||||
|
||||
if (rightEdgeMatch == -1)
|
||||
{
|
||||
return (leftEdge, leftEdge + 1);
|
||||
}
|
||||
|
||||
left = rightEdgeMatch;
|
||||
right = rightEdgeNoMatch > 0 ? rightEdgeNoMatch : Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = right - (range >> 1);
|
||||
|
||||
ref RangeItem<T> item = ref Items[middle];
|
||||
|
||||
bool match = item.OverlapsWith(address, endAddress);
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
if (match)
|
||||
return (leftEdge, middle + 1);
|
||||
else
|
||||
return (leftEdge, middle);
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
left = middle;
|
||||
}
|
||||
else if (address < item.Address)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (leftEdge, right + 1);
|
||||
}
|
||||
|
||||
public abstract IEnumerator<T> GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
using Ryujinx.Common.Pools;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@ -76,17 +75,16 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
lock (TrackingLock)
|
||||
{
|
||||
ref VirtualRegion[] overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
|
||||
|
||||
for (int type = 0; type < 2; type++)
|
||||
{
|
||||
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
||||
regions.Lock.EnterReadLock();
|
||||
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(va, size);
|
||||
|
||||
int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
RangeItem<VirtualRegion> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
VirtualRegion region = overlaps[i];
|
||||
VirtualRegion region = current.Value;
|
||||
|
||||
// If the region has been fully remapped, signal that it has been mapped again.
|
||||
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
|
||||
@ -96,7 +94,9 @@ namespace Ryujinx.Memory.Tracking
|
||||
}
|
||||
|
||||
region.UpdateProtection();
|
||||
current = current.Next;
|
||||
}
|
||||
regions.Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,20 +114,21 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
lock (TrackingLock)
|
||||
{
|
||||
ref VirtualRegion[] overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
|
||||
|
||||
for (int type = 0; type < 2; type++)
|
||||
{
|
||||
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
||||
regions.Lock.EnterReadLock();
|
||||
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(va, size);
|
||||
|
||||
int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
RangeItem<VirtualRegion> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
VirtualRegion region = overlaps[i];
|
||||
VirtualRegion region = current.Value;
|
||||
|
||||
region.SignalMappingChanged(false);
|
||||
current = current.Next;
|
||||
}
|
||||
regions.Lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,9 +166,10 @@ namespace Ryujinx.Memory.Tracking
|
||||
/// <returns>A list of virtual regions within the given range</returns>
|
||||
internal List<VirtualRegion> GetVirtualRegionsForHandle(ulong va, ulong size, bool guest)
|
||||
{
|
||||
List<VirtualRegion> result = [];
|
||||
NonOverlappingRangeList<VirtualRegion> regions = guest ? _guestVirtualRegions : _virtualRegions;
|
||||
regions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size, guest));
|
||||
regions.Lock.EnterUpgradeableReadLock();
|
||||
regions.GetOrAddRegions(out List<VirtualRegion> result, va, size, (va, size) => new VirtualRegion(this, va, size, guest));
|
||||
regions.Lock.ExitUpgradeableReadLock();
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -296,25 +298,33 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
lock (TrackingLock)
|
||||
{
|
||||
ref VirtualRegion[] overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
|
||||
|
||||
NonOverlappingRangeList<VirtualRegion> regions = guest ? _guestVirtualRegions : _virtualRegions;
|
||||
List<RangeItem<VirtualRegion>> overlaps = [];
|
||||
|
||||
int count = regions.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||
// We use the non-span method here because keeping the lock will cause a deadlock.
|
||||
regions.Lock.EnterReadLock();
|
||||
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(address, size);
|
||||
|
||||
if (count == 0 && !precise)
|
||||
RangeItem<VirtualRegion> current = first;
|
||||
while (last != null && current != last.Next)
|
||||
{
|
||||
overlaps.Add(current);
|
||||
current = current.Next;
|
||||
}
|
||||
regions.Lock.ExitReadLock();
|
||||
|
||||
if (first is null && !precise)
|
||||
{
|
||||
if (_memoryManager.IsRangeMapped(address, size))
|
||||
{
|
||||
// TODO: There is currently the possibility that a page can be protected after its virtual region is removed.
|
||||
// This code handles that case when it happens, but it would be better to find out how this happens.
|
||||
_memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite, guest);
|
||||
|
||||
return true; // This memory _should_ be mapped, so we need to try again.
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldThrow = true;
|
||||
}
|
||||
|
||||
shouldThrow = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -324,9 +334,9 @@ namespace Ryujinx.Memory.Tracking
|
||||
size += (ulong)_pageSize;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
for (int i = 0; i < overlaps.Count; i++)
|
||||
{
|
||||
VirtualRegion region = overlaps[i];
|
||||
VirtualRegion region = overlaps[i].Value;
|
||||
|
||||
if (precise)
|
||||
{
|
||||
|
@ -338,6 +338,9 @@ namespace Ryujinx.Headless
|
||||
false,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
options.EnableGdbStub,
|
||||
options.GdbStubPort,
|
||||
options.DebuggerSuspendOnStart,
|
||||
options.CustomVSyncInterval
|
||||
)
|
||||
.Configure(
|
||||
|
@ -423,6 +423,17 @@ namespace Ryujinx.Headless
|
||||
[Option("skip-user-profiles-manager", Required = false, Default = false, HelpText = "Enable skips the Profiles Manager popup during gameplay. Select the desired profile before starting the game")]
|
||||
public bool SkipUserProfilesManager { get; set; }
|
||||
|
||||
// Debug
|
||||
|
||||
[Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")]
|
||||
public bool EnableGdbStub { get; set; }
|
||||
|
||||
[Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
|
||||
public ushort GdbStubPort { get; set; }
|
||||
|
||||
[Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]
|
||||
public bool DebuggerSuspendOnStart { get; set; }
|
||||
|
||||
// Values
|
||||
|
||||
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
||||
|
@ -218,6 +218,10 @@ namespace Ryujinx.Ava.Systems
|
||||
ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState;
|
||||
ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState;
|
||||
|
||||
ConfigurationState.Instance.Debug.EnableGdbStub.Event += UpdateEnableGdbStubState;
|
||||
ConfigurationState.Instance.Debug.GdbStubPort.Event += UpdateGdbStubPortState;
|
||||
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Event += UpdateDebuggerSuspendOnStartState;
|
||||
|
||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||
_gpuDoneEvent = new ManualResetEvent(false);
|
||||
}
|
||||
@ -564,6 +568,21 @@ namespace Ryujinx.Ava.Systems
|
||||
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
|
||||
}
|
||||
|
||||
private void UpdateEnableGdbStubState(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Device.Configuration.EnableGdbStub = e.NewValue;
|
||||
}
|
||||
|
||||
private void UpdateGdbStubPortState(object sender, ReactiveEventArgs<ushort> e)
|
||||
{
|
||||
Device.Configuration.GdbStubPort = e.NewValue;
|
||||
}
|
||||
|
||||
private void UpdateDebuggerSuspendOnStartState(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Device.Configuration.DebuggerSuspendOnStart = e.NewValue;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isActive = false;
|
||||
|
@ -464,6 +464,21 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// </summary>
|
||||
public bool UseHypervisor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the GDB stub
|
||||
/// </summary>
|
||||
public bool EnableGdbStub { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which TCP port should the GDB stub listen on
|
||||
/// </summary>
|
||||
public ushort GdbStubPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Suspend execution when starting an application
|
||||
/// </summary>
|
||||
public bool DebuggerSuspendOnStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show toggles for dirty hacks in the UI.
|
||||
/// </summary>
|
||||
|
@ -156,6 +156,10 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Multiplayer.LdnPassphrase.Value = cff.MultiplayerLdnPassphrase;
|
||||
Multiplayer.LdnServer.Value = cff.LdnServer;
|
||||
|
||||
Debug.EnableGdbStub.Value = shouldLoadFromFile ? cff.EnableGdbStub : Debug.EnableGdbStub.Value; // Get from global config only
|
||||
Debug.GdbStubPort.Value = shouldLoadFromFile ? cff.GdbStubPort : Debug.GdbStubPort.Value; // Get from global config only
|
||||
Debug.DebuggerSuspendOnStart.Value = shouldLoadFromFile ? cff.DebuggerSuspendOnStart : Debug.DebuggerSuspendOnStart.Value; // Get from global config only
|
||||
|
||||
{
|
||||
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks : Hacks.ShowDirtyHacks.Value; // Get from global config only
|
||||
|
||||
|
@ -703,6 +703,37 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug configuration section
|
||||
/// </summary>
|
||||
public class DebugSection
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables or disables the GDB stub
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableGdbStub { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which TCP port should the GDB stub listen on
|
||||
/// </summary>
|
||||
public ReactiveObject<ushort> GdbStubPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Suspend execution when starting an application
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> DebuggerSuspendOnStart { get; private set; }
|
||||
|
||||
public DebugSection()
|
||||
{
|
||||
EnableGdbStub = new ReactiveObject<bool>();
|
||||
EnableGdbStub.LogChangesToValue(nameof(EnableGdbStub));
|
||||
GdbStubPort = new ReactiveObject<ushort>();
|
||||
GdbStubPort.LogChangesToValue(nameof(GdbStubPort));
|
||||
DebuggerSuspendOnStart = new ReactiveObject<bool>();
|
||||
DebuggerSuspendOnStart.LogChangesToValue(nameof(DebuggerSuspendOnStart));
|
||||
}
|
||||
}
|
||||
|
||||
public class HacksSection
|
||||
{
|
||||
/// <summary>
|
||||
@ -801,6 +832,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// </summary>
|
||||
public MultiplayerSection Multiplayer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Debug
|
||||
/// </summary>
|
||||
public DebugSection Debug { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Dirty Hacks section
|
||||
/// </summary>
|
||||
@ -854,6 +890,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Graphics = new GraphicsSection();
|
||||
Hid = new HidSection();
|
||||
Multiplayer = new MultiplayerSection();
|
||||
Debug = new DebugSection();
|
||||
Hacks = new HacksSection();
|
||||
UpdateCheckerType = new ReactiveObject<UpdaterType>();
|
||||
FocusLostActionType = new ReactiveObject<FocusLostType>();
|
||||
@ -893,6 +930,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Multiplayer.DisableP2p,
|
||||
Multiplayer.LdnPassphrase,
|
||||
Multiplayer.GetLdnServer(),
|
||||
Debug.EnableGdbStub,
|
||||
Debug.GdbStubPort,
|
||||
Debug.DebuggerSuspendOnStart,
|
||||
Graphics.CustomVSyncInterval,
|
||||
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
|
||||
}
|
||||
|
@ -147,6 +147,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
MultiplayerDisableP2p = Multiplayer.DisableP2p,
|
||||
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
|
||||
LdnServer = Multiplayer.LdnServer,
|
||||
EnableGdbStub = Debug.EnableGdbStub,
|
||||
GdbStubPort = Debug.GdbStubPort,
|
||||
DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
|
||||
ShowDirtyHacks = Hacks.ShowDirtyHacks,
|
||||
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
|
||||
};
|
||||
@ -324,6 +327,9 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
},
|
||||
}
|
||||
];
|
||||
Debug.EnableGdbStub.Value = false;
|
||||
Debug.GdbStubPort.Value = 55555;
|
||||
Debug.DebuggerSuspendOnStart.Value = false;
|
||||
}
|
||||
|
||||
private static GraphicsBackend DefaultGraphicsBackend()
|
||||
|
57
src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml
Normal file → Executable file
57
src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml
Normal file → Executable file
@ -14,8 +14,7 @@
|
||||
Command="{Binding ToggleFavorite}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuToggleFavorite}"
|
||||
Icon="{ext:Icon fa-solid fa-star}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-star}" />
|
||||
<MenuItem
|
||||
Command="{Binding CreateApplicationShortcut}"
|
||||
CommandParameter="{Binding}"
|
||||
@ -41,71 +40,64 @@
|
||||
Command="{Binding OpenApplicationCompatibility}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuShowCompatEntry}"
|
||||
Icon="{ext:Icon mdi-gamepad}"
|
||||
Icon="{ext:Icon fa-solid fa-database}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuShowCompatEntryToolTip}"/>
|
||||
<MenuItem
|
||||
Command="{Binding OpenApplicationData}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuShowGameData}"
|
||||
Icon="{ext:Icon mdi-chart-line}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuShowGameDataToolTip}"/>
|
||||
Icon="{ext:Icon fa-solid fa-chart-line}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenUserSaveDirectory}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||
Icon="{ext:Icon mdi-folder-account}"
|
||||
IsEnabled="{Binding OpenUserSaveDirectoryEnabled}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-sd-card}"
|
||||
IsEnabled="{Binding OpenUserSaveDirectoryEnabled}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDeviceSaveDirectory}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuOpenDeviceSaveDirectory}"
|
||||
IsEnabled="{Binding OpenDeviceSaveDirectoryEnabled}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-hard-drive}"
|
||||
IsEnabled="{Binding OpenDeviceSaveDirectoryEnabled}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBcatSaveDirectory}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuOpenBcatSaveDirectory}"
|
||||
IsEnabled="{Binding OpenBcatSaveDirectoryEnabled}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-box-archive}"
|
||||
IsEnabled="{Binding OpenBcatSaveDirectoryEnabled}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenTitleUpdateManager}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuManageTitleUpdates}"
|
||||
Icon="{ext:Icon fa-solid fa-code-compare}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-code-compare}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDownloadableContentManager}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuManageDlc}"
|
||||
Icon="{ext:Icon fa-solid fa-download}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuManageDlcToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-download}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenCheatManager}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuManageCheat}"
|
||||
Icon="{ext:Icon fa-solid fa-code}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuManageCheatToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-code}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenModManager}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuManageMod}"
|
||||
Icon="{ext:Icon mdi-view-module}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuManageModToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-sliders}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenModsDirectory}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuOpenModsDirectory}"
|
||||
Icon="{ext:Icon mdi-folder-file}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-folder}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenSdModsDirectory}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuOpenSdModsDirectory}"
|
||||
Icon="{ext:Icon mdi-folder-file}"
|
||||
Icon="{ext:Icon fa-solid fa-folder}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
@ -113,40 +105,37 @@
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuTrimXCI}"
|
||||
IsEnabled="{Binding TrimXCIEnabled}"
|
||||
Icon="{ext:Icon fa-solid fa-scissors}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuTrimXCIToolTip}" />
|
||||
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon mdi-cached}">
|
||||
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon fa-solid fa-memory}">
|
||||
<MenuItem
|
||||
Command="{Binding PurgePtcCache}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuCacheManagementPurgePptc}"
|
||||
Icon="{ext:Icon mdi-refresh}"
|
||||
Icon="{ext:Icon fa-solid fa-arrow-rotate-right}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding NukePtcCache}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuCacheManagementNukePptc}"
|
||||
Icon="{ext:Icon mdi-delete-alert}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementNukePptcToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-trash-can}" />
|
||||
<MenuItem
|
||||
Command="{Binding PurgeShaderCache}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
|
||||
Icon="{ext:Icon mdi-delete-alert}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-trash-can}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenPtcDirectory}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
|
||||
Icon="{ext:Icon mdi-folder-arrow-up-down}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-folder}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenShaderCacheDirectory}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{ext:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
|
||||
Icon="{ext:Icon mdi-folder-arrow-up-down}"
|
||||
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
|
||||
Icon="{ext:Icon fa-solid fa-folder}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{ext:Locale GameListContextMenuExtractData}">
|
||||
<MenuItem Header="{ext:Locale GameListContextMenuExtractData}" Icon="{ext:Icon fa-solid fa-file-export}">
|
||||
<MenuItem
|
||||
Command="{Binding ExtractApplicationExeFs}"
|
||||
CommandParameter="{Binding}"
|
||||
|
@ -71,6 +71,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private string _ldnPassphrase;
|
||||
[ObservableProperty] private string _ldnServer;
|
||||
|
||||
private bool _enableGDBStub;
|
||||
private ushort _gdbStubPort;
|
||||
private bool _debuggerSuspendOnStart;
|
||||
|
||||
public SettingsHacksViewModel DirtyHacks { get; }
|
||||
|
||||
private readonly bool _isGameRunning;
|
||||
@ -387,6 +391,36 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
||||
|
||||
public bool EnableGdbStub
|
||||
{
|
||||
get => _enableGDBStub;
|
||||
set
|
||||
{
|
||||
_enableGDBStub = value;
|
||||
ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort GDBStubPort
|
||||
{
|
||||
get => _gdbStubPort;
|
||||
set
|
||||
{
|
||||
_gdbStubPort = value;
|
||||
ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort;
|
||||
}
|
||||
}
|
||||
|
||||
public bool DebuggerSuspendOnStart
|
||||
{
|
||||
get => _debuggerSuspendOnStart;
|
||||
set
|
||||
{
|
||||
_debuggerSuspendOnStart = value;
|
||||
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _debuggerSuspendOnStart;
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
@ -680,10 +714,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
// Multiplayer
|
||||
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
|
||||
DisableP2P = config.Multiplayer.DisableP2p;
|
||||
LdnPassphrase = config.Multiplayer.LdnPassphrase;
|
||||
LdnServer = config.Multiplayer.LdnServer;
|
||||
|
||||
// Debug
|
||||
EnableGdbStub = config.Debug.EnableGdbStub.Value;
|
||||
GDBStubPort = config.Debug.GdbStubPort.Value;
|
||||
DebuggerSuspendOnStart = config.Debug.DebuggerSuspendOnStart.Value;
|
||||
}
|
||||
|
||||
public void SaveSettings(bool global = false)
|
||||
@ -800,12 +840,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
// Multiplayer
|
||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
||||
config.Multiplayer.DisableP2p.Value = DisableP2P;
|
||||
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
|
||||
config.Multiplayer.LdnServer.Value = LdnServer;
|
||||
|
||||
// Debug
|
||||
config.Debug.EnableGdbStub.Value = EnableGdbStub;
|
||||
config.Debug.GdbStubPort.Value = GDBStubPort;
|
||||
config.Debug.DebuggerSuspendOnStart.Value = DebuggerSuspendOnStart;
|
||||
|
||||
// Dirty Hacks
|
||||
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
|
||||
config.Hacks.DisableNifmIsAnyInternetRequestAccepted.Value =
|
||||
|
68
src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
Normal file → Executable file
68
src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
Normal file → Executable file
@ -32,27 +32,23 @@
|
||||
Command="{Binding OpenFile}"
|
||||
Header="{ext:Locale MenuBarFileOpenFromFile}"
|
||||
Icon="{ext:Icon fa-solid fa-file}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{ext:Locale LoadApplicationFileTooltip}" />
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenFolder}"
|
||||
Header="{ext:Locale MenuBarFileOpenUnpacked}"
|
||||
Icon="{ext:Icon fa-solid fa-folder}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{ext:Locale LoadApplicationFolderTooltip}" />
|
||||
Icon="{ext:Icon fa-solid fa-folder-open}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
||||
<MenuItem
|
||||
Command="{Binding LoadDlcFromFolder}"
|
||||
Header="{ext:Locale MenuBarFileLoadDlcFromFolder}"
|
||||
Icon="{ext:Icon fa-solid fa-download}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{ext:Locale LoadDlcFromFolderTooltip}" />
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
||||
<MenuItem
|
||||
Command="{Binding LoadTitleUpdatesFromFolder}"
|
||||
Header="{ext:Locale MenuBarFileLoadTitleUpdatesFromFolder}"
|
||||
Icon="{ext:Icon fa-solid fa-code-compare}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{ext:Locale LoadTitleUpdatesFromFolderTooltip}" />
|
||||
<MenuItem Header="{ext:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}" Icon="{ext:Icon mdi-launch}">
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
||||
<MenuItem Header="{ext:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}" Icon="{ext:Icon fa-solid fa-microchip}">
|
||||
<MenuItem
|
||||
Name="MiiAppletMenuItem"
|
||||
Header="{ext:Locale MenuBarFileOpenAppletOpenMiiApplet}"
|
||||
@ -63,21 +59,20 @@
|
||||
<MenuItem
|
||||
Command="{Binding OpenRyujinxFolder}"
|
||||
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
|
||||
ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" />
|
||||
Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenScreenshotsFolder}"
|
||||
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
|
||||
ToolTip.Tip="{ext:Locale OpenScreenshotFolderTooltip}"/>
|
||||
Icon="{ext:Icon fa-solid fa-desktop}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenLogsFolder}"
|
||||
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
|
||||
ToolTip.Tip="{ext:Locale OpenRyujinxLogsTooltip}" />
|
||||
Icon="{ext:Icon fa-solid fa-file-lines}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Name="CloseRyujinxMenuItem"
|
||||
Header="{ext:Locale MenuBarFileExit}"
|
||||
Icon="{ext:Icon fa-solid fa-xmark}"
|
||||
ToolTip.Tip="{ext:Locale ExitTooltip}" />
|
||||
Icon="{ext:Icon fa-solid fa-power-off}" />
|
||||
</MenuItem>
|
||||
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarOptions}">
|
||||
<MenuItem
|
||||
@ -133,7 +128,7 @@
|
||||
Name="ChangeLanguageMenuItem"
|
||||
Padding="0"
|
||||
Header="{ext:Locale MenuBarOptionsChangeLanguage}"
|
||||
Icon="{ext:Icon fa-solid fa-language}"
|
||||
Icon="{ext:Icon fa-solid fa-globe}"
|
||||
Classes="withCheckbox">
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
@ -146,16 +141,14 @@
|
||||
Padding="0"
|
||||
Header="{ext:Locale MenuBarOptionsSettings}"
|
||||
Icon="{ext:Icon fa-solid fa-gear}"
|
||||
ToolTip.Tip="{ext:Locale OpenSettingsTooltip}"
|
||||
Classes="withCheckbox">
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
Command="{Binding ManageProfiles}"
|
||||
Padding="0"
|
||||
Header="{ext:Locale MenuBarOptionsManageUserProfiles}"
|
||||
Icon="{ext:Icon mdi-account}"
|
||||
Icon="{ext:Icon fa-solid fa-user}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{ext:Locale OpenProfileManagerTooltip}"
|
||||
Classes="withCheckbox">
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
@ -183,35 +176,34 @@
|
||||
Header="{ext:Locale MenuBarOptionsStopEmulation}"
|
||||
Icon="{ext:Icon fa-solid fa-stop}"
|
||||
InputGesture="Escape"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
ToolTip.Tip="{ext:Locale StopEmulationTooltip}" />
|
||||
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" />
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Icon="{ext:Icon fa-solid fa-sun}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenAmiiboWindow}"
|
||||
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBarActionsScanAmiibo}"
|
||||
Icon="{ext:Icon mdi-cube-scan}"
|
||||
Icon="{ext:Icon fa-solid fa-cube}"
|
||||
InputGesture="Ctrl + A"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBinFile}"
|
||||
AttachedToVisualTree="ScanBinAmiiboMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBarActionsScanAmiiboBin}"
|
||||
Icon="{ext:Icon mdi-cube-scan}"
|
||||
Icon="{ext:Icon fa-solid fa-cube}"
|
||||
IsVisible="{Binding CanScanAmiiboBinaries}"
|
||||
InputGesture="Ctrl + B"
|
||||
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding TakeScreenshot}"
|
||||
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
Icon="{ext:Icon mdi-monitor-screenshot}"
|
||||
Icon="{ext:Icon fa-solid fa-camera}"
|
||||
InputGesture="{Binding ScreenshotKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
Command="{Binding HideUi}"
|
||||
Header="{ext:Locale MenuBarFileToolsHideUi}"
|
||||
Icon="{ext:Icon mdi-eye-off}"
|
||||
Icon="{ext:Icon fa-solid fa-eye-slash}"
|
||||
InputGesture="{Binding ShowUiKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
@ -222,12 +214,12 @@
|
||||
</MenuItem>
|
||||
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarActions}" IsVisible="{Binding EnableNonGameRunningControls}">
|
||||
<MenuItem Header="{ext:Locale MenuBarActionsInstallKeys}" Icon="{ext:Icon fa-solid fa-key}">
|
||||
<MenuItem Command="{Binding InstallKeysFromFile}" Header="{ext:Locale MenuBarFileActionsInstallKeysFromFile}" Icon="{ext:Icon mdi-file-cog}" />
|
||||
<MenuItem Command="{Binding InstallKeysFromFolder}" Header="{ext:Locale MenuBarFileActionsInstallKeysFromFolder}" Icon="{ext:Icon mdi-folder-cog}" />
|
||||
<MenuItem Command="{Binding InstallKeysFromFile}" Header="{ext:Locale MenuBarFileActionsInstallKeysFromFile}" Icon="{ext:Icon fa-solid fa-file-code}" />
|
||||
<MenuItem Command="{Binding InstallKeysFromFolder}" Header="{ext:Locale MenuBarFileActionsInstallKeysFromFolder}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{ext:Locale MenuBarActionsInstallFirmware}" Icon="{ext:Icon fa-solid fa-download}">
|
||||
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromFile}" Icon="{ext:Icon mdi-file-cog}" />
|
||||
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromDirectory}" Icon="{ext:Icon mdi-folder-cog}" />
|
||||
<MenuItem Header="{ext:Locale MenuBarActionsInstallFirmware}" Icon="{ext:Icon fa-solid fa-floppy-disk}">
|
||||
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromFile}" Icon="{ext:Icon fa-solid fa-file-code}" />
|
||||
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromDirectory}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{ext:Locale MenuBarActionsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
|
||||
<MenuItem Name="InstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsInstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered, Converter={x:Static BoolConverters.Not}}" />
|
||||
@ -248,36 +240,34 @@
|
||||
<MenuItem
|
||||
Name="AboutWindowMenuItem"
|
||||
Header="{ext:Locale MenuBarHelpAbout}"
|
||||
Icon="{ext:Icon fa-solid fa-circle-info}"
|
||||
ToolTip.Tip="{ext:Locale OpenAboutTooltip}" />
|
||||
Icon="{ext:Icon fa-solid fa-circle-info}" />
|
||||
<MenuItem
|
||||
Name="UpdateMenuItem"
|
||||
IsEnabled="{Binding CanUpdate}"
|
||||
Header="{ext:Locale MenuBarHelpCheckForUpdates}"
|
||||
Icon="{ext:Icon mdi-update}"
|
||||
ToolTip.Tip="{ext:Locale CheckUpdatesTooltip}" />
|
||||
Icon="{ext:Icon fa-solid fa-rotate}" />
|
||||
<MenuItem
|
||||
Name="CompatibilityListMenuItem"
|
||||
Header="{ext:Locale CompatibilityListOpen}"
|
||||
Icon="{ext:Icon mdi-gamepad}"/>
|
||||
Icon="{ext:Icon fa-solid fa-database}"/>
|
||||
<Separator />
|
||||
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelpFaqAndGuides}" Icon="{ext:Icon fa-solid fa-question}" >
|
||||
<MenuItem
|
||||
Name="FaqMenuItem"
|
||||
Header="{ext:Locale MenuBarHelpFaq}"
|
||||
Icon="{ext:Icon fa-github}"
|
||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||
CommandParameter="https://git.ryujinx.app/ryubing/ryujinx/-/wikis/FAQ-&-Troubleshooting"
|
||||
ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" />
|
||||
<MenuItem
|
||||
Name="SetupGuideMenuItem"
|
||||
Header="{ext:Locale MenuBarHelpSetup}"
|
||||
Icon="{ext:Icon fa-github}"
|
||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||
CommandParameter="https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Setup-&-Configuration-Guide"
|
||||
ToolTip.Tip="{ext:Locale MenuBarHelpSetupTooltip}" />
|
||||
<MenuItem
|
||||
Name="LdnGuideMenuItem"
|
||||
Header="{ext:Locale MenuBarHelpMultiplayer}"
|
||||
Icon="{ext:Icon fa-github}"
|
||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||
CommandParameter="https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide"
|
||||
ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" />
|
||||
</MenuItem>
|
||||
|
@ -236,7 +236,6 @@
|
||||
Minimum="0"
|
||||
SmallChange="0.01"
|
||||
TickFrequency="0.05"
|
||||
ToolTip.Tip="{ext:Locale AudioVolumeTooltip}"
|
||||
Value="{Binding Volume}" />
|
||||
</Grid>
|
||||
</Flyout>
|
||||
|
@ -49,8 +49,7 @@
|
||||
<TextBlock
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale IconSize}"
|
||||
ToolTip.Tip="{ext:Locale IconSizeTooltip}" />
|
||||
Text="{ext:Locale IconSize}" />
|
||||
<controls:SliderScroll
|
||||
Width="150"
|
||||
Height="35"
|
||||
@ -61,7 +60,6 @@
|
||||
Maximum="4"
|
||||
Minimum="1"
|
||||
TickFrequency="1"
|
||||
ToolTip.Tip="{ext:Locale IconSizeTooltip}"
|
||||
Value="{Binding GridSizeScale}" />
|
||||
<CheckBox
|
||||
Margin="0"
|
||||
|
@ -33,6 +33,7 @@
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding AudioBackend}"
|
||||
Width="350"
|
||||
ToolTip.Tip="{ext:Locale AudioBackendTooltip}"
|
||||
HorizontalContentAlignment="Left">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemAudioBackendDummy}" />
|
||||
@ -51,10 +52,8 @@
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabSystemAudioVolume}"
|
||||
ToolTip.Tip="{ext:Locale AudioVolumeTooltip}"
|
||||
Width="250" />
|
||||
<ui:NumberBox Value="{Binding Volume}"
|
||||
ToolTip.Tip="{ext:Locale AudioVolumeTooltip}"
|
||||
Width="350"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
@ -66,7 +65,6 @@
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<controls:SliderScroll Value="{Binding Volume}"
|
||||
Margin="250,0,0,0"
|
||||
ToolTip.Tip="{ext:Locale AudioVolumeTooltip}"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
SmallChange="1"
|
||||
|
66
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
Normal file
66
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
Normal file
@ -0,0 +1,66 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsDebugView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="DebugPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabDebugTitle}" />
|
||||
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{ext:Locale SettingsTabDebugNote}" />
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnableGdbStub}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabDebugEnableGDBStub}"
|
||||
ToolTip.Tip="{ext:Locale SettingsTabDebugGDBStubToggleTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabDebugGDBStubPort}"
|
||||
Width="250" />
|
||||
<ui:NumberBox Value="{Binding GDBStubPort}"
|
||||
Width="350"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
SimpleNumberFormat="F0"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Minimum="1024"
|
||||
Maximum="65535" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding DebuggerSuspendOnStart}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabDebugSuspendOnStart}"
|
||||
ToolTip.Tip="{ext:Locale SettingsTabDebugSuspendOnStartTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
13
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
Normal file
13
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsDebugView : UserControl
|
||||
{
|
||||
public SettingsDebugView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -285,12 +285,10 @@
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ShaderDumpPathTooltip}"
|
||||
Text="{ext:Locale SettingsTabGraphicsShaderDumpPath}"
|
||||
Width="250" />
|
||||
<TextBox Text="{Binding ShaderDumpPath}"
|
||||
Width="350"
|
||||
ToolTip.Tip="{ext:Locale ShaderDumpPathTooltip}" />
|
||||
Width="350" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
@ -40,7 +40,6 @@
|
||||
Width="250" />
|
||||
<ComboBox
|
||||
SelectedIndex="{Binding Region}"
|
||||
ToolTip.Tip="{ext:Locale RegionTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Width="350">
|
||||
<ComboBoxItem>
|
||||
@ -72,11 +71,9 @@
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabSystemSystemLanguage}"
|
||||
ToolTip.Tip="{ext:Locale LanguageTooltip}"
|
||||
Width="250" />
|
||||
<ComboBox
|
||||
SelectedIndex="{Binding Language}"
|
||||
ToolTip.Tip="{ext:Locale LanguageTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Width="350">
|
||||
<ComboBoxItem>
|
||||
@ -141,7 +138,6 @@
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabSystemSystemTimeZone}"
|
||||
ToolTip.Tip="{ext:Locale TimezoneTooltip}"
|
||||
Width="250" />
|
||||
<AutoCompleteBox
|
||||
Name="TimeZoneBox"
|
||||
@ -152,7 +148,6 @@
|
||||
SelectionChanged="TimeZoneBox_OnSelectionChanged"
|
||||
Text="{Binding Path=TimeZone, Mode=OneWay}"
|
||||
TextChanged="TimeZoneBox_OnTextChanged"
|
||||
ToolTip.Tip="{ext:Locale TimezoneTooltip}"
|
||||
ValueMemberBinding="{Binding Mode=OneWay, Converter={x:Static helpers:TimeZoneConverter.Instance}}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
@ -163,13 +158,11 @@
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabSystemSystemTime}"
|
||||
ToolTip.Tip="{ext:Locale TimeTooltip}"
|
||||
Width="250"/>
|
||||
<DatePicker
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding !MatchSystemTime}"
|
||||
SelectedDate="{Binding CurrentDate}"
|
||||
ToolTip.Tip="{ext:Locale TimeTooltip}"
|
||||
Width="350" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
@ -183,8 +176,7 @@
|
||||
ClockIdentifier="24HourClock"
|
||||
IsEnabled="{Binding !MatchSystemTime}"
|
||||
SelectedTime="{Binding CurrentTime}"
|
||||
Width="350"
|
||||
ToolTip.Tip="{ext:Locale TimeTooltip}" />
|
||||
Width="350" />
|
||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
@ -289,6 +281,7 @@
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabSystemDramSize}"
|
||||
ToolTip.Tip="{ext:Locale DRamTooltip}"
|
||||
Width="250" />
|
||||
<ComboBox
|
||||
SelectedIndex="{Binding DramSize}"
|
||||
|
@ -46,6 +46,7 @@
|
||||
<settings:SettingsAudioView Name="AudioPage" />
|
||||
<settings:SettingsNetworkView Name="NetworkPage" />
|
||||
<settings:SettingsLoggingView Name="LoggingPage" />
|
||||
<settings:SettingsDebugView Name="DebugPage" />
|
||||
<settings:SettingsHacksView Name="HacksPage" />
|
||||
</Grid>
|
||||
<ui:NavigationView
|
||||
@ -100,6 +101,10 @@
|
||||
Content="{ext:Locale SettingsTabLogging}"
|
||||
Tag="LoggingPage"
|
||||
IconSource="Document" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{ext:Locale SettingsTabDebug}"
|
||||
Tag="DebugPage"
|
||||
IconSource="Star" />
|
||||
<ui:NavigationViewItem
|
||||
IsVisible="{Binding ShowDirtyHacks}"
|
||||
Content="Dirty Hacks"
|
||||
|
@ -98,6 +98,9 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
case "LoggingPage":
|
||||
NavPanel.Content = LoggingPage;
|
||||
break;
|
||||
case "DebugPage":
|
||||
NavPanel.Content = DebugPage;
|
||||
break;
|
||||
case nameof(HacksPage):
|
||||
HacksPage.DataContext = ViewModel;
|
||||
NavPanel.Content = HacksPage;
|
||||
|
Loading…
x
Reference in New Issue
Block a user