Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fdd3263e31 | ||
|
ce607db944 | ||
|
6b4ee82e5d | ||
|
e2a655f1a4 | ||
|
d9a18919b0 | ||
|
8354434a37 | ||
|
5a900f38c5 | ||
|
a3a63d4394 | ||
|
3924bd1a43 | ||
|
50458b2472 | ||
|
dda0f26067 | ||
|
4e1a60328e | ||
|
2505a1abcd | ||
|
bc4d99a078 | ||
|
ec6cb0abb4 | ||
|
53b5985da6 | ||
|
87f238be60 |
@@ -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]
|
||||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -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
2
.github/labeler.yml
vendored
@@ -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:
|
||||||
|
7
.github/reviewers.yml
vendored
7
.github/reviewers.yml
vendored
@@ -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:
|
||||||
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@@ -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
|
||||||
|
10
.github/workflows/nightly_pr_comment.yml
vendored
10
.github/workflows/nightly_pr_comment.yml
vendored
@@ -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;
|
||||||
|
|
||||||
|
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@@ -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
|
||||||
|
@@ -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" />
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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>
|
|
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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.
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs
Normal file
48
src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
@@ -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)
|
||||||
{
|
{
|
||||||
|
@@ -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)
|
||||||
|
389
src/Ryujinx.Cpu/ManagedPageFlags.cs
Normal file
389
src/Ryujinx.Cpu/ManagedPageFlags.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
|
@@ -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;
|
||||||
|
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
{
|
{
|
||||||
|
@@ -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>
|
||||||
|
@@ -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";
|
||||||
|
@@ -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);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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}";
|
||||||
|
|
@@ -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
378
src/Ryujinx.Gtk3/Program.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
Normal file
104
src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
Normal 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>
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
@@ -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;
|
||||||
|
|
@@ -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"))
|
||||||
{
|
{
|
@@ -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"),
|
@@ -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,
|
||||||
};
|
};
|
@@ -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"))
|
||||||
{
|
{
|
@@ -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"))
|
||||||
{
|
{
|
@@ -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"))
|
||||||
{
|
{
|
@@ -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"))
|
||||||
{
|
{
|
@@ -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"))
|
||||||
{
|
{
|
@@ -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"))
|
||||||
{
|
{
|
@@ -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);
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
21
src/Ryujinx.Memory/Tracking/RegionFlags.cs
Normal file
21
src/Ryujinx.Memory/Tracking/RegionFlags.cs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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.
|
||||||
|
91
src/Ryujinx.Memory/VirtualMemoryManagerBase.cs
Normal file
91
src/Ryujinx.Memory/VirtualMemoryManagerBase.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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
Reference in New Issue
Block a user