Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f07a9737db | ||
|
73638582c5 | ||
|
246b4bd555 | ||
|
c77dced809 | ||
|
1e8235414f | ||
|
fc41960fd3 | ||
|
26319ba487 | ||
|
aeeeaca37c | ||
|
8b3fa0011b | ||
|
c4bfe7ec21 | ||
|
5b6449d916 | ||
|
71646c3757 | ||
|
966ca60847 | ||
|
f1a222bb9a | ||
|
8f789dd04c | ||
|
f239ca2de0 | ||
|
ac577e76a1 | ||
|
04e6d44607 | ||
|
4f9cf2be6b | ||
|
44fcb4f5cd | ||
|
f838f00c04 | ||
|
0d369fc1da | ||
|
7c3e89c4f4 | ||
|
87394b8d4d | ||
|
b59d8f9e4e | ||
|
468f6f782d | ||
|
aae92bbf15 | ||
|
f23547d911 | ||
|
9573476db6 | ||
|
7f279cd4ed | ||
|
cde38fb1c5 | ||
|
164cf7ea7f | ||
|
29c4435791 | ||
|
84cc3559f0 | ||
|
1963cda121 | ||
|
b59b8ac943 | ||
|
b40cf692c8 | ||
|
fd2972ec84 | ||
|
9190796c62 | ||
|
08d2929ea0 | ||
|
df6798cf7a | ||
|
484dc13314 | ||
|
bada08258e | ||
|
dc4ac64faf | ||
|
67bb3dc2d9 | ||
|
5e5b3aeaf1 | ||
|
84d340b4fb | ||
|
ef7ce19867 | ||
|
394fabb8cc | ||
|
68525ab7f1 | ||
|
904d249dad | ||
|
413eb755cb | ||
|
2ed3836093 | ||
|
b5d5256c61 | ||
|
32bfa06024 | ||
|
b31a8946ae | ||
|
0f85cadf28 | ||
|
4892e72907 | ||
|
c565da03c5 | ||
|
0cf125a4d9 | ||
|
f8c53f03fd |
.github
.gitignoreDirectory.Packages.propsREADME.mddistribution
linux
macos
misc
src
ARMeilleure
ARMeilleure.csproj
Common
AddressTable.csAddressTableLevel.csAddressTablePresets.csAllocator.csIAddressTable.csNativeAllocator.cs
Instructions
Signal
State
Translation
Ryujinx.Audio.Backends.OpenAL
Ryujinx.Audio.Backends.SDL2
Ryujinx.Audio.Backends.SoundIo
Ryujinx.Audio
Ryujinx.Common
Ryujinx.Cpu
AddressTable.cs
AppleHv
ICpuContext.csJit
LightningJit
Ryujinx.Cpu.csprojRyujinx.Graphics.Device
Ryujinx.Graphics.GAL
Ryujinx.Graphics.Gpu
Ryujinx.Graphics.Host1x
Ryujinx.Graphics.Nvdec.FFmpeg
Ryujinx.Graphics.Nvdec.Vp9
Ryujinx.Graphics.Nvdec
Ryujinx.Graphics.OpenGL
Ryujinx.Graphics.Shader
Ryujinx.Graphics.Texture
Ryujinx.Graphics.Vic
Ryujinx.Graphics.Video
Ryujinx.Graphics.Vulkan
Ryujinx.HLE.Generators
Ryujinx.HLE
FileSystem
HLEConfiguration.csHOS
Applets
ArmProcessContext.csArmProcessContextFactory.csServices
Ldn
Lp2p
UserServiceCreator/LdnRyu/Proxy
Nfc/Nfp
SurfaceFlinger
Loaders/Processes
Ryujinx.HLE.csprojStructHelpers.csSwitch.csUI
8
.github/labeler.yml
vendored
8
.github/labeler.yml
vendored
@ -33,3 +33,11 @@ kernel:
|
|||||||
infra:
|
infra:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props']
|
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props']
|
||||||
|
|
||||||
|
documentation:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'docs/**'
|
||||||
|
|
||||||
|
ldn:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**'
|
||||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -122,7 +122,7 @@ jobs:
|
|||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||||
path: publish_sdl2_headless
|
path: publish_sdl2_headless
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
@ -185,6 +185,6 @@ jobs:
|
|||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||||
path: "publish_headless/*.tar.gz"
|
path: "publish_headless/*.tar.gz"
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
|
58
.github/workflows/canary.yml
vendored
58
.github/workflows/canary.yml
vendored
@ -54,7 +54,19 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
body: |
|
||||||
|
# Canary builds:
|
||||||
|
|
||||||
|
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/${{ github.repository }}/releases/latest) instead if that sounds like something you don't want to deal with.
|
||||||
|
|
||||||
|
| Platform | Artifact |
|
||||||
|
|--|--|
|
||||||
|
| Windows 64 bit | [Canary Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
||||||
|
| Linux 64 bit | [Canary Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
||||||
|
| Linux arm 64 bit | [Canary Linux arm Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
||||||
|
| macOS | [Canary macOS artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||||
@ -103,20 +115,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
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 -p:IncludeNativeLibrariesForSelfExtract=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
|
||||||
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 -p:IncludeNativeLibrariesForSelfExtract=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
|
||||||
|
|
||||||
- name: Packing Windows builds
|
- name: Packing Windows builds
|
||||||
if: matrix.platform.os == 'windows-latest'
|
if: matrix.platform.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_ava
|
pushd publish_ava
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
rm publish/libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
rm publish/libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
7z a ../release_output/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@ -126,13 +138,13 @@ jobs:
|
|||||||
pushd publish_ava
|
pushd publish_ava
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
rm publish/libarmeilleure-jitsupport.dylib
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
chmod +x publish/Ryujinx.sh 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-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
rm publish/libarmeilleure-jitsupport.dylib
|
||||||
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/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@ -181,7 +193,19 @@ jobs:
|
|||||||
artifacts: "release_output/*.tar.gz,release_output/*.zip"
|
artifacts: "release_output/*.tar.gz,release_output/*.zip"
|
||||||
#artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*"
|
#artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*"
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
body: |
|
||||||
|
# Canary builds:
|
||||||
|
|
||||||
|
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/GreemDev/Ryujinx/releases/latest) instead if that sounds like something you don't want to deal with.
|
||||||
|
|
||||||
|
| Platform | Artifact |
|
||||||
|
|--|--|
|
||||||
|
| Windows 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip |
|
||||||
|
| Linux 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz |
|
||||||
|
| Linux arm 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz |
|
||||||
|
| Macos | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz |
|
||||||
|
|
||||||
|
"**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
replacesArtifacts: true
|
replacesArtifacts: true
|
||||||
@ -236,11 +260,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish macOS Ryujinx
|
- 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 1
|
||||||
|
|
||||||
- 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 1
|
||||||
|
|
||||||
- name: Pushing new release
|
- name: Pushing new release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
@ -248,7 +272,19 @@ jobs:
|
|||||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||||
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
|
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
body: |
|
||||||
|
# Canary builds:
|
||||||
|
|
||||||
|
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/GreemDev/Ryujinx/releases/latest) instead if that sounds like something you don't want to deal with.
|
||||||
|
|
||||||
|
| Platform | Artifact |
|
||||||
|
|--|--|
|
||||||
|
| Windows 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip |
|
||||||
|
| Linux 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz |
|
||||||
|
| Linux arm 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz |
|
||||||
|
| Macos | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz |
|
||||||
|
|
||||||
|
"**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
replacesArtifacts: true
|
replacesArtifacts: true
|
||||||
|
4
.github/workflows/nightly_pr_comment.yml
vendored
4
.github/workflows/nightly_pr_comment.yml
vendored
@ -38,12 +38,12 @@ 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_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less</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('sdl2-ryujinx-headless')) {
|
} else if(art.name.includes('nogui-ryujinx')) {
|
||||||
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)`;
|
||||||
|
45
.github/workflows/release.yml
vendored
45
.github/workflows/release.yml
vendored
@ -53,7 +53,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
name: ${{ steps.version_info.outputs.build_version }}
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
|
body: |
|
||||||
|
# Regular builds:
|
||||||
|
| Platform | Artifact |
|
||||||
|
|--|--|
|
||||||
|
| Windows 64 bit | [Release Windows Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
||||||
|
| Linux 64 bit | [Release Linux Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
||||||
|
| Linux arm 64 bit | [Release Linux arm Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
||||||
|
| macOS | [Release macOS Artifact](https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||||
@ -102,8 +111,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./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 -p:IncludeNativeLibrariesForSelfExtract=true
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./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
|
||||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -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 -p:IncludeNativeLibrariesForSelfExtract=true
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -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
|
||||||
|
|
||||||
- name: Packing Windows builds
|
- name: Packing Windows builds
|
||||||
if: matrix.platform.os == 'windows-latest'
|
if: matrix.platform.os == 'windows-latest'
|
||||||
@ -115,7 +124,7 @@ jobs:
|
|||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
7z a ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@ -166,7 +175,7 @@ jobs:
|
|||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
chmod +x Ryujinx.sh Ryujinx.Headless.SDL2
|
chmod +x Ryujinx.sh 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/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@ -176,7 +185,16 @@ jobs:
|
|||||||
name: ${{ steps.version_info.outputs.build_version }}
|
name: ${{ steps.version_info.outputs.build_version }}
|
||||||
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
|
body: |
|
||||||
|
# Regular builds:
|
||||||
|
| Platform | Artifact |
|
||||||
|
|--|--|
|
||||||
|
| Windows 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip |
|
||||||
|
| Linux 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz |
|
||||||
|
| Linux arm 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz |
|
||||||
|
| Macos | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz |
|
||||||
|
|
||||||
|
"**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
replacesArtifacts: true
|
replacesArtifacts: true
|
||||||
@ -231,11 +249,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./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 ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||||
|
|
||||||
- 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 0
|
||||||
|
|
||||||
- name: Pushing new release
|
- name: Pushing new release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
@ -243,7 +261,16 @@ jobs:
|
|||||||
name: ${{ steps.version_info.outputs.build_version }}
|
name: ${{ steps.version_info.outputs.build_version }}
|
||||||
artifacts: "publish/*.tar.gz, publish_headless/*.tar.gz"
|
artifacts: "publish/*.tar.gz, publish_headless/*.tar.gz"
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
|
body: |
|
||||||
|
# Regular builds:
|
||||||
|
| Platform | Artifact |
|
||||||
|
|--|--|
|
||||||
|
| Windows 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip |
|
||||||
|
| Linux 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz |
|
||||||
|
| Linux arm 64 bit | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz |
|
||||||
|
| Macos | https://github.com/${{ github.repository }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz |
|
||||||
|
|
||||||
|
"**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
replacesArtifacts: true
|
replacesArtifacts: true
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -175,3 +175,6 @@ PublishProfiles/
|
|||||||
|
|
||||||
# Glade backup files
|
# Glade backup files
|
||||||
*.glade~
|
*.glade~
|
||||||
|
|
||||||
|
# Ignore MacOS Attribute Files
|
||||||
|
._*
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||||
<PackageVersion Include="Gommon" Version="2.6.5" />
|
<PackageVersion Include="Gommon" Version="2.6.8" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
@ -52,4 +52,4 @@
|
|||||||
<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" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
45
README.md
45
README.md
@ -1,35 +1,26 @@
|
|||||||
<h1 align="center">
|
<table align="center">
|
||||||
|
<tr>
|
||||||
|
<td align="center" width="25%">
|
||||||
|
<img src="https://raw.githubusercontent.com/GreemDev/ryuassets/refs/heads/main/RyujinxApp_1024.png" alt="Ryujinx" >
|
||||||
|
</td>
|
||||||
|
<td align="center" width="75%">
|
||||||
|
|
||||||
|
# Ryujinx
|
||||||
|
|
||||||
|
[](https://github.com/GreemDev/Ryujinx/actions/workflows/release.yml)
|
||||||
|
[](https://github.com/GreemDev/Ryujinx/releases/latest)
|
||||||
<br>
|
<br>
|
||||||
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
|
[](https://github.com/GreemDev/Ryujinx/actions/workflows/canary.yml)
|
||||||
<br>
|
[](https://github.com/GreemDev/Ryujinx-Canary/releases/latest)
|
||||||
<b>Ryujinx</b>
|
</td>
|
||||||
<br>
|
</tr>
|
||||||
<sub><sup><b>(REE-YOU-JINX)</b></sup></sub>
|
</table>
|
||||||
<br>
|
|
||||||
<a href="https://github.com/GreemDev/Ryujinx/actions/workflows/release.yml">
|
|
||||||
<img src="https://github.com/GreemDev/Ryujinx/actions/workflows/release.yml/badge.svg"
|
|
||||||
alt="">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/GreemDev/Ryujinx/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/GreemDev/Ryujinx"
|
|
||||||
alt="Latest Release">
|
|
||||||
</a>
|
|
||||||
<br>
|
|
||||||
<a href="https://github.com/GreemDev/Ryujinx/actions/workflows/canary.yml">
|
|
||||||
<img src="https://github.com/GreemDev/Ryujinx/actions/workflows/canary.yml/badge.svg"
|
|
||||||
alt="">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/GreemDev/Ryujinx-Canary/releases/latest">
|
|
||||||
<img src="https://img.shields.io/github/v/release/GreemDev/Ryujinx-Canary?label=canary"
|
|
||||||
alt="Latest Canary Release">
|
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
|
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
|
||||||
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
||||||
It was written from scratch and development on the project began in September 2017.
|
It was written from scratch and development on the project began in September 2017.
|
||||||
Ryujinx is available on Github under the <a href="https://github.com/GreemDev/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
|
Ryujinx is available on GitHub under the <a href="https://github.com/GreemDev/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -42,7 +33,7 @@
|
|||||||
Guides and documentation can be found on the <a href="https://github.com/GreemDev/Ryujinx/wiki">Wiki tab</a>.
|
Guides and documentation can be found on the <a href="https://github.com/GreemDev/Ryujinx/wiki">Wiki tab</a>.
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
If you would like a version more preservative fork of Ryujinx, check out <a href="https://github.com/ryujinx-mirror/ryujinx">ryujinx-mirror</a>.
|
If you would like a more preservative fork of Ryujinx, check out <a href="https://github.com/ryujinx-mirror/ryujinx">ryujinx-mirror</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
@ -14,7 +14,7 @@ if [ -z "$RYUJINX_BIN" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
COMMAND="env LANG=C.UTF-8 DOTNET_EnableAlternateStackCheck=1"
|
||||||
|
|
||||||
if command -v gamemoderun > /dev/null 2>&1; then
|
if command -v gamemoderun > /dev/null 2>&1; then
|
||||||
COMMAND="$COMMAND gamemoderun"
|
COMMAND="$COMMAND gamemoderun"
|
||||||
|
@ -40,11 +40,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.1</string>
|
<string>1.2</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.1.0</string>
|
<string>1.2.0</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CSResourcesFileMapped</key>
|
<key>CSResourcesFileMapped</key>
|
||||||
|
Binary file not shown.
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$#" -lt 7 ]; then
|
if [ "$#" -lt 8 ]; then
|
||||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
|
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <CANARY>"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -18,10 +18,11 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
|||||||
VERSION=$5
|
VERSION=$5
|
||||||
SOURCE_REVISION_ID=$6
|
SOURCE_REVISION_ID=$6
|
||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
EXTRA_ARGS=$8
|
CANARY=$8
|
||||||
|
|
||||||
if [ "$VERSION" == "1.1.0" ];
|
if [ "$CANARY" == "1" ]; then
|
||||||
then
|
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar
|
||||||
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
RELEASE_TAR_FILE_NAME=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=ryujinx-$VERSION-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
|
||||||
@ -61,7 +62,7 @@ mkdir -p "$OUTPUT_DIRECTORY"
|
|||||||
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
|
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
|
||||||
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
|
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
|
||||||
|
|
||||||
# Make it libraries universal
|
# Make its libraries universal
|
||||||
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
|
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
|
||||||
|
|
||||||
if ! [ -x "$(command -v lipo)" ];
|
if ! [ -x "$(command -v lipo)" ];
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$#" -lt 7 ]; then
|
if [ "$#" -lt 8 ]; then
|
||||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
|
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <CANARY>"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -18,13 +18,14 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
|||||||
VERSION=$5
|
VERSION=$5
|
||||||
SOURCE_REVISION_ID=$6
|
SOURCE_REVISION_ID=$6
|
||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
EXTRA_ARGS=$8
|
CANARY=$8
|
||||||
|
|
||||||
if [ "$VERSION" == "1.1.0" ];
|
if [ "$CANARY" == "1" ]; then
|
||||||
then
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
||||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
||||||
else
|
else
|
||||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
|
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
|
||||||
@ -56,7 +57,7 @@ mkdir -p "$OUTPUT_DIRECTORY"
|
|||||||
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
|
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
|
||||||
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
|
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
|
||||||
|
|
||||||
# Make it libraries universal
|
# Make its libraries universal
|
||||||
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
|
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
|
||||||
|
|
||||||
if ! [ -x "$(command -v lipo)" ];
|
if ! [ -x "$(command -v lipo)" ];
|
||||||
|
@ -17,7 +17,7 @@ error_handler() {
|
|||||||
set the button_pressed to the button returned of the result
|
set the button_pressed to the button returned of the result
|
||||||
|
|
||||||
if the button_pressed is \"Open Download Page\" then
|
if the button_pressed is \"Open Download Page\" then
|
||||||
open location \"https://ryujinx.org/download\"
|
open location \"https://ryujinx.app/download\"
|
||||||
end if
|
end if
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -54,4 +54,4 @@ if [ "$#" -le 3 ]; then
|
|||||||
open -a "$INSTALL_DIRECTORY"
|
open -a "$INSTALL_DIRECTORY"
|
||||||
else
|
else
|
||||||
open -a "$INSTALL_DIRECTORY" --args "${APP_ARGUMENTS[@]}"
|
open -a "$INSTALL_DIRECTORY" --args "${APP_ARGUMENTS[@]}"
|
||||||
fi
|
fi
|
||||||
|
File diff suppressed because one or more lines are too long
Before (image error) Size: 1.5 KiB After (image error) Size: 1.2 MiB |
61
distribution/misc/macOS.svg
Normal file
61
distribution/misc/macOS.svg
Normal file
File diff suppressed because one or more lines are too long
After (image error) Size: 48 KiB |
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,252 +0,0 @@
|
|||||||
using ARMeilleure.Diagnostics;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ARMeilleure.Common
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a table of guest address to a value.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TEntry">Type of the value</typeparam>
|
|
||||||
public unsafe class AddressTable<TEntry> : IDisposable where TEntry : unmanaged
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a level in an <see cref="AddressTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct Level
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the index of the <see cref="Level"/> in the guest address.
|
|
||||||
/// </summary>
|
|
||||||
public int Index { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the length of the <see cref="Level"/> in the guest address.
|
|
||||||
/// </summary>
|
|
||||||
public int Length { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the mask which masks the bits used by the <see cref="Level"/>.
|
|
||||||
/// </summary>
|
|
||||||
public ulong Mask => ((1ul << Length) - 1) << Index;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Level"/> structure with the specified
|
|
||||||
/// <paramref name="index"/> and <paramref name="length"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Index of the <see cref="Level"/></param>
|
|
||||||
/// <param name="length">Length of the <see cref="Level"/></param>
|
|
||||||
public Level(int index, int length)
|
|
||||||
{
|
|
||||||
(Index, Length) = (index, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of the <see cref="Level"/> from the specified guest <paramref name="address"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns>Value of the <see cref="Level"/> from the specified guest <paramref name="address"/></returns>
|
|
||||||
public int GetValue(ulong address)
|
|
||||||
{
|
|
||||||
return (int)((address & Mask) >> Index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
private TEntry** _table;
|
|
||||||
private readonly List<nint> _pages;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public ulong Mask { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="Level"/>s used by the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public Level[] Levels { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the default fill value of newly created leaf pages.
|
|
||||||
/// </summary>
|
|
||||||
public TEntry Fill { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
|
||||||
public nint Base
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
||||||
|
|
||||||
lock (_pages)
|
|
||||||
{
|
|
||||||
return (nint)GetRootPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
|
|
||||||
/// <see cref="Level"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
|
|
||||||
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
|
|
||||||
public AddressTable(Level[] levels)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(levels);
|
|
||||||
|
|
||||||
if (levels.Length < 2)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels));
|
|
||||||
}
|
|
||||||
|
|
||||||
_pages = new List<nint>(capacity: 16);
|
|
||||||
|
|
||||||
Levels = levels;
|
|
||||||
Mask = 0;
|
|
||||||
|
|
||||||
foreach (var level in Levels)
|
|
||||||
{
|
|
||||||
Mask |= level.Mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the specified <paramref name="address"/> is in the range of the
|
|
||||||
/// <see cref="AddressTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
|
|
||||||
public bool IsValid(ulong address)
|
|
||||||
{
|
|
||||||
return (address & ~Mask) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
|
|
||||||
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
|
||||||
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
|
|
||||||
public ref TEntry GetValue(ulong address)
|
|
||||||
{
|
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
||||||
|
|
||||||
if (!IsValid(address))
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_pages)
|
|
||||||
{
|
|
||||||
return ref GetPage(address)[Levels[^1].GetValue(address)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the leaf page for the specified guest <paramref name="address"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns>Leaf page for the specified guest <paramref name="address"/></returns>
|
|
||||||
private TEntry* GetPage(ulong address)
|
|
||||||
{
|
|
||||||
TEntry** page = GetRootPage();
|
|
||||||
|
|
||||||
for (int i = 0; i < Levels.Length - 1; i++)
|
|
||||||
{
|
|
||||||
ref Level level = ref Levels[i];
|
|
||||||
ref TEntry* nextPage = ref page[level.GetValue(address)];
|
|
||||||
|
|
||||||
if (nextPage == null)
|
|
||||||
{
|
|
||||||
ref Level nextLevel = ref Levels[i + 1];
|
|
||||||
|
|
||||||
nextPage = i == Levels.Length - 2 ?
|
|
||||||
(TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) :
|
|
||||||
(TEntry*)Allocate(1 << nextLevel.Length, nint.Zero, leaf: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
page = (TEntry**)nextPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (TEntry*)page;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns>
|
|
||||||
private TEntry** GetRootPage()
|
|
||||||
{
|
|
||||||
if (_table == null)
|
|
||||||
{
|
|
||||||
_table = (TEntry**)Allocate(1 << Levels[0].Length, fill: nint.Zero, leaf: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Allocates a block of memory of the specified type and length.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of elements</typeparam>
|
|
||||||
/// <param name="length">Number of elements</param>
|
|
||||||
/// <param name="fill">Fill value</param>
|
|
||||||
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
|
|
||||||
/// <returns>Allocated block</returns>
|
|
||||||
private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
|
|
||||||
{
|
|
||||||
var size = sizeof(T) * length;
|
|
||||||
var page = (nint)NativeAllocator.Instance.Allocate((uint)size);
|
|
||||||
var span = new Span<T>((void*)page, length);
|
|
||||||
|
|
||||||
span.Fill(fill);
|
|
||||||
|
|
||||||
_pages.Add(page);
|
|
||||||
|
|
||||||
TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
|
|
||||||
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/>
|
|
||||||
/// instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!_disposed)
|
|
||||||
{
|
|
||||||
foreach (var page in _pages)
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
~AddressTable()
|
|
||||||
{
|
|
||||||
Dispose(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
44
src/ARMeilleure/Common/AddressTableLevel.cs
Normal file
44
src/ARMeilleure/Common/AddressTableLevel.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a level in an <see cref="IAddressTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct AddressTableLevel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the <see cref="Level"/> in the guest address.
|
||||||
|
/// </summary>
|
||||||
|
public int Index { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length of the <see cref="AddressTableLevel"/> in the guest address.
|
||||||
|
/// </summary>
|
||||||
|
public int Length { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mask which masks the bits used by the <see cref="AddressTableLevel"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Mask => ((1ul << Length) - 1) << Index;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddressTableLevel"/> structure with the specified
|
||||||
|
/// <paramref name="index"/> and <paramref name="length"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the <see cref="AddressTableLevel"/></param>
|
||||||
|
/// <param name="length">Length of the <see cref="AddressTableLevel"/></param>
|
||||||
|
public AddressTableLevel(int index, int length)
|
||||||
|
{
|
||||||
|
(Index, Length) = (index, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns>Value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/></returns>
|
||||||
|
public long GetValue(ulong address)
|
||||||
|
{
|
||||||
|
return (long)((address & Mask) >> Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/ARMeilleure/Common/AddressTablePresets.cs
Normal file
75
src/ARMeilleure/Common/AddressTablePresets.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
public static class AddressTablePresets
|
||||||
|
{
|
||||||
|
private static readonly AddressTableLevel[] _levels64Bit =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new(31, 17),
|
||||||
|
new(23, 8),
|
||||||
|
new(15, 8),
|
||||||
|
new( 7, 8),
|
||||||
|
new( 2, 5),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels32Bit =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new(31, 17),
|
||||||
|
new(23, 8),
|
||||||
|
new(15, 8),
|
||||||
|
new( 7, 8),
|
||||||
|
new( 1, 6),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels64BitSparseTiny =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 11, 28),
|
||||||
|
new( 2, 9),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels32BitSparseTiny =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 10, 22),
|
||||||
|
new( 1, 9),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels64BitSparseGiant =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 38, 1),
|
||||||
|
new( 2, 36),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels32BitSparseGiant =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 31, 1),
|
||||||
|
new( 1, 30),
|
||||||
|
};
|
||||||
|
|
||||||
|
//high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization
|
||||||
|
//low power will never run worse than non-sparse, but for most systems it won't be necessary
|
||||||
|
//high power is always used, but I've left low power in here for future reference
|
||||||
|
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false)
|
||||||
|
{
|
||||||
|
if (sparse)
|
||||||
|
{
|
||||||
|
if (lowPower)
|
||||||
|
{
|
||||||
|
return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return for64Bits ? _levels64Bit : _levels32Bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace ARMeilleure.Common
|
namespace ARMeilleure.Common
|
||||||
{
|
{
|
||||||
unsafe abstract class Allocator : IDisposable
|
public unsafe abstract class Allocator : IDisposable
|
||||||
{
|
{
|
||||||
public T* Allocate<T>(ulong count = 1) where T : unmanaged
|
public T* Allocate<T>(ulong count = 1) where T : unmanaged
|
||||||
{
|
{
|
||||||
|
51
src/ARMeilleure/Common/IAddressTable.cs
Normal file
51
src/ARMeilleure/Common/IAddressTable.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
public interface IAddressTable<TEntry> : IDisposable where TEntry : unmanaged
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True if the address table's bottom level is sparsely mapped.
|
||||||
|
/// This also ensures the second bottom level is filled with a dummy page rather than 0.
|
||||||
|
/// </summary>
|
||||||
|
bool Sparse { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
ulong Mask { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="AddressTableLevel"/>s used by the <see cref="IAddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
AddressTableLevel[] Levels { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default fill value of newly created leaf pages.
|
||||||
|
/// </summary>
|
||||||
|
TEntry Fill { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
||||||
|
nint Base { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the specified <paramref name="address"/> is in the range of the
|
||||||
|
/// <see cref="IAddressTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
|
||||||
|
bool IsValid(ulong address);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
|
||||||
|
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
||||||
|
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
|
||||||
|
ref TEntry GetValue(ulong address);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace ARMeilleure.Common
|
namespace ARMeilleure.Common
|
||||||
{
|
{
|
||||||
unsafe sealed class NativeAllocator : Allocator
|
public unsafe sealed class NativeAllocator : Allocator
|
||||||
{
|
{
|
||||||
public static NativeAllocator Instance { get; } = new();
|
public static NativeAllocator Instance { get; } = new();
|
||||||
|
|
||||||
|
@ -193,6 +193,8 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand hostAddress;
|
Operand hostAddress;
|
||||||
|
|
||||||
|
var table = context.FunctionTable;
|
||||||
|
|
||||||
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
|
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
|
||||||
// onto the dispatch stub.
|
// onto the dispatch stub.
|
||||||
if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value))
|
if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value))
|
||||||
@ -203,6 +205,30 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
|
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
|
||||||
}
|
}
|
||||||
|
else if (table.Sparse)
|
||||||
|
{
|
||||||
|
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||||
|
// Deliberately attempts to avoid branches.
|
||||||
|
|
||||||
|
Operand tableBase = !context.HasPtc ?
|
||||||
|
Const(table.Base) :
|
||||||
|
Const(table.Base, Ptc.FunctionTableSymbol);
|
||||||
|
|
||||||
|
hostAddress = tableBase;
|
||||||
|
|
||||||
|
for (int i = 0; i < table.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
var level = table.Levels[i];
|
||||||
|
int clearBits = 64 - (level.Index + level.Length);
|
||||||
|
|
||||||
|
Operand index = context.ShiftLeft(
|
||||||
|
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
|
||||||
|
Const(3)
|
||||||
|
);
|
||||||
|
|
||||||
|
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hostAddress = !context.HasPtc ?
|
hostAddress = !context.HasPtc ?
|
||||||
|
@ -49,6 +49,9 @@ namespace ARMeilleure.Instructions
|
|||||||
case 0b11_011_1101_0000_011:
|
case 0b11_011_1101_0000_011:
|
||||||
EmitGetTpidrroEl0(context);
|
EmitGetTpidrroEl0(context);
|
||||||
return;
|
return;
|
||||||
|
case 0b11_011_1101_0000_101:
|
||||||
|
EmitGetTpidr2El0(context);
|
||||||
|
return;
|
||||||
case 0b11_011_1110_0000_000:
|
case 0b11_011_1110_0000_000:
|
||||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0));
|
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0));
|
||||||
break;
|
break;
|
||||||
@ -84,6 +87,9 @@ namespace ARMeilleure.Instructions
|
|||||||
case 0b11_011_1101_0000_010:
|
case 0b11_011_1101_0000_010:
|
||||||
EmitSetTpidrEl0(context);
|
EmitSetTpidrEl0(context);
|
||||||
return;
|
return;
|
||||||
|
case 0b11_011_1101_0000_101:
|
||||||
|
EmitSetTpidr2El0(context);
|
||||||
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||||
@ -213,6 +219,17 @@ namespace ARMeilleure.Instructions
|
|||||||
SetIntOrZR(context, op.Rt, result);
|
SetIntOrZR(context, op.Rt, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitGetTpidr2El0(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||||
|
|
||||||
|
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||||
|
|
||||||
|
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidr2El0Offset())));
|
||||||
|
|
||||||
|
SetIntOrZR(context, op.Rt, result);
|
||||||
|
}
|
||||||
|
|
||||||
private static void EmitSetNzcv(ArmEmitterContext context)
|
private static void EmitSetNzcv(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||||
@ -274,5 +291,16 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
|
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitSetTpidr2El0(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||||
|
|
||||||
|
Operand value = GetIntOrZR(context, op.Rt);
|
||||||
|
|
||||||
|
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||||
|
|
||||||
|
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidr2El0Offset())), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace ARMeilleure.Signal
|
|||||||
{
|
{
|
||||||
public static class NativeSignalHandlerGenerator
|
public static class NativeSignalHandlerGenerator
|
||||||
{
|
{
|
||||||
public const int MaxTrackedRanges = 8;
|
public const int MaxTrackedRanges = 16;
|
||||||
|
|
||||||
private const int StructAddressOffset = 0;
|
private const int StructAddressOffset = 0;
|
||||||
private const int StructWriteOffset = 4;
|
private const int StructWriteOffset = 4;
|
||||||
|
@ -21,6 +21,7 @@ namespace ARMeilleure.State
|
|||||||
public ulong ExclusiveValueLow;
|
public ulong ExclusiveValueLow;
|
||||||
public ulong ExclusiveValueHigh;
|
public ulong ExclusiveValueHigh;
|
||||||
public int Running;
|
public int Running;
|
||||||
|
public long Tpidr2El0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NativeCtxStorage _dummyStorage = new();
|
private static NativeCtxStorage _dummyStorage = new();
|
||||||
@ -176,6 +177,9 @@ namespace ARMeilleure.State
|
|||||||
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
|
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
|
||||||
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
|
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
|
||||||
|
|
||||||
|
public long GetTpidr2El0() => GetStorage().Tpidr2El0;
|
||||||
|
public void SetTpidr2El0(long value) => GetStorage().Tpidr2El0 = value;
|
||||||
|
|
||||||
public int GetCounter() => GetStorage().Counter;
|
public int GetCounter() => GetStorage().Counter;
|
||||||
public void SetCounter(int value) => GetStorage().Counter = value;
|
public void SetCounter(int value) => GetStorage().Counter = value;
|
||||||
|
|
||||||
@ -232,6 +236,11 @@ namespace ARMeilleure.State
|
|||||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int GetTpidr2El0Offset()
|
||||||
|
{
|
||||||
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Tpidr2El0);
|
||||||
|
}
|
||||||
|
|
||||||
public static int GetCounterOffset()
|
public static int GetCounterOffset()
|
||||||
{
|
{
|
||||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||||
|
@ -46,7 +46,7 @@ namespace ARMeilleure.Translation
|
|||||||
public IMemoryManager Memory { get; }
|
public IMemoryManager Memory { get; }
|
||||||
|
|
||||||
public EntryTable<uint> CountTable { get; }
|
public EntryTable<uint> CountTable { get; }
|
||||||
public AddressTable<ulong> FunctionTable { get; }
|
public IAddressTable<ulong> FunctionTable { get; }
|
||||||
public TranslatorStubs Stubs { get; }
|
public TranslatorStubs Stubs { get; }
|
||||||
|
|
||||||
public ulong EntryAddress { get; }
|
public ulong EntryAddress { get; }
|
||||||
@ -62,7 +62,7 @@ namespace ARMeilleure.Translation
|
|||||||
public ArmEmitterContext(
|
public ArmEmitterContext(
|
||||||
IMemoryManager memory,
|
IMemoryManager memory,
|
||||||
EntryTable<uint> countTable,
|
EntryTable<uint> countTable,
|
||||||
AddressTable<ulong> funcTable,
|
IAddressTable<ulong> funcTable,
|
||||||
TranslatorStubs stubs,
|
TranslatorStubs stubs,
|
||||||
ulong entryAddress,
|
ulong entryAddress,
|
||||||
bool highCq,
|
bool highCq,
|
||||||
|
@ -30,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 6997; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -41,6 +41,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
|
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
|
||||||
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
|
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
|
||||||
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
|
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
|
||||||
|
public static readonly Symbol FunctionTableSymbol = new(SymbolType.Special, 4);
|
||||||
|
|
||||||
private const byte FillingByte = 0x00;
|
private const byte FillingByte = 0x00;
|
||||||
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
|
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
|
||||||
@ -101,7 +102,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
Disable();
|
Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode)
|
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode, string cacheSelector)
|
||||||
{
|
{
|
||||||
Wait();
|
Wait();
|
||||||
|
|
||||||
@ -127,6 +128,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
||||||
_memoryMode = memoryMode;
|
_memoryMode = memoryMode;
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Ptc, $"PPTC (v{InternalVersion}) Profile: {DisplayVersion}-{cacheSelector}");
|
||||||
|
|
||||||
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
||||||
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
|
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
|
||||||
|
|
||||||
@ -140,8 +143,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
Directory.CreateDirectory(workPathBackup);
|
Directory.CreateDirectory(workPathBackup);
|
||||||
}
|
}
|
||||||
|
|
||||||
CachePathActual = Path.Combine(workPathActual, DisplayVersion);
|
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector;
|
||||||
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
|
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector;
|
||||||
|
|
||||||
PreLoad();
|
PreLoad();
|
||||||
Profiler.PreLoad();
|
Profiler.PreLoad();
|
||||||
@ -706,6 +709,10 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
imm = translator.Stubs.DispatchStub;
|
imm = translator.Stubs.DispatchStub;
|
||||||
}
|
}
|
||||||
|
else if (symbol == FunctionTableSymbol)
|
||||||
|
{
|
||||||
|
imm = translator.FunctionTable.Base;
|
||||||
|
}
|
||||||
|
|
||||||
if (imm == null)
|
if (imm == null)
|
||||||
{
|
{
|
||||||
|
@ -22,33 +22,13 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
public class Translator
|
public class Translator
|
||||||
{
|
{
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels64Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(31, 17),
|
|
||||||
new(23, 8),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 2, 5),
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels32Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(31, 17),
|
|
||||||
new(23, 8),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 1, 6),
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly IJitMemoryAllocator _allocator;
|
private readonly IJitMemoryAllocator _allocator;
|
||||||
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||||
|
|
||||||
private readonly Ptc _ptc;
|
private readonly Ptc _ptc;
|
||||||
|
|
||||||
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
||||||
internal AddressTable<ulong> FunctionTable { get; }
|
internal IAddressTable<ulong> FunctionTable { get; }
|
||||||
internal EntryTable<uint> CountTable { get; }
|
internal EntryTable<uint> CountTable { get; }
|
||||||
internal TranslatorStubs Stubs { get; }
|
internal TranslatorStubs Stubs { get; }
|
||||||
internal TranslatorQueue Queue { get; }
|
internal TranslatorQueue Queue { get; }
|
||||||
@ -57,7 +37,7 @@ namespace ARMeilleure.Translation
|
|||||||
private Thread[] _backgroundTranslationThreads;
|
private Thread[] _backgroundTranslationThreads;
|
||||||
private volatile int _threadCount;
|
private volatile int _threadCount;
|
||||||
|
|
||||||
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, IAddressTable<ulong> functionTable)
|
||||||
{
|
{
|
||||||
_allocator = allocator;
|
_allocator = allocator;
|
||||||
Memory = memory;
|
Memory = memory;
|
||||||
@ -72,15 +52,15 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
CountTable = new EntryTable<uint>();
|
CountTable = new EntryTable<uint>();
|
||||||
Functions = new TranslatorCache<TranslatedFunction>();
|
Functions = new TranslatorCache<TranslatedFunction>();
|
||||||
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
FunctionTable = functionTable;
|
||||||
Stubs = new TranslatorStubs(FunctionTable);
|
Stubs = new TranslatorStubs(FunctionTable);
|
||||||
|
|
||||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type);
|
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type, cacheSelector);
|
||||||
return _ptc;
|
return _ptc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly AddressTable<ulong> _functionTable;
|
private readonly IAddressTable<ulong> _functionTable;
|
||||||
private readonly Lazy<nint> _dispatchStub;
|
private readonly Lazy<nint> _dispatchStub;
|
||||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||||
private readonly Lazy<WrapperFunction> _contextWrapper;
|
private readonly Lazy<WrapperFunction> _contextWrapper;
|
||||||
@ -86,7 +86,7 @@ namespace ARMeilleure.Translation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||||
public TranslatorStubs(AddressTable<ulong> functionTable)
|
public TranslatorStubs(IAddressTable<ulong> functionTable)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(functionTable);
|
ArgumentNullException.ThrowIfNull(functionTable);
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||||
{
|
{
|
||||||
public class JoyconConfigControllerStick<TButton, TStick> where TButton : unmanaged where TStick : unmanaged
|
public class JoyconConfigControllerStick<TButton, TStick>
|
||||||
|
where TButton : unmanaged
|
||||||
|
where TStick : unmanaged
|
||||||
{
|
{
|
||||||
public TStick Joystick { get; set; }
|
public TStick Joystick { get; set; }
|
||||||
public bool InvertStickX { get; set; }
|
public bool InvertStickX { get; set; }
|
||||||
|
@ -2,7 +2,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||||||
{
|
{
|
||||||
public class KeyboardHotkeys
|
public class KeyboardHotkeys
|
||||||
{
|
{
|
||||||
public Key ToggleVsync { get; set; }
|
public Key ToggleVSyncMode { get; set; }
|
||||||
public Key Screenshot { get; set; }
|
public Key Screenshot { get; set; }
|
||||||
public Key ShowUI { get; set; }
|
public Key ShowUI { get; set; }
|
||||||
public Key Pause { get; set; }
|
public Key Pause { get; set; }
|
||||||
@ -11,5 +11,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||||||
public Key ResScaleDown { get; set; }
|
public Key ResScaleDown { get; set; }
|
||||||
public Key VolumeUp { get; set; }
|
public Key VolumeUp { get; set; }
|
||||||
public Key VolumeDown { get; set; }
|
public Key VolumeDown { get; set; }
|
||||||
|
public Key CustomVSyncIntervalIncrement { get; set; }
|
||||||
|
public Key CustomVSyncIntervalDecrement { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
src/Ryujinx.Common/Configuration/VSyncMode.cs
Normal file
9
src/Ryujinx.Common/Configuration/VSyncMode.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
public enum VSyncMode
|
||||||
|
{
|
||||||
|
Switch,
|
||||||
|
Unbounded,
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
}
|
@ -69,9 +69,10 @@ namespace Ryujinx.Common.Logging.Targets
|
|||||||
}
|
}
|
||||||
|
|
||||||
string version = ReleaseInformation.Version;
|
string version = ReleaseInformation.Version;
|
||||||
|
string appName = ReleaseInformation.IsCanaryBuild ? "Ryujinx_Canary" : "Ryujinx";
|
||||||
|
|
||||||
// Get path for the current time
|
// Get path for the current time
|
||||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
path = Path.Combine(logDir.FullName, $"{appName}_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
@ -35,5 +36,13 @@ namespace Ryujinx.Common
|
|||||||
public static bool IsReleaseBuild => IsValid && ReleaseChannelName.Equals(ReleaseChannel);
|
public static bool IsReleaseBuild => IsValid && ReleaseChannelName.Equals(ReleaseChannel);
|
||||||
|
|
||||||
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
|
||||||
|
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||||
|
IsCanaryBuild
|
||||||
|
? $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||||
|
: $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/releases/tag/{newVersion}";
|
||||||
|
|
||||||
|
public static string GetChangelogForVersion(Version version) =>
|
||||||
|
$"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelRepo}/releases/tag/{version}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
482
src/Ryujinx.Cpu/AddressTable.cs
Normal file
482
src/Ryujinx.Cpu/AddressTable.cs
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
using ARMeilleure.Memory;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Cpu.Signal;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using static Ryujinx.Cpu.MemoryEhMeilleure;
|
||||||
|
|
||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a table of guest address to a value.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntry">Type of the value</typeparam>
|
||||||
|
public unsafe class AddressTable<TEntry> : IAddressTable<TEntry> where TEntry : unmanaged
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a page of the address table.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct AddressTablePage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True if the allocation belongs to a sparse block, false otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool IsSparse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base address for the page.
|
||||||
|
/// </summary>
|
||||||
|
public readonly IntPtr Address;
|
||||||
|
|
||||||
|
public AddressTablePage(bool isSparse, IntPtr address)
|
||||||
|
{
|
||||||
|
IsSparse = isSparse;
|
||||||
|
Address = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sparsely mapped block of memory with a signal handler to map pages as they're accessed.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct TableSparseBlock : IDisposable
|
||||||
|
{
|
||||||
|
public readonly SparseMemoryBlock Block;
|
||||||
|
private readonly TrackingEventDelegate _trackingEvent;
|
||||||
|
|
||||||
|
public TableSparseBlock(ulong size, Action<IntPtr> ensureMapped, PageInitDelegate pageInit)
|
||||||
|
{
|
||||||
|
var block = new SparseMemoryBlock(size, pageInit, null);
|
||||||
|
|
||||||
|
_trackingEvent = (ulong address, ulong size, bool write) =>
|
||||||
|
{
|
||||||
|
ulong pointer = (ulong)block.Block.Pointer + address;
|
||||||
|
ensureMapped((IntPtr)pointer);
|
||||||
|
return pointer;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool added = NativeSignalHandler.AddTrackedRegion(
|
||||||
|
(nuint)block.Block.Pointer,
|
||||||
|
(nuint)(block.Block.Pointer + (IntPtr)block.Block.Size),
|
||||||
|
Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||||
|
|
||||||
|
if (!added)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Block = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
NativeSignalHandler.RemoveTrackedRegion((nuint)Block.Block.Pointer);
|
||||||
|
|
||||||
|
Block.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
private TEntry** _table;
|
||||||
|
private readonly List<AddressTablePage> _pages;
|
||||||
|
private TEntry _fill;
|
||||||
|
|
||||||
|
private readonly MemoryBlock _sparseFill;
|
||||||
|
private readonly SparseMemoryBlock _fillBottomLevel;
|
||||||
|
private readonly TEntry* _fillBottomLevelPtr;
|
||||||
|
|
||||||
|
private readonly List<TableSparseBlock> _sparseReserved;
|
||||||
|
private readonly ReaderWriterLockSlim _sparseLock;
|
||||||
|
|
||||||
|
private ulong _sparseBlockSize;
|
||||||
|
private ulong _sparseReservedOffset;
|
||||||
|
|
||||||
|
public bool Sparse { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong Mask { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public AddressTableLevel[] Levels { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TEntry Fill
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fill;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
UpdateFill(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IntPtr Base
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
|
lock (_pages)
|
||||||
|
{
|
||||||
|
return (IntPtr)GetRootPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
|
||||||
|
/// <see cref="Level"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="levels">Levels for the address table</param>
|
||||||
|
/// <param name="sparse">True if the bottom page should be sparsely mapped</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
|
||||||
|
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
|
||||||
|
public AddressTable(AddressTableLevel[] levels, bool sparse)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(levels);
|
||||||
|
|
||||||
|
_pages = new List<AddressTablePage>(capacity: 16);
|
||||||
|
|
||||||
|
Levels = levels;
|
||||||
|
Mask = 0;
|
||||||
|
|
||||||
|
foreach (var level in Levels)
|
||||||
|
{
|
||||||
|
Mask |= level.Mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sparse = sparse;
|
||||||
|
|
||||||
|
if (sparse)
|
||||||
|
{
|
||||||
|
// If the address table is sparse, allocate a fill block
|
||||||
|
|
||||||
|
_sparseFill = new MemoryBlock(268435456ul, MemoryAllocationFlags.Mirrorable); //low Power TC uses size: 65536ul
|
||||||
|
|
||||||
|
ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry);
|
||||||
|
|
||||||
|
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
|
||||||
|
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
|
||||||
|
|
||||||
|
_sparseReserved = new List<TableSparseBlock>();
|
||||||
|
_sparseLock = new ReaderWriterLockSlim();
|
||||||
|
|
||||||
|
_sparseBlockSize = bottomLevelSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an <see cref="AddressTable{TEntry}"/> instance for an ARM function table.
|
||||||
|
/// Selects the best table structure for A32/A64, taking into account the selected memory manager type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="for64Bits">True if the guest is A64, false otherwise</param>
|
||||||
|
/// <param name="type">Memory manager type</param>
|
||||||
|
/// <returns>An <see cref="AddressTable{TEntry}"/> for ARM function lookup</returns>
|
||||||
|
public static AddressTable<TEntry> CreateForArm(bool for64Bits, MemoryManagerType type)
|
||||||
|
{
|
||||||
|
// Assume software memory means that we don't want to use any signal handlers.
|
||||||
|
bool sparse = type != MemoryManagerType.SoftwareMmu && type != MemoryManagerType.SoftwarePageTable;
|
||||||
|
|
||||||
|
return new AddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the fill value for the bottom level of the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fillValue">New fill value</param>
|
||||||
|
private void UpdateFill(TEntry fillValue)
|
||||||
|
{
|
||||||
|
if (_sparseFill != null)
|
||||||
|
{
|
||||||
|
Span<byte> span = _sparseFill.GetSpan(0, (int)_sparseFill.Size);
|
||||||
|
MemoryMarshal.Cast<byte, TEntry>(span).Fill(fillValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fill = fillValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal that the given code range exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="size"></param>
|
||||||
|
public void SignalCodeRange(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
AddressTableLevel bottom = Levels.Last();
|
||||||
|
ulong bottomLevelEntries = 1ul << bottom.Length;
|
||||||
|
|
||||||
|
ulong entryIndex = address >> bottom.Index;
|
||||||
|
ulong entries = size >> bottom.Index;
|
||||||
|
entries += entryIndex - BitUtils.AlignDown(entryIndex, bottomLevelEntries);
|
||||||
|
|
||||||
|
_sparseBlockSize = Math.Max(_sparseBlockSize, BitUtils.AlignUp(entries, bottomLevelEntries) * (ulong)sizeof(TEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsValid(ulong address)
|
||||||
|
{
|
||||||
|
return (address & ~Mask) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ref TEntry GetValue(ulong address)
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
|
if (!IsValid(address))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_pages)
|
||||||
|
{
|
||||||
|
TEntry* page = GetPage(address);
|
||||||
|
|
||||||
|
long index = Levels[^1].GetValue(address);
|
||||||
|
|
||||||
|
EnsureMapped((IntPtr)(page + index));
|
||||||
|
|
||||||
|
return ref page[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the leaf page for the specified guest <paramref name="address"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns>Leaf page for the specified guest <paramref name="address"/></returns>
|
||||||
|
private TEntry* GetPage(ulong address)
|
||||||
|
{
|
||||||
|
TEntry** page = GetRootPage();
|
||||||
|
|
||||||
|
for (int i = 0; i < Levels.Length - 1; i++)
|
||||||
|
{
|
||||||
|
ref AddressTableLevel level = ref Levels[i];
|
||||||
|
ref TEntry* nextPage = ref page[level.GetValue(address)];
|
||||||
|
|
||||||
|
if (nextPage == null || nextPage == _fillBottomLevelPtr)
|
||||||
|
{
|
||||||
|
ref AddressTableLevel nextLevel = ref Levels[i + 1];
|
||||||
|
|
||||||
|
if (i == Levels.Length - 2)
|
||||||
|
{
|
||||||
|
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, GetFillValue(i), leaf: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page = (TEntry**)nextPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TEntry*)page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">Pointer to be mapped</param>
|
||||||
|
private void EnsureMapped(IntPtr ptr)
|
||||||
|
{
|
||||||
|
if (Sparse)
|
||||||
|
{
|
||||||
|
// Check sparse allocations to see if the pointer is in any of them.
|
||||||
|
// Ensure the page is committed if there's a match.
|
||||||
|
|
||||||
|
_sparseLock.EnterReadLock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (TableSparseBlock reserved in _sparseReserved)
|
||||||
|
{
|
||||||
|
SparseMemoryBlock sparse = reserved.Block;
|
||||||
|
|
||||||
|
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size)
|
||||||
|
{
|
||||||
|
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_sparseLock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the fill value for a non-leaf level of the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">Level to get the fill value for</param>
|
||||||
|
/// <returns>The fill value</returns>
|
||||||
|
private IntPtr GetFillValue(int level)
|
||||||
|
{
|
||||||
|
if (_fillBottomLevel != null && level == Levels.Length - 2)
|
||||||
|
{
|
||||||
|
return (IntPtr)_fillBottomLevelPtr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns>
|
||||||
|
private TEntry** GetRootPage()
|
||||||
|
{
|
||||||
|
if (_table == null)
|
||||||
|
{
|
||||||
|
if (Levels.Length == 1)
|
||||||
|
_table = (TEntry**)Allocate(1 << Levels[0].Length, Fill, leaf: true);
|
||||||
|
else
|
||||||
|
_table = (TEntry**)Allocate(1 << Levels[0].Length, GetFillValue(0), leaf: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize a leaf page with the fill value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">Page to initialize</param>
|
||||||
|
private void InitLeafPage(Span<byte> page)
|
||||||
|
{
|
||||||
|
MemoryMarshal.Cast<byte, TEntry>(page).Fill(_fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserve a new sparse block, and add it to the list.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The new sparse block that was added</returns>
|
||||||
|
private TableSparseBlock ReserveNewSparseBlock()
|
||||||
|
{
|
||||||
|
var block = new TableSparseBlock(_sparseBlockSize, EnsureMapped, InitLeafPage);
|
||||||
|
|
||||||
|
_sparseReserved.Add(block);
|
||||||
|
_sparseReservedOffset = 0;
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allocates a block of memory of the specified type and length.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of elements</typeparam>
|
||||||
|
/// <param name="length">Number of elements</param>
|
||||||
|
/// <param name="fill">Fill value</param>
|
||||||
|
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
|
||||||
|
/// <returns>Allocated block</returns>
|
||||||
|
private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
|
||||||
|
{
|
||||||
|
var size = sizeof(T) * length;
|
||||||
|
|
||||||
|
AddressTablePage page;
|
||||||
|
|
||||||
|
if (Sparse && leaf)
|
||||||
|
{
|
||||||
|
_sparseLock.EnterWriteLock();
|
||||||
|
|
||||||
|
SparseMemoryBlock block;
|
||||||
|
|
||||||
|
if (_sparseReserved.Count == 0)
|
||||||
|
{
|
||||||
|
block = ReserveNewSparseBlock().Block;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
block = _sparseReserved.Last().Block;
|
||||||
|
|
||||||
|
if (_sparseReservedOffset == block.Block.Size)
|
||||||
|
{
|
||||||
|
block = ReserveNewSparseBlock().Block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset);
|
||||||
|
|
||||||
|
_sparseReservedOffset += (ulong)size;
|
||||||
|
|
||||||
|
_sparseLock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
|
||||||
|
page = new AddressTablePage(false, address);
|
||||||
|
|
||||||
|
var span = new Span<T>((void*)page.Address, length);
|
||||||
|
span.Fill(fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pages.Add(page);
|
||||||
|
|
||||||
|
//TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
|
||||||
|
|
||||||
|
return page.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/>
|
||||||
|
/// instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
foreach (var page in _pages)
|
||||||
|
{
|
||||||
|
if (!page.IsSparse)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(page.Address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sparse)
|
||||||
|
{
|
||||||
|
foreach (TableSparseBlock block in _sparseReserved)
|
||||||
|
{
|
||||||
|
block.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_sparseReserved.Clear();
|
||||||
|
|
||||||
|
_fillBottomLevel.Dispose();
|
||||||
|
_sparseFill.Dispose();
|
||||||
|
_sparseLock.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
~AddressTable()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
return new DummyDiskCacheLoadState();
|
return new DummyDiskCacheLoadState();
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Cpu
|
|||||||
/// <param name="displayVersion">Version of the application</param>
|
/// <param name="displayVersion">Version of the application</param>
|
||||||
/// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
|
/// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
|
||||||
/// <returns>Disk cache load progress reporter and manager</returns>
|
/// <returns>Disk cache load progress reporter and manager</returns>
|
||||||
IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled);
|
IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
|
/// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using ARMeilleure.Common;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using Ryujinx.Cpu.Signal;
|
using Ryujinx.Cpu.Signal;
|
||||||
@ -9,11 +10,13 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
private readonly ITickSource _tickSource;
|
private readonly ITickSource _tickSource;
|
||||||
private readonly Translator _translator;
|
private readonly Translator _translator;
|
||||||
|
private readonly AddressTable<ulong> _functionTable;
|
||||||
|
|
||||||
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||||
{
|
{
|
||||||
_tickSource = tickSource;
|
_tickSource = tickSource;
|
||||||
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit);
|
_functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
|
||||||
|
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable);
|
||||||
|
|
||||||
if (memory.Type.IsHostMappedOrTracked())
|
if (memory.Type.IsHostMappedOrTracked())
|
||||||
{
|
{
|
||||||
@ -47,14 +50,15 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled));
|
return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled, cacheSelector));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void PrepareCodeRange(ulong address, ulong size)
|
public void PrepareCodeRange(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
|
_functionTable.SignalCodeRange(address, size);
|
||||||
_translator.PrepareCodeRange(address, size);
|
_translator.PrepareCodeRange(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +140,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
bool isTail = false)
|
bool isTail = false)
|
||||||
{
|
{
|
||||||
int tempRegister;
|
int tempRegister;
|
||||||
|
int tempGuestAddress = -1;
|
||||||
|
|
||||||
|
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
|
||||||
|
funcTable is { Sparse: true };
|
||||||
|
|
||||||
if (guestAddress.Kind == OperandKind.Constant)
|
if (guestAddress.Kind == OperandKind.Constant)
|
||||||
{
|
{
|
||||||
@ -153,9 +157,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
||||||
|
|
||||||
|
if (inlineLookup && guestAddress.Value == 0)
|
||||||
|
{
|
||||||
|
// X0 will be overwritten. Move the address to a temp register.
|
||||||
|
tempGuestAddress = regAlloc.AllocateTempGprRegister();
|
||||||
|
asm.Mov(Register(tempGuestAddress), guestAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1;
|
tempRegister = NextFreeRegister(1, tempGuestAddress);
|
||||||
|
|
||||||
if (!isTail)
|
if (!isTail)
|
||||||
{
|
{
|
||||||
@ -176,6 +187,40 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
||||||
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
||||||
}
|
}
|
||||||
|
else if (inlineLookup)
|
||||||
|
{
|
||||||
|
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||||
|
|
||||||
|
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
guestAddress = Register(tempGuestAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong tableBase = (ulong)funcTable.Base;
|
||||||
|
|
||||||
|
// Index into the table.
|
||||||
|
asm.Mov(rn, tableBase);
|
||||||
|
|
||||||
|
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
var level = funcTable.Levels[i];
|
||||||
|
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||||
|
asm.Lsl(indexReg, indexReg, Const(3));
|
||||||
|
|
||||||
|
// Index into the page.
|
||||||
|
asm.Add(rn, rn, indexReg);
|
||||||
|
|
||||||
|
// Load the page address.
|
||||||
|
asm.LdrRiUn(rn, rn, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
regAlloc.FreeTempGprRegister(tempGuestAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.Mov(rn, (ulong)funcPtr);
|
asm.Mov(rn, (ulong)funcPtr);
|
||||||
@ -252,5 +297,20 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
{
|
{
|
||||||
return new Operand(register, RegisterType.Integer, type);
|
return new Operand(register, RegisterType.Integer, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Operand Const(long value, OperandType type = OperandType.I64)
|
||||||
|
{
|
||||||
|
return new Operand(type, (ulong)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int NextFreeRegister(int start, int avoid)
|
||||||
|
{
|
||||||
|
if (start == avoid)
|
||||||
|
{
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,6 +305,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
bool isTail = false)
|
bool isTail = false)
|
||||||
{
|
{
|
||||||
int tempRegister;
|
int tempRegister;
|
||||||
|
int tempGuestAddress = -1;
|
||||||
|
|
||||||
|
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
|
||||||
|
funcTable is { Sparse: true };
|
||||||
|
|
||||||
if (guestAddress.Kind == OperandKind.Constant)
|
if (guestAddress.Kind == OperandKind.Constant)
|
||||||
{
|
{
|
||||||
@ -318,9 +322,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
||||||
|
|
||||||
|
if (inlineLookup && guestAddress.Value == 0)
|
||||||
|
{
|
||||||
|
// X0 will be overwritten. Move the address to a temp register.
|
||||||
|
tempGuestAddress = regAlloc.AllocateTempGprRegister();
|
||||||
|
asm.Mov(Register(tempGuestAddress), guestAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1;
|
tempRegister = NextFreeRegister(1, tempGuestAddress);
|
||||||
|
|
||||||
if (!isTail)
|
if (!isTail)
|
||||||
{
|
{
|
||||||
@ -341,6 +352,40 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
||||||
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
||||||
}
|
}
|
||||||
|
else if (inlineLookup)
|
||||||
|
{
|
||||||
|
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||||
|
|
||||||
|
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
guestAddress = Register(tempGuestAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong tableBase = (ulong)funcTable.Base;
|
||||||
|
|
||||||
|
// Index into the table.
|
||||||
|
asm.Mov(rn, tableBase);
|
||||||
|
|
||||||
|
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
var level = funcTable.Levels[i];
|
||||||
|
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||||
|
asm.Lsl(indexReg, indexReg, Const(3));
|
||||||
|
|
||||||
|
// Index into the page.
|
||||||
|
asm.Add(rn, rn, indexReg);
|
||||||
|
|
||||||
|
// Load the page address.
|
||||||
|
asm.LdrRiUn(rn, rn, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
regAlloc.FreeTempGprRegister(tempGuestAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.Mov(rn, (ulong)funcPtr);
|
asm.Mov(rn, (ulong)funcPtr);
|
||||||
@ -613,5 +658,20 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
{
|
{
|
||||||
return new Operand(register, RegisterType.Integer, type);
|
return new Operand(register, RegisterType.Integer, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Operand Const(long value, OperandType type = OperandType.I64)
|
||||||
|
{
|
||||||
|
return new Operand(type, (ulong)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int NextFreeRegister(int start, int avoid)
|
||||||
|
{
|
||||||
|
if (start == avoid)
|
||||||
|
{
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using ARMeilleure.Common;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using Ryujinx.Cpu.Jit;
|
using Ryujinx.Cpu.Jit;
|
||||||
using Ryujinx.Cpu.LightningJit.State;
|
using Ryujinx.Cpu.LightningJit.State;
|
||||||
@ -8,11 +9,16 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
{
|
{
|
||||||
private readonly ITickSource _tickSource;
|
private readonly ITickSource _tickSource;
|
||||||
private readonly Translator _translator;
|
private readonly Translator _translator;
|
||||||
|
private readonly AddressTable<ulong> _functionTable;
|
||||||
|
|
||||||
public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||||
{
|
{
|
||||||
_tickSource = tickSource;
|
_tickSource = tickSource;
|
||||||
_translator = new Translator(memory, for64Bit);
|
|
||||||
|
_functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
|
||||||
|
|
||||||
|
_translator = new Translator(memory, _functionTable);
|
||||||
|
|
||||||
memory.UnmapEvent += UnmapHandler;
|
memory.UnmapEvent += UnmapHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +46,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
return new DummyDiskCacheLoadState();
|
return new DummyDiskCacheLoadState();
|
||||||
}
|
}
|
||||||
@ -48,6 +54,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void PrepareCodeRange(ulong address, ulong size)
|
public void PrepareCodeRange(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
|
_functionTable.SignalCodeRange(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -19,25 +19,6 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
// Should be enabled on platforms that enforce W^X.
|
// Should be enabled on platforms that enforce W^X.
|
||||||
private static bool IsNoWxPlatform => false;
|
private static bool IsNoWxPlatform => false;
|
||||||
|
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels64Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(31, 17),
|
|
||||||
new(23, 8),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 2, 5),
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels32Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(23, 9),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 1, 6),
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||||
private readonly NoWxCache _noWxCache;
|
private readonly NoWxCache _noWxCache;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
@ -47,7 +28,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
internal TranslatorStubs Stubs { get; }
|
internal TranslatorStubs Stubs { get; }
|
||||||
internal IMemoryManager Memory { get; }
|
internal IMemoryManager Memory { get; }
|
||||||
|
|
||||||
public Translator(IMemoryManager memory, bool for64Bits)
|
public Translator(IMemoryManager memory, AddressTable<ulong> functionTable)
|
||||||
{
|
{
|
||||||
Memory = memory;
|
Memory = memory;
|
||||||
|
|
||||||
@ -63,7 +44,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
}
|
}
|
||||||
|
|
||||||
Functions = new TranslatorCache<TranslatedFunction>();
|
Functions = new TranslatorCache<TranslatedFunction>();
|
||||||
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
FunctionTable = functionTable;
|
||||||
Stubs = new TranslatorStubs(FunctionTable, _noWxCache);
|
Stubs = new TranslatorStubs(FunctionTable, _noWxCache);
|
||||||
|
|
||||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||||
|
@ -23,7 +23,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly AddressTable<ulong> _functionTable;
|
private readonly IAddressTable<ulong> _functionTable;
|
||||||
private readonly NoWxCache _noWxCache;
|
private readonly NoWxCache _noWxCache;
|
||||||
private readonly GetFunctionAddressDelegate _getFunctionAddressRef;
|
private readonly GetFunctionAddressDelegate _getFunctionAddressRef;
|
||||||
private readonly nint _getFunctionAddress;
|
private readonly nint _getFunctionAddress;
|
||||||
@ -79,7 +79,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
||||||
/// <param name="noWxCache">Cache used on platforms that enforce W^X, otherwise should be null</param>
|
/// <param name="noWxCache">Cache used on platforms that enforce W^X, otherwise should be null</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||||
public TranslatorStubs(AddressTable<ulong> functionTable, NoWxCache noWxCache)
|
public TranslatorStubs(IAddressTable<ulong> functionTable, NoWxCache noWxCache)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(functionTable);
|
ArgumentNullException.ThrowIfNull(functionTable);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
void SetSize(int width, int height);
|
void SetSize(int width, int height);
|
||||||
|
|
||||||
void ChangeVSyncMode(bool vsyncEnabled);
|
void ChangeVSyncMode(VSyncMode vSyncMode);
|
||||||
|
|
||||||
void SetAntiAliasing(AntiAliasing antialiasing);
|
void SetAntiAliasing(AntiAliasing antialiasing);
|
||||||
void SetScalingFilter(ScalingFilter type);
|
void SetScalingFilter(ScalingFilter type);
|
||||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
_impl.Window.SetSize(width, height);
|
_impl.Window.SetSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
public void ChangeVSyncMode(VSyncMode vSyncMode) { }
|
||||||
|
|
||||||
public void SetAntiAliasing(AntiAliasing effect) { }
|
public void SetAntiAliasing(AntiAliasing effect) { }
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
9
src/Ryujinx.Graphics.GAL/VSyncMode.cs
Normal file
9
src/Ryujinx.Graphics.GAL/VSyncMode.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public enum VSyncMode
|
||||||
|
{
|
||||||
|
Switch,
|
||||||
|
Unbounded,
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -54,7 +54,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
public void ChangeVSyncMode(VSyncMode vSyncMode) { }
|
||||||
|
|
||||||
public void SetSize(int width, int height)
|
public void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
@ -182,6 +182,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Prevent the sum of descriptors from exceeding MaxPushDescriptors
|
||||||
|
int totalDescriptors = 0;
|
||||||
|
foreach (ResourceDescriptor desc in layout.Sets.First().Descriptors)
|
||||||
|
{
|
||||||
|
if (!reserved.Contains(desc.Binding))
|
||||||
|
totalDescriptors += desc.Count;
|
||||||
|
}
|
||||||
|
if (totalDescriptors > gd.Capabilities.MaxPushDescriptors)
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private int _width;
|
private int _width;
|
||||||
private int _height;
|
private int _height;
|
||||||
private bool _vsyncEnabled;
|
private VSyncMode _vSyncMode;
|
||||||
private bool _swapchainIsDirty;
|
private bool _swapchainIsDirty;
|
||||||
private VkFormat _format;
|
private VkFormat _format;
|
||||||
private AntiAliasing _currentAntiAliasing;
|
private AntiAliasing _currentAntiAliasing;
|
||||||
@ -139,7 +139,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ImageArrayLayers = 1,
|
ImageArrayLayers = 1,
|
||||||
PreTransform = capabilities.CurrentTransform,
|
PreTransform = capabilities.CurrentTransform,
|
||||||
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
|
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
|
||||||
PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
|
PresentMode = ChooseSwapPresentMode(presentModes, _vSyncMode),
|
||||||
Clipped = true,
|
Clipped = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -279,9 +279,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled)
|
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode)
|
||||||
{
|
{
|
||||||
if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
|
if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
|
||||||
{
|
{
|
||||||
return PresentModeKHR.ImmediateKhr;
|
return PresentModeKHR.ImmediateKhr;
|
||||||
}
|
}
|
||||||
@ -634,9 +634,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ChangeVSyncMode(bool vsyncEnabled)
|
public override void ChangeVSyncMode(VSyncMode vSyncMode)
|
||||||
{
|
{
|
||||||
_vsyncEnabled = vsyncEnabled;
|
_vSyncMode = vSyncMode;
|
||||||
|
//present mode may change, so mark the swapchain for recreation
|
||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public abstract void Dispose();
|
public abstract void Dispose();
|
||||||
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
||||||
public abstract void SetSize(int width, int height);
|
public abstract void SetSize(int width, int height);
|
||||||
public abstract void ChangeVSyncMode(bool vsyncEnabled);
|
public abstract void ChangeVSyncMode(VSyncMode vSyncMode);
|
||||||
public abstract void SetAntiAliasing(AntiAliasing effect);
|
public abstract void SetAntiAliasing(AntiAliasing effect);
|
||||||
public abstract void SetScalingFilter(ScalingFilter scalerType);
|
public abstract void SetScalingFilter(ScalingFilter scalerType);
|
||||||
public abstract void SetScalingFilterLevel(float scale);
|
public abstract void SetScalingFilterLevel(float scale);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||||
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||||
<IsRoslynComponent>true</IsRoslynComponent>
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -21,6 +21,7 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
@ -474,6 +475,74 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
FinishInstallation(temporaryDirectory, registeredDirectory);
|
FinishInstallation(temporaryDirectory, registeredDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InstallKeys(string keysSource, string installDirectory)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(keysSource))
|
||||||
|
{
|
||||||
|
foreach (var filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
|
||||||
|
{
|
||||||
|
VerifyKeysFile(filePath);
|
||||||
|
File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(keysSource))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Keys file does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInfo info = new(keysSource);
|
||||||
|
|
||||||
|
using FileStream file = File.OpenRead(keysSource);
|
||||||
|
|
||||||
|
switch (info.Extension)
|
||||||
|
{
|
||||||
|
case ".zip":
|
||||||
|
using (ZipArchive archive = ZipFile.OpenRead(keysSource))
|
||||||
|
{
|
||||||
|
InstallKeysFromZip(archive, installDirectory);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ".keys":
|
||||||
|
VerifyKeysFile(keysSource);
|
||||||
|
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InstallKeysFromZip(ZipArchive archive, string installDirectory)
|
||||||
|
{
|
||||||
|
string temporaryDirectory = Path.Combine(installDirectory, "temp");
|
||||||
|
if (Directory.Exists(temporaryDirectory))
|
||||||
|
{
|
||||||
|
Directory.Delete(temporaryDirectory, true);
|
||||||
|
}
|
||||||
|
Directory.CreateDirectory(temporaryDirectory);
|
||||||
|
foreach (var entry in archive.Entries)
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string extractDestination = Path.Combine(temporaryDirectory, entry.Name);
|
||||||
|
entry.ExtractToFile(extractDestination, overwrite: true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
VerifyKeysFile(extractDestination);
|
||||||
|
File.Move(extractDestination, Path.Combine(installDirectory, entry.Name), true);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Directory.Delete(temporaryDirectory, true);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Directory.Delete(temporaryDirectory, true);
|
||||||
|
}
|
||||||
|
|
||||||
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(registeredDirectory))
|
if (Directory.Exists(registeredDirectory))
|
||||||
@ -947,5 +1016,70 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void VerifyKeysFile(string filePath)
|
||||||
|
{
|
||||||
|
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
|
||||||
|
string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
|
||||||
|
string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
|
||||||
|
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
// Read all lines from the file
|
||||||
|
string fileName = Path.GetFileName(filePath);
|
||||||
|
string[] lines = File.ReadAllLines(filePath);
|
||||||
|
|
||||||
|
bool verified = false;
|
||||||
|
switch (fileName)
|
||||||
|
{
|
||||||
|
case "prod.keys":
|
||||||
|
verified = verifyKeys(lines, genericPattern);
|
||||||
|
break;
|
||||||
|
case "title.keys":
|
||||||
|
verified = verifyKeys(lines, titlePattern);
|
||||||
|
break;
|
||||||
|
case "console.keys":
|
||||||
|
verified = verifyKeys(lines, genericPattern);
|
||||||
|
break;
|
||||||
|
case "dev.keys":
|
||||||
|
verified = verifyKeys(lines, genericPattern);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
||||||
|
}
|
||||||
|
if (!verified)
|
||||||
|
{
|
||||||
|
throw new FormatException($"Invalid \"{filePath}\" file format.");
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"Keys file not found at \"{filePath}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool verifyKeys(string[] lines, string regex)
|
||||||
|
{
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
if (!Regex.IsMatch(line, regex))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AreKeysAlredyPresent(string pathToCheck)
|
||||||
|
{
|
||||||
|
string[] fileNames = { "prod.keys", "title.keys", "console.keys", "dev.keys" };
|
||||||
|
foreach (var file in fileNames)
|
||||||
|
{
|
||||||
|
if (File.Exists(Path.Combine(pathToCheck, file)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,9 +223,10 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
KeySet ??= KeySet.CreateDefaultKeySet();
|
KeySet ??= KeySet.CreateDefaultKeySet();
|
||||||
|
|
||||||
string keyFile = null;
|
string prodKeyFile = null;
|
||||||
string titleKeyFile = null;
|
string titleKeyFile = null;
|
||||||
string consoleKeyFile = null;
|
string consoleKeyFile = null;
|
||||||
|
string devKeyFile = null;
|
||||||
|
|
||||||
if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile)
|
if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile)
|
||||||
{
|
{
|
||||||
@ -236,13 +237,14 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
void LoadSetAtPath(string basePath)
|
void LoadSetAtPath(string basePath)
|
||||||
{
|
{
|
||||||
string localKeyFile = Path.Combine(basePath, "prod.keys");
|
string localProdKeyFile = Path.Combine(basePath, "prod.keys");
|
||||||
string localTitleKeyFile = Path.Combine(basePath, "title.keys");
|
string localTitleKeyFile = Path.Combine(basePath, "title.keys");
|
||||||
string localConsoleKeyFile = Path.Combine(basePath, "console.keys");
|
string localConsoleKeyFile = Path.Combine(basePath, "console.keys");
|
||||||
|
string localDevKeyFile = Path.Combine(basePath, "dev.keys");
|
||||||
|
|
||||||
if (File.Exists(localKeyFile))
|
if (File.Exists(localProdKeyFile))
|
||||||
{
|
{
|
||||||
keyFile = localKeyFile;
|
prodKeyFile = localProdKeyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(localTitleKeyFile))
|
if (File.Exists(localTitleKeyFile))
|
||||||
@ -254,9 +256,14 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
consoleKeyFile = localConsoleKeyFile;
|
consoleKeyFile = localConsoleKeyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (File.Exists(localDevKeyFile))
|
||||||
|
{
|
||||||
|
devKeyFile = localDevKeyFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalKeyReader.ReadKeyFile(KeySet, keyFile, titleKeyFile, consoleKeyFile, null);
|
ExternalKeyReader.ReadKeyFile(KeySet, prodKeyFile, devKeyFile, titleKeyFile, consoleKeyFile, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ImportTickets(IFileSystem fs)
|
public void ImportTickets(IFileSystem fs)
|
||||||
|
@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
|
||||||
|
|
||||||
namespace Ryujinx.HLE
|
namespace Ryujinx.HLE
|
||||||
{
|
{
|
||||||
@ -84,9 +85,14 @@ namespace Ryujinx.HLE
|
|||||||
internal readonly RegionCode Region;
|
internal readonly RegionCode Region;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the vertical sync in the SurfaceFlinger service.
|
/// Control the initial state of the present interval in the SurfaceFlinger service (previously Vsync).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool EnableVsync;
|
internal readonly VSyncMode VSyncMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Control the custom VSync interval, if enabled and active.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly int CustomVSyncInterval;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the docked mode.
|
/// Control the initial state of the docked mode.
|
||||||
@ -195,7 +201,7 @@ namespace Ryujinx.HLE
|
|||||||
IHostUIHandler hostUIHandler,
|
IHostUIHandler hostUIHandler,
|
||||||
SystemLanguage systemLanguage,
|
SystemLanguage systemLanguage,
|
||||||
RegionCode region,
|
RegionCode region,
|
||||||
bool enableVsync,
|
VSyncMode vSyncMode,
|
||||||
bool enableDockedMode,
|
bool enableDockedMode,
|
||||||
bool enablePtc,
|
bool enablePtc,
|
||||||
bool enableInternetAccess,
|
bool enableInternetAccess,
|
||||||
@ -212,7 +218,8 @@ namespace Ryujinx.HLE
|
|||||||
MultiplayerMode multiplayerMode,
|
MultiplayerMode multiplayerMode,
|
||||||
bool multiplayerDisableP2p,
|
bool multiplayerDisableP2p,
|
||||||
string multiplayerLdnPassphrase,
|
string multiplayerLdnPassphrase,
|
||||||
string multiplayerLdnServer)
|
string multiplayerLdnServer,
|
||||||
|
int customVSyncInterval)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
LibHacHorizonManager = libHacHorizonManager;
|
LibHacHorizonManager = libHacHorizonManager;
|
||||||
@ -225,7 +232,8 @@ namespace Ryujinx.HLE
|
|||||||
HostUIHandler = hostUIHandler;
|
HostUIHandler = hostUIHandler;
|
||||||
SystemLanguage = systemLanguage;
|
SystemLanguage = systemLanguage;
|
||||||
Region = region;
|
Region = region;
|
||||||
EnableVsync = enableVsync;
|
VSyncMode = vSyncMode;
|
||||||
|
CustomVSyncInterval = customVSyncInterval;
|
||||||
EnableDockedMode = enableDockedMode;
|
EnableDockedMode = enableDockedMode;
|
||||||
EnablePtc = enablePtc;
|
EnablePtc = enablePtc;
|
||||||
EnableInternetAccess = enableInternetAccess;
|
EnableInternetAccess = enableInternetAccess;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Applets.Browser;
|
using Ryujinx.HLE.HOS.Applets.Browser;
|
||||||
|
using Ryujinx.HLE.HOS.Applets.Cabinet;
|
||||||
using Ryujinx.HLE.HOS.Applets.Dummy;
|
using Ryujinx.HLE.HOS.Applets.Dummy;
|
||||||
using Ryujinx.HLE.HOS.Applets.Error;
|
using Ryujinx.HLE.HOS.Applets.Error;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
@ -23,14 +24,14 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
case AppletId.SoftwareKeyboard:
|
case AppletId.SoftwareKeyboard:
|
||||||
return new SoftwareKeyboardApplet(system);
|
return new SoftwareKeyboardApplet(system);
|
||||||
case AppletId.LibAppletWeb:
|
case AppletId.LibAppletWeb:
|
||||||
return new BrowserApplet(system);
|
|
||||||
case AppletId.LibAppletShop:
|
case AppletId.LibAppletShop:
|
||||||
return new BrowserApplet(system);
|
|
||||||
case AppletId.LibAppletOff:
|
case AppletId.LibAppletOff:
|
||||||
return new BrowserApplet(system);
|
return new BrowserApplet();
|
||||||
case AppletId.MiiEdit:
|
case AppletId.MiiEdit:
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Please use the MiiEdit inside File/Open Applet");
|
Logger.Warning?.Print(LogClass.Application, $"Please use the MiiEdit inside File/Open Applet");
|
||||||
return new DummyApplet(system);
|
return new DummyApplet(system);
|
||||||
|
case AppletId.Cabinet:
|
||||||
|
return new CabinetApplet(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Applet {applet} not implemented!");
|
Logger.Warning?.Print(LogClass.Application, $"Applet {applet} not implemented!");
|
||||||
|
@ -18,13 +18,6 @@ namespace Ryujinx.HLE.HOS.Applets.Browser
|
|||||||
private List<BrowserArgument> _arguments;
|
private List<BrowserArgument> _arguments;
|
||||||
private ShimKind _shimKind;
|
private ShimKind _shimKind;
|
||||||
|
|
||||||
public BrowserApplet(Horizon system) { }
|
|
||||||
|
|
||||||
public ResultCode GetResult()
|
|
||||||
{
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
||||||
{
|
{
|
||||||
_normalSession = normalSession;
|
_normalSession = normalSession;
|
||||||
|
182
src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Normal file
182
src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.Cabinet
|
||||||
|
{
|
||||||
|
internal unsafe class CabinetApplet : IApplet
|
||||||
|
{
|
||||||
|
private readonly Horizon _system;
|
||||||
|
private AppletSession _normalSession;
|
||||||
|
|
||||||
|
public event EventHandler AppletStateChanged;
|
||||||
|
|
||||||
|
public CabinetApplet(Horizon system)
|
||||||
|
{
|
||||||
|
_system = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
||||||
|
{
|
||||||
|
_normalSession = normalSession;
|
||||||
|
|
||||||
|
byte[] launchParams = _normalSession.Pop();
|
||||||
|
byte[] startParamBytes = _normalSession.Pop();
|
||||||
|
|
||||||
|
StartParamForAmiiboSettings startParam = IApplet.ReadStruct<StartParamForAmiiboSettings>(startParamBytes);
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceAm, $"CabinetApplet Start Type: {startParam.Type}");
|
||||||
|
|
||||||
|
switch (startParam.Type)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
StartNicknameAndOwnerSettings(ref startParam);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 3:
|
||||||
|
StartFormatter(ref startParam);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logger.Error?.Print(LogClass.ServiceAm, $"Unknown AmiiboSettings type: {startParam.Type}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the response
|
||||||
|
ReturnValueForAmiiboSettings returnValue = new()
|
||||||
|
{
|
||||||
|
AmiiboSettingsReturnFlag = (byte)AmiiboSettingsReturnFlag.HasRegisterInfo,
|
||||||
|
DeviceHandle = new DeviceHandle
|
||||||
|
{
|
||||||
|
Handle = 0 // Dummy device handle
|
||||||
|
},
|
||||||
|
RegisterInfo = startParam.RegisterInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push the response
|
||||||
|
_normalSession.Push(BuildResponse(returnValue));
|
||||||
|
AppletStateChanged?.Invoke(this, null);
|
||||||
|
|
||||||
|
_system.ReturnFocus();
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultCode GetResult()
|
||||||
|
{
|
||||||
|
_system.Device.System.NfpDevices.RemoveAt(0);
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartFormatter(ref StartParamForAmiiboSettings startParam)
|
||||||
|
{
|
||||||
|
// Initialize RegisterInfo
|
||||||
|
startParam.RegisterInfo = new RegisterInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartNicknameAndOwnerSettings(ref StartParamForAmiiboSettings startParam)
|
||||||
|
{
|
||||||
|
_system.Device.UIHandler.DisplayCabinetDialog(out string newName);
|
||||||
|
byte[] nameBytes = Encoding.UTF8.GetBytes(newName);
|
||||||
|
Array41<byte> nickName = new Array41<byte>();
|
||||||
|
nameBytes.CopyTo(nickName.AsSpan());
|
||||||
|
startParam.RegisterInfo.Nickname = nickName;
|
||||||
|
NfpDevice devicePlayer1 = new()
|
||||||
|
{
|
||||||
|
NpadIdType = NpadIdType.Player1,
|
||||||
|
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
|
||||||
|
State = NfpDeviceState.SearchingForTag,
|
||||||
|
};
|
||||||
|
_system.Device.System.NfpDevices.Add(devicePlayer1);
|
||||||
|
_system.Device.UIHandler.DisplayCabinetMessageDialog();
|
||||||
|
string amiiboId = string.Empty;
|
||||||
|
bool scanned = false;
|
||||||
|
while (!scanned)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _system.Device.System.NfpDevices.Count; i++)
|
||||||
|
{
|
||||||
|
if (_system.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
|
||||||
|
{
|
||||||
|
amiiboId = _system.Device.System.NfpDevices[i].AmiiboId;
|
||||||
|
scanned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VirtualAmiibo.UpdateNickName(amiiboId, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] BuildResponse(ReturnValueForAmiiboSettings returnValue)
|
||||||
|
{
|
||||||
|
int size = Unsafe.SizeOf<ReturnValueForAmiiboSettings>();
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
|
||||||
|
fixed (byte* bytesPtr = bytes)
|
||||||
|
{
|
||||||
|
Unsafe.Write(bytesPtr, returnValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Structs
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct TagInfo
|
||||||
|
{
|
||||||
|
public fixed byte Data[0x58];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct StartParamForAmiiboSettings
|
||||||
|
{
|
||||||
|
public byte ZeroValue; // Left at zero by sdknso
|
||||||
|
public byte Type;
|
||||||
|
public byte Flags;
|
||||||
|
public byte AmiiboSettingsStartParamOffset28;
|
||||||
|
public ulong AmiiboSettingsStartParam0;
|
||||||
|
|
||||||
|
public TagInfo TagInfo; // Only enabled when flags bit 1 is set
|
||||||
|
public RegisterInfo RegisterInfo; // Only enabled when flags bit 2 is set
|
||||||
|
|
||||||
|
public fixed byte StartParamExtraData[0x20];
|
||||||
|
|
||||||
|
public fixed byte Reserved[0x24];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct ReturnValueForAmiiboSettings
|
||||||
|
{
|
||||||
|
public byte AmiiboSettingsReturnFlag;
|
||||||
|
private byte Padding1;
|
||||||
|
private byte Padding2;
|
||||||
|
private byte Padding3;
|
||||||
|
public DeviceHandle DeviceHandle;
|
||||||
|
public TagInfo TagInfo;
|
||||||
|
public RegisterInfo RegisterInfo;
|
||||||
|
public fixed byte IgnoredBySdknso[0x24];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct DeviceHandle
|
||||||
|
{
|
||||||
|
public ulong Handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AmiiboSettingsReturnFlag : byte
|
||||||
|
{
|
||||||
|
Cancel = 0,
|
||||||
|
HasTagInfo = 2,
|
||||||
|
HasRegisterInfo = 4,
|
||||||
|
HasTagInfoAndRegisterInfo = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -117,11 +117,6 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode GetResult()
|
|
||||||
{
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] BuildResponse(ControllerSupportResultInfo result)
|
private static byte[] BuildResponse(ControllerSupportResultInfo result)
|
||||||
{
|
{
|
||||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||||
|
@ -11,11 +11,14 @@ namespace Ryujinx.HLE.HOS.Applets.Dummy
|
|||||||
{
|
{
|
||||||
private readonly Horizon _system;
|
private readonly Horizon _system;
|
||||||
private AppletSession _normalSession;
|
private AppletSession _normalSession;
|
||||||
|
|
||||||
public event EventHandler AppletStateChanged;
|
public event EventHandler AppletStateChanged;
|
||||||
|
|
||||||
public DummyApplet(Horizon system)
|
public DummyApplet(Horizon system)
|
||||||
{
|
{
|
||||||
_system = system;
|
_system = system;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
||||||
{
|
{
|
||||||
_normalSession = normalSession;
|
_normalSession = normalSession;
|
||||||
@ -24,10 +27,7 @@ namespace Ryujinx.HLE.HOS.Applets.Dummy
|
|||||||
_system.ReturnFocus();
|
_system.ReturnFocus();
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
private static T ReadStruct<T>(byte[] data) where T : struct
|
|
||||||
{
|
|
||||||
return MemoryMarshal.Read<T>(data.AsSpan());
|
|
||||||
}
|
|
||||||
private static byte[] BuildResponse()
|
private static byte[] BuildResponse()
|
||||||
{
|
{
|
||||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||||
@ -35,9 +35,5 @@ namespace Ryujinx.HLE.HOS.Applets.Dummy
|
|||||||
writer.Write((ulong)ResultCode.Success);
|
writer.Write((ulong)ResultCode.Success);
|
||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
public ResultCode GetResult()
|
|
||||||
{
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,10 +203,5 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
|||||||
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
|
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode GetResult()
|
|
||||||
{
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
ResultCode Start(AppletSession normalSession,
|
ResultCode Start(AppletSession normalSession,
|
||||||
AppletSession interactiveSession);
|
AppletSession interactiveSession);
|
||||||
|
|
||||||
ResultCode GetResult();
|
ResultCode GetResult() => ResultCode.Success;
|
||||||
|
|
||||||
bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position) => false;
|
bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position) => false;
|
||||||
|
|
||||||
|
@ -37,11 +37,6 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode GetResult()
|
|
||||||
{
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] BuildResponse()
|
private byte[] BuildResponse()
|
||||||
{
|
{
|
||||||
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
||||||
|
@ -144,11 +144,6 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode GetResult()
|
|
||||||
{
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsKeyboardActive()
|
private bool IsKeyboardActive()
|
||||||
{
|
{
|
||||||
return _backgroundState >= InlineKeyboardState.Appearing && _backgroundState < InlineKeyboardState.Disappearing;
|
return _backgroundState >= InlineKeyboardState.Appearing && _backgroundState < InlineKeyboardState.Disappearing;
|
||||||
|
@ -2,7 +2,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wraps a type in a class so it gets stored in the GC managed heap. This is used as communication mechanism
|
/// Wraps a type in a class so it gets stored in the GC managed heap. This is used as communication mechanism
|
||||||
/// between classed that need to be disposed and, thus, can't share their references.
|
/// between classes that need to be disposed and, thus, can't share their references.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The internal type.</typeparam>
|
/// <typeparam name="T">The internal type.</typeparam>
|
||||||
class TRef<T>
|
class TRef<T>
|
||||||
|
@ -13,7 +13,8 @@ namespace Ryujinx.HLE.HOS
|
|||||||
string displayVersion,
|
string displayVersion,
|
||||||
bool diskCacheEnabled,
|
bool diskCacheEnabled,
|
||||||
ulong codeAddress,
|
ulong codeAddress,
|
||||||
ulong codeSize);
|
ulong codeSize,
|
||||||
|
string cacheSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
|
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
|
||||||
@ -67,10 +68,11 @@ namespace Ryujinx.HLE.HOS
|
|||||||
string displayVersion,
|
string displayVersion,
|
||||||
bool diskCacheEnabled,
|
bool diskCacheEnabled,
|
||||||
ulong codeAddress,
|
ulong codeAddress,
|
||||||
ulong codeSize)
|
ulong codeSize,
|
||||||
|
string cacheSelector)
|
||||||
{
|
{
|
||||||
_cpuContext.PrepareCodeRange(codeAddress, codeSize);
|
_cpuContext.PrepareCodeRange(codeAddress, codeSize);
|
||||||
return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled);
|
return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled, cacheSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||||
|
@ -114,7 +114,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize);
|
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, "default"); //Ready for exefs profiles
|
||||||
|
|
||||||
return processContext;
|
return processContext;
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,23 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
|
|||||||
class IServiceCreator : IpcService
|
class IServiceCreator : IpcService
|
||||||
{
|
{
|
||||||
public IServiceCreator(ServiceCtx context) { }
|
public IServiceCreator(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandCmif(0)]
|
||||||
|
// CreateNetworkService(pid, u64, u32) -> object<nn::ldn::detail::ISfService>
|
||||||
|
public ResultCode CreateNetworkService(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new ISfService(context));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8)]
|
||||||
|
// CreateNetworkServiceMonitor(pid, u64) -> object<nn::ldn::detail::ISfServiceMonitor>
|
||||||
|
public ResultCode CreateNetworkServiceMonitor(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new ISfServiceMonitor(context));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
54
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs
Normal file
54
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
|
||||||
|
{
|
||||||
|
class ISfService : IpcService
|
||||||
|
{
|
||||||
|
public ISfService(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandCmif(0)]
|
||||||
|
// Initialize()
|
||||||
|
public ResultCode Initialize(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(0);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(768)]
|
||||||
|
// CreateGroup(buffer<nn::lp2p::GroupInfo, 0x31)
|
||||||
|
public ResultCode CreateGroup(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(776)]
|
||||||
|
// DestroyGroup()
|
||||||
|
public ResultCode DestroyGroup(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(1536)]
|
||||||
|
// SendToOtherGroup(nn::lp2p::MacAddress, nn::lp2p::GroupId, s16, s16, u32, buffer<unknown, 0x21>)
|
||||||
|
public ResultCode SendToOtherGroup(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(1544)]
|
||||||
|
// RecvFromOtherGroup(u32, buffer<unknown, 0x22>) -> (nn::lp2p::MacAddress, u16, s16, u32, s32)
|
||||||
|
public ResultCode RecvFromOtherGroup(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs
Normal file
86
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
|
||||||
|
{
|
||||||
|
class ISfServiceMonitor : IpcService
|
||||||
|
{
|
||||||
|
private readonly KEvent _stateChangeEvent;
|
||||||
|
private readonly KEvent _jointEvent;
|
||||||
|
private int _stateChangeEventHandle = 0;
|
||||||
|
private int _jointEventHandle = 0;
|
||||||
|
|
||||||
|
public ISfServiceMonitor(ServiceCtx context)
|
||||||
|
{
|
||||||
|
_stateChangeEvent = new KEvent(context.Device.System.KernelContext);
|
||||||
|
_jointEvent = new KEvent(context.Device.System.KernelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(0)]
|
||||||
|
// Initialize()
|
||||||
|
public ResultCode Initialize(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(0);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(256)]
|
||||||
|
// AttachNetworkInterfaceStateChangeEvent() -> handle<copy>
|
||||||
|
public ResultCode AttachNetworkInterfaceStateChangeEvent(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(288)]
|
||||||
|
// GetGroupInfo(buffer<nn::lp2p::GroupInfo, 0x32>)
|
||||||
|
public ResultCode GetGroupInfo(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(296)]
|
||||||
|
// GetGroupInfo2(buffer<nn::lp2p::GroupInfo, 0x32>, buffer<nn::lp2p::GroupInfo, 0x31>)
|
||||||
|
public ResultCode GetGroupInfo2(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(312)]
|
||||||
|
// GetIpConfig(buffer<unknown<0x100>, 0x1a>)
|
||||||
|
public ResultCode GetIpConfig(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(328)]
|
||||||
|
// AttachNetworkInterfaceStateChangeEvent() -> handle<copy>
|
||||||
|
public ResultCode AttachJoinEvent(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_jointEvent.ReadableEvent, out _jointEventHandle) != Result.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_jointEventHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
using Gommon;
|
||||||
|
using Humanizer;
|
||||||
using NetCoreServer;
|
using NetCoreServer;
|
||||||
using Open.Nat;
|
using Open.Nat;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
@ -153,7 +155,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|||||||
|
|
||||||
if (_publicPort != 0)
|
if (_publicPort != 0)
|
||||||
{
|
{
|
||||||
_ = Task.Delay(PortLeaseRenew * 1000, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease));
|
_ = Executor.ExecuteAfterDelayAsync(
|
||||||
|
PortLeaseRenew.Seconds(),
|
||||||
|
_disposedCancellation.Token,
|
||||||
|
RefreshLease);
|
||||||
}
|
}
|
||||||
|
|
||||||
_natDevice = device;
|
_natDevice = device;
|
||||||
@ -257,7 +262,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = Task.Delay(PortLeaseRenew, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease));
|
_ = Executor.ExecuteAfterDelayAsync(
|
||||||
|
PortLeaseRenew.Milliseconds(),
|
||||||
|
_disposedCancellation.Token,
|
||||||
|
RefreshLease);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRegisterUser(P2pProxySession session, ExternalProxyConfig config)
|
public bool TryRegisterUser(P2pProxySession session, ExternalProxyConfig config)
|
||||||
|
@ -93,6 +93,13 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||||||
return registerInfo;
|
return registerInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void UpdateNickName(string amiiboId, string newNickName)
|
||||||
|
{
|
||||||
|
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
||||||
|
virtualAmiiboFile.NickName = newNickName;
|
||||||
|
SaveAmiiboFile(virtualAmiiboFile);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId)
|
public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId)
|
||||||
{
|
{
|
||||||
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
|
||||||
|
@ -10,13 +10,12 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
{
|
{
|
||||||
class SurfaceFlinger : IConsumerListener, IDisposable
|
class SurfaceFlinger : IConsumerListener, IDisposable
|
||||||
{
|
{
|
||||||
private const int TargetFps = 60;
|
|
||||||
|
|
||||||
private readonly Switch _device;
|
private readonly Switch _device;
|
||||||
|
|
||||||
private readonly Dictionary<long, Layer> _layers;
|
private readonly Dictionary<long, Layer> _layers;
|
||||||
@ -32,6 +31,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
private readonly long _spinTicks;
|
private readonly long _spinTicks;
|
||||||
private readonly long _1msTicks;
|
private readonly long _1msTicks;
|
||||||
|
|
||||||
|
private VSyncMode _vSyncMode;
|
||||||
|
private long _targetVSyncInterval;
|
||||||
|
|
||||||
private int _swapInterval;
|
private int _swapInterval;
|
||||||
private int _swapIntervalDelay;
|
private int _swapIntervalDelay;
|
||||||
|
|
||||||
@ -88,7 +90,8 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
_ticksPerFrame = Stopwatch.Frequency / _device.TargetVSyncInterval;
|
||||||
|
_targetVSyncInterval = _device.TargetVSyncInterval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,15 +373,20 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
|
|
||||||
if (acquireStatus == Status.Success)
|
if (acquireStatus == Status.Success)
|
||||||
{
|
{
|
||||||
// If device vsync is disabled, reflect the change.
|
if (_device.VSyncMode == VSyncMode.Unbounded)
|
||||||
if (!_device.EnableDeviceVsync)
|
|
||||||
{
|
{
|
||||||
if (_swapInterval != 0)
|
if (_swapInterval != 0)
|
||||||
{
|
{
|
||||||
UpdateSwapInterval(0);
|
UpdateSwapInterval(0);
|
||||||
|
_vSyncMode = _device.VSyncMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (item.SwapInterval != _swapInterval)
|
else if (_device.VSyncMode != _vSyncMode)
|
||||||
|
{
|
||||||
|
UpdateSwapInterval(_device.VSyncMode == VSyncMode.Unbounded ? 0 : item.SwapInterval);
|
||||||
|
_vSyncMode = _device.VSyncMode;
|
||||||
|
}
|
||||||
|
else if (item.SwapInterval != _swapInterval || _device.TargetVSyncInterval != _targetVSyncInterval)
|
||||||
{
|
{
|
||||||
UpdateSwapInterval(item.SwapInterval);
|
UpdateSwapInterval(item.SwapInterval);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
{
|
{
|
||||||
private static readonly TitleUpdateMetadataJsonSerializerContext _applicationSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly TitleUpdateMetadataJsonSerializerContext _applicationSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
|
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca, BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
||||||
{
|
{
|
||||||
// Extract RomFs and ExeFs from NCA.
|
// Extract RomFs and ExeFs from NCA.
|
||||||
IStorage romFs = nca.GetRomFs(device, patchNca);
|
IStorage romFs = nca.GetRomFs(device, patchNca);
|
||||||
@ -55,6 +55,10 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
{
|
{
|
||||||
nacpData = controlNca.GetNacp(device);
|
nacpData = controlNca.GetNacp(device);
|
||||||
}
|
}
|
||||||
|
else if (customNacpData != null) // if the Application doesn't provide a nacp file but the Application provides an override, use the provided nacp override
|
||||||
|
{
|
||||||
|
nacpData = (BlitStruct<ApplicationControlProperty>)customNacpData;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update.
|
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update.
|
||||||
|
|
||||||
|
@ -98,12 +98,12 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadNca(string path)
|
public bool LoadNca(string path, BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
||||||
{
|
{
|
||||||
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||||
Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||||
|
|
||||||
ProcessResult processResult = nca.Load(_device, null, null);
|
ProcessResult processResult = nca.Load(_device, null, null, customNacpData);
|
||||||
|
|
||||||
if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
|
if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
|
||||||
{
|
{
|
||||||
|
@ -84,12 +84,19 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isFirmware = ProgramId is >= 0x0100000000000819 and <= 0x010000000000081C;
|
||||||
|
bool isFirmwareApplication = ProgramId <= 0x0100000000007FFF;
|
||||||
|
|
||||||
|
string name = !isFirmware
|
||||||
|
? (isFirmwareApplication ? "Firmware Application " : "") + (!string.IsNullOrWhiteSpace(Name) ? Name : "<Unknown Name>")
|
||||||
|
: "Firmware";
|
||||||
|
|
||||||
// TODO: LibHac npdm currently doesn't support version field.
|
// TODO: LibHac npdm currently doesn't support version field.
|
||||||
string version = ProgramId > 0x0100000000007FFF
|
string version = !isFirmware
|
||||||
? DisplayVersion
|
? (!string.IsNullOrWhiteSpace(DisplayVersion) ? DisplayVersion : "<Unknown Version>")
|
||||||
: device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? "?";
|
: device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? "?";
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]");
|
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
37
src/Ryujinx.HLE/StructHelpers.cs
Normal file
37
src/Ryujinx.HLE/StructHelpers.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Ns;
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE
|
||||||
|
{
|
||||||
|
public static class StructHelpers
|
||||||
|
{
|
||||||
|
public static BlitStruct<ApplicationControlProperty> CreateCustomNacpData(string name, string version)
|
||||||
|
{
|
||||||
|
// https://switchbrew.org/wiki/NACP
|
||||||
|
const int OffsetOfDisplayVersion = 0x3060;
|
||||||
|
|
||||||
|
// https://switchbrew.org/wiki/NACP#ApplicationTitle
|
||||||
|
const int TotalApplicationTitles = 0x10;
|
||||||
|
const int SizeOfApplicationTitle = 0x300;
|
||||||
|
const int OffsetOfApplicationPublisherStrings = 0x200;
|
||||||
|
|
||||||
|
|
||||||
|
var nacpData = new BlitStruct<ApplicationControlProperty>(1);
|
||||||
|
|
||||||
|
// name and publisher buffer
|
||||||
|
// repeat once for each locale (the ApplicationControlProperty has 16 locales)
|
||||||
|
for (int i = 0; i < TotalApplicationTitles; i++)
|
||||||
|
{
|
||||||
|
Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(nacpData.ByteSpan[(i * SizeOfApplicationTitle)..]);
|
||||||
|
"Ryujinx"u8.CopyTo(nacpData.ByteSpan[(i * SizeOfApplicationTitle + OffsetOfApplicationPublisherStrings)..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// version buffer
|
||||||
|
Encoding.ASCII.GetBytes(version).AsSpan().CopyTo(nacpData.ByteSpan[OffsetOfDisplayVersion..]);
|
||||||
|
|
||||||
|
return nacpData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Ns;
|
||||||
using Ryujinx.Audio.Backends.CompatLayer;
|
using Ryujinx.Audio.Backends.CompatLayer;
|
||||||
using Ryujinx.Audio.Integration;
|
using Ryujinx.Audio.Integration;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
@ -27,7 +29,11 @@ namespace Ryujinx.HLE
|
|||||||
public TamperMachine TamperMachine { get; }
|
public TamperMachine TamperMachine { get; }
|
||||||
public IHostUIHandler UIHandler { get; }
|
public IHostUIHandler UIHandler { get; }
|
||||||
|
|
||||||
public bool EnableDeviceVsync { get; set; }
|
public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch;
|
||||||
|
public bool CustomVSyncIntervalEnabled { get; set; } = false;
|
||||||
|
public int CustomVSyncInterval { get; set; }
|
||||||
|
|
||||||
|
public long TargetVSyncInterval { get; set; } = 60;
|
||||||
|
|
||||||
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
|
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
|
||||||
|
|
||||||
@ -59,12 +65,14 @@ namespace Ryujinx.HLE
|
|||||||
System.State.SetLanguage(Configuration.SystemLanguage);
|
System.State.SetLanguage(Configuration.SystemLanguage);
|
||||||
System.State.SetRegion(Configuration.Region);
|
System.State.SetRegion(Configuration.Region);
|
||||||
|
|
||||||
EnableDeviceVsync = Configuration.EnableVsync;
|
VSyncMode = Configuration.VSyncMode;
|
||||||
|
CustomVSyncInterval = Configuration.CustomVSyncInterval;
|
||||||
System.State.DockedMode = Configuration.EnableDockedMode;
|
System.State.DockedMode = Configuration.EnableDockedMode;
|
||||||
System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
|
System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
|
||||||
System.EnablePtc = Configuration.EnablePtc;
|
System.EnablePtc = Configuration.EnablePtc;
|
||||||
System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel;
|
System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel;
|
||||||
System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode;
|
System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode;
|
||||||
|
UpdateVSyncInterval();
|
||||||
#pragma warning restore IDE0055
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,9 +83,37 @@ namespace Ryujinx.HLE
|
|||||||
Gpu.GPFifo.DispatchCalls();
|
Gpu.GPFifo.DispatchCalls();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void IncrementCustomVSyncInterval()
|
||||||
|
{
|
||||||
|
CustomVSyncInterval += 1;
|
||||||
|
UpdateVSyncInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DecrementCustomVSyncInterval()
|
||||||
|
{
|
||||||
|
CustomVSyncInterval -= 1;
|
||||||
|
UpdateVSyncInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateVSyncInterval()
|
||||||
|
{
|
||||||
|
switch (VSyncMode)
|
||||||
|
{
|
||||||
|
case VSyncMode.Custom:
|
||||||
|
TargetVSyncInterval = CustomVSyncInterval;
|
||||||
|
break;
|
||||||
|
case VSyncMode.Switch:
|
||||||
|
TargetVSyncInterval = 60;
|
||||||
|
break;
|
||||||
|
case VSyncMode.Unbounded:
|
||||||
|
TargetVSyncInterval = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool LoadCart(string exeFsDir, string romFsFile = null) => Processes.LoadUnpackedNca(exeFsDir, romFsFile);
|
public bool LoadCart(string exeFsDir, string romFsFile = null) => Processes.LoadUnpackedNca(exeFsDir, romFsFile);
|
||||||
public bool LoadXci(string xciFile, ulong applicationId = 0) => Processes.LoadXci(xciFile, applicationId);
|
public bool LoadXci(string xciFile, ulong applicationId = 0) => Processes.LoadXci(xciFile, applicationId);
|
||||||
public bool LoadNca(string ncaFile) => Processes.LoadNca(ncaFile);
|
public bool LoadNca(string ncaFile, BlitStruct<ApplicationControlProperty>? customNacpData = null) => Processes.LoadNca(ncaFile, customNacpData);
|
||||||
public bool LoadNsp(string nspFile, ulong applicationId = 0) => Processes.LoadNsp(nspFile, applicationId);
|
public bool LoadNsp(string nspFile, ulong applicationId = 0) => Processes.LoadNsp(nspFile, applicationId);
|
||||||
public bool LoadProgram(string fileName) => Processes.LoadNxo(fileName);
|
public bool LoadProgram(string fileName) => Processes.LoadNxo(fileName);
|
||||||
|
|
||||||
|
@ -24,6 +24,18 @@ namespace Ryujinx.HLE.UI
|
|||||||
/// <returns>True when OK is pressed, False otherwise.</returns>
|
/// <returns>True when OK is pressed, False otherwise.</returns>
|
||||||
bool DisplayMessageDialog(ControllerAppletUIArgs args);
|
bool DisplayMessageDialog(ControllerAppletUIArgs args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays an Input Dialog box to the user so they can enter the Amiibo's new name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userText">Text that the user entered. Set to `null` on internal errors</param>
|
||||||
|
/// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns>
|
||||||
|
bool DisplayCabinetDialog(out string userText);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a Message Dialog box to the user to notify them to scan the Amiibo.
|
||||||
|
/// </summary>
|
||||||
|
void DisplayCabinetMessageDialog();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tell the UI that we need to transition to another program.
|
/// Tell the UI that we need to transition to another program.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user