Compare commits

...

17 Commits

Author SHA1 Message Date
riperiperi
fdd3263e31 Separate guest/host tracking + unaligned protection (#6486)
* WIP: Separate guest/host tracking + unaligned protection

Allow memory manager to define support for single byte guest tracking

* Formatting

* Improve docs

* Properly handle cases where the address space bits are too low

* Address feedback
2024-03-14 19:38:27 -03:00
Isaac Marovitz
ce607db944 Ava UI: Update Ava (#6430)
* Update Ava

* Newline
2024-03-14 02:29:13 +01:00
TSRBerry
6b4ee82e5d infra: Fix updater for old Ava users (#6441)
* Add binaries with both names to release archives

* Add migration code for the new filename

* Add Ryujinx.Ava to all win/linux releases for a while
2024-03-13 23:26:35 +01:00
Keaton
e2a655f1a4 Update AutoDeleteCache.cs (#6471)
Increase the texture cache limit from 512 MB to 1 GB.
2024-03-13 17:39:39 -03:00
Nicolas Abram
d9a18919b0 Fix geometry shader passthrough issue (#6462)
* Fix geometry shader passthrough issue (Diagnosed by gdkchan)

* Fix whitespace formatting

* Fix whitespace formatting

* Bump shader cache version

* Don't apply PassthroughNV decorations to output geometry shader variables
2024-03-13 17:26:19 -03:00
Emmanuel Hansen
8354434a37 Passthrough mouse for win32 (#6450)
* passthrough mouse for win32

* remove unused enums
2024-03-11 21:21:39 -03:00
gdkchan
5a900f38c5 Fix lost copy and swap problem on shader SSA deconstruction (#6455)
* Fix lost copy on shader SSA deconstruction

* Shader cache version bump
2024-03-10 21:16:40 -03:00
jhorv
a3a63d4394 Refactor memory managers to a common base class, consolidate Read() method logic (#6360)
* - add new abstract class `VirtualMemoryManagerBase`
- rename `MemoryManagerBase` to `VirtualMemoryManagerRefCountedBase` and derive from `VirtualMemoryManagerBase`
- change `AddressSpaceManager`, `HvMemoryManager`, `MemoryManager`, and `MemoryManagerHostMapped` to implement abstract members and use the inherited `void VirtualMemoryManagerBase.Read(TVirtual va, Span<byte> data)` implementation.

* move property `AddressSpaceSize` up by the other properties
2024-03-09 21:01:51 -03:00
TSRBerry
3924bd1a43 Update dependencies from SixLabors to the latest version before the license change (#6440)
* nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 (#3976)

* nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3

Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 1.0.4 to 2.1.3.
- [Release notes](https://github.com/SixLabors/ImageSharp/releases)
- [Commits](https://github.com/SixLabors/ImageSharp/compare/v1.0.4...v2.1.3)

---
updated-dependencies:
- dependency-name: SixLabors.ImageSharp
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Update for 2.x changes

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mary <mary@mary.zone>

* Update SixLabors.ImageSharp to 2.1.7

This is the latest version we can update to without the license change.

* Update SixLabors.ImageSharp.Drawing to v1.0.0

This is the latest version we can update to without the license change.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mary <mary@mary.zone>
2024-03-08 13:16:32 -03:00
gdkchan
50458b2472 LightningJit: Disable some cache ops and CTR_EL0 access on Windows Arm (#6326)
* LightningJit: Disable some cache ops and CTR_EL0 access on Windows Arm

* Format whitespace

* Delete unused code

* Fix typo

Co-authored-by: riperiperi <rhy3756547@hotmail.com>

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2024-03-07 20:55:54 -03:00
MutantAura
dda0f26067 UI: Update minimum window size to 800x500 (#6425) 2024-03-07 20:38:56 -03:00
Kyle
4e1a60328e Add title of game to screenshot text (#6266)
* Add sanitize method

* Add app name to screenshot text output

* Add app name to screenshot text
2024-03-07 22:49:57 +00:00
Mary Guillemard
2505a1abcd misc: Remove myself from reviews
I have been mostly inactive on the project for the past year and a half
apart from handling CI and reviews because of a lack of motivation and
time.
2024-03-05 17:54:35 +01:00
Mary Guillemard
bc4d99a078 ci: try to fix toctou on release creation
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 12:58:03 +01:00
Mary Guillemard
ec6cb0abb4 infra: Make Avalonia the default UI (#6375)
* misc: Move Ryujinx project to Ryujinx.Gtk3

This breaks release CI for now but that's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* misc: Move Ryujinx.Ava project to Ryujinx

This breaks CI for now, but it's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* infra: Make Avalonia the default UI

Should fix CI after the previous changes.

GTK3 isn't build by the release job anymore, only by PR CI.

This also ensure that the test-ava update package is still generated to
allow update from the old testing channel.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix missing copy in create_app_bundle.sh

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix syntax error

Signed-off-by: Mary Guillemard <mary@mary.zone>

---------

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 12:51:05 +01:00
Mary Guillemard
53b5985da6 Avalonia: only enable gamescope workaround under it (#6374)
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 11:16:46 +01:00
gdkchan
87f238be60 Change packed aliasing formats to UInt (#6358) 2024-02-24 19:02:20 -03:00
274 changed files with 2386 additions and 2444 deletions

View File

@@ -259,12 +259,12 @@ dotnet_diagnostic.CA1861.severity = none
# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'" # Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'"
dotnet_diagnostic.CA1862.severity = none dotnet_diagnostic.CA1862.severity = none
[src/Ryujinx.HLE/HOS/Services/**.cs] [src/Ryujinx/UI/ViewModels/**.cs]
# Disable "mark members as static" rule for services # Disable "mark members as static" rule for ViewModels
dotnet_diagnostic.CA1822.severity = none dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Ava/UI/ViewModels/**.cs] [src/Ryujinx.HLE/HOS/Services/**.cs]
# Disable "mark members as static" rule for ViewModels # Disable "mark members as static" rule for services
dotnet_diagnostic.CA1822.severity = none dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Tests/Cpu/*.cs] [src/Ryujinx.Tests/Cpu/*.cs]

View File

@@ -7,7 +7,7 @@ updates:
labels: labels:
- "infra" - "infra"
reviewers: reviewers:
- marysaka - TSRBerry
commit-message: commit-message:
prefix: "ci" prefix: "ci"
@@ -19,7 +19,7 @@ updates:
labels: labels:
- "infra" - "infra"
reviewers: reviewers:
- marysaka - TSRBerry
commit-message: commit-message:
prefix: nuget prefix: nuget
groups: groups:

2
.github/labeler.yml vendored
View File

@@ -20,7 +20,7 @@ gpu:
gui: gui:
- changed-files: - changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Ava/**'] - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Gtk3/**']
horizon: horizon:
- changed-files: - changed-files:

View File

@@ -1,31 +1,24 @@
audio:
- marysaka
cpu: cpu:
- gdkchan - gdkchan
- riperiperi - riperiperi
- marysaka
- LDj3SNuD - LDj3SNuD
gpu: gpu:
- gdkchan - gdkchan
- riperiperi - riperiperi
- marysaka
gui: gui:
- Ack77 - Ack77
- emmauss - emmauss
- TSRBerry - TSRBerry
- marysaka
horizon: horizon:
- gdkchan - gdkchan
- Ack77 - Ack77
- marysaka
- TSRBerry - TSRBerry
infra: infra:
- marysaka
- TSRBerry - TSRBerry
default: default:

View File

@@ -67,15 +67,15 @@ jobs:
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Publish Ryujinx.Ava - name: Publish Ryujinx.Gtk3
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Set executable bit - name: Set executable bit
run: | run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- name: Upload Ryujinx artifact - name: Upload Ryujinx artifact
@@ -92,11 +92,11 @@ jobs:
path: publish_sdl2_headless path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Upload Ryujinx.Ava artifact - name: Upload Ryujinx.Gtk3 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_ava path: publish_gtk
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
build_macos: build_macos:
@@ -140,19 +140,19 @@ jobs:
shell: bash shell: bash
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Publish macOS Ryujinx.Ava - name: Publish macOS Ryujinx
run: | run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" ./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Publish macOS Ryujinx.Headless.SDL2 - name: Publish macOS Ryujinx.Headless.SDL2
run: | run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx.Ava artifact - name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_ava/*.tar.gz" path: "publish/*.tar.gz"
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact - name: Upload Ryujinx.Headless.SDL2 artifact

View File

@@ -39,24 +39,24 @@ jobs:
return core.error(`No artifacts found`); return core.error(`No artifacts found`);
} }
let body = `Download the artifacts for this pull request:\n`; let body = `Download the artifacts for this pull request:\n`;
let hidden_avalonia_artifacts = `\n\n <details><summary>Experimental GUI (Avalonia)</summary>\n`; let hidden_gtk_artifacts = `\n\n <details><summary>Old GUI (GTK3)</summary>\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`; let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`; let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) { for (const art of artifacts) {
if(art.name.includes('Debug')) { if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('ava-ryujinx')) { } else if(art.name.includes('gtk-ryujinx')) {
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('sdl2-ryujinx-headless')) { } else if(art.name.includes('sdl2-ryujinx-headless')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else { } else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} }
} }
hidden_avalonia_artifacts += `\n</details>`; hidden_gtk_artifacts += `\n</details>`;
hidden_headless_artifacts += `\n</details>`; hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`; hidden_debug_artifacts += `\n</details>`;
body += hidden_avalonia_artifacts; body += hidden_gtk_artifacts;
body += hidden_headless_artifacts; body += hidden_headless_artifacts;
body += hidden_debug_artifacts; body += hidden_debug_artifacts;

View File

@@ -44,6 +44,17 @@ jobs:
sha: context.sha sha: context.sha
}) })
- name: Create release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
tag: ${{ steps.version_info.outputs.build_version }}
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
release: release:
name: Release for ${{ matrix.platform.name }} name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }} runs-on: ${{ matrix.platform.os }}
@@ -60,7 +71,7 @@ jobs:
- uses: actions/setup-dotnet@v4 - uses: actions/setup-dotnet@v4
with: with:
global-json-file: global.json global-json-file: global.json
- name: Overwrite csc problem matcher - name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json" run: echo "::add-matcher::.github/csc.json"
@@ -86,43 +97,37 @@ jobs:
- name: Publish - name: Publish
run: | run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
- name: Packing Windows builds - name: Packing Windows builds
if: matrix.platform.os == 'windows-latest' if: matrix.platform.os == 'windows-latest'
run: | run: |
pushd publish_gtk pushd publish_ava
cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd popd
pushd publish_sdl2_headless pushd publish_sdl2_headless
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd popd
pushd publish_ava
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
shell: bash shell: bash
- name: Packing Linux builds - name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest' if: matrix.platform.os == 'ubuntu-latest'
run: | run: |
pushd publish_gtk pushd publish_ava
chmod +x publish/Ryujinx.sh publish/Ryujinx cp publish/Ryujinx publish/Ryujinx.Ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd popd
pushd publish_sdl2_headless pushd publish_sdl2_headless
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2 chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd popd
pushd publish_ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
shell: bash shell: bash
- name: Pushing new release - name: Pushing new release
@@ -183,10 +188,10 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash shell: bash
- name: Publish macOS Ryujinx.Ava - name: Publish macOS Ryujinx
run: | run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
- name: Publish macOS Ryujinx.Headless.SDL2 - name: Publish macOS Ryujinx.Headless.SDL2
run: | run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release

View File

@@ -3,13 +3,13 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.7" /> <PackageVersion Include="Avalonia" Version="11.0.10" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.7" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" /> <PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.13" /> <PackageVersion Include="Avalonia.Svg" Version="11.0.0.14" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.13" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.14" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
@@ -42,10 +42,9 @@
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.2" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" /> <PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430 VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Gtk3", "src\Ryujinx.Gtk3\Ryujinx.Gtk3.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
EndProject EndProject
@@ -69,7 +69,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
EndProject EndProject

View File

@@ -6,10 +6,6 @@ if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2" RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi fi
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
RYUJINX_BIN="Ryujinx.Ava"
fi
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
RYUJINX_BIN="Ryujinx" RYUJINX_BIN="Ryujinx"
fi fi

View File

@@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS" mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources" mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
# Copy executables first # Copy executable and nsure executable can be executed
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
# Then all libraries # Then all libraries

View File

@@ -22,9 +22,9 @@ EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ]; if [ "$VERSION" == "1.1.0" ];
then then
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
else else
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
fi fi
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app" ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
@@ -38,9 +38,9 @@ mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
dotnet restore dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava dotnet build -c "$CONFIGURATION" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) # Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
@@ -108,6 +108,13 @@ tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME"
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx" python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME" rm "$RELEASE_TAR_FILE_NAME"
# Create legacy update package for Avalonia to not left behind old testers.
if [ "$VERSION" != "1.1.0" ];
then
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
fi
popd popd
echo "Done" echo "Done"

View File

@@ -91,54 +91,54 @@ namespace ARMeilleure.Instructions
#region "Read" #region "Read"
public static byte ReadByte(ulong address) public static byte ReadByte(ulong address)
{ {
return GetMemoryManager().ReadTracked<byte>(address); return GetMemoryManager().ReadGuest<byte>(address);
} }
public static ushort ReadUInt16(ulong address) public static ushort ReadUInt16(ulong address)
{ {
return GetMemoryManager().ReadTracked<ushort>(address); return GetMemoryManager().ReadGuest<ushort>(address);
} }
public static uint ReadUInt32(ulong address) public static uint ReadUInt32(ulong address)
{ {
return GetMemoryManager().ReadTracked<uint>(address); return GetMemoryManager().ReadGuest<uint>(address);
} }
public static ulong ReadUInt64(ulong address) public static ulong ReadUInt64(ulong address)
{ {
return GetMemoryManager().ReadTracked<ulong>(address); return GetMemoryManager().ReadGuest<ulong>(address);
} }
public static V128 ReadVector128(ulong address) public static V128 ReadVector128(ulong address)
{ {
return GetMemoryManager().ReadTracked<V128>(address); return GetMemoryManager().ReadGuest<V128>(address);
} }
#endregion #endregion
#region "Write" #region "Write"
public static void WriteByte(ulong address, byte value) public static void WriteByte(ulong address, byte value)
{ {
GetMemoryManager().Write(address, value); GetMemoryManager().WriteGuest(address, value);
} }
public static void WriteUInt16(ulong address, ushort value) public static void WriteUInt16(ulong address, ushort value)
{ {
GetMemoryManager().Write(address, value); GetMemoryManager().WriteGuest(address, value);
} }
public static void WriteUInt32(ulong address, uint value) public static void WriteUInt32(ulong address, uint value)
{ {
GetMemoryManager().Write(address, value); GetMemoryManager().WriteGuest(address, value);
} }
public static void WriteUInt64(ulong address, ulong value) public static void WriteUInt64(ulong address, ulong value)
{ {
GetMemoryManager().Write(address, value); GetMemoryManager().WriteGuest(address, value);
} }
public static void WriteVector128(ulong address, V128 value) public static void WriteVector128(ulong address, V128 value)
{ {
GetMemoryManager().Write(address, value); GetMemoryManager().WriteGuest(address, value);
} }
#endregion #endregion

View File

@@ -28,6 +28,17 @@ namespace ARMeilleure.Memory
/// <returns>The data</returns> /// <returns>The data</returns>
T ReadTracked<T>(ulong va) where T : unmanaged; T ReadTracked<T>(ulong va) where T : unmanaged;
/// <summary>
/// Reads data from CPU mapped memory, from guest code. (with read tracking)
/// </summary>
/// <typeparam name="T">Type of the data being read</typeparam>
/// <param name="va">Virtual address of the data in memory</param>
/// <returns>The data</returns>
T ReadGuest<T>(ulong va) where T : unmanaged
{
return ReadTracked<T>(va);
}
/// <summary> /// <summary>
/// Writes data to CPU mapped memory. /// Writes data to CPU mapped memory.
/// </summary> /// </summary>
@@ -36,6 +47,17 @@ namespace ARMeilleure.Memory
/// <param name="value">Data to be written</param> /// <param name="value">Data to be written</param>
void Write<T>(ulong va, T value) where T : unmanaged; void Write<T>(ulong va, T value) where T : unmanaged;
/// <summary>
/// Writes data to CPU mapped memory, from guest code.
/// </summary>
/// <typeparam name="T">Type of the data being written</typeparam>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="value">Data to be written</param>
void WriteGuest<T>(ulong va, T value) where T : unmanaged
{
Write(va, value);
}
/// <summary> /// <summary>
/// Gets a read-only span of data from CPU mapped memory. /// Gets a read-only span of data from CPU mapped memory.
/// </summary> /// </summary>

View File

@@ -1,237 +0,0 @@
using Avalonia;
using Avalonia.Threading;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.SystemInfo;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal partial class Program
{
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
private const uint MbIconwarning = 0x30;
public static void Main(string[] args)
{
Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
}
PreviewerDetached = true;
Initialize(args);
LoggerAdapter.Register();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
EnableIme = true,
EnableInputFocusProxy = true,
RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software },
})
.With(new Win32PlatformOptions
{
WinUICompositionBackdropCornerRadius = 8.0f,
RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software },
})
.UseSkia();
}
private static void Initialize(string[] args)
{
// Parse arguments
CommandLineState.ParseArguments(args);
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Console.Title = $"Ryujinx Console {Version}";
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit();
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
// Initialize the configuration.
ConfigurationState.Initialize();
// Initialize the logger system.
LoggerModule.Initialize();
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
ReloadConfig();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
MainWindow.ShowKeyErrorOnLoad = true;
}
}
if (CommandLineState.LaunchPathArg != null)
{
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
}
}
public static void ReloadConfig()
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
ConfigurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
ConfigurationPath = appDataConfigurationPath;
}
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
ConfigurationPath = appDataConfigurationPath;
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
}
else
{
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
}
else
{
ConfigurationState.Instance.LoadDefault();
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
}
}
// Check if graphics backend was overridden
if (CommandLineState.OverrideGraphicsBackend != null)
{
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null)
{
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value,
};
}
}
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
}
else
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
}
}
private static void ProcessUnhandledException(Exception ex, bool isTerminating)
{
string message = $"Unhandled exception caught: {ex}";
Logger.Error?.PrintMsg(LogClass.Application, message);
if (Logger.Error == null)
{
Logger.Notice.PrintMsg(LogClass.Application, message);
}
if (isTerminating)
{
Exit();
}
}
public static void Exit()
{
DiscordIntegrationModule.Exit();
Logger.Shutdown();
}
}
}

View File

@@ -1,167 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<RootNamespace>Ryujinx.Ava</RootNamespace>
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
<TieredPGO>true</TieredPGO>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<!--
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
See:
https://github.com/amwx/FluentAvalonia/issues/481
https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/
-->
<PropertyGroup>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="Avalonia.Svg" />
<PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
<PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
<!--NOTE: DO NOT REMOVE, THIS IS REQUIRED AS A RESULT OF A TRIMMING ISSUE IN AVALONIA -->
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
<ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>alsoft.ini</TargetPath>
</Content>
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
<Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="UI\**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" />
<AvaloniaResource Include="Assets\Styles\Themes.xaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Locales\el_GR.json" />
<None Remove="Assets\Locales\en_US.json" />
<None Remove="Assets\Locales\es_ES.json" />
<None Remove="Assets\Locales\fr_FR.json" />
<None Remove="Assets\Locales\he_IL.json" />
<None Remove="Assets\Locales\de_DE.json" />
<None Remove="Assets\Locales\it_IT.json" />
<None Remove="Assets\Locales\ja_JP.json" />
<None Remove="Assets\Locales\ko_KR.json" />
<None Remove="Assets\Locales\pl_PL.json" />
<None Remove="Assets\Locales\pt_BR.json" />
<None Remove="Assets\Locales\ru_RU.json" />
<None Remove="Assets\Locales\tr_TR.json" />
<None Remove="Assets\Locales\uk_UA.json" />
<None Remove="Assets\Locales\zh_CN.json" />
<None Remove="Assets\Locales\zh_TW.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\Themes.xaml" />
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
<None Remove="Assets\Icons\Controller_JoyConPair.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight.svg" />
<None Remove="Assets\Icons\Controller_ProCon.svg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\Locales\el_GR.json" />
<EmbeddedResource Include="Assets\Locales\en_US.json" />
<EmbeddedResource Include="Assets\Locales\es_ES.json" />
<EmbeddedResource Include="Assets\Locales\fr_FR.json" />
<EmbeddedResource Include="Assets\Locales\he_IL.json" />
<EmbeddedResource Include="Assets\Locales\de_DE.json" />
<EmbeddedResource Include="Assets\Locales\it_IT.json" />
<EmbeddedResource Include="Assets\Locales\ja_JP.json" />
<EmbeddedResource Include="Assets\Locales\ko_KR.json" />
<EmbeddedResource Include="Assets\Locales\pl_PL.json" />
<EmbeddedResource Include="Assets\Locales\pt_BR.json" />
<EmbeddedResource Include="Assets\Locales\ru_RU.json" />
<EmbeddedResource Include="Assets\Locales\tr_TR.json" />
<EmbeddedResource Include="Assets\Locales\uk_UA.json" />
<EmbeddedResource Include="Assets\Locales\zh_CN.json" />
<EmbeddedResource Include="Assets\Locales\zh_TW.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
namespace Ryujinx.Common.Utilities namespace Ryujinx.Common.Utilities
{ {
@@ -44,5 +46,11 @@ namespace Ryujinx.Common.Utilities
CopyDirectory(sourceDir, destinationDir, true); CopyDirectory(sourceDir, destinationDir, true);
Directory.Delete(sourceDir, true); Directory.Delete(sourceDir, true);
} }
public static string SanitizeFileName(string fileName)
{
var reservedChars = new HashSet<char>(Path.GetInvalidFileNameChars());
return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c));
}
} }
} }

View File

@@ -8,7 +8,6 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
@@ -16,31 +15,10 @@ namespace Ryujinx.Cpu.AppleHv
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
/// </summary> /// </summary>
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public class HvMemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{ {
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
private enum HostMappedPtBits : ulong
{
Unmapped = 0,
Mapped,
WriteTracked,
ReadWriteTracked,
MappedReplicated = 0x5555555555555555,
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
ReadWriteTrackedReplicated = ulong.MaxValue,
}
private readonly InvalidAccessHandler _invalidAccessHandler; private readonly InvalidAccessHandler _invalidAccessHandler;
private readonly ulong _addressSpaceSize;
private readonly HvAddressSpace _addressSpace; private readonly HvAddressSpace _addressSpace;
internal HvAddressSpace AddressSpace => _addressSpace; internal HvAddressSpace AddressSpace => _addressSpace;
@@ -48,7 +26,7 @@ namespace Ryujinx.Cpu.AppleHv
private readonly MemoryBlock _backingMemory; private readonly MemoryBlock _backingMemory;
private readonly PageTable<ulong> _pageTable; private readonly PageTable<ulong> _pageTable;
private readonly ulong[] _pageBitmap; private readonly ManagedPageFlags _pages;
public bool Supports4KBPages => true; public bool Supports4KBPages => true;
@@ -62,6 +40,8 @@ namespace Ryujinx.Cpu.AppleHv
public event Action<ulong, ulong> UnmapEvent; public event Action<ulong, ulong> UnmapEvent;
protected override ulong AddressSpaceSize { get; }
/// <summary> /// <summary>
/// Creates a new instance of the Hypervisor memory manager. /// Creates a new instance of the Hypervisor memory manager.
/// </summary> /// </summary>
@@ -73,7 +53,7 @@ namespace Ryujinx.Cpu.AppleHv
_backingMemory = backingMemory; _backingMemory = backingMemory;
_pageTable = new PageTable<ulong>(); _pageTable = new PageTable<ulong>();
_invalidAccessHandler = invalidAccessHandler; _invalidAccessHandler = invalidAccessHandler;
_addressSpaceSize = addressSpaceSize; AddressSpaceSize = addressSpaceSize;
ulong asSize = PageSize; ulong asSize = PageSize;
int asBits = PageBits; int asBits = PageBits;
@@ -88,46 +68,10 @@ namespace Ryujinx.Cpu.AppleHv
AddressSpaceBits = asBits; AddressSpaceBits = asBits;
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; _pages = new ManagedPageFlags(AddressSpaceBits);
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler); Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
} }
/// <summary>
/// Checks if the virtual address is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address</param>
/// <returns>True if the virtual address is part of the addressable space</returns>
private bool ValidateAddress(ulong va)
{
return va < _addressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
/// <inheritdoc/> /// <inheritdoc/>
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
{ {
@@ -135,7 +79,7 @@ namespace Ryujinx.Cpu.AppleHv
PtMap(va, pa, size); PtMap(va, pa, size);
_addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute); _addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute);
AddMapping(va, size); _pages.AddMapping(va, size);
Tracking.Map(va, size); Tracking.Map(va, size);
} }
@@ -166,7 +110,7 @@ namespace Ryujinx.Cpu.AppleHv
UnmapEvent?.Invoke(va, size); UnmapEvent?.Invoke(va, size);
Tracking.Unmap(va, size); Tracking.Unmap(va, size);
RemoveMapping(va, size); _pages.RemoveMapping(va, size);
_addressSpace.UnmapUser(va, size); _addressSpace.UnmapUser(va, size);
PtUnmap(va, size); PtUnmap(va, size);
} }
@@ -209,9 +153,19 @@ namespace Ryujinx.Cpu.AppleHv
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Read(ulong va, Span<byte> data) public override void Read(ulong va, Span<byte> data)
{ {
ReadImpl(va, data); try
{
base.Read(va, data);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -340,7 +294,7 @@ namespace Ryujinx.Cpu.AppleHv
{ {
Span<byte> data = new byte[size]; Span<byte> data = new byte[size];
ReadImpl(va, data); base.Read(va, data);
return data; return data;
} }
@@ -367,7 +321,7 @@ namespace Ryujinx.Cpu.AppleHv
{ {
Memory<byte> memory = new byte[size]; Memory<byte> memory = new byte[size];
ReadImpl(va, memory.Span); base.Read(va, memory.Span);
return new WritableRegion(this, va, memory); return new WritableRegion(this, va, memory);
} }
@@ -390,22 +344,7 @@ namespace Ryujinx.Cpu.AppleHv
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va) public bool IsMapped(ulong va)
{ {
return ValidateAddress(va) && IsMappedImpl(va); return ValidateAddress(va) && _pages.IsMapped(va);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsMappedImpl(ulong va)
{
ulong page = va >> PageBits;
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
return ((pte >> bit) & 3) != 0;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -413,58 +352,7 @@ namespace Ryujinx.Cpu.AppleHv
{ {
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
return IsRangeMappedImpl(va, size); return _pages.IsRangeMapped(va, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
{
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
pageIndex = (int)(pageStart >> PageToPteShift);
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
}
private bool IsRangeMappedImpl(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
if (pages == 1)
{
return IsMappedImpl(va);
}
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
// Check if either bit in each 2 bit page entry is set.
// OR the block with itself shifted down by 1, and check the first bit of each entry.
ulong mask = BlockMappedMask & startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
if ((pte & mask) != mask)
{
return false;
}
mask = BlockMappedMask;
}
return true;
} }
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
@@ -576,48 +464,6 @@ namespace Ryujinx.Cpu.AppleHv
return regions; return regions;
} }
private void ReadImpl(ulong va, Span<byte> data)
{
if (data.Length == 0)
{
return;
}
try
{
AssertValidAddressAndSize(va, (ulong)data.Length);
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressChecked(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
_backingMemory.GetSpan(pa, size).CopyTo(data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
/// <remarks> /// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not. /// This function also validates that the given range is both valid and mapped, and will throw if it is not.
@@ -632,76 +478,7 @@ namespace Ryujinx.Cpu.AppleHv
return; return;
} }
// Software table, used for managed memory tracking. _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
if (state >= tag)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
return;
}
else if (state == 0)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
ulong mappedPte = pte | (pte >> 1);
if ((mappedPte & mappedMask) != mappedMask)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
pte &= mask;
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
{
// Writes trigger any tracking.
// Only trigger tracking from reads if both bits are set on any page.
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
}
mask = ulong.MaxValue;
}
}
} }
/// <summary> /// <summary>
@@ -728,103 +505,28 @@ namespace Ryujinx.Cpu.AppleHv
} }
/// <inheritdoc/> /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
{ {
// Protection is inverted on software pages, since the default value is 0. if (guest)
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, size, out va);
ulong pageStart = va >> PageBits;
if (pages == 1)
{ {
ulong protTag = protection switch _addressSpace.ReprotectUser(va, size, protection);
{
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
};
int bit = (int)((pageStart & 31) << 1);
ulong tagMask = 3UL << bit;
ulong invTagMask = ~tagMask;
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
} }
else else
{ {
ulong pageEnd = pageStart + (ulong)pages; _pages.TrackingReprotect(va, size, protection);
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
};
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Change the protection of all 2 bit entries that are mapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask &= mask; // Only update mapped pages within the given range.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
mask = ulong.MaxValue;
}
} }
protection = protection switch
{
MemoryPermission.None => MemoryPermission.ReadAndWrite,
MemoryPermission.Write => MemoryPermission.Read,
_ => MemoryPermission.None,
};
_addressSpace.ReprotectUser(va, size, protection);
} }
/// <inheritdoc/> /// <inheritdoc/>
public RegionHandle BeginTracking(ulong address, ulong size, int id) public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{ {
return Tracking.BeginTracking(address, size, id); return Tracking.BeginTracking(address, size, id, flags);
} }
/// <inheritdoc/> /// <inheritdoc/>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id) public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
{ {
return Tracking.BeginGranularTracking(address, size, handles, granularity, id); return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -833,86 +535,6 @@ namespace Ryujinx.Cpu.AppleHv
return Tracking.BeginSmartGranularTracking(address, size, granularity, id); return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
} }
/// <summary>
/// Adds the given address mapping to the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be mapped</param>
private void AddMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Map all 2-bit entries that are unmapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
mask = ulong.MaxValue;
}
}
/// <summary>
/// Removes the given address mapping from the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be unmapped</param>
private void RemoveMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
startMask = ~startMask;
endMask = ~endMask;
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask |= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
mask = 0;
}
}
private ulong GetPhysicalAddressChecked(ulong va) private ulong GetPhysicalAddressChecked(ulong va)
{ {
if (!IsMapped(va)) if (!IsMapped(va))
@@ -936,6 +558,10 @@ namespace Ryujinx.Cpu.AppleHv
_addressSpace.Dispose(); _addressSpace.Dispose();
} }
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
=> _backingMemory.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va)
=> GetPhysicalAddressChecked(va);
} }
} }

View File

@@ -28,8 +28,9 @@ namespace Ryujinx.Cpu
/// <param name="address">CPU virtual address of the region</param> /// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param> /// <param name="size">Size of the region</param>
/// <param name="id">Handle ID</param> /// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns> /// <returns>The memory tracking handle</returns>
RegionHandle BeginTracking(ulong address, ulong size, int id); RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None);
/// <summary> /// <summary>
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
@@ -39,8 +40,9 @@ namespace Ryujinx.Cpu
/// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param> /// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param>
/// <param name="granularity">Desired granularity of write tracking</param> /// <param name="granularity">Desired granularity of write tracking</param>
/// <param name="id">Handle ID</param> /// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns> /// <returns>The memory tracking handle</returns>
MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id); MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None);
/// <summary> /// <summary>
/// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.

View File

@@ -14,12 +14,8 @@ namespace Ryujinx.Cpu.Jit
/// <summary> /// <summary>
/// Represents a CPU memory manager. /// Represents a CPU memory manager.
/// </summary> /// </summary>
public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{ {
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
private const int PteSize = 8; private const int PteSize = 8;
private const int PointerTagBit = 62; private const int PointerTagBit = 62;
@@ -35,10 +31,10 @@ namespace Ryujinx.Cpu.Jit
/// </summary> /// </summary>
public int AddressSpaceBits { get; } public int AddressSpaceBits { get; }
private readonly ulong _addressSpaceSize;
private readonly MemoryBlock _pageTable; private readonly MemoryBlock _pageTable;
private readonly ManagedPageFlags _pages;
/// <summary> /// <summary>
/// Page table base pointer. /// Page table base pointer.
/// </summary> /// </summary>
@@ -50,6 +46,8 @@ namespace Ryujinx.Cpu.Jit
public event Action<ulong, ulong> UnmapEvent; public event Action<ulong, ulong> UnmapEvent;
protected override ulong AddressSpaceSize { get; }
/// <summary> /// <summary>
/// Creates a new instance of the memory manager. /// Creates a new instance of the memory manager.
/// </summary> /// </summary>
@@ -71,9 +69,11 @@ namespace Ryujinx.Cpu.Jit
} }
AddressSpaceBits = asBits; AddressSpaceBits = asBits;
_addressSpaceSize = asSize; AddressSpaceSize = asSize;
_pageTable = new MemoryBlock((asSize / PageSize) * PteSize); _pageTable = new MemoryBlock((asSize / PageSize) * PteSize);
_pages = new ManagedPageFlags(AddressSpaceBits);
Tracking = new MemoryTracking(this, PageSize); Tracking = new MemoryTracking(this, PageSize);
} }
@@ -93,6 +93,7 @@ namespace Ryujinx.Cpu.Jit
remainingSize -= PageSize; remainingSize -= PageSize;
} }
_pages.AddMapping(oVa, size);
Tracking.Map(oVa, size); Tracking.Map(oVa, size);
} }
@@ -115,6 +116,7 @@ namespace Ryujinx.Cpu.Jit
UnmapEvent?.Invoke(va, size); UnmapEvent?.Invoke(va, size);
Tracking.Unmap(va, size); Tracking.Unmap(va, size);
_pages.RemoveMapping(va, size);
ulong remainingSize = size; ulong remainingSize = size;
while (remainingSize != 0) while (remainingSize != 0)
@@ -153,9 +155,39 @@ namespace Ryujinx.Cpu.Jit
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Read(ulong va, Span<byte> data) public T ReadGuest<T>(ulong va) where T : unmanaged
{ {
ReadImpl(va, data); try
{
SignalMemoryTrackingImpl(va, (ulong)Unsafe.SizeOf<T>(), false, true);
return Read<T>(va);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return default;
}
}
/// <inheritdoc/>
public override void Read(ulong va, Span<byte> data)
{
try
{
base.Read(va, data);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -177,6 +209,16 @@ namespace Ryujinx.Cpu.Jit
WriteImpl(va, data); WriteImpl(va, data);
} }
/// <inheritdoc/>
public void WriteGuest<T>(ulong va, T value) where T : unmanaged
{
Span<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1));
SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true);
WriteImpl(va, data);
}
/// <inheritdoc/> /// <inheritdoc/>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data) public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{ {
@@ -290,7 +332,7 @@ namespace Ryujinx.Cpu.Jit
{ {
Span<byte> data = new byte[size]; Span<byte> data = new byte[size];
ReadImpl(va, data); base.Read(va, data);
return data; return data;
} }
@@ -462,48 +504,6 @@ namespace Ryujinx.Cpu.Jit
return regions; return regions;
} }
private void ReadImpl(ulong va, Span<byte> data)
{
if (data.Length == 0)
{
return;
}
try
{
AssertValidAddressAndSize(va, (ulong)data.Length);
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
_backingMemory.GetSpan(pa, size).CopyTo(data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsRangeMapped(ulong va, ulong size) public bool IsRangeMapped(ulong va, ulong size)
{ {
@@ -544,37 +544,6 @@ namespace Ryujinx.Cpu.Jit
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0; return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
} }
private bool ValidateAddress(ulong va)
{
return va < _addressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
private ulong GetPhysicalAddressInternal(ulong va) private ulong GetPhysicalAddressInternal(ulong va)
{ {
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask); return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
@@ -587,50 +556,57 @@ namespace Ryujinx.Cpu.Jit
} }
/// <inheritdoc/> /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
{ {
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
// Protection is inverted on software pages, since the default value is 0. if (guest)
protection = (~protection) & MemoryPermission.ReadAndWrite;
long tag = protection switch
{ {
MemoryPermission.None => 0L, // Protection is inverted on software pages, since the default value is 0.
MemoryPermission.Write => 2L << PointerTagBit, protection = (~protection) & MemoryPermission.ReadAndWrite;
_ => 3L << PointerTagBit,
};
int pages = GetPagesCount(va, (uint)size, out va); long tag = protection switch
ulong pageStart = va >> PageBits;
long invTagMask = ~(0xffffL << 48);
for (int page = 0; page < pages; page++)
{
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
long pte;
do
{ {
pte = Volatile.Read(ref pageRef); MemoryPermission.None => 0L,
} MemoryPermission.Write => 2L << PointerTagBit,
while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); _ => 3L << PointerTagBit,
};
pageStart++; int pages = GetPagesCount(va, (uint)size, out va);
ulong pageStart = va >> PageBits;
long invTagMask = ~(0xffffL << 48);
for (int page = 0; page < pages; page++)
{
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
long pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
pageStart++;
}
}
else
{
_pages.TrackingReprotect(va, size, protection);
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public RegionHandle BeginTracking(ulong address, ulong size, int id) public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{ {
return Tracking.BeginTracking(address, size, id); return Tracking.BeginTracking(address, size, id, flags);
} }
/// <inheritdoc/> /// <inheritdoc/>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id) public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
{ {
return Tracking.BeginGranularTracking(address, size, handles, granularity, id); return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -639,8 +615,7 @@ namespace Ryujinx.Cpu.Jit
return Tracking.BeginSmartGranularTracking(address, size, granularity, id); return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
} }
/// <inheritdoc/> private void SignalMemoryTrackingImpl(ulong va, ulong size, bool write, bool guest, bool precise = false, int? exemptId = null)
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{ {
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
@@ -650,31 +625,47 @@ namespace Ryujinx.Cpu.Jit
return; return;
} }
// We emulate guard pages for software memory access. This makes for an easy transition to // If the memory tracking is coming from the guest, use the tag bits in the page table entry.
// tracking using host guard pages in future, but also supporting platforms where this is not possible. // Otherwise, use the managed page flags.
// Write tag includes read protection, since we don't have any read actions that aren't performed before write too. if (guest)
long tag = (write ? 3L : 1L) << PointerTagBit;
int pages = GetPagesCount(va, (uint)size, out _);
ulong pageStart = va >> PageBits;
for (int page = 0; page < pages; page++)
{ {
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize); // We emulate guard pages for software memory access. This makes for an easy transition to
// tracking using host guard pages in future, but also supporting platforms where this is not possible.
long pte; // Write tag includes read protection, since we don't have any read actions that aren't performed before write too.
long tag = (write ? 3L : 1L) << PointerTagBit;
pte = Volatile.Read(ref pageRef); int pages = GetPagesCount(va, (uint)size, out _);
ulong pageStart = va >> PageBits;
if ((pte & tag) != 0) for (int page = 0; page < pages; page++)
{ {
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
break;
}
pageStart++; long pte;
pte = Volatile.Read(ref pageRef);
if ((pte & tag) != 0)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId, true);
break;
}
pageStart++;
}
} }
else
{
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
}
}
/// <inheritdoc/>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{
SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId);
} }
private ulong PaToPte(ulong pa) private ulong PaToPte(ulong pa)
@@ -691,5 +682,11 @@ namespace Ryujinx.Cpu.Jit
/// Disposes of resources used by the memory manager. /// Disposes of resources used by the memory manager.
/// </summary> /// </summary>
protected override void Destroy() => _pageTable.Dispose(); protected override void Destroy() => _pageTable.Dispose();
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
=> _backingMemory.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va)
=> GetPhysicalAddressInternal(va);
} }
} }

View File

@@ -6,46 +6,24 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Cpu.Jit namespace Ryujinx.Cpu.Jit
{ {
/// <summary> /// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary> /// </summary>
public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{ {
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
private enum HostMappedPtBits : ulong
{
Unmapped = 0,
Mapped,
WriteTracked,
ReadWriteTracked,
MappedReplicated = 0x5555555555555555,
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
ReadWriteTrackedReplicated = ulong.MaxValue,
}
private readonly InvalidAccessHandler _invalidAccessHandler; private readonly InvalidAccessHandler _invalidAccessHandler;
private readonly bool _unsafeMode; private readonly bool _unsafeMode;
private readonly AddressSpace _addressSpace; private readonly AddressSpace _addressSpace;
public ulong AddressSpaceSize { get; }
private readonly PageTable<ulong> _pageTable; private readonly PageTable<ulong> _pageTable;
private readonly MemoryEhMeilleure _memoryEh; private readonly MemoryEhMeilleure _memoryEh;
private readonly ulong[] _pageBitmap; private readonly ManagedPageFlags _pages;
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize; public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
@@ -60,6 +38,8 @@ namespace Ryujinx.Cpu.Jit
public event Action<ulong, ulong> UnmapEvent; public event Action<ulong, ulong> UnmapEvent;
protected override ulong AddressSpaceSize { get; }
/// <summary> /// <summary>
/// Creates a new instance of the host mapped memory manager. /// Creates a new instance of the host mapped memory manager.
/// </summary> /// </summary>
@@ -85,48 +65,12 @@ namespace Ryujinx.Cpu.Jit
AddressSpaceBits = asBits; AddressSpaceBits = asBits;
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))]; _pages = new ManagedPageFlags(AddressSpaceBits);
Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler); Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler);
_memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking); _memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking);
} }
/// <summary>
/// Checks if the virtual address is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address</param>
/// <returns>True if the virtual address is part of the addressable space</returns>
private bool ValidateAddress(ulong va)
{
return va < AddressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= AddressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
/// <summary> /// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped. /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
/// </summary> /// </summary>
@@ -134,7 +78,7 @@ namespace Ryujinx.Cpu.Jit
/// <param name="size">Size of the range in bytes</param> /// <param name="size">Size of the range in bytes</param>
private void AssertMapped(ulong va, ulong size) private void AssertMapped(ulong va, ulong size)
{ {
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size)) if (!ValidateAddressAndSize(va, size) || !_pages.IsRangeMapped(va, size))
{ {
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
} }
@@ -146,7 +90,7 @@ namespace Ryujinx.Cpu.Jit
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
_addressSpace.Map(va, pa, size, flags); _addressSpace.Map(va, pa, size, flags);
AddMapping(va, size); _pages.AddMapping(va, size);
PtMap(va, pa, size); PtMap(va, pa, size);
Tracking.Map(va, size); Tracking.Map(va, size);
@@ -166,7 +110,7 @@ namespace Ryujinx.Cpu.Jit
UnmapEvent?.Invoke(va, size); UnmapEvent?.Invoke(va, size);
Tracking.Unmap(va, size); Tracking.Unmap(va, size);
RemoveMapping(va, size); _pages.RemoveMapping(va, size);
PtUnmap(va, size); PtUnmap(va, size);
_addressSpace.Unmap(va, size); _addressSpace.Unmap(va, size);
} }
@@ -235,7 +179,7 @@ namespace Ryujinx.Cpu.Jit
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Read(ulong va, Span<byte> data) public override void Read(ulong va, Span<byte> data)
{ {
try try
{ {
@@ -377,22 +321,7 @@ namespace Ryujinx.Cpu.Jit
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va) public bool IsMapped(ulong va)
{ {
return ValidateAddress(va) && IsMappedImpl(va); return ValidateAddress(va) && _pages.IsMapped(va);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsMappedImpl(ulong va)
{
ulong page = va >> PageBits;
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
return ((pte >> bit) & 3) != 0;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -400,58 +329,7 @@ namespace Ryujinx.Cpu.Jit
{ {
AssertValidAddressAndSize(va, size); AssertValidAddressAndSize(va, size);
return IsRangeMappedImpl(va, size); return _pages.IsRangeMapped(va, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
{
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
pageIndex = (int)(pageStart >> PageToPteShift);
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
}
private bool IsRangeMappedImpl(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
if (pages == 1)
{
return IsMappedImpl(va);
}
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
// Check if either bit in each 2 bit page entry is set.
// OR the block with itself shifted down by 1, and check the first bit of each entry.
ulong mask = BlockMappedMask & startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
if ((pte & mask) != mask)
{
return false;
}
mask = BlockMappedMask;
}
return true;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -526,76 +404,7 @@ namespace Ryujinx.Cpu.Jit
return; return;
} }
// Software table, used for managed memory tracking. _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
if (state >= tag)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
return;
}
else if (state == 0)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
ulong mappedPte = pte | (pte >> 1);
if ((mappedPte & mappedMask) != mappedMask)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
pte &= mask;
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
{
// Writes trigger any tracking.
// Only trigger tracking from reads if both bits are set on any page.
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
}
mask = ulong.MaxValue;
}
}
} }
/// <summary> /// <summary>
@@ -622,103 +431,28 @@ namespace Ryujinx.Cpu.Jit
} }
/// <inheritdoc/> /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
{ {
// Protection is inverted on software pages, since the default value is 0. if (guest)
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, size, out va);
ulong pageStart = va >> PageBits;
if (pages == 1)
{ {
ulong protTag = protection switch _addressSpace.Base.Reprotect(va, size, protection, false);
{
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
};
int bit = (int)((pageStart & 31) << 1);
ulong tagMask = 3UL << bit;
ulong invTagMask = ~tagMask;
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
} }
else else
{ {
ulong pageEnd = pageStart + (ulong)pages; _pages.TrackingReprotect(va, size, protection);
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
};
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Change the protection of all 2 bit entries that are mapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask &= mask; // Only update mapped pages within the given range.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
mask = ulong.MaxValue;
}
} }
protection = protection switch
{
MemoryPermission.None => MemoryPermission.ReadAndWrite,
MemoryPermission.Write => MemoryPermission.Read,
_ => MemoryPermission.None,
};
_addressSpace.Base.Reprotect(va, size, protection, false);
} }
/// <inheritdoc/> /// <inheritdoc/>
public RegionHandle BeginTracking(ulong address, ulong size, int id) public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{ {
return Tracking.BeginTracking(address, size, id); return Tracking.BeginTracking(address, size, id, flags);
} }
/// <inheritdoc/> /// <inheritdoc/>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id) public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
{ {
return Tracking.BeginGranularTracking(address, size, handles, granularity, id); return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -727,86 +461,6 @@ namespace Ryujinx.Cpu.Jit
return Tracking.BeginSmartGranularTracking(address, size, granularity, id); return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
} }
/// <summary>
/// Adds the given address mapping to the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be mapped</param>
private void AddMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Map all 2-bit entries that are unmapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
mask = ulong.MaxValue;
}
}
/// <summary>
/// Removes the given address mapping from the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be unmapped</param>
private void RemoveMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
startMask = ~startMask;
endMask = ~endMask;
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask |= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
mask = 0;
}
}
/// <summary> /// <summary>
/// Disposes of resources used by the memory manager. /// Disposes of resources used by the memory manager.
/// </summary> /// </summary>
@@ -816,6 +470,10 @@ namespace Ryujinx.Cpu.Jit
_memoryEh.Dispose(); _memoryEh.Dispose();
} }
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
=> _addressSpace.Mirror.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va)
=> va;
} }
} }

View File

@@ -1106,6 +1106,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
case InstName.Mrs: case InstName.Mrs:
case InstName.MsrImm: case InstName.MsrImm:
case InstName.MsrReg: case InstName.MsrReg:
case InstName.Sysl:
return true; return true;
} }

View File

@@ -0,0 +1,48 @@
using System.Diagnostics;
namespace Ryujinx.Cpu.LightningJit.Arm64
{
static class SysUtils
{
public static (uint, uint, uint, uint) UnpackOp1CRnCRmOp2(uint encoding)
{
uint op1 = (encoding >> 16) & 7;
uint crn = (encoding >> 12) & 0xf;
uint crm = (encoding >> 8) & 0xf;
uint op2 = (encoding >> 5) & 7;
return (op1, crn, crm, op2);
}
public static bool IsCacheInstEl0(uint encoding)
{
(uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding);
return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch
{
0b011_0111_0100_001 => true, // DC ZVA
0b011_0111_1010_001 => true, // DC CVAC
0b011_0111_1100_001 => true, // DC CVAP
0b011_0111_1011_001 => true, // DC CVAU
0b011_0111_1110_001 => true, // DC CIVAC
0b011_0111_0101_001 => true, // IC IVAU
_ => false,
};
}
public static bool IsCacheInstUciTrapped(uint encoding)
{
(uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding);
return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch
{
0b011_0111_1010_001 => true, // DC CVAC
0b011_0111_1100_001 => true, // DC CVAP
0b011_0111_1011_001 => true, // DC CVAU
0b011_0111_1110_001 => true, // DC CIVAC
0b011_0111_0101_001 => true, // IC IVAU
_ => false,
};
}
}
}

View File

@@ -257,7 +257,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
(name, flags, AddressForm addressForm) = InstTable.GetInstNameAndFlags(encoding, cpuPreset.Version, cpuPreset.Features); (name, flags, AddressForm addressForm) = InstTable.GetInstNameAndFlags(encoding, cpuPreset.Version, cpuPreset.Features);
if (name.IsPrivileged()) if (name.IsPrivileged() || (name == InstName.Sys && IsPrivilegedSys(encoding)))
{ {
name = InstName.UdfPermUndef; name = InstName.UdfPermUndef;
flags = InstFlags.None; flags = InstFlags.None;
@@ -341,6 +341,11 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
return new(startAddress, address, insts, !isTruncated && !name.IsException(), isTruncated, isLoopEnd); return new(startAddress, address, insts, !isTruncated && !name.IsException(), isTruncated, isLoopEnd);
} }
private static bool IsPrivilegedSys(uint encoding)
{
return !SysUtils.IsCacheInstEl0(encoding);
}
private static bool IsMrsNzcv(uint encoding) private static bool IsMrsNzcv(uint encoding)
{ {
return (encoding & ~0x1fu) == 0xd53b4200u; return (encoding & ~0x1fu) == 0xd53b4200u;

View File

@@ -13,6 +13,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
public static void RewriteSysInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding) public static void RewriteSysInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding)
{ {
// TODO: Handle IC instruction, it should invalidate the JIT cache.
if (InstEmitSystem.IsCacheInstForbidden(encoding))
{
// Current OS does not allow cache maintenance instructions from user mode, just do nothing.
return;
}
int rtIndex = RegisterUtils.ExtractRt(encoding); int rtIndex = RegisterUtils.ExtractRt(encoding);
if (rtIndex == RegisterUtils.ZrIndex) if (rtIndex == RegisterUtils.ZrIndex)
{ {

View File

@@ -69,7 +69,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
asm.LdrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrEl0Offset); asm.LdrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrEl0Offset);
} }
} }
else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0 else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0
{ {
uint rd = encoding & 0x1f; uint rd = encoding & 0x1f;
@@ -115,7 +115,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
{ {
return true; return true;
} }
else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0 else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0
{ {
return true; return true;
} }
@@ -127,9 +127,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
return false; return false;
} }
private static bool IsAppleOS() private static bool IsCtrEl0AccessForbidden()
{ {
return OperatingSystem.IsMacOS() || OperatingSystem.IsIOS(); // Only Linux allows accessing CTR_EL0 from user mode.
return OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
}
public static bool IsCacheInstForbidden(uint encoding)
{
// Windows does not allow the cache maintenance instructions to be used from user mode.
return OperatingSystem.IsWindows() && SysUtils.IsCacheInstUciTrapped(encoding);
} }
public static bool NeedsContextStoreLoad(InstName name) public static bool NeedsContextStoreLoad(InstName name)

View File

@@ -0,0 +1,389 @@
using Ryujinx.Memory;
using Ryujinx.Memory.Tracking;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Cpu
{
/// <summary>
/// A page bitmap that keeps track of mapped state and tracking protection
/// for managed memory accesses (not using host page protection).
/// </summary>
internal readonly struct ManagedPageFlags
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
private readonly ulong[] _pageBitmap;
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
private enum ManagedPtBits : ulong
{
Unmapped = 0,
Mapped,
WriteTracked,
ReadWriteTracked,
MappedReplicated = 0x5555555555555555,
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
ReadWriteTrackedReplicated = ulong.MaxValue,
}
public ManagedPageFlags(int addressSpaceBits)
{
int bits = Math.Max(0, addressSpaceBits - (PageBits + PageToPteShift));
_pageBitmap = new ulong[1 << bits];
}
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPagesCount(ulong va, ulong size, out ulong startVa)
{
// WARNING: Always check if ulong does not overflow during the operations.
startVa = va & ~(ulong)PageMask;
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
return (int)(vaSpan / PageSize);
}
/// <summary>
/// Checks if the page at a given CPU virtual address is mapped.
/// </summary>
/// <param name="va">Virtual address to check</param>
/// <returns>True if the address is mapped, false otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsMapped(ulong va)
{
ulong page = va >> PageBits;
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
return ((pte >> bit) & 3) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
{
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
pageIndex = (int)(pageStart >> PageToPteShift);
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
}
/// <summary>
/// Checks if a memory range is mapped.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the entire range is mapped, false otherwise</returns>
public readonly bool IsRangeMapped(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
if (pages == 1)
{
return IsMapped(va);
}
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
// Check if either bit in each 2 bit page entry is set.
// OR the block with itself shifted down by 1, and check the first bit of each entry.
ulong mask = BlockMappedMask & startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
if ((pte & mask) != mask)
{
return false;
}
mask = BlockMappedMask;
}
return true;
}
/// <summary>
/// Reprotect a region of virtual memory for tracking.
/// </summary>
/// <param name="va">Virtual address base</param>
/// <param name="size">Size of the region to protect</param>
/// <param name="protection">Memory protection to set</param>
public readonly void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
// Protection is inverted on software pages, since the default value is 0.
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, size, out va);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)ManagedPtBits.Mapped,
MemoryPermission.Write => (ulong)ManagedPtBits.WriteTracked,
_ => (ulong)ManagedPtBits.ReadWriteTracked,
};
int bit = (int)((pageStart & 31) << 1);
ulong tagMask = 3UL << bit;
ulong invTagMask = ~tagMask;
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)ManagedPtBits.MappedReplicated,
MemoryPermission.Write => (ulong)ManagedPtBits.WriteTrackedReplicated,
_ => (ulong)ManagedPtBits.ReadWriteTrackedReplicated,
};
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Change the protection of all 2 bit entries that are mapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask &= mask; // Only update mapped pages within the given range.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
mask = ulong.MaxValue;
}
}
}
/// <summary>
/// Alerts the memory tracking that a given region has been read from or written to.
/// This should be called before read/write is performed.
/// </summary>
/// <param name="tracking">Memory tracking structure to call when pages are protected</param>
/// <param name="va">Virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="write">True if the region was written, false if read</param>
/// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
/// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SignalMemoryTracking(MemoryTracking tracking, ulong va, ulong size, bool write, int? exemptId = null)
{
// Software table, used for managed memory tracking.
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong tag = (ulong)(write ? ManagedPtBits.WriteTracked : ManagedPtBits.ReadWriteTracked);
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
if (state >= tag)
{
tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
return;
}
else if (state == 0)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong anyTrackingTag = (ulong)ManagedPtBits.WriteTrackedReplicated;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
ulong mappedPte = pte | (pte >> 1);
if ((mappedPte & mappedMask) != mappedMask)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
pte &= mask;
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
{
// Writes trigger any tracking.
// Only trigger tracking from reads if both bits are set on any page.
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
{
tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
}
mask = ulong.MaxValue;
}
}
}
/// <summary>
/// Adds the given address mapping to the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be mapped</param>
public readonly void AddMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Map all 2-bit entries that are unmapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
mask = ulong.MaxValue;
}
}
/// <summary>
/// Removes the given address mapping from the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be unmapped</param>
public readonly void RemoveMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
startMask = ~startMask;
endMask = ~endMask;
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask |= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
mask = 0;
}
}
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
}
}

View File

@@ -1,10 +1,13 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu namespace Ryujinx.Cpu
{ {
public abstract class MemoryManagerBase : IRefCounted public abstract class VirtualMemoryManagerRefCountedBase<TVirtual, TPhysical> : VirtualMemoryManagerBase<TVirtual, TPhysical>, IRefCounted
where TVirtual : IBinaryInteger<TVirtual>
where TPhysical : IBinaryInteger<TPhysical>
{ {
private int _referenceCount; private int _referenceCount;

View File

@@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
private const int MinCountForDeletion = 32; private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048; private const int MaxCapacity = 2048;
private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB; private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB;
private readonly LinkedList<Texture> _textures; private readonly LinkedList<Texture> _textures;
private ulong _totalSize; private ulong _totalSize;

View File

@@ -674,9 +674,9 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1), 1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1),
2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1), 2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1),
4 => new FormatInfo(Format.R32Float, 1, 1, 4, 1), 4 => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
8 => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2), 8 => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2),
16 => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4), 16 => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4),
_ => format, _ => format,
}; };
} }

View File

@@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Address = address; Address = address;
Size = size; Size = size;
_memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool); _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool, RegionFlags.None);
_memoryTracking.RegisterPreciseAction(address, size, PreciseAction); _memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
_modifiedDelegate = RegionModified; _modifiedDelegate = RegionModified;
} }

View File

@@ -128,13 +128,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (_useGranular) if (_useGranular)
{ {
_memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles); _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess, baseHandles);
_memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction); _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
} }
else else
{ {
_memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer); _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess);
if (baseHandles != null) if (baseHandles != null)
{ {

View File

@@ -368,10 +368,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">CPU virtual address of the region</param> /// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param> /// <param name="size">Size of the region</param>
/// <param name="kind">Kind of the resource being tracked</param> /// <param name="kind">Kind of the resource being tracked</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns> /// <returns>The memory tracking handle</returns>
public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind) public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind, RegionFlags flags = RegionFlags.None)
{ {
return _cpuMemory.BeginTracking(address, size, (int)kind); return _cpuMemory.BeginTracking(address, size, (int)kind, flags);
} }
/// <summary> /// <summary>
@@ -408,12 +409,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">CPU virtual address of the region</param> /// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param> /// <param name="size">Size of the region</param>
/// <param name="kind">Kind of the resource being tracked</param> /// <param name="kind">Kind of the resource being tracked</param>
/// <param name="flags">Region flags</param>
/// <param name="handles">Handles to inherit state from or reuse</param> /// <param name="handles">Handles to inherit state from or reuse</param>
/// <param name="granularity">Desired granularity of write tracking</param> /// <param name="granularity">Desired granularity of write tracking</param>
/// <returns>The memory tracking handle</returns> /// <returns>The memory tracking handle</returns>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096) public MultiRegionHandle BeginGranularTracking(
ulong address,
ulong size,
ResourceKind kind,
RegionFlags flags = RegionFlags.None,
IEnumerable<IRegionHandle> handles = null,
ulong granularity = 4096)
{ {
return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind); return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind, flags);
} }
/// <summary> /// <summary>

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 6253; private const uint CodeGenVersion = 6462;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@@ -356,6 +356,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddGlobalVariable(perVertexInputVariable); context.AddGlobalVariable(perVertexInputVariable);
context.Inputs.Add(new IoDefinition(StorageKind.Input, IoVariable.Position), perVertexInputVariable); context.Inputs.Add(new IoDefinition(StorageKind.Input, IoVariable.Position), perVertexInputVariable);
if (context.Definitions.Stage == ShaderStage.Geometry &&
context.Definitions.GpPassthrough &&
context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.MemberDecorate(perVertexInputStructType, 0, Decoration.PassthroughNV);
context.MemberDecorate(perVertexInputStructType, 1, Decoration.PassthroughNV);
context.MemberDecorate(perVertexInputStructType, 2, Decoration.PassthroughNV);
context.MemberDecorate(perVertexInputStructType, 3, Decoration.PassthroughNV);
}
} }
var perVertexOutputStructType = CreatePerVertexStructType(context); var perVertexOutputStructType = CreatePerVertexStructType(context);

View File

@@ -24,17 +24,21 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
continue; continue;
} }
Operand temp = OperandHelper.Local();
for (int index = 0; index < phi.SourcesCount; index++) for (int index = 0; index < phi.SourcesCount; index++)
{ {
Operand src = phi.GetSource(index); Operand src = phi.GetSource(index);
BasicBlock srcBlock = phi.GetBlock(index); BasicBlock srcBlock = phi.GetBlock(index);
Operation copyOp = new(Instruction.Copy, phi.Dest, src); Operation copyOp = new(Instruction.Copy, temp, src);
srcBlock.Append(copyOp); srcBlock.Append(copyOp);
} }
Operation copyOp2 = new(Instruction.Copy, phi.Dest, temp);
nextNode = block.Operations.AddAfter(node, copyOp2).Next;
block.Operations.Remove(node); block.Operations.Remove(node);
node = nextNode; node = nextNode;

View File

@@ -25,7 +25,7 @@ namespace Ryujinx.Modules
private readonly string _buildUrl; private readonly string _buildUrl;
private bool _restartQuery; private bool _restartQuery;
public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Gtk3.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { }
private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog")) private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog"))
{ {
@@ -34,7 +34,7 @@ namespace Ryujinx.Modules
_mainWindow = mainWindow; _mainWindow = mainWindow;
_buildUrl = buildUrl; _buildUrl = buildUrl;
Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png");
MainText.Text = "Do you want to update Ryujinx to the latest version?"; MainText.Text = "Do you want to update Ryujinx to the latest version?";
SecondaryText.Text = $"{Program.Version} -> {newVersion}"; SecondaryText.Text = $"{Program.Version} -> {newVersion}";

View File

@@ -1,75 +1,93 @@
using Avalonia.Controls; using Gtk;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI;
using Ryujinx.UI.Common.Models.Github; using Ryujinx.UI.Common.Models.Github;
using Ryujinx.UI.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Modules namespace Ryujinx.Modules
{ {
internal static class Updater public static class Updater
{ {
private const string GitHubApiUrl = "https://api.github.com"; private const string GitHubApiUrl = "https://api.github.com";
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private const int ConnectionCount = 4;
internal static bool Running;
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private const int ConnectionCount = 4;
private static string _buildVer; private static string _buildVer;
private static string _platformExt; private static string _platformExt;
private static string _buildUrl; private static string _buildUrl;
private static long _buildSize; private static long _buildSize;
private static bool _updateSuccessful;
private static bool _running;
private static readonly string[] _windowsDependencyDirs = Array.Empty<string>(); private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates.
private static readonly string[] _windowsDependencyDirs = { "bin", "etc", "lib", "share" };
private static HttpClient ConstructHttpClient()
{ {
if (_running) HttpClient result = new();
// Required by GitHub to interact with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
return result;
}
public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
{
if (Running)
{ {
return; return;
} }
_running = true; Running = true;
mainWindow.UpdateMenuItem.Sensitive = false;
int artifactIndex = -1;
// Detect current platform // Detect current platform
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
{ {
_platformExt = "macos_universal.app.tar.gz"; _platformExt = "osx_x64.zip";
artifactIndex = 1;
} }
else if (OperatingSystem.IsWindows()) else if (OperatingSystem.IsWindows())
{ {
_platformExt = "win_x64.zip"; _platformExt = "win_x64.zip";
artifactIndex = 2;
} }
else if (OperatingSystem.IsLinux()) else if (OperatingSystem.IsLinux())
{ {
var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64";
_platformExt = $"linux_{arch}.tar.gz"; _platformExt = $"linux_{arch}.tar.gz";
artifactIndex = 0;
}
if (artifactIndex == -1)
{
GtkDialog.CreateErrorDialog("Your platform is not supported!");
return;
} }
Version newVersion; Version newVersion;
@@ -81,14 +99,9 @@ namespace Ryujinx.Modules
} }
catch catch
{ {
GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!");
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
return; return;
} }
@@ -96,15 +109,16 @@ namespace Ryujinx.Modules
try try
{ {
using HttpClient jsonClient = ConstructHttpClient(); using HttpClient jsonClient = ConstructHttpClient();
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
// Fetch latest build information
string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl);
var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
_buildVer = fetched.Name; _buildVer = fetched.Name;
foreach (var asset in fetched.Assets) foreach (var asset in fetched.Assets)
{ {
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt)) if (asset.Name.StartsWith("gtk-ryujinx") && asset.Name.EndsWith(_platformExt))
{ {
_buildUrl = asset.BrowserDownloadUrl; _buildUrl = asset.BrowserDownloadUrl;
@@ -112,13 +126,9 @@ namespace Ryujinx.Modules
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog( GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
} }
_running = false;
return; return;
} }
@@ -126,29 +136,20 @@ namespace Ryujinx.Modules
} }
} }
// If build not done, assume no new update are available. if (_buildUrl == null)
if (_buildUrl is null)
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog( GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
} }
_running = false;
return; return;
} }
} }
catch (Exception exception) catch (Exception exception)
{ {
Logger.Error?.Print(LogClass.Application, exception.Message); Logger.Error?.Print(LogClass.Application, exception.Message);
GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.");
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
_running = false;
return; return;
} }
@@ -159,13 +160,8 @@ namespace Ryujinx.Modules
} }
catch catch
{ {
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!");
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!");
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
return; return;
} }
@@ -174,12 +170,11 @@ namespace Ryujinx.Modules
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog( GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
} }
_running = false; Running = false;
mainWindow.UpdateMenuItem.Sensitive = true;
return; return;
} }
@@ -202,39 +197,13 @@ namespace Ryujinx.Modules
_buildSize = -1; _buildSize = -1;
} }
await Dispatcher.UIThread.InvokeAsync(async () => // Show a message asking the user if they want to update
{ UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl);
// Show a message asking the user if they want to update updateDialog.Show();
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
$"{Program.Version} -> {newVersion}");
if (shouldUpdate)
{
await UpdateRyujinx(mainWindow, _buildUrl);
}
else
{
_running = false;
}
});
} }
private static HttpClient ConstructHttpClient() public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl)
{ {
HttpClient result = new();
// Required by GitHub to interact with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
return result;
}
private static async Task UpdateRyujinx(Window parent, string downloadUrl)
{
_updateSuccessful = false;
// Empty update dir, although it shouldn't ever have anything inside it // Empty update dir, although it shouldn't ever have anything inside it
if (Directory.Exists(_updateDir)) if (Directory.Exists(_updateDir))
{ {
@@ -245,93 +214,22 @@ namespace Ryujinx.Modules
string updateFile = Path.Combine(_updateDir, "update.bin"); string updateFile = Path.Combine(_updateDir, "update.bin");
TaskDialog taskDialog = new() // Download the update .zip
updateDialog.MainText.Text = "Downloading Update...";
updateDialog.ProgressBar.Value = 0;
updateDialog.ProgressBar.MaxValue = 100;
if (_buildSize >= 0)
{ {
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile);
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], }
IconSource = new SymbolIconSource { Symbol = Symbol.Download }, else
ShowProgressBar = true,
XamlRoot = parent,
};
taskDialog.Opened += (s, e) =>
{ {
if (_buildSize >= 0) DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
{
DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile);
}
else
{
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
}
};
await taskDialog.ShowAsync(true);
if (_updateSuccessful)
{
bool shouldRestart = true;
if (!OperatingSystem.IsMacOS())
{
shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
}
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
// On macOS we perform the update at relaunch.
if (OperatingSystem.IsMacOS())
{
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
string currentPid = Environment.ProcessId.ToString();
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
Process.Start("/bin/bash", arguments);
}
else
{
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath);
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
if (ryuName.EndsWith(".ryuold"))
{
ryuName = ryuName[..^7];
}
// Fallback if the executable could not be found.
if (!Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (string argument in CommandLineState.Arguments)
{
processStart.ArgumentList.Add(argument);
}
Process.Start(processStart);
}
Environment.Exit(0);
}
} }
} }
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile)
{ {
// Multi-Threaded Updater // Multi-Threaded Updater
long chunkSize = _buildSize / ConnectionCount; long chunkSize = _buildSize / ConnectionCount;
@@ -355,7 +253,6 @@ namespace Ryujinx.Modules
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
using WebClient client = new(); using WebClient client = new();
#pragma warning restore SYSLIB0014 #pragma warning restore SYSLIB0014
webClients.Add(client); webClients.Add(client);
if (i == ConnectionCount - 1) if (i == ConnectionCount - 1)
@@ -375,7 +272,7 @@ namespace Ryujinx.Modules
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount;
}; };
client.DownloadDataCompleted += (_, args) => client.DownloadDataCompleted += (_, args) =>
@@ -386,8 +283,6 @@ namespace Ryujinx.Modules
{ {
webClients[index].Dispose(); webClients[index].Dispose();
taskDialog.Hide();
return; return;
} }
@@ -405,24 +300,18 @@ namespace Ryujinx.Modules
File.WriteAllBytes(updateFile, mergedFileBytes); File.WriteAllBytes(updateFile, mergedFileBytes);
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
if (OperatingSystem.IsMacOS())
{
using Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile });
xattrProcess.WaitForExit();
}
try try
{ {
InstallUpdate(taskDialog, updateFile); InstallUpdate(updateDialog, updateFile);
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warning?.Print(LogClass.Application, e.Message); Logger.Warning?.Print(LogClass.Application, e.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
return;
} }
} }
}; };
@@ -441,14 +330,14 @@ namespace Ryujinx.Modules
webClient.CancelAsync(); webClient.CancelAsync();
} }
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
return; return;
} }
} }
} }
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile)
{ {
using HttpClient client = new(); using HttpClient client = new();
// We do not want to timeout while downloading // We do not want to timeout while downloading
@@ -474,118 +363,101 @@ namespace Ryujinx.Modules
byteWritten += readSize; byteWritten += readSize;
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100;
updateFileStream.Write(buffer, 0, readSize); updateFileStream.Write(buffer, 0, readSize);
} }
InstallUpdate(taskDialog, updateFile); InstallUpdate(updateDialog, updateFile);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile)
private static double GetPercentage(double value, double max)
{ {
return max == 0 ? 0 : value / max * 100; Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile))
}
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
{ {
Name = "Updater.SingleThreadWorker", Name = "Updater.SingleThreadWorker",
}; };
worker.Start(); worker.Start();
} }
[SupportedOSPlatform("linux")] private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile)
[SupportedOSPlatform("macos")]
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{
using Stream inStream = File.OpenRead(archivePath);
using GZipInputStream gzipStream = new(inStream);
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
TarEntry tarEntry;
while ((tarEntry = tarStream.GetNextEntry()) is not null)
{
if (tarEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using FileStream outStream = File.OpenWrite(outPath);
tarStream.CopyEntryContents(outStream);
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
if (tarEntry is null)
{
return;
}
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
});
}
}
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{
using Stream inStream = File.OpenRead(archivePath);
using ZipFile zipFile = new(inStream);
double count = 0;
foreach (ZipEntry zipEntry in zipFile)
{
count++;
if (zipEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using Stream zipStream = zipFile.GetInputStream(zipEntry);
using FileStream outStream = File.OpenWrite(outPath);
zipStream.CopyTo(outStream);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
});
}
}
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
{ {
// Extract Update // Extract Update
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; updateDialog.MainText.Text = "Extracting Update...";
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = 0;
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) if (OperatingSystem.IsLinux())
{ {
ExtractTarGzipFile(taskDialog, updateFile, _updateDir); using Stream inStream = File.OpenRead(updateFile);
} using Stream gzipStream = new GZipInputStream(inStream);
else if (OperatingSystem.IsWindows()) using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
{ updateDialog.ProgressBar.MaxValue = inStream.Length;
ExtractZipFile(taskDialog, updateFile, _updateDir);
await Task.Run(() =>
{
TarEntry tarEntry;
if (!OperatingSystem.IsWindows())
{
while ((tarEntry = tarStream.GetNextEntry()) != null)
{
if (tarEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(_updateDir, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using FileStream outStream = File.OpenWrite(outPath);
tarStream.CopyEntryContents(outStream);
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
TarEntry entry = tarEntry;
Application.Invoke(delegate
{
updateDialog.ProgressBar.Value += entry.Size;
});
}
}
});
updateDialog.ProgressBar.Value = inStream.Length;
} }
else else
{ {
throw new NotSupportedException(); using Stream inStream = File.OpenRead(updateFile);
using ZipFile zipFile = new(inStream);
updateDialog.ProgressBar.MaxValue = zipFile.Count;
await Task.Run(() =>
{
foreach (ZipEntry zipEntry in zipFile)
{
if (zipEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(_updateDir, zipEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using Stream zipStream = zipFile.GetInputStream(zipEntry);
using FileStream outStream = File.OpenWrite(outPath);
zipStream.CopyTo(outStream);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Application.Invoke(delegate
{
updateDialog.ProgressBar.Value++;
});
}
});
} }
// Delete downloaded zip // Delete downloaded zip
@@ -593,46 +465,49 @@ namespace Ryujinx.Modules
List<string> allFiles = EnumerateFilesToDelete().ToList(); List<string> allFiles = EnumerateFilesToDelete().ToList();
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; updateDialog.MainText.Text = "Renaming Old Files...";
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = 0;
updateDialog.ProgressBar.MaxValue = allFiles.Count;
// NOTE: On macOS, replacement is delayed to the restart phase. // Replace old files
if (!OperatingSystem.IsMacOS()) await Task.Run(() =>
{ {
// Replace old files
double count = 0;
foreach (string file in allFiles) foreach (string file in allFiles)
{ {
count++;
try try
{ {
File.Move(file, file + ".ryuold"); File.Move(file, file + ".ryuold");
Dispatcher.UIThread.InvokeAsync(() => Application.Invoke(delegate
{ {
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value++;
}); });
} }
catch catch
{ {
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); Logger.Warning?.Print(LogClass.Application, "Updater was unable to rename file: " + file);
} }
} }
Dispatcher.UIThread.InvokeAsync(() => Application.Invoke(delegate
{ {
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; updateDialog.MainText.Text = "Adding New Files...";
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = 0;
updateDialog.ProgressBar.MaxValue = Directory.GetFiles(_updatePublishDir, "*", SearchOption.AllDirectories).Length;
}); });
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); MoveAllFilesOver(_updatePublishDir, _homeDir, updateDialog);
});
Directory.Delete(_updateDir, true); Directory.Delete(_updateDir, true);
}
_updateSuccessful = true; updateDialog.MainText.Text = "Update Complete!";
updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?";
updateDialog.Modal = true;
taskDialog.Hide(); updateDialog.ProgressBar.Hide();
updateDialog.YesButton.Show();
updateDialog.NoButton.Show();
} }
public static bool CanUpdate(bool showWarnings) public static bool CanUpdate(bool showWarnings)
@@ -642,11 +517,7 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
);
} }
return false; return false;
@@ -656,11 +527,7 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("You cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
} }
return false; return false;
@@ -672,19 +539,11 @@ namespace Ryujinx.Modules
{ {
if (ReleaseInformation.IsFlatHubBuild) if (ReleaseInformation.IsFlatHubBuild)
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub.");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
);
} }
else else
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
} }
} }
@@ -698,7 +557,7 @@ namespace Ryujinx.Modules
var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir.
// Determine and exclude user files only when the updater is running, not when cleaning old files // Determine and exclude user files only when the updater is running, not when cleaning old files
if (_running && !OperatingSystem.IsMacOS()) if (Running)
{ {
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
@@ -724,9 +583,8 @@ namespace Ryujinx.Modules
return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
} }
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog)
{ {
int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
foreach (string directory in Directory.GetDirectories(root)) foreach (string directory in Directory.GetDirectories(root))
{ {
string dirName = Path.GetFileName(directory); string dirName = Path.GetFileName(directory);
@@ -736,28 +594,28 @@ namespace Ryujinx.Modules
Directory.CreateDirectory(Path.Combine(dest, dirName)); Directory.CreateDirectory(Path.Combine(dest, dirName));
} }
MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog);
} }
double count = 0;
foreach (string file in Directory.GetFiles(root)) foreach (string file in Directory.GetFiles(root))
{ {
count++;
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
Dispatcher.UIThread.InvokeAsync(() => Application.Invoke(delegate
{ {
taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); dialog.ProgressBar.Value++;
}); });
} }
} }
public static void CleanupUpdate() public static void CleanupUpdate()
{ {
foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) foreach (string file in EnumerateFilesToDelete())
{ {
File.Delete(file); if (Path.GetExtension(file).EndsWith(".ryuold"))
{
File.Delete(file);
}
} }
} }
} }

378
src/Ryujinx.Gtk3/Program.cs Normal file
View File

@@ -0,0 +1,378 @@
using Gtk;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.SystemInfo;
using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp.Formats.Jpeg;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Ryujinx
{
partial class Program
{
public static double WindowScaleFactor { get; private set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; set; }
public static string CommandLineProfile { get; set; }
private const string X11LibraryName = "libX11";
[LibraryImport(X11LibraryName)]
private static partial int XInitThreads();
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
[LibraryImport("libc", SetLastError = true)]
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
private const uint MbIconWarning = 0x30;
static Program()
{
if (OperatingSystem.IsLinux())
{
NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, assembly, path) =>
{
if (name != X11LibraryName)
{
return IntPtr.Zero;
}
if (!NativeLibrary.TryLoad("libX11.so.6", assembly, path, out IntPtr result))
{
if (!NativeLibrary.TryLoad("libX11.so", assembly, path, out result))
{
return IntPtr.Zero;
}
}
return result;
});
}
}
static void Main(string[] args)
{
Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning);
}
// Parse arguments
CommandLineState.ParseArguments(args);
// Hook unhandled exception and process exit events.
GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit();
// Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Console.Title = $"Ryujinx Console {Version}";
// NOTE: GTK3 doesn't init X11 in a multi threaded way.
// This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads).
if (OperatingSystem.IsLinux())
{
if (XInitThreads() == 0)
{
throw new NotSupportedException("Failed to initialize multi-threading support.");
}
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
setenv("GDK_BACKEND", "x11", 1);
}
if (OperatingSystem.IsMacOS())
{
string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
string resourcesDataDir;
if (Path.GetFileName(baseDirectory) == "MacOS")
{
resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources");
}
else
{
resourcesDataDir = baseDirectory;
}
static void SetEnvironmentVariableNoCaching(string key, string value)
{
int res = setenv(key, value, 1);
Debug.Assert(res != -1);
}
// On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories.
SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
// On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories.
SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
}
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
// Initialize the configuration.
ConfigurationState.Initialize();
// Initialize the logger system.
LoggerModule.Initialize();
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver
SDL2Driver.MainThreadDispatcher = action =>
{
Application.Invoke(delegate
{
action();
});
};
// Sets ImageSharp Jpeg Encoder Quality.
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
{
Quality = 100,
});
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered
ConfigurationPath = File.Exists(localConfigurationPath)
? localConfigurationPath
: File.Exists(appDataConfigurationPath)
? appDataConfigurationPath
: null;
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
ConfigurationPath = appDataConfigurationPath;
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
}
else
{
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
}
else
{
ConfigurationState.Instance.LoadDefault();
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
}
}
// Check if graphics backend was overridden.
if (CommandLineState.OverrideGraphicsBackend != null)
{
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null)
{
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value,
};
}
// Check if docked mode was overridden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
// Initialize Gtk.
Application.Init();
// Check if keys exists.
bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"));
bool hasCommonProdKeys = AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"));
if (!hasSystemProdKeys && !hasCommonProdKeys)
{
UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys);
}
// Show the main window UI.
MainWindow mainWindow = new();
mainWindow.Show();
if (OperatingSystem.IsLinux())
{
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null)
{
var buttonTexts = new Dictionary<int, string>()
{
{ 0, "Yes, until the next restart" },
{ 1, "Yes, permanently" },
{ 2, "No" },
};
ResponseType response = GtkDialog.CreateCustomDialog(
"Ryujinx - Low limit for memory mappings detected",
$"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?",
"Some games might try to create more memory mappings than currently allowed. " +
"Ryujinx will crash as soon as this limit gets exceeded.",
buttonTexts,
MessageType.Question);
int rc;
switch ((int)response)
{
case 0:
rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}");
}
break;
case 1:
rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
}
break;
}
}
else
{
GtkDialog.CreateWarningDialog(
"Max amount of memory mappings is lower than recommended.",
$"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." +
"Some games might try to create more memory mappings than currently allowed. " +
"Ryujinx will crash as soon as this limit gets exceeded.\n\n" +
"You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.");
}
}
}
if (CommandLineState.LaunchPathArg != null)
{
mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
{
Updater.BeginParse(mainWindow, false).ContinueWith(task =>
{
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
}, TaskContinuationOptions.OnlyOnFaulted);
}
Application.Run();
}
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print();
var enabledLogs = Logger.GetEnabledLevels();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
}
else
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
}
}
private static void ProcessUnhandledException(Exception ex, bool isTerminating)
{
string message = $"Unhandled exception caught: {ex}";
Logger.Error?.PrintMsg(LogClass.Application, message);
if (Logger.Error == null)
{
Logger.Notice.PrintMsg(LogClass.Application, message);
}
if (isTerminating)
{
Exit();
}
}
public static void Exit()
{
DiscordIntegrationModule.Exit();
Logger.Shutdown();
}
}
}

View File

@@ -0,0 +1,104 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<!-- As we already provide GTK3 on Windows via GtkSharp.Dependencies this is redundant. -->
<SkipGtkInstall>true</SkipGtkInstall>
<TieredPGO>true</TieredPGO>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ryujinx.GtkSharp" />
<PackageReference Include="GtkSharp.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="GtkSharp.Dependencies.osx" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="OpenTK.Graphics" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>alsoft.ini</TargetPath>
</Content>
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
<Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="UI\MainWindow.glade" />
<None Remove="UI\Widgets\ProfileDialog.glade" />
<None Remove="UI\Windows\CheatWindow.glade" />
<None Remove="UI\Windows\ControllerWindow.glade" />
<None Remove="UI\Windows\DlcWindow.glade" />
<None Remove="UI\Windows\SettingsWindow.glade" />
<None Remove="UI\Windows\TitleUpdateWindow.glade" />
<None Remove="Modules\Updater\UpdateDialog.glade" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UI\MainWindow.glade" />
<EmbeddedResource Include="UI\Widgets\ProfileDialog.glade" />
<EmbeddedResource Include="UI\Windows\CheatWindow.glade" />
<EmbeddedResource Include="UI\Windows\ControllerWindow.glade" />
<EmbeddedResource Include="UI\Windows\DlcWindow.glade" />
<EmbeddedResource Include="UI\Windows\SettingsWindow.glade" />
<EmbeddedResource Include="UI\Windows\TitleUpdateWindow.glade" />
<EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" />
</ItemGroup>
</Project>

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.UI.Applet
{ {
public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null)
{ {
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png");
int responseId = 0; int responseId = 0;

View File

@@ -143,7 +143,7 @@ namespace Ryujinx.UI
#pragma warning restore CS0649, IDE0044, CS0169, IDE0051 #pragma warning restore CS0649, IDE0044, CS0169, IDE0051
public MainWindow() : this(new Builder("Ryujinx.UI.MainWindow.glade")) { } public MainWindow() : this(new Builder("Ryujinx.Gtk3.UI.MainWindow.glade")) { }
private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin")) private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin"))
{ {

View File

@@ -3,6 +3,7 @@ using Gtk;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
@@ -378,8 +379,12 @@ namespace Ryujinx.UI
{ {
lock (this) lock (this)
{ {
var currentTime = DateTime.Now; string applicationName = Device.Processes.ActiveApplication.Name;
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; string sanitizedApplicationName = FileSystemUtils.SanitizeFileName(applicationName);
DateTime currentTime = DateTime.Now;
string filename = $"{sanitizedApplicationName}_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
string directory = AppDataManager.Mode switch string directory = AppDataManager.Mode switch
{ {
AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"), AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"),

View File

@@ -189,7 +189,7 @@ namespace Ryujinx.UI.Widgets
_dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null)
{ {
Title = "Ryujinx - NCA Section Extractor", Title = "Ryujinx - NCA Section Extractor",
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...", SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
WindowPosition = WindowPosition.Center, WindowPosition = WindowPosition.Center,
}; };

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.UI.Widgets
[GUI] Label _errorMessage; [GUI] Label _errorMessage;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public ProfileDialog() : this(new Builder("Ryujinx.UI.Widgets.ProfileDialog.glade")) { } public ProfileDialog() : this(new Builder("Ryujinx.Gtk3.UI.Widgets.ProfileDialog.glade")) { }
private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog")) private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog"))
{ {

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.UI.Windows
[GUI] Button _saveButton; [GUI] Button _saveButton;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.Gtk3.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { }
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow")) private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
{ {

View File

@@ -117,7 +117,7 @@ namespace Ryujinx.UI.Windows
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.UI.Windows.ControllerWindow.glade"), controllerId) { } public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Gtk3.UI.Windows.ControllerWindow.glade"), controllerId) { }
private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin"))
{ {

View File

@@ -32,7 +32,7 @@ namespace Ryujinx.UI.Windows
[GUI] TreeSelection _dlcTreeSelection; [GUI] TreeSelection _dlcTreeSelection;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow")) private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
{ {

View File

@@ -120,7 +120,7 @@ namespace Ryujinx.UI.Windows
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin")) private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin"))
{ {

View File

@@ -38,7 +38,7 @@ namespace Ryujinx.UI.Windows
[GUI] RadioButton _noUpdateRadioButton; [GUI] RadioButton _noUpdateRadioButton;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow")) private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
{ {

View File

@@ -44,10 +44,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
private readonly Color _textSelectedColor; private readonly Color _textSelectedColor;
private readonly Color _textOverCursorColor; private readonly Color _textOverCursorColor;
private readonly IBrush _panelBrush; private readonly Brush _panelBrush;
private readonly IBrush _disabledBrush; private readonly Brush _disabledBrush;
private readonly IBrush _cursorBrush; private readonly Brush _cursorBrush;
private readonly IBrush _selectionBoxBrush; private readonly Brush _selectionBoxBrush;
private readonly Pen _textBoxOutlinePen; private readonly Pen _textBoxOutlinePen;
private readonly Pen _cursorPen; private readonly Pen _cursorPen;
@@ -97,10 +97,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
_cursorBrush = new SolidBrush(_textNormalColor); _cursorBrush = new SolidBrush(_textNormalColor);
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor); _selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth); _textBoxOutlinePen = Pens.Solid(borderColor, _textBoxOutlineWidth);
_cursorPen = new Pen(_textNormalColor, cursorWidth); _cursorPen = Pens.Solid(_textNormalColor, cursorWidth);
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth); _selectionBoxPen = Pens.Solid(selectionBackgroundColor, cursorWidth);
_padPressedPen = new Pen(borderColor, _padPressedPenWidth); _padPressedPen = Pens.Solid(borderColor, _padPressedPenWidth);
_inputTextFontSize = 20; _inputTextFontSize = 20;
@@ -178,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
private static void SetGraphicsOptions(IImageProcessingContext context) private static void SetGraphicsOptions(IImageProcessingContext context)
{ {
context.GetGraphicsOptions().Antialias = true; context.GetGraphicsOptions().Antialias = true;
context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true; context.GetDrawingOptions().GraphicsOptions.Antialias = true;
} }
private void DrawImmutableElements() private void DrawImmutableElements()
@@ -293,31 +293,31 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
} }
private static RectangleF MeasureString(string text, Font font) private static RectangleF MeasureString(string text, Font font)
{ {
RendererOptions options = new(font); TextOptions options = new(font);
if (text == "") if (text == "")
{ {
FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options); FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options);
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
} }
FontRectangle rectangle = TextMeasurer.Measure(text, options); FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
} }
private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font) private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
{ {
RendererOptions options = new(font); TextOptions options = new(font);
if (text == "") if (text == "")
{ {
FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options); FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options);
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height); return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
} }
FontRectangle rectangle = TextMeasurer.Measure(text, options); FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
} }
@@ -350,7 +350,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
// Draw the cursor on top of the text and redraw the text with a different color if necessary. // Draw the cursor on top of the text and redraw the text with a different color if necessary.
Color cursorTextColor; Color cursorTextColor;
IBrush cursorBrush; Brush cursorBrush;
Pen cursorPen; Pen cursorPen;
float cursorPositionYTop = inputTextY + 1; float cursorPositionYTop = inputTextY + 1;
@@ -435,7 +435,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
new PointF(cursorPositionXLeft, cursorPositionYBottom), new PointF(cursorPositionXLeft, cursorPositionYBottom),
}; };
context.DrawLines(cursorPen, points); context.DrawLine(cursorPen, points);
} }
else else
{ {
@@ -562,12 +562,12 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
// Convert the pixel format used in the image to the one used in the Switch surface. // Convert the pixel format used in the image to the one used in the Switch surface.
if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels)) if (!_surface.DangerousTryGetSinglePixelMemory(out Memory<Argb32> pixels))
{ {
return; return;
} }
_bufferData = MemoryMarshal.AsBytes(pixels).ToArray(); _bufferData = MemoryMarshal.AsBytes(pixels.Span).ToArray();
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData); Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
Debug.Assert(_bufferData.Length == _surfaceInfo.Size); Debug.Assert(_bufferData.Length == _surfaceInfo.Size);

View File

@@ -11,12 +11,8 @@ namespace Ryujinx.Memory
/// Represents a address space manager. /// Represents a address space manager.
/// Supports virtual memory region mapping, address translation and read/write access to mapped regions. /// Supports virtual memory region mapping, address translation and read/write access to mapped regions.
/// </summary> /// </summary>
public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock public sealed class AddressSpaceManager : VirtualMemoryManagerBase<ulong, nuint>, IVirtualMemoryManager, IWritableBlock
{ {
public const int PageBits = PageTable<nuint>.PageBits;
public const int PageSize = PageTable<nuint>.PageSize;
public const int PageMask = PageTable<nuint>.PageMask;
/// <inheritdoc/> /// <inheritdoc/>
public bool Supports4KBPages => true; public bool Supports4KBPages => true;
@@ -25,11 +21,11 @@ namespace Ryujinx.Memory
/// </summary> /// </summary>
public int AddressSpaceBits { get; } public int AddressSpaceBits { get; }
private readonly ulong _addressSpaceSize;
private readonly MemoryBlock _backingMemory; private readonly MemoryBlock _backingMemory;
private readonly PageTable<nuint> _pageTable; private readonly PageTable<nuint> _pageTable;
protected override ulong AddressSpaceSize { get; }
/// <summary> /// <summary>
/// Creates a new instance of the memory manager. /// Creates a new instance of the memory manager.
/// </summary> /// </summary>
@@ -47,7 +43,7 @@ namespace Ryujinx.Memory
} }
AddressSpaceBits = asBits; AddressSpaceBits = asBits;
_addressSpaceSize = asSize; AddressSpaceSize = asSize;
_backingMemory = backingMemory; _backingMemory = backingMemory;
_pageTable = new PageTable<nuint>(); _pageTable = new PageTable<nuint>();
} }
@@ -102,12 +98,6 @@ namespace Ryujinx.Memory
return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0]; return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0];
} }
/// <inheritdoc/>
public void Read(ulong va, Span<byte> data)
{
ReadImpl(va, data);
}
/// <inheritdoc/> /// <inheritdoc/>
public void Write<T>(ulong va, T value) where T : unmanaged public void Write<T>(ulong va, T value) where T : unmanaged
{ {
@@ -174,7 +164,7 @@ namespace Ryujinx.Memory
{ {
Span<byte> data = new byte[size]; Span<byte> data = new byte[size];
ReadImpl(va, data); Read(va, data);
return data; return data;
} }
@@ -346,34 +336,6 @@ namespace Ryujinx.Memory
return regions; return regions;
} }
private void ReadImpl(ulong va, Span<byte> data)
{
if (data.Length == 0)
{
return;
}
AssertValidAddressAndSize(va, (ulong)data.Length);
int offset = 0, size;
if ((va & PageMask) != 0)
{
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
GetHostSpanContiguous(va, size).CopyTo(data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
size = Math.Min(data.Length - offset, PageSize);
GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
}
}
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va) public bool IsMapped(ulong va)
@@ -414,37 +376,6 @@ namespace Ryujinx.Memory
return true; return true;
} }
private bool ValidateAddress(ulong va)
{
return va < _addressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size) private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
{ {
return new Span<byte>((void*)GetHostAddress(va), size); return new Span<byte>((void*)GetHostAddress(va), size);
@@ -461,7 +392,7 @@ namespace Ryujinx.Memory
} }
/// <inheritdoc/> /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -471,5 +402,11 @@ namespace Ryujinx.Memory
{ {
// Only the ARM Memory Manager has tracking for now. // Only the ARM Memory Manager has tracking for now.
} }
protected override unsafe Span<byte> GetPhysicalAddressSpan(nuint pa, int size)
=> new((void*)pa, size);
protected override nuint TranslateVirtualAddressForRead(ulong va)
=> GetHostAddress(va);
} }
} }

View File

@@ -214,6 +214,7 @@ namespace Ryujinx.Memory
/// <param name="va">Virtual address base</param> /// <param name="va">Virtual address base</param>
/// <param name="size">Size of the region to protect</param> /// <param name="size">Size of the region to protect</param>
/// <param name="protection">Memory protection to set</param> /// <param name="protection">Memory protection to set</param>
void TrackingReprotect(ulong va, ulong size, MemoryPermission protection); /// <param name="guest">True if the protection is for guest access, false otherwise</param>
void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest);
} }
} }

View File

@@ -14,9 +14,14 @@ namespace Ryujinx.Memory.Tracking
// Only use these from within the lock. // Only use these from within the lock.
private readonly NonOverlappingRangeList<VirtualRegion> _virtualRegions; private readonly NonOverlappingRangeList<VirtualRegion> _virtualRegions;
// Guest virtual regions are a subset of the normal virtual regions, with potentially different protection
// and expanded area of effect on platforms that don't support misaligned page protection.
private readonly NonOverlappingRangeList<VirtualRegion> _guestVirtualRegions;
private readonly int _pageSize; private readonly int _pageSize;
private readonly bool _singleByteGuestTracking;
/// <summary> /// <summary>
/// This lock must be obtained when traversing or updating the region-handle hierarchy. /// This lock must be obtained when traversing or updating the region-handle hierarchy.
/// It is not required when reading dirty flags. /// It is not required when reading dirty flags.
@@ -27,16 +32,27 @@ namespace Ryujinx.Memory.Tracking
/// Create a new tracking structure for the given "physical" memory block, /// Create a new tracking structure for the given "physical" memory block,
/// with a given "virtual" memory manager that will provide mappings and virtual memory protection. /// with a given "virtual" memory manager that will provide mappings and virtual memory protection.
/// </summary> /// </summary>
/// <remarks>
/// If <paramref name="singleByteGuestTracking" /> is true, the memory manager must also support protection on partially
/// unmapped regions without throwing exceptions or dropping protection on the mapped portion.
/// </remarks>
/// <param name="memoryManager">Virtual memory manager</param> /// <param name="memoryManager">Virtual memory manager</param>
/// <param name="block">Physical memory block</param>
/// <param name="pageSize">Page size of the virtual memory space</param> /// <param name="pageSize">Page size of the virtual memory space</param>
public MemoryTracking(IVirtualMemoryManager memoryManager, int pageSize, InvalidAccessHandler invalidAccessHandler = null) /// <param name="invalidAccessHandler">Method to call for invalid memory accesses</param>
/// <param name="singleByteGuestTracking">True if the guest only signals writes for the first byte</param>
public MemoryTracking(
IVirtualMemoryManager memoryManager,
int pageSize,
InvalidAccessHandler invalidAccessHandler = null,
bool singleByteGuestTracking = false)
{ {
_memoryManager = memoryManager; _memoryManager = memoryManager;
_pageSize = pageSize; _pageSize = pageSize;
_invalidAccessHandler = invalidAccessHandler; _invalidAccessHandler = invalidAccessHandler;
_singleByteGuestTracking = singleByteGuestTracking;
_virtualRegions = new NonOverlappingRangeList<VirtualRegion>(); _virtualRegions = new NonOverlappingRangeList<VirtualRegion>();
_guestVirtualRegions = new NonOverlappingRangeList<VirtualRegion>();
} }
private (ulong address, ulong size) PageAlign(ulong address, ulong size) private (ulong address, ulong size) PageAlign(ulong address, ulong size)
@@ -62,20 +78,25 @@ namespace Ryujinx.Memory.Tracking
{ {
ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get(); ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); for (int type = 0; type < 2; type++)
for (int i = 0; i < count; i++)
{ {
VirtualRegion region = overlaps[i]; NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
// If the region has been fully remapped, signal that it has been mapped again. int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps);
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
if (remapped) for (int i = 0; i < count; i++)
{ {
region.SignalMappingChanged(true); VirtualRegion region = overlaps[i];
}
region.UpdateProtection(); // If the region has been fully remapped, signal that it has been mapped again.
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
if (remapped)
{
region.SignalMappingChanged(true);
}
region.UpdateProtection();
}
} }
} }
} }
@@ -95,27 +116,58 @@ namespace Ryujinx.Memory.Tracking
{ {
ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get(); ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
int count = _virtualRegions.FindOverlapsNonOverlapping(va, size, ref overlaps); for (int type = 0; type < 2; type++)
for (int i = 0; i < count; i++)
{ {
VirtualRegion region = overlaps[i]; NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
region.SignalMappingChanged(false); int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps);
for (int i = 0; i < count; i++)
{
VirtualRegion region = overlaps[i];
region.SignalMappingChanged(false);
}
} }
} }
} }
/// <summary>
/// Alter a tracked memory region to properly capture unaligned accesses.
/// For most memory manager modes, this does nothing.
/// </summary>
/// <param name="address">Original region address</param>
/// <param name="size">Original region size</param>
/// <returns>A new address and size for tracking unaligned accesses</returns>
internal (ulong newAddress, ulong newSize) GetUnalignedSafeRegion(ulong address, ulong size)
{
if (_singleByteGuestTracking)
{
// The guest only signals the first byte of each memory access with the current memory manager.
// To catch unaligned access properly, we need to also protect the page before the address.
// Assume that the address and size are already aligned.
return (address - (ulong)_pageSize, size + (ulong)_pageSize);
}
else
{
return (address, size);
}
}
/// <summary> /// <summary>
/// Get a list of virtual regions that a handle covers. /// Get a list of virtual regions that a handle covers.
/// </summary> /// </summary>
/// <param name="va">Starting virtual memory address of the handle</param> /// <param name="va">Starting virtual memory address of the handle</param>
/// <param name="size">Size of the handle's memory region</param> /// <param name="size">Size of the handle's memory region</param>
/// <param name="guest">True if getting handles for guest protection, false otherwise</param>
/// <returns>A list of virtual regions within the given range</returns> /// <returns>A list of virtual regions within the given range</returns>
internal List<VirtualRegion> GetVirtualRegionsForHandle(ulong va, ulong size) internal List<VirtualRegion> GetVirtualRegionsForHandle(ulong va, ulong size, bool guest)
{ {
List<VirtualRegion> result = new(); List<VirtualRegion> result = new();
_virtualRegions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size)); NonOverlappingRangeList<VirtualRegion> regions = guest ? _guestVirtualRegions : _virtualRegions;
regions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size, guest));
return result; return result;
} }
@@ -126,7 +178,14 @@ namespace Ryujinx.Memory.Tracking
/// <param name="region">Region to remove</param> /// <param name="region">Region to remove</param>
internal void RemoveVirtual(VirtualRegion region) internal void RemoveVirtual(VirtualRegion region)
{ {
_virtualRegions.Remove(region); if (region.Guest)
{
_guestVirtualRegions.Remove(region);
}
else
{
_virtualRegions.Remove(region);
}
} }
/// <summary> /// <summary>
@@ -137,10 +196,11 @@ namespace Ryujinx.Memory.Tracking
/// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param> /// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param>
/// <param name="granularity">Desired granularity of write tracking</param> /// <param name="granularity">Desired granularity of write tracking</param>
/// <param name="id">Handle ID</param> /// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns> /// <returns>The memory tracking handle</returns>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id) public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
{ {
return new MultiRegionHandle(this, address, size, handles, granularity, id); return new MultiRegionHandle(this, address, size, handles, granularity, id, flags);
} }
/// <summary> /// <summary>
@@ -164,15 +224,16 @@ namespace Ryujinx.Memory.Tracking
/// <param name="address">CPU virtual address of the region</param> /// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param> /// <param name="size">Size of the region</param>
/// <param name="id">Handle ID</param> /// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns> /// <returns>The memory tracking handle</returns>
public RegionHandle BeginTracking(ulong address, ulong size, int id) public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{ {
var (paAddress, paSize) = PageAlign(address, size); var (paAddress, paSize) = PageAlign(address, size);
lock (TrackingLock) lock (TrackingLock)
{ {
bool mapped = _memoryManager.IsRangeMapped(address, size); bool mapped = _memoryManager.IsRangeMapped(address, size);
RegionHandle handle = new(this, paAddress, paSize, address, size, id, mapped); RegionHandle handle = new(this, paAddress, paSize, address, size, id, flags, mapped);
return handle; return handle;
} }
@@ -186,15 +247,16 @@ namespace Ryujinx.Memory.Tracking
/// <param name="bitmap">The bitmap owning the dirty flag for this handle</param> /// <param name="bitmap">The bitmap owning the dirty flag for this handle</param>
/// <param name="bit">The bit of this handle within the dirty flag</param> /// <param name="bit">The bit of this handle within the dirty flag</param>
/// <param name="id">Handle ID</param> /// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns> /// <returns>The memory tracking handle</returns>
internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id) internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id, RegionFlags flags = RegionFlags.None)
{ {
var (paAddress, paSize) = PageAlign(address, size); var (paAddress, paSize) = PageAlign(address, size);
lock (TrackingLock) lock (TrackingLock)
{ {
bool mapped = _memoryManager.IsRangeMapped(address, size); bool mapped = _memoryManager.IsRangeMapped(address, size);
RegionHandle handle = new(this, paAddress, paSize, address, size, bitmap, bit, id, mapped); RegionHandle handle = new(this, paAddress, paSize, address, size, bitmap, bit, id, flags, mapped);
return handle; return handle;
} }
@@ -202,6 +264,7 @@ namespace Ryujinx.Memory.Tracking
/// <summary> /// <summary>
/// Signal that a virtual memory event happened at the given location. /// Signal that a virtual memory event happened at the given location.
/// The memory event is assumed to be triggered by guest code.
/// </summary> /// </summary>
/// <param name="address">Virtual address accessed</param> /// <param name="address">Virtual address accessed</param>
/// <param name="size">Size of the region affected in bytes</param> /// <param name="size">Size of the region affected in bytes</param>
@@ -209,7 +272,7 @@ namespace Ryujinx.Memory.Tracking
/// <returns>True if the event triggered any tracking regions, false otherwise</returns> /// <returns>True if the event triggered any tracking regions, false otherwise</returns>
public bool VirtualMemoryEvent(ulong address, ulong size, bool write) public bool VirtualMemoryEvent(ulong address, ulong size, bool write)
{ {
return VirtualMemoryEvent(address, size, write, precise: false, null); return VirtualMemoryEvent(address, size, write, precise: false, exemptId: null, guest: true);
} }
/// <summary> /// <summary>
@@ -222,8 +285,9 @@ namespace Ryujinx.Memory.Tracking
/// <param name="write">Whether the region was written to or read</param> /// <param name="write">Whether the region was written to or read</param>
/// <param name="precise">True if the access is precise, false otherwise</param> /// <param name="precise">True if the access is precise, false otherwise</param>
/// <param name="exemptId">Optional ID that of the handles that should not be signalled</param> /// <param name="exemptId">Optional ID that of the handles that should not be signalled</param>
/// <param name="guest">True if the access is from the guest, false otherwise</param>
/// <returns>True if the event triggered any tracking regions, false otherwise</returns> /// <returns>True if the event triggered any tracking regions, false otherwise</returns>
public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null) public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null, bool guest = false)
{ {
// Look up the virtual region using the region list. // Look up the virtual region using the region list.
// Signal up the chain to relevant handles. // Signal up the chain to relevant handles.
@@ -234,7 +298,9 @@ namespace Ryujinx.Memory.Tracking
{ {
ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get(); ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
int count = _virtualRegions.FindOverlapsNonOverlapping(address, size, ref overlaps); NonOverlappingRangeList<VirtualRegion> regions = guest ? _guestVirtualRegions : _virtualRegions;
int count = regions.FindOverlapsNonOverlapping(address, size, ref overlaps);
if (count == 0 && !precise) if (count == 0 && !precise)
{ {
@@ -242,7 +308,7 @@ namespace Ryujinx.Memory.Tracking
{ {
// TODO: There is currently the possibility that a page can be protected after its virtual region is removed. // 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. // 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); _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite, guest);
return true; // This memory _should_ be mapped, so we need to try again. return true; // This memory _should_ be mapped, so we need to try again.
} }
else else
@@ -252,6 +318,12 @@ namespace Ryujinx.Memory.Tracking
} }
else else
{ {
if (guest && _singleByteGuestTracking)
{
// Increase the access size to trigger handles with misaligned accesses.
size += (ulong)_pageSize;
}
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
VirtualRegion region = overlaps[i]; VirtualRegion region = overlaps[i];
@@ -285,9 +357,10 @@ namespace Ryujinx.Memory.Tracking
/// </summary> /// </summary>
/// <param name="region">Region to reprotect</param> /// <param name="region">Region to reprotect</param>
/// <param name="permission">Memory permission to protect with</param> /// <param name="permission">Memory permission to protect with</param>
internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission) /// <param name="guest">True if the protection is for guest access, false otherwise</param>
internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission, bool guest)
{ {
_memoryManager.TrackingReprotect(region.Address, region.Size, permission); _memoryManager.TrackingReprotect(region.Address, region.Size, permission, guest);
} }
/// <summary> /// <summary>

View File

@@ -37,7 +37,8 @@ namespace Ryujinx.Memory.Tracking
ulong size, ulong size,
IEnumerable<IRegionHandle> handles, IEnumerable<IRegionHandle> handles,
ulong granularity, ulong granularity,
int id) int id,
RegionFlags flags)
{ {
_handles = new RegionHandle[(size + granularity - 1) / granularity]; _handles = new RegionHandle[(size + granularity - 1) / granularity];
Granularity = granularity; Granularity = granularity;
@@ -62,7 +63,7 @@ namespace Ryujinx.Memory.Tracking
// Fill any gap left before this handle. // Fill any gap left before this handle.
while (i < startIndex) while (i < startIndex)
{ {
RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags);
fillHandle.Parent = this; fillHandle.Parent = this;
_handles[i++] = fillHandle; _handles[i++] = fillHandle;
} }
@@ -83,7 +84,7 @@ namespace Ryujinx.Memory.Tracking
while (i < endIndex) while (i < endIndex)
{ {
RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags);
splitHandle.Parent = this; splitHandle.Parent = this;
splitHandle.Reprotect(handle.Dirty); splitHandle.Reprotect(handle.Dirty);
@@ -106,7 +107,7 @@ namespace Ryujinx.Memory.Tracking
// Fill any remaining space with new handles. // Fill any remaining space with new handles.
while (i < _handles.Length) while (i < _handles.Length)
{ {
RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id); RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags);
handle.Parent = this; handle.Parent = this;
_handles[i++] = handle; _handles[i++] = handle;
} }

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ryujinx.Memory.Tracking
{
[Flags]
public enum RegionFlags
{
None = 0,
/// <summary>
/// Access to the resource is expected to occasionally be unaligned.
/// With some memory managers, guest protection must extend into the previous page to cover unaligned access.
/// If this is not expected, protection is not altered, which can avoid unintended resource dirty/flush.
/// </summary>
UnalignedAccess = 1,
}
}

View File

@@ -55,6 +55,8 @@ namespace Ryujinx.Memory.Tracking
private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access. private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access.
private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write. private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write.
private readonly List<VirtualRegion> _regions; private readonly List<VirtualRegion> _regions;
private readonly List<VirtualRegion> _guestRegions;
private readonly List<VirtualRegion> _allRegions;
private readonly MemoryTracking _tracking; private readonly MemoryTracking _tracking;
private bool _disposed; private bool _disposed;
@@ -99,6 +101,7 @@ namespace Ryujinx.Memory.Tracking
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param> /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
/// <param name="bit">The bit index representing the dirty flag for this handle</param> /// <param name="bit">The bit index representing the dirty flag for this handle</param>
/// <param name="id">Handle ID</param> /// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <param name="mapped">True if the region handle starts mapped</param> /// <param name="mapped">True if the region handle starts mapped</param>
internal RegionHandle( internal RegionHandle(
MemoryTracking tracking, MemoryTracking tracking,
@@ -109,6 +112,7 @@ namespace Ryujinx.Memory.Tracking
ConcurrentBitmap bitmap, ConcurrentBitmap bitmap,
int bit, int bit,
int id, int id,
RegionFlags flags,
bool mapped = true) bool mapped = true)
{ {
Bitmap = bitmap; Bitmap = bitmap;
@@ -128,11 +132,12 @@ namespace Ryujinx.Memory.Tracking
RealEndAddress = realAddress + realSize; RealEndAddress = realAddress + realSize;
_tracking = tracking; _tracking = tracking;
_regions = tracking.GetVirtualRegionsForHandle(address, size);
foreach (var region in _regions) _regions = tracking.GetVirtualRegionsForHandle(address, size, false);
{ _guestRegions = GetGuestRegions(tracking, address, size, flags);
region.Handles.Add(this); _allRegions = new List<VirtualRegion>(_regions.Count + _guestRegions.Count);
}
InitializeRegions();
} }
/// <summary> /// <summary>
@@ -145,8 +150,9 @@ namespace Ryujinx.Memory.Tracking
/// <param name="realAddress">The real, unaligned address of the handle</param> /// <param name="realAddress">The real, unaligned address of the handle</param>
/// <param name="realSize">The real, unaligned size of the handle</param> /// <param name="realSize">The real, unaligned size of the handle</param>
/// <param name="id">Handle ID</param> /// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <param name="mapped">True if the region handle starts mapped</param> /// <param name="mapped">True if the region handle starts mapped</param>
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, bool mapped = true) internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, RegionFlags flags, bool mapped = true)
{ {
Bitmap = new ConcurrentBitmap(1, mapped); Bitmap = new ConcurrentBitmap(1, mapped);
@@ -163,8 +169,37 @@ namespace Ryujinx.Memory.Tracking
RealEndAddress = realAddress + realSize; RealEndAddress = realAddress + realSize;
_tracking = tracking; _tracking = tracking;
_regions = tracking.GetVirtualRegionsForHandle(address, size);
foreach (var region in _regions) _regions = tracking.GetVirtualRegionsForHandle(address, size, false);
_guestRegions = GetGuestRegions(tracking, address, size, flags);
_allRegions = new List<VirtualRegion>(_regions.Count + _guestRegions.Count);
InitializeRegions();
}
private List<VirtualRegion> GetGuestRegions(MemoryTracking tracking, ulong address, ulong size, RegionFlags flags)
{
ulong guestAddress;
ulong guestSize;
if (flags.HasFlag(RegionFlags.UnalignedAccess))
{
(guestAddress, guestSize) = tracking.GetUnalignedSafeRegion(address, size);
}
else
{
(guestAddress, guestSize) = (address, size);
}
return tracking.GetVirtualRegionsForHandle(guestAddress, guestSize, true);
}
private void InitializeRegions()
{
_allRegions.AddRange(_regions);
_allRegions.AddRange(_guestRegions);
foreach (var region in _allRegions)
{ {
region.Handles.Add(this); region.Handles.Add(this);
} }
@@ -321,7 +356,7 @@ namespace Ryujinx.Memory.Tracking
lock (_tracking.TrackingLock) lock (_tracking.TrackingLock)
{ {
foreach (VirtualRegion region in _regions) foreach (VirtualRegion region in _allRegions)
{ {
protectionChanged |= region.UpdateProtection(); protectionChanged |= region.UpdateProtection();
} }
@@ -379,7 +414,7 @@ namespace Ryujinx.Memory.Tracking
{ {
lock (_tracking.TrackingLock) lock (_tracking.TrackingLock)
{ {
foreach (VirtualRegion region in _regions) foreach (VirtualRegion region in _allRegions)
{ {
region.UpdateProtection(); region.UpdateProtection();
} }
@@ -414,7 +449,16 @@ namespace Ryujinx.Memory.Tracking
/// <param name="region">Virtual region to add as a child</param> /// <param name="region">Virtual region to add as a child</param>
internal void AddChild(VirtualRegion region) internal void AddChild(VirtualRegion region)
{ {
_regions.Add(region); if (region.Guest)
{
_guestRegions.Add(region);
}
else
{
_regions.Add(region);
}
_allRegions.Add(region);
} }
/// <summary> /// <summary>
@@ -469,7 +513,7 @@ namespace Ryujinx.Memory.Tracking
lock (_tracking.TrackingLock) lock (_tracking.TrackingLock)
{ {
foreach (VirtualRegion region in _regions) foreach (VirtualRegion region in _allRegions)
{ {
region.RemoveHandle(this); region.RemoveHandle(this);
} }

View File

@@ -13,10 +13,14 @@ namespace Ryujinx.Memory.Tracking
private readonly MemoryTracking _tracking; private readonly MemoryTracking _tracking;
private MemoryPermission _lastPermission; private MemoryPermission _lastPermission;
public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size) public bool Guest { get; }
public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, bool guest, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size)
{ {
_lastPermission = lastPermission; _lastPermission = lastPermission;
_tracking = tracking; _tracking = tracking;
Guest = guest;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -103,7 +107,7 @@ namespace Ryujinx.Memory.Tracking
if (_lastPermission != permission) if (_lastPermission != permission)
{ {
_tracking.ProtectVirtualRegion(this, permission); _tracking.ProtectVirtualRegion(this, permission, Guest);
_lastPermission = permission; _lastPermission = permission;
return true; return true;
@@ -131,7 +135,7 @@ namespace Ryujinx.Memory.Tracking
public override INonOverlappingRange Split(ulong splitAddress) public override INonOverlappingRange Split(ulong splitAddress)
{ {
VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, _lastPermission); VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission);
Size = splitAddress - Address; Size = splitAddress - Address;
// The new region inherits all of our parents. // The new region inherits all of our parents.

View File

@@ -0,0 +1,91 @@
using System;
using System.Numerics;
namespace Ryujinx.Memory
{
public abstract class VirtualMemoryManagerBase<TVirtual, TPhysical>
where TVirtual : IBinaryInteger<TVirtual>
where TPhysical : IBinaryInteger<TPhysical>
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
protected abstract TVirtual AddressSpaceSize { get; }
public virtual void Read(TVirtual va, Span<byte> data)
{
if (data.Length == 0)
{
return;
}
AssertValidAddressAndSize(va, TVirtual.CreateChecked(data.Length));
int offset = 0, size;
if ((int.CreateTruncating(va) & PageMask) != 0)
{
TPhysical pa = TranslateVirtualAddressForRead(va);
size = Math.Min(data.Length, PageSize - ((int.CreateTruncating(va) & PageMask)));
GetPhysicalAddressSpan(pa, size).CopyTo(data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
TPhysical pa = TranslateVirtualAddressForRead(va + TVirtual.CreateChecked(offset));
size = Math.Min(data.Length - offset, PageSize);
GetPhysicalAddressSpan(pa, size).CopyTo(data.Slice(offset, size));
}
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
protected void AssertValidAddressAndSize(TVirtual va, TVirtual size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
protected abstract Span<byte> GetPhysicalAddressSpan(TPhysical pa, int size);
protected abstract TPhysical TranslateVirtualAddressForRead(TVirtual va);
/// <summary>
/// Checks if the virtual address is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address</param>
/// <returns>True if the virtual address is part of the addressable space</returns>
protected bool ValidateAddress(TVirtual va)
{
return va < AddressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
protected bool ValidateAddressAndSize(TVirtual va, TVirtual size)
{
TVirtual endVa = va + size;
return endVa >= va && endVa >= size && endVa <= AddressSpaceSize;
}
protected static void ThrowInvalidMemoryRegionException(string message)
=> throw new InvalidMemoryRegionException(message);
}
}

View File

@@ -107,7 +107,7 @@ namespace Ryujinx.Tests.Memory
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
{ {
OnProtect?.Invoke(va, size, protection); OnProtect?.Invoke(va, size, protection);
} }

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